最近学习了stm32,就想用它来控制舵机,然后写下这篇文章分享给大家,如果有理解不到位的地方欢迎大家指正。(我使用的是stm32f103ve型号的开发板,即使和你的型号不同,也有参考价值) ) b2 ~8 f, d o6 [$ ]" K9 f( _
想要控制舵机的转动,首先你得知道舵的工作原理。' h* f* S8 D3 N$ w9 F# `& _* y
舵机的主要组成部分为伺服电机,所谓伺服就是服从信号的要求而动作。在信号来之前,转子停止不动;信号来到之后,转子立即运动。因此我们就可以给舵机输入不同的信号,来控制其旋转到不同的角度。
# L( N# q5 ~- N& A0 ?& G; F" q1 C# G舵机接收的是PWM信号,当信号进入内部电路产生一个偏置电压,触发电机通过减速齿轮带动电位器移动,使电压差为零时,电机停转,从而达到伺服的效果。简单来说就是给舵机一个特定的PWM信号,舵机就可以旋转到指定的位置。
4 A& K- I1 s' R% o6 y/ b
舵机上有三根线,分别是GND、VCC和SIG,也就是地线、电源线和信号线,其中的PWM波就是从信号线输入给舵机的。 - v& F- ~: N% Q8 y! @
一般来说,舵机接收的PWM信号频率为50HZ,即周期为20ms。当高电平的脉宽在0.5ms-2.5ms之间时舵机就可以对应旋转到不同的角度。如下图。
* X1 U& K- D: A, a; p) q1 ~
6 h8 T" S) G' \/ ]
" j# m7 E9 B ?
0 }( ^4 T: I3 t" J: b
那么我们如何使用stm32给舵机输入信号,让它听从我们的指挥呢?
) a) q4 u! s! r, }想要输出PWM信号自然就得用上TIM定时器,而基本定时器没有PWM信号的输出功能,所以只能选用通用定时器和高级定时器。对于初始化这些外设无非也就是那些套路,我总结为如下几点:1、开启该外设的时钟2、配置初始化结构体(如果有对应的GPIO还需要初始化该GPIO)3、调用结构体初始化函数4、该使能的使能
7 ^/ K5 g4 P! j8 U( I对于TIM来说初始化结构体有两个,分别是时基结构体和输出比较结构体,除此之外还需要做的是先选择具体开启哪条输出通道,我选择的是TIM1(高级定时器)的CH1(通道一),对应的GPIO是PA8。我还初始化了通道一的互补通道PB13,为了更加方便测试。下面是初始化部分的代码。
, f7 ^9 j- M7 G
- <font color="#000000"><font size="3" face="Tahoma">+ g. M/ X1 N# M9 I* @: Z
- static void TIM_GPIO_Config(void)
, o) g/ H# P, E& F0 T8 f! P7 b G: g - {/ w5 q3 }4 @* w* d( O& Y( \
- GPIO_InitTypeDef GPIO_InitStructure;* z( p) E @% [2 ?1 T% F% a* q
-
# j' f+ k* m! x; e- V% m" s0 q - // 输出比较通道 GPIO 初始化+ w y& [- }, a/ ?& b5 d
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
N' B. ~4 C: {# v) w( z Y - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; N% c6 E: m, j9 J9 ~
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
, m4 s( E# {4 ] `2 x" b/ x- { - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
6 O9 _% z5 Y/ {# _ - GPIO_Init(GPIOA, &GPIO_InitStructure);
. y: w) s1 ]+ p9 K! S* r; Y -
0 I. p5 J4 x4 x1 K+ } - // 输出比较通道互补通道 GPIO 初始化. f2 c/ j9 k. P
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);% E3 k& L0 x8 G; p5 z: A/ h* c* q" o
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;0 A* G, h) J: \$ U
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
5 M3 k0 _* r: R# \% l - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
& y8 \3 u7 N1 z& B2 N - GPIO_Init(GPIOB, &GPIO_InitStructure);4 }& d3 R9 o# G" p8 b) A: S
- ) d8 c" W$ L' u
- // BKIN引脚默认先输出低电平3 U: R! e- N! q. \4 E8 K5 ^% H' J2 K$ W
- GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT,ADVANCE_TIM_BKIN_PIN);
4 O- o/ i( L/ o! L+ C - }$ ^) |6 k1 @& m" U% A- M9 p% T& G. B
-
/ c+ |3 u) W/ v7 W9 I u - static void Advance_TIM_Config(void) Z8 [' G. d1 `4 m' Z% S
- {) @2 i. c0 p7 K z; L5 M7 j9 S
- // 开启定时器时钟,即内部时钟CK_INT=72M
- T; `) M& d7 x2 _, @4 D - RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
. M/ v$ ?0 C5 E2 Z - 0 ?# j5 y; Q; W0 c( @- S
- /*--------------------时基结构体初始化-------------------------*/
% I) j) ?! K) y9 s/ ~ - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
1 P) Z! `/ J2 r5 n9 ]# L - // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断# a1 F- E8 W4 {+ S2 M" o
- TIM_TimeBaseStructure.TIM_Period= (200-1); & h- Z+ L3 U/ b" f! j
- // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
: o* N2 [1 B1 G - TIM_TimeBaseStructure.TIM_Prescaler= (7200-1); 6 X& Y$ A: R$ s" c
- // 时钟分频因子 ,用于配置死区时间,没用到,随意
+ x* R9 B P4 ~! Z+ ^& g - TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
1 f! ~* A# |% O% D3 m' p - // 计数器计数模式,设置为向上计数: D$ k5 e0 O9 M8 a/ v, s+ z! D
- TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; ! j P2 h4 l! z) _# ?3 \' I
- // 重复计数器的值,没用到,可以随意设置
6 o, G; K0 N4 b' g! ` - TIM_TimeBaseStructure.TIM_RepetitionCounter=0; % j9 G8 i0 z+ B8 F8 h
- // 初始化定时器
& j! j+ w3 p+ i1 v! s - TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
; D* W9 p# d# O. w& i -
, M7 q, z6 U0 S2 j8 Y* M' x - /*--------------------输出比较结构体初始化-------------------*/ % L& q, T4 c5 q
- TIM_OCInitTypeDef TIM_OCInitStructure;
- p0 r, ?8 D- E" }$ A, R - // 配置为PWM模式2
) v$ f) P/ D: ^% d - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
1 _' t' s# W+ E6 `; p- d - // 输出使能; f- Q( w+ Z( o5 e3 o
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
; M- d! z: L2 L/ F/ _ f, ] - // 互补输出使能
% D( \6 p# g" ^2 K/ k - TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; 8 G( r- {4 J: i' \0 ^) b, Y
- // 设置占空比大小
, G9 {3 ~* y t& J8 E6 e - TIM_OCInitStructure.TIM_Pulse = 0;6 R3 q0 C. S/ a4 F
- // 输出通道电平极性配置
/ r# _- N% R% j5 g7 k - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;: [! D) L* I2 e/ O2 B0 C
- // 互补输出通道电平极性配置
5 G' n1 V5 y9 k( f - TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; Q& d, i% @- y$ j: j* T; j* E; W
- // 输出通道空闲电平极性配置2 _7 {! p7 O4 ^1 @; }/ ]
- TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
- z" G$ `+ U$ D. n7 y& z3 S0 ]8 k - // 互补输出通道空闲电平极性配置
: @: t( m9 r7 K - TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
/ I+ o& v; h( k) x! O8 w - TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);
& D' F: [3 C4 t6 \7 P8 x; e' z' v - TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
3 G" d# U/ u' K$ {5 H* }, `( O - 2 h9 f4 B9 x) x4 e8 _
- // 使能计数器9 P) `1 L& X* P4 T
- TIM_Cmd(TIM1, ENABLE);
2 A0 r" N2 o+ v3 V* Q+ [ - // 主输出使能,当使用的是通用定时器时,这句不需要. l1 U+ |! d- I4 J+ O8 q
- TIM_CtrlPWMOutputs(TIM1, ENABLE);' k2 J& m9 {( u" x5 }' [' N
- }' D% ?. n l1 D# N# R4 D6 C
- . T) l& g% | f; `# \, n4 M
- void TIM_Init(void)$ G5 N; N, P+ J# B* v
- {
( O8 d+ L3 `6 U0 i - TIM_GPIO_Config();; l) d( ]0 P5 |3 U+ E E# ^( n( X% X
- Advance_TIM_Config();
0 B# s1 L1 s' `- M3 y - }</font></font>
复制代码$ [5 X B+ P, j8 \) d4 V
在代码中要特别注意的是时基结构体的TIM_Period(自动重装载寄存器值,简称arr)和TIM_Prescaler(预分频寄存器值,简称psc),因为这两个决定了输出PWM信号的周期。具体的周期计算公式为:周期=(arr+1)*(psc+1)/CLK。其中CLK为计数器的时钟频率,我的是72MHZ,也就是72000000。最后计算结果单位为秒,结果为0.02s,也就是20ms。这样的配置就是为了让输出的PWM信号达到前面说到的舵机要求的20ms周期。 3 u& K- R( N$ M5 H2 L1 r" _
在初始化完成之后,就可以在main函数中实现信号的输出了。9 I" V2 U, [) F# W4 Q+ I( M
6 k0 P! T' P% S ]! {; H s
3 O' e& _6 Y6 j8 U; [
前面说过,在周期20ms的PWM信号中,不同的脉宽对应舵机不同的转动角度,在0.5ms-2.5ms间有效,因此我们可以在main函数中配置几个不同的脉宽。要注意的是stm32并不直接配置脉宽,而是通过配置占空比来配置脉宽的。 ! ]9 Y# H, G) r. I6 Q% R4 e* z
配置占空比有个重要的函数TIM_SetCompare1(),具体用法如果不懂可以去看手册。main函数代码如下。 ' L, o2 R' H6 n9 J: }7 t" ~- O5 H
- <font color="#000000"><font size="3" face="Tahoma">- j9 t$ B5 o; n. [) ~
- #include "stm32f10x.h"
# S# F7 x+ k# I! s" x - #include "bsp_Advance_tim.h"
: K: d6 c8 r) F, e9 f6 h6 j - #include "delay.h"+ I ^2 M2 I. q9 V
-
" K3 Z2 B9 V% d - int main(void)% N3 }- l: [6 e1 a, {' |; [
- {
, l) x8 B2 v" t' [5 B$ h1 @% @6 K - int delay_time;4 d% ]" K* q O8 }9 h. Y
- delay_init(); //延时函数初始化
+ z6 b5 J! R0 u9 A - TIM_Init(); //定时器初始化
( V5 b: B. k% ~# p( X -
0 }6 C! d5 M% f* B - delay_time = 500;
2 |& G! c7 X- n L% H; V$ k - while(1), c) [ o! i2 Y# F1 @9 F
- {
0 C5 j" ?+ ~1 }- \ - delay_ms(delay_time);' ]' ]# z0 a J# Z5 e7 i8 Q
- TIM_SetCompare1(ADVANCE_TIM, 175); //对应180度0 v$ |. G: u+ ^9 Z I2 \
- delay_ms(delay_time);
; Q( K1 E4 g- I- J2 M9 C - TIM_SetCompare1(ADVANCE_TIM, 180); //对应135度
5 @9 h7 r* e9 Q6 n - delay_ms(delay_time);6 s, s% I2 [ D6 T0 C) ]
- TIM_SetCompare1(ADVANCE_TIM, 185); //对应90度5 _/ R* e6 i) b
- delay_ms(delay_time);
4 c$ M" Z2 c; r! S$ c - TIM_SetCompare1(ADVANCE_TIM, 190); //对应45度/ `* A; I5 o9 [4 _1 e9 ~) H8 L
- delay_ms(delay_time);
7 G8 R" i" G! ~ I: @ - TIM_SetCompare1(ADVANCE_TIM, 195); //对应0度
2 [1 p& J9 U: {, I5 ~+ |* w - }
8 B( T# f. r3 O! {# {( w6 D& F - }</font></font>
复制代码
2 A2 h. A& h( H- C0 t3 Q8 s {
如果在定时器初始化时TIM_OCInitStructure.TIM_OCMode配置的是PWM1模式那么main中的占空比就依次为25、20、15、10、5。在你们自己试验时,可以将占空比设置成各种不同的值,看看有什么不同的效果。
8 o8 U. A5 Z3 Y3 ]$ { 在这补充一点如何连线:可以用杜邦线将舵机的电源与stm32的5v或3.3v引脚连接,将地线与stm32的GND连接,将舵机的信号线与stm32的PWM信号输出引脚连接。
0 x n6 g3 i5 T) S# {# W$ ~3 t
X, t. W$ I# u& H
9 H" N/ q* U+ j3 h1 P8 B* n7 t! A! H8 _, Y
|