12.1. PWM简介
- }; e- v9 `" I0 l3 S
6 g. c5 O, ^. aPWM全称为“Pulse Width Modulation”。中文翻译为:脉冲宽度调制。脉冲宽度指的是 脉冲持续的时间,既高电平或低电平保持(持续)的时间。而PWM通俗的说就是人为的(通过微处理器)去控制电平高低保持的时间。这里引出一个新名词,占空比:在一个脉冲的循环中,通电时间相对于总时间所占的比例。
! m- W& o, m0 t5 {/ |/ V( l
7 L+ x4 @7 w) {STM32 的定时器除了 TIM6 和 TIM7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出!+ I) V7 [' n) @" k; k% I
G* [+ d" x! S7 \ s. S; J. Y& B; A0 x5 ?9 s1 t
12.2. PWM相关寄存器
! t. i" A1 Z4 g$ w2 v u" f2 H+ P4 p9 U2 q- L
除了定时器章节介绍的几个寄存器( ARR、PSC、 CR1 等) 外,还会用到 4 个寄存器(通用定时器则只需要 3 个),来控制 PWM 的输出。这四个寄存器分别是:捕获/比较模式寄存器( TIMx_CCMR1/2)、捕获/比较使能寄存器( TIMx_CCER)、捕获/比较寄存器( TIMx_CCR1~4) 以及刹车和死区寄存器( TIMx_BDTR)。2 ] b5 Y( l$ g' l
: W' s8 Q3 C$ H2 B(1)捕获/比较模式寄存器( TIMx_CCMR1/2)+ H# t5 U A/ e" Q, Z5 f; U% l
" t& N% N. x* \$ e$ _
该寄存器总共有 2 个, TIMx _CCMR1 和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制 CH3 和 CH4。/ L8 U. T9 _( x7 P$ c& ?6 T
7 B+ o, _6 ^, h
寄存器分了 2 层,上面一层对应输出时的设置而下面的则对应输入时的设置。模式设置位 OCxM,此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是 PWM 模式,这 3 位必须设置为110/111。这两种 PWM 模式的区别就是输出电平的极性相反。 另外 CCxS 用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。2 W5 h0 g2 M1 K) P7 S' c; x9 y
# z2 ~: y4 U ?, ^3 E* V$ H8 |
- /** f( x |; C6 L% z% I2 t
- * 没有重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PA6, PA7, PB0, PB1
8 f8 Y( X9 _# M* L5 L - * 部分重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PB4, PB5, PB0, PB1
/ g: y% L1 _: C( Z( T - * 完全重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PC6, PC7, PC8, PC9; F: F* R; |8 S: L: N/ h; v2 p$ ?8 C
- * 0 ]1 _; @7 D E/ y. ^
- * 110:PWM模式1 - 在向上计数时,一旦TIMx_CNT<TIMx_CRRx时,通道x为有效电平," \* L' k% v. X! e9 H; J9 c+ i
- * 否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CRRx时,通道x为无效电平,
8 B/ E% M9 V/ |# I& X1 G - * 否则为有效电平。
( p# m" i" V8 Z' l - *
4 l# R/ V. S2 R& j - * 111:PWM模式2 - 在向上计数时,一旦TIMx_CNT<TIMx_CRRx时,通道x为无效电平,
# {! c! X- E" C. ] - * 否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CRRx时,通道x为有效电平,
3 A4 x) Z& E+ t' n/ W - * 否则为无效电平。
5 t3 t& F0 n1 S$ y! v - */
复制代码
/ y4 O- ]; B" D* [+ W$ g _. r( O(2)捕获/比较使能寄存器( TIMx_CCER)
0 E3 C" @; u: k6 |$ ^: Z+ L" {3 [7 o0 `+ d& V0 x
这里只用到了 CC1E 位,该位是输入/捕获 1 输出使能位,要想PWM 从 IO 口输出,这个位必须设置为 1。
" B, j4 r* ?/ G* h9 {
- W: g3 t' g$ n4 v N( y [
% @6 r& D; w- J' f5 {( s& T- g9 u# {8 K# E) u. d7 F
(3)捕获/比较寄存器( TIMx_CCR1~4)
! D% w8 u- }9 F( D( ~/ b }2 h9 n: C2 x: R
该寄存器总共有 4 个,对应 4 个输通道 CH1~CH4。在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。! e$ C5 H. i& A1 r; y
X% K7 y& k, |2 ^8 q3 `
6 o% Y6 x/ X0 ]" P1 e
4 S: j+ D$ D L(4)刹车和死区寄存器( TIMx_BDTR)8 O4 H) @$ }& s$ v
) a# d3 G( O) \$ t# V; J如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器( TIMx_BDTR)。该寄存器,我们只需要关注最高位: MOE 位,要想高级定时器的 PWM 正常输出,则必须设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。
2 J; ^7 M* A% v) V! k1 G4 X" L7 s0 [8 i
9 H2 |: ?: T8 m. u; {' k6 {" b3 X4 m12.3. PWM波形产生原理
7 J% q& d5 m. w8 J2 p4 F& V6 E, s( ^6 y# e; V
通用定时器可以利用 GPIO 引脚进行脉冲输出,在配置为比较输出、PWM 输出功能时,捕获/比较寄存器 TIMx_CCR 被用作比较功能,下面把它简称为比较寄存器。
: W0 D7 F/ c9 X/ L4 X6 `' G; ^1 V# i' u; h4 z
这里直接举例说明定时器的PWM输出工作过程:若配置脉冲计数器TIMx_CNT 为向上计数,而重载寄存器 TIMx_ARR 被配置为 N,即 TIMx_CNT 的当前计数值数值X在 TIMxCLK 时钟源的驱动下不断累加,当 TIMx_CNT 的数值X大于 N 时,会重置 TIMx_CNT 数值为 0 重新计数。
q: q! g2 @; K" ?; l" e
/ M0 v/ [0 [- a" { G8 \/ v% V而在 TIMxCNT 计数的同时,TIMxCNT 的计数值X会与比较寄存器TIMx_CCR 预先存储了的数值 A 进行比较,当脉冲计数器 TIMx_CNT 的数值X小于比较寄存器 TIMx_CCR 的值A时,输出高电平(或低电平),相反地,当脉冲计数器的数值X大于或等于比较寄存器的值 A 时,输出低电平(或高电平)。
( T2 S% x. e& R3 @* A0 a( z; ^4 e5 @# {
如此循环,得到的输出脉冲周期就为重载寄存器 TIMx_ARR 存储的数值 (N+1) 乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器 TIMx_CCR 的值 A 乘以触发脉冲的时钟周期,即输出PWM的占空比为 A/(N+1) 。
; u; p( r w6 D9 Y% }& }! g1 ] ~9 s+ O* ] M7 S
7 _9 z( @3 b5 `, t, ^6 I" ~* n12.4 PWM配置步骤
- v* Z& m0 P+ ~- ~8 A
$ h' t$ b! R9 y. d. t1.开启TIM3时钟、GPIOB时钟和复用功能时钟! B4 m- Z8 ]2 q: N, Q
2.配置GPIOB5为复用输出
" X8 R& C% o4 l! @! a3.设置TIM3_CH2重映射到PB5上
^) u5 Y$ I9 g+ w. w D4.初始化TIM3,设置ARR和PSC
! H' a* ?9 W/ f% Z6 g5.设置TIM3_CH2的PWM模式, t9 R) g. v! \0 B
6.使能TIM3的CH2输出& {' I; A' j$ _5 L
7.使能TIM3
( N5 E# O; {; d7 d: ~8.在主函数中改变占空比完成呼吸灯
y4 p! Z2 G+ |, R! n8 o: z& `2 n" W: y& y" }' l0 ]5 Q9 c, q
4 c& S0 U& t$ u* w: M5 \! m7 d
12.5 定时器引脚复用功能映射
" z2 `9 h6 F5 l6 G
5 X$ P0 W- n( b
- {! ^: R3 p& I: J
/ M3 f3 }8 n1 `& @. M% ~- L1 w8 B
- S: M7 U( _, F1 f+ _7 E+ ~0 I; j$ q( J
L$ Q$ ?- a9 d0 J' F/ Y+ F/ a: F5 Y7 `
根据以上重映像表,我们使用定时器3的通道2作为PWM的输出引脚,所以需要对PB5引脚进行配置。注意:如果使用PB4当做TIM3部分重映射的CH1输出,除了要进行部分重映射配置外,还需要禁用JTAG!并在开启复用时钟后禁用JTAG!% X+ `, |! ?+ T& c. h, U
9 Z6 j' i' t7 c: l8 z- @. P! ?3 x
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁用JTAG,在开启复用时钟后禁用( f8 S* Z& l5 R+ l- B" C
1 a; g* J/ C/ N3 c! F7 U }/ ?! C" s, |9 s, X$ l
1. 新建两个文件,pwm.c 和 pwm.h
f0 D1 Y! E" D q2 k4 z3 F( @$ g8 C) P- }( J& f( a" @# _ f
9 d% j: L0 z% k: O/ a, o) t: e, n) q1 R
! l8 f# L; }- S5 M7 V
2. 在头文件 pwm.h 添加下面代码:
& O; B: K* r @5 ^$ ^
/ H! ?$ m3 @7 J9 P" w7 t8 V/ y0 j$ {; l- #ifndef _PWM_H
" Q5 a* w; d- w; t+ M, j7 m - #define _PWM_H
* R' p K5 O% a# y! T0 I* Z9 G0 ? - #include "stm32f10x.h"' Z' i( D+ a% f+ {- h0 V/ W. o
- O9 ^# e$ [8 @( o5 B" O
- void PWM_Init(u16 arr, u16 psc);
/ U3 ?) q2 I: V) B! U d" n - # b8 F+ v a! S* N7 W. O1 E
- #endif;
复制代码 7 S8 V- j7 s2 G: s# @+ {2 }2 H
' x2 A) }# g( n: ^* ?& R+ v7 {
/ C) q5 ?, B! O* V7 {! k
2 j7 ]) l6 G% G5 ]+ [# \% ?; ~$ l
3. 把 pwm.c 添加到工程中
0 X) t; g) i* f5 f- @7 [* s: b% N6 W" D7 G' |
% \$ y W; t5 |: [& O9 [2 P% _! r# X# Z
: C9 v/ U/ z+ Y- S1 c1 W4. 在 pwm.c 中添加以下代码:) D2 o6 `" [& Y3 ]& ?& E3 `9 x2 z) k+ j' _
1 l" z3 x! E8 m% M
- #include "pwm.h"
) l* }- @% h2 Z - / e$ f: Y) W6 f, e9 p& H9 @
- void PWM_Init(u16 arr,u16 psc)7 x$ A) K5 t# U0 s$ P2 B, X. j6 S
- {
+ d5 |) ~ e& G7 S9 [ - GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体" h' G+ [" O0 a+ m
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIMx定时器结构体% N( P7 h- |4 y$ H4 p
- TIM_OCInitTypeDef TIM_OCInitStructure; //定义定时器脉宽调制结构体
- `8 v2 {4 n. v* _; _8 P% R! }4 F - + s2 R. n0 h" W$ v9 n% E( r, p: q+ `
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能TIM3时钟- Q2 t/ j( C9 Y2 b# \
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);//使能GPIOB时钟和AFIO复用时钟
+ d: n5 i5 J. \3 Y4 ]. ^: Q( a0 G - . y* g, p" n# w( \
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //TIM3部分重映射 TIM3_CH2->PB5
3 ^7 r3 Z9 x* R5 P - ( `. E7 E; X6 w3 P4 [( P, Z
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
4 n3 \ e4 n) v" V2 N& Y! ], [! [ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
, U8 X+ L. @: v1 s+ e! F6 K - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //配置输出速率; o8 E2 s- b1 K+ X& M. |6 V
- GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB6 {% q1 [3 X- i5 U$ e3 f
, m2 c9 J2 k3 {3 z& N9 `2 h- TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值 arr=value-1
' c [7 r" s9 E- R* s - TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置预分频值 psc=value-1. V1 y" B' O+ z9 u! C! ^8 `& z
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim; ^; j9 R" e+ T% l& O4 y
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式. N7 k. q" n2 s3 W/ a6 y; D
- TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //初始化TIMx时间基数
% Z. {- x' I5 Q7 C6 J
) E) [8 n7 B! l2 s- //初始化TIM3 Channel2 PWM模式
! f9 V/ G0 m! v" D, ?' k8 q - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
3 _$ f: c4 _( H; \# h8 ~/ q - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能比较输出
/ o2 |9 m! u1 O4 O& o# Y - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高0 M* u4 @, }* c ` a( L
- TIM_OC2Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC20 G& g, C4 A/ L! \9 e' q2 Q
- 3 z; z, ^; G' ?. |4 l* W9 Z/ a* L- v
- TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器, s) {$ x5 C, ]' F
- TIM_Cmd(TIM3, ENABLE); //使能TIM3
& n. F7 M- U# ] - }
复制代码
0 F8 J3 k1 _6 B" ~& y7 }
, i. s- \, E5 v
2 M" `" R& H7 z {/ n& d0 _: l
$ @& e1 f/ D/ S0 N
( w E& H: V, U# z+ b# I# O8 M
. q: L# Z8 p8 m P. _0 p5. 实现PWM呼吸灯功能
* V$ R/ @& O+ Y E6 ]
9 `0 j! t6 p) p1 i# Y1 I- #include "stm32f10x.h"" ?- s* M: ~) k' ]
- #include "delay.h"
) z* j4 B, F/ X9 k _* Z X: e7 r0 T - #include "led.h"
/ o1 l' D, B) }8 r: A5 \ - #include "tim.h"
* U8 w( c$ w9 H5 } - #include "key.h", `# n! X" a. V: ~6 l
- #include "pwm.h"
. f; f" W8 A I1 e( ?% j) M( n
# L: m# }' A& D% q5 ]/ H# K' o& `- int main(void)8 U1 L9 y* n, j( k) q4 n8 d- `
- {
$ ~! Q9 `2 W8 l$ Z$ ]; Y- A5 z4 T+ f - u16 pwmValue = 0;! C# Y; s( \, E+ O
- u8 dir = 0;
# ?# B* A. ~, v& c& x) `( r
! f. W: m) b8 q: C- l( _7 s5 L- delay_init();+ q6 V+ w7 }+ r3 T; f7 Q
- PWM_Init(999, 719);* o$ V' n9 Z5 T0 z
- 2 c _0 _/ N2 E1 h/ x- ~# q
- while(1)
% N2 B0 f* ^) p - {8 n$ t8 I2 X; ?& ?
- if(dir) {8 j9 }* n% w) l' o
- if(pwmValue > 550) TIM_SetCompare2(TIM3, --pwmValue);
6 K) P6 z0 V1 _8 k4 R - else dir = 0;6 E. m! W ~& W3 S9 Y* O- @
- }
( H2 N1 d3 w- P: a - else {
- k4 p8 e; H# r: t - if(pwmValue < 990) TIM_SetCompare2(TIM3, ++pwmValue);% m; Y1 C0 t O5 O' Z: S6 c4 `
- else dir = 1;8 ]+ r7 L- Y; O( k) J
- }
+ T2 l4 h( L* i; D - delay_ms(3);
$ m7 [2 {; `1 w; r- A! w& K! h - }
3 b3 r% }1 d. G9 t3 v2 w9 { - }
复制代码 3 p! y ~$ G! z6 i) H
% {0 q+ n' T. ~! |5 L小提示:如果身边没有LED电阻面包板搭电路,可以用杜邦线连接PB5和PC13,同时PC13设置成开漏输出(GPIO_Mode_Out_OD),这样就能完成呼吸灯实验了(不瞒你说我也是这样干的)。
, B2 s5 G8 U( _0 z% [7 E* S- C) S' E% J4 V
9 q f1 }) ]% O: N
|