前言 i0 m0 t5 P6 I
我们这里拿STM32F103C8T6作为我们的芯片,通过PB9端口输出PWM,实现控制舵机。我们这里就拿180°的SG90舵机。5 X) S$ [1 m: F F
! |& Y0 v j: g! l" n一、PWM
* e4 I: v2 ~& gPWM是什么?9 n0 W% W% u! s: ?/ s) j7 ^
PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通过调节占空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比,例如方波的占空比就是50%.
/ N4 \- P, E0 u# v x3 e& }
: q5 ~/ k, Y3 `# i( j, XSTM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样, STM32 最多可以同时产生 30 路 PWM 输出!这里我们仅利用 TIM4的 CH4产生一路 PWM 输出。% z* C! o' f' E
* ~& u4 J6 ?/ M* q
( j" u" @+ y6 u: N1 T7 q
PWM的频率: 是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
$ T9 y) y* r# A( R' k* }* l也就是说一秒钟PWM有多少个周期4 s+ L( O7 F* A ?5 D8 D/ @! K) R, ^
# ?( \7 ?0 F: z( e单位: Hz
z5 }, b) r: l& Y) x7 ?+ ]/ ~4 ]
表示方式: 50Hz 100Hz
4 x$ p2 }2 }8 a# v' i& {
( w7 u* Y) W! Z) R1 c1 |PWM的周期:1 T2 b/ I; [: m! L
( ^' R2 z5 p( D& ]9 ^: Y
T=1/f
3 U9 Q, F# q0 r9 m) y& s
2 _' d" o( a: M6 F周期=1/频率7 M6 X& i8 l r5 r: ^9 q t
# q- F8 z* w( m0 p7 c9 E- {
50Hz = 20ms 一个周期& V3 o+ i8 w' T& C7 F0 \
: Y% K5 B* h, a& _如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
7 @- s# V& D$ P- k8 U% p3 |" ~# v$ WPWM占空比: 是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比,由于PWM周期为20ms,所以(以舵机会转动 45°为例),占空比就应该为1ms/20ms = 5%,所以TIM_SetCompare1的 TIMx 捕获比较 1 寄存器值就为200-200*5% = 190' z7 e, t: o( c% ?7 T$ Q1 S
$ x4 q W7 }0 i2 ?. k9 @单位: % (0%-100%)
6 i. x1 P9 c; [' ]9 f8 a) C: [ v/ |* {$ s
表示方式:20%
* R. f* n v& l7 c! x8 F# @3 d- F% t: k6 w
如何实现PWM信号输出?. P5 E+ A2 }) W& j8 F# v
<1> 可以直接通过芯片内部模块输出PWM信号,前提是这个I/O口要有集成模块,只需要简单几步操作即可,这种自带有PWM输出的功能模块在程序设计更简便,同时数据更精确。如下图,一般的IC口都会标明这个是否是PWM口;
4 i8 y; S& Z) x& }3 l3 Y& x% o+ _& ]9 ~5 k+ N( X8 x
1 j8 Y# }7 m; t- p( E" p! q
+ I d; K9 p" n3 a3 e0 U<2> 但是如果IC内部没有PWM功能模块,或者要求不是很高的话可以利用I/O口设置一些参数来输出PWM信号,因为PWM 信号其实就是一高一低的一系列电平组合在一起。具体方法是给I/O加一个定时器,对于你要求输出的PWM信号频率与你的定时器一致,用定时器中断来计数,但是这种方法一般不采用,除非对于精度、频率等要求不是很高可以这样实现。
* l/ G/ K" z' I X2 b) y5 a/ n, ~. Q0 M* {& ^: N+ B1 n
二、舵机
5 j2 W0 O* E$ }# R# O4 D- w& t什么是舵机?( L! Z- Z# }6 @' @/ |; w; }
舵机也叫也叫 RC 伺服器,通常用于机器人项目,也可以在遥控汽车,飞机等航模中找到它们。% t# u/ G* G# y5 E0 F+ v) @/ x! p
类似舵机这样的伺服系统通常由小型电动机,电位计,嵌入式控制系统和变速箱组成。4 f$ C1 ~% e7 ~9 T* u$ {( H
电机输出轴的位置由内部电位计不断采样测量,并与微控制器(例如STM32,Arduino)设置的目标位置进行比较;8 l$ g" j2 ~) s: c
根据相应的偏差,控制设备会调整电机输出轴的实际位置,使其与目标位置匹配。这样就形成了闭环控制系统。2 S8 Y* c/ x0 f3 s C
5 e. i2 ?6 Y. U+ w1 `
6 x/ L# \: l+ @ E5 A4 Q! o- z
# X3 m, ]% q/ X
控制原理/ [, T8 D: a1 O* }, O! o
通过向舵机的信号信号线发送PWM信号来控制舵机的输出量;9 _9 _' d) U& h
; I$ |8 G1 N8 H一般来说,PWM的周期以及占空比,我们是可控的,所以PWM脉冲的占空比直接决定了输出轴的位置。3 H" R- |. h0 u& a$ e( y
' i3 v2 E! {. c7 T下面举个例子;
5 p4 c; e4 N; q* Y2 \6 u P, R; h: P$ E+ d# ~
当我们向舵机发送脉冲宽度为1.5毫秒(ms)的信号时,舵机的输出轴将移至中间位置(90度);
5 b5 X2 |5 Y! k. e! z" v! {$ {, P8 f% n4 ?5 a8 r( P: ]) ^
脉冲宽度为1ms时,舵机的输出轴将移至最小的位置(0度);/ m. F5 |+ {, M# V
( ^5 P$ o; Q) g( e7 g* Q
脉冲宽度为2ms时,舵机的输出轴将移至最小的位置(180度);- s% J5 w( M: m
! v7 H$ D3 W0 S2 O6 `
注意:不同类型和品牌的伺服电机之间最大位置和最小位置的角度可能会不同。许多伺服器仅旋转约170度(或者只有90度),但宽度为1.5 ms的伺服脉冲通常会将伺服设置为中间位置(通常是指定全范围的一半);) X' s3 |# q, Y3 M, A
5 e5 a. F+ R; S
具体可以参考下图;
2 f+ z( o! {* o6 c8 Z' l* X
' Q8 m, [* N7 W# [; `
+ g4 r: G5 G: P) p$ ]6 n
8 B z) @" s v6 e/ G占空比 = t / T 相关参数如下:
' Y, a6 T2 A* |8 H
; U' V" t4 T" p) Nt = 0.5ms——————-舵机会转到 0 °) z+ f0 D2 O) l) h
t = 1.0ms——————-舵机会转到 45°4 i3 a: S; m' v/ [0 B% M
t = 1.5ms——————-舵机会转到 90°
% O* a1 a' e) ], It = 2.0ms——————-舵机会转到 135°! O3 t# L4 }! G
t = 2.5ms——————-舵机会转到 180°) Z4 c- R9 {: L' ]1 V D: }
0 y1 V# e( m) M" d因为:PWM周期为20ms = (7200*200)/72000000=0.029 C% v% i; D# n/ n5 U& }8 k
所以:TIM_Period = 199,TIM_Prescaler = 7199,就是我的程序入口参数
" t: J8 J z% d( T# e( D( D5 h0 D) R, ? t
参考代码
! W; S& k+ o7 ?$ I/ m/ k- V编写步骤:- F4 P' r, F+ i4 X) I$ L# W
* A7 n* C# ^5 d$ X' D3 E! q4 D
<1>开启 TIM4 ,GPIOB时钟,配置 PB9 为复用输出。- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器 4 时钟/ R" V; X/ y- o, W5 g
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE); //复用时钟使能
复制代码 $ _$ Y, Z- O1 L5 H: t0 l8 c% J
<2>初始化 TIM4,设置 TIM4 的 ARR 和 PSC。
, b, ?/ _4 |! s9 ^# I. {8 F0 h3 K! \! |8 ?( g/ f2 w0 @
- TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值 w: q/ G) G+ K& Y
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
$ t8 B2 ]2 @3 c2 {: a - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
o' Q: n, s* ]# w8 L/ c6 a2 d+ C - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式2 }. x& E% I9 d9 K% K$ ^) n, [4 P
- TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx 的
复制代码
# A0 v4 H2 o6 y+ C# P) l: A" x5 ?<3>设置 TIM4_CH4的 PWM 模式,使能 TIM4 的 CH4 输出。, K; X0 }3 W( e$ P' \# D" a5 u
/ R9 i# y1 o6 L) K1 T! p! k8 B
- void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
复制代码 6 E4 V$ e0 ~7 J9 |& `& e
看看结构体 TIM_OCInitTypeDef的定义:5 c/ a0 t: m& a% P
, E( I9 B9 [' {' Y' Q- typedef struct
% ?) Y5 a1 H! l0 F/ S: M - {4 X4 q/ d2 ^) p
- uint16_t TIM_OCMode;
! Z. m& _* ]9 G* @- p - uint16_t TIM_OutputState;$ N. g* m1 k. j7 @% c
- uint16_t TIM_OutputNState;
8 A1 q) c9 k5 H$ S! u - uint16_t TIM_Pulse;- ~; F5 D+ {4 l+ V1 x8 Z
- uint16_t TIM_OCPolarity;
& k L8 v, K/ @, n# v3 k m5 M - uint16_t TIM_OCNPolarity;
( T1 j! g. y$ t0 F% U - uint16_t TIM_OCIdleState;3 ^- }; j9 p1 T' U- K
- uint16_t TIM_OCNIdleState;1 R( W; C& v1 ?& x
- } TIM_OCInitTypeDef;
复制代码
, ?( I; n" ]7 G5 X S* D {我们来看看里面的参数:
" n E" H) L1 P" Q. b5 b2 X
" ~6 |+ @& V& Q0 o6 `参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。5 y: e, H9 K% d$ x" t* ^
参数 TIM_OutputState 用来设置比较输出使能,也就是使能 PWM 输出到端口。
, J$ h7 j2 v6 Y参数 TIM_OCPolarity 用来设置极性是高还是低。
, }/ ^% ^, t( u其他的参数 TIM_OutputNState, TIM_OCNPolarity, TIM_OCIdleState 和 TIM_OCNIdleState 是
8 ^2 I8 m( W& P1 f' k, \! J9 S高级定时器 TIM1 和 TIM8 才用到的。
9 n2 C5 A% m+ G- t
$ C( C' }3 l ~8 f" ]# e配置如下:; Q$ k# G- B2 Z: c- t0 _9 h
, J/ M6 N( ~- v+ l) _- TIM_OCInitTypeDef TIM_OCInitStructure;
' s" V" z) A8 O7 n, @ - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2$ `* }( L0 c* F5 Z
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能5 r4 _* L- i6 P
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高$ E2 o. z! Y( n
- TIM_OC4Init(TIM4, &TIM_OCInitStructure); //初始化 TIM4 OC4
复制代码
6 J0 \; L$ ~/ y<4>使能 TIM4。
; ~2 |% R3 j: r; G) y( \. u4 a3 r
- TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
4 ^/ D, |! R/ A% C - TIM_Cmd(TIM4, ENABLE); //使能 TIM4
复制代码 ) r) e8 g$ B( @+ r
<5>修改 TIM4_CCR4 来控制占空比。, M/ x. I1 j' n$ S' @5 a( K; C5 e
! a! l% ~5 M+ L* ~- void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
复制代码
' T( u- N `# i! `最终代码段:& }) g8 X# X3 M
# v/ b4 Q6 ~" f
- //time.c5 f0 q8 T/ x M! j' f8 H
- #include "time.h"3 W7 U% Y ?2 _+ _3 ~
- #include "stm32f10x.h"
) _8 k B: h$ i1 b - #include "sys.h"+ S" D- w+ T3 d) C8 u
- % _/ h4 Z9 z, v; r |* _" F2 e3 e
- void TIM4_PWM_Init(u16 arr,u16 psc)
8 t$ S: q2 ~* R% u. y p' P - {! G: Q! X. L; h: I& q, I; l5 A, s/ s
- GPIO_InitTypeDef GPIO_InitStrue;
# ~# S: {$ f+ C) m& s# Z - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
" Q. i8 p! X- e# } - TIM_OCInitTypeDef TIM_OCInitStructure;+ D& _/ a" S8 k I4 w8 R
- 2 ^+ x/ r8 s0 [6 W4 G" G8 m# I" D1 V
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);: ]3 H0 W1 F( ~1 p6 _( Z) R
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
! N/ K# o3 S# @# A" V - / Y7 N# x: P+ B( p6 C
- GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;
0 w& f% z G( O2 K# G - GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;
# `4 c: ]9 h5 c - GPIO_InitStrue.GPIO_Speed=GPIO_Speed_50MHz;+ X/ N7 J E/ H2 v: ~' H
- GPIO_Init(GPIOB,&GPIO_InitStrue);
6 M+ n* B3 |; X% r5 o, {" h - 9 B K6 ~7 D( P, Q+ D7 m& j
- TIM_TimeBaseStructure.TIM_Period = arr;
( R/ y2 J, ]# M( [. U - TIM_TimeBaseStructure.TIM_Prescaler =psc;
3 ]4 r, j& g) _7 n2 U5 @2 r) n2 t - TIM_TimeBaseStructure.TIM_ClockDivision = 0; / t' c: M/ e+ P" S9 i; |2 G
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
1 C- {9 L2 }9 Z' ]1 o' x - TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
% I8 g, m6 H7 F) a4 Y - 0 F& a4 ^' J" `: l$ C# @2 I
- TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
# J2 M8 A% G3 s: X* r - TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; % T x$ u8 W; L
- TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
4 \# n* a7 E/ z4 D' Z) d' ? - TIM_OC4Init(TIM4,&TIM_OCInitStructure);, m4 _* l! D8 M- ~, L6 |9 b
- % W- Z$ }" u3 y2 K2 r l0 c
- TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
# H, e5 U5 J8 u9 j6 ]; \8 ~* k - TIM_Cmd(TIM4,ENABLE);
1 `7 G! u3 ~( N2 b- W - }
复制代码
( q7 c* \; x& }5 U/ S$ r下面就是主函数的代码:, Q6 p* f$ T, j; D' a2 D: E
1 o5 X5 ^2 u7 Y' P3 z- //main.c
% Y _' I+ Z' g# X/ } - #include "stm32f10x.h"( v( b' o, Q2 B! U5 x
- #include "time.h"1 V& k7 G* n0 W* |% k [7 [5 r
- #include "sys.h"3 A' T9 D4 j" n( B2 G1 ~0 s! r/ ?# M
- #include "usart.h"3 P2 \; [) t% I
- #include "delay.h"
5 Y( V7 y2 s6 `! G; j3 F" x
% K3 y) l6 Z8 A. T- int main(void)5 ^/ m9 c6 ^7 d+ H1 X
- {0 L$ q. Y" L6 r8 w( `
- delay_init();
" l7 P9 F( F2 b5 r - TIM4_PWM_Init(199,7199);# c4 b; B, p6 o: n, R2 O- G a
- while (1): F3 D( [6 O5 D+ {, t- g4 [1 T
- {+ B5 R1 P5 K) j2 z, [6 F3 _
- 3 v( |% G# |/ d, L S
- delay_ms(10);8 t# D g7 U& t+ v
-
& E& {& v/ }# T: h" j$ j - TIM_SetCompare4(TIM4,190);
( f( U& [. ?, w- R3 ~! V; N& E -
1 c2 B: f& }+ U' f - }
. O2 k; S2 @9 b: I+ B3 ?6 g! S! U - }
& d3 [+ f3 S! j1 p+ J% V; E
复制代码
m% T1 ?. [8 Z: v) W
4 p( U, S0 G5 @* I
# x2 k4 w2 ]5 M
% k$ q' `/ L- v/ L$ {: k: ]' h( X# G4 i
|