|
最近学习了stm32,就想用它来控制舵机,然后写下这篇文章分享给大家,如果有理解不到位的地方欢迎大家指正。(我使用的是stm32f103ve型号的开发板,即使和你的型号不同,也有参考价值)
6 C2 O+ i$ a( e0 } 想要控制舵机的转动,首先你得知道舵的工作原理。
: n/ p# ~- ^# y: `舵机的主要组成部分为伺服电机,所谓伺服就是服从信号的要求而动作。在信号来之前,转子停止不动;信号来到之后,转子立即运动。因此我们就可以给舵机输入不同的信号,来控制其旋转到不同的角度。
6 V* ]4 m) p; g1 P% [$ E舵机接收的是PWM信号,当信号进入内部电路产生一个偏置电压,触发电机通过减速齿轮带动电位器移动,使电压差为零时,电机停转,从而达到伺服的效果。简单来说就是给舵机一个特定的PWM信号,舵机就可以旋转到指定的位置。
7 A5 J, D. T+ L5 p+ V舵机上有三根线,分别是GND、VCC和SIG,也就是地线、电源线和信号线,其中的PWM波就是从信号线输入给舵机的。
0 M. C* l/ P2 c: i
一般来说,舵机接收的PWM信号频率为50HZ,即周期为20ms。当高电平的脉宽在0.5ms-2.5ms之间时舵机就可以对应旋转到不同的角度。如下图。
" P9 @" {- Y' Y7 {6 m
2 R( `/ i% y) v8 Y5 `
6 B! ^1 L; l* N6 i0 {3 c 9 u+ ]8 O# j A' V+ ^8 s
那么我们如何使用stm32给舵机输入信号,让它听从我们的指挥呢?$ T: x; ?$ S z7 }8 x
想要输出PWM信号自然就得用上TIM定时器,而基本定时器没有PWM信号的输出功能,所以只能选用通用定时器和高级定时器。对于初始化这些外设无非也就是那些套路,我总结为如下几点:1、开启该外设的时钟2、配置初始化结构体(如果有对应的GPIO还需要初始化该GPIO)3、调用结构体初始化函数4、该使能的使能
3 m4 o' m& [, u对于TIM来说初始化结构体有两个,分别是时基结构体和输出比较结构体,除此之外还需要做的是先选择具体开启哪条输出通道,我选择的是TIM1(高级定时器)的CH1(通道一),对应的GPIO是PA8。我还初始化了通道一的互补通道PB13,为了更加方便测试。下面是初始化部分的代码。
+ U* V% P4 M* @3 x; N
- <font color="#000000"><font size="3" face="Tahoma">3 Y6 O. R& Z- Y0 [, S
- static void TIM_GPIO_Config(void)( }0 L2 N! P5 Q6 T5 o1 r
- {
- i1 @/ y$ N3 h! v. e( H4 [ - GPIO_InitTypeDef GPIO_InitStructure; q9 W% W* t5 g) r7 E. m
- 8 f w. K* U" L' ~2 U, m$ ?
- // 输出比较通道 GPIO 初始化3 I% I1 g+ O- X/ R3 e
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);! Q) f2 N5 Z5 |
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;+ S6 X; r3 h8 c
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
9 L9 ~9 B$ ]# ?& ]& r2 G; G - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
% a( B! z+ c. z3 |; L. W0 ?5 `& x - GPIO_Init(GPIOA, &GPIO_InitStructure);; W( ~* f: R% I Q$ {
-
2 M1 K* R( C, x6 _( d - // 输出比较通道互补通道 GPIO 初始化' C1 G/ ^1 Q h/ }/ ^: T
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
# X+ J4 ~) m+ |8 P g: C1 ?1 J+ p' B - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;* O2 i' H$ w. F5 |& t
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;0 O5 t4 e" G; b
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;5 w; A P: y4 [- |8 c9 C# R1 `
- GPIO_Init(GPIOB, &GPIO_InitStructure);
' Y# g' N. y3 Q9 e; L( e v% q& V/ L -
% l% h* Q1 C% b& I* C' x- M1 Z - // BKIN引脚默认先输出低电平0 v* N" e9 F8 z, C2 @) n$ N
- GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT,ADVANCE_TIM_BKIN_PIN);
" e+ V9 I6 K/ I/ U - }* b! m1 t" I. x% x! R# ^2 O
-
& z$ X6 V# Y: B3 @ - static void Advance_TIM_Config(void)8 U; ?4 \6 ], Z( F; `* C
- {: a9 w) z+ z4 T7 ?- h" ^( I
- // 开启定时器时钟,即内部时钟CK_INT=72M0 X0 s |- G+ Z1 ^( K5 Y& y
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);$ C/ T% m c# N) j- @9 V
-
) x3 u: ^9 R; V' d {2 C7 \8 Z - /*--------------------时基结构体初始化-------------------------*/
9 w7 X1 I+ c! s3 w - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
2 t% D+ |& n( I7 ]1 o- V - // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
- K6 P2 q1 B& w1 G, A- z9 Y/ E7 u - TIM_TimeBaseStructure.TIM_Period= (200-1); " r$ f, R5 D. C, R8 R/ s: p' M
- // 驱动CNT计数器的时钟 = Fck_int/(psc+1)& w* S2 H5 p: w
- TIM_TimeBaseStructure.TIM_Prescaler= (7200-1);
6 N n1 |. U' ^9 y& G. P8 v' h - // 时钟分频因子 ,用于配置死区时间,没用到,随意$ e( o0 j$ @; r3 _6 b0 V1 ] _
- TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
" N$ J ^+ X7 g0 t& o L. f: s - // 计数器计数模式,设置为向上计数; o; P8 P4 r& \. `
- TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; V8 I* h$ ]# W3 v' a2 E8 ~
- // 重复计数器的值,没用到,可以随意设置
# n9 M* O8 }/ n8 n6 K, i. o. p - TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
5 T; T3 u; `2 y& V1 e, @1 X7 E$ w! ^ - // 初始化定时器
}0 F- ]! u$ J - TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);; K M; F; c9 j5 G7 r
-
$ h9 }" G1 S" D& m7 { - /*--------------------输出比较结构体初始化-------------------*/ 2 { H* W) v9 C% K
- TIM_OCInitTypeDef TIM_OCInitStructure;
% p, i$ j1 e& S% `# L - // 配置为PWM模式2( P, e& Q; `; k& k5 b, I3 ?
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;% e: T( z2 [% `2 D' d# S& G! O8 w
- // 输出使能
/ q0 n- X9 D) Q9 F5 |' k7 K - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
X$ W/ X: b. d; J) Z# t - // 互补输出使能( J6 r/ y& b7 |9 h9 {7 a
- TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; . K$ N+ B9 J; k& x
- // 设置占空比大小( U6 I5 ~1 d) i2 T3 g M
- TIM_OCInitStructure.TIM_Pulse = 0;0 ?# N$ s4 W2 ?' {* k
- // 输出通道电平极性配置 S4 ^! S' C9 O# U' I
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;. A- a& G- @- G L) R* M7 q4 f8 t
- // 互补输出通道电平极性配置 m1 q/ @4 D8 e& D( D ^& r
- TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
# T$ N/ Z! ?' o* G% o7 z% r - // 输出通道空闲电平极性配置* c2 @& @( c) x0 z$ v& q' m
- TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
# _1 d* A# \4 Q3 |! o$ C$ `' b - // 互补输出通道空闲电平极性配置; z- V) w; l1 L6 [3 _
- TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;- C$ M# z1 w7 L' E, p: F
- TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);! L% v1 V& j4 B3 M8 \: K
- TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
% f; O0 Z5 X# z" Z -
: f$ z3 l2 H8 g$ M - // 使能计数器- D3 C) m! q( g) t" e4 U4 R
- TIM_Cmd(TIM1, ENABLE); ( E6 K; k# v8 ?1 i/ A6 D
- // 主输出使能,当使用的是通用定时器时,这句不需要4 |! Z" O# Z! k/ ~1 o' ]- m* \
- TIM_CtrlPWMOutputs(TIM1, ENABLE);& a2 J" n8 \3 m
- }9 B3 N7 \: l% m" ~
- $ j0 R! M" _8 j7 C& Q
- void TIM_Init(void)
( @8 H$ ^: f8 y* Z$ C - {
! S# {/ W$ k3 e' s- s3 M, ^7 q7 r - TIM_GPIO_Config();
8 f4 T' H% s/ a2 E% Z3 y - Advance_TIM_Config();& L+ F# g5 P E+ b+ w0 p# _+ c/ \
- }</font></font>
复制代码+ `5 m; _- c+ a9 D
在代码中要特别注意的是时基结构体的TIM_Period(自动重装载寄存器值,简称arr)和TIM_Prescaler(预分频寄存器值,简称psc),因为这两个决定了输出PWM信号的周期。具体的周期计算公式为:周期=(arr+1)*(psc+1)/CLK。其中CLK为计数器的时钟频率,我的是72MHZ,也就是72000000。最后计算结果单位为秒,结果为0.02s,也就是20ms。这样的配置就是为了让输出的PWM信号达到前面说到的舵机要求的20ms周期。 ' z9 V% s/ T( w: Y
在初始化完成之后,就可以在main函数中实现信号的输出了。
/ h9 T) \% j' S) ]% T$ k( \8 U, ]* W1 W
@% U6 d$ D7 O0 l) U. e) J
前面说过,在周期20ms的PWM信号中,不同的脉宽对应舵机不同的转动角度,在0.5ms-2.5ms间有效,因此我们可以在main函数中配置几个不同的脉宽。要注意的是stm32并不直接配置脉宽,而是通过配置占空比来配置脉宽的。 2 N9 Q' u, M# u% A9 S" K) y
配置占空比有个重要的函数TIM_SetCompare1(),具体用法如果不懂可以去看手册。main函数代码如下。
" B# a5 A; C+ \7 n* Y
- <font color="#000000"><font size="3" face="Tahoma">% m( ~1 B- [ S1 _# ?! A4 t
- #include "stm32f10x.h"" O" k" K8 E5 }# \% S ]; P6 A
- #include "bsp_Advance_tim.h". M ]( _7 g# x. R+ H' k
- #include "delay.h"
/ e2 I7 X* s a' ?1 P% H: {/ P - 6 |" S- n- E5 O% ], f% m. x, n. c) o( ~
- int main(void)2 u2 V! L: f; x$ g5 r( s( f
- {, R! U8 [, |+ B
- int delay_time;0 {7 r0 L7 @3 i! V; p
- delay_init(); //延时函数初始化; K/ @' K* T( `1 L" ]3 t+ X
- TIM_Init(); //定时器初始化
* i' V& u: f! y: P i% p - , u! q, E1 \. A3 \: z+ B
- delay_time = 500;! [. A& U9 W# u1 e
- while(1)
; U5 y2 Q* L% H( _ - {: s4 S. d6 V* @! `# ?; ]$ @9 n. i
- delay_ms(delay_time);
, j1 }& \" Y! k5 c - TIM_SetCompare1(ADVANCE_TIM, 175); //对应180度! c" e! Y5 x" n$ X# o4 G
- delay_ms(delay_time);
) K) r- ?5 j- {- s! n7 r - TIM_SetCompare1(ADVANCE_TIM, 180); //对应135度
! {! O- R" `$ B3 r( J6 r& a- e$ X - delay_ms(delay_time);- r- d" q. F9 j; c
- TIM_SetCompare1(ADVANCE_TIM, 185); //对应90度
+ ?4 ?0 C8 G% s1 F, e5 P& K - delay_ms(delay_time);
* q/ M* F) t) s: Y8 Y" \ - TIM_SetCompare1(ADVANCE_TIM, 190); //对应45度
8 r a2 W; {) ^( B! i6 m - delay_ms(delay_time);/ g( I* g- d8 s' @% {( C- o9 y6 {
- TIM_SetCompare1(ADVANCE_TIM, 195); //对应0度
7 R. L* _! Z2 c7 t' n8 W - }- C/ x) i$ V9 U/ I4 z% o/ \
- }</font></font>
复制代码
# @' c1 q5 E( i0 o2 F
如果在定时器初始化时TIM_OCInitStructure.TIM_OCMode配置的是PWM1模式那么main中的占空比就依次为25、20、15、10、5。在你们自己试验时,可以将占空比设置成各种不同的值,看看有什么不同的效果。
& y* G: f# Z, j 在这补充一点如何连线:可以用杜邦线将舵机的电源与stm32的5v或3.3v引脚连接,将地线与stm32的GND连接,将舵机的信号线与stm32的PWM信号输出引脚连接。$ }! M x/ H1 t8 [" g
$ @0 ^& F% l3 G4 G
) i$ V, Q4 _% ?# J
9 m( l; E% V" {. F4 j- W
|