12.1. PWM简介
]2 |1 ~. [9 E! L" T3 K* @
6 Z) m! Y5 R7 B2 L" b& M# z$ r) VPWM全称为“Pulse Width Modulation”。中文翻译为:脉冲宽度调制。脉冲宽度指的是 脉冲持续的时间,既高电平或低电平保持(持续)的时间。而PWM通俗的说就是人为的(通过微处理器)去控制电平高低保持的时间。这里引出一个新名词,占空比:在一个脉冲的循环中,通电时间相对于总时间所占的比例。3 }1 ?8 Y3 F' J) D5 [4 t
" d2 K2 P Z R& \/ y9 s+ @8 T% b
STM32 的定时器除了 TIM6 和 TIM7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出!7 n, M) [" o. v9 e2 O
. {/ @. k2 e. E6 ?1 m$ E
4 k" w# S1 H% O4 C, f+ v12.2. PWM相关寄存器+ m0 A" ^( \ s7 ?2 a& `0 r
) J+ B) M, |4 i P8 a
除了定时器章节介绍的几个寄存器( ARR、PSC、 CR1 等) 外,还会用到 4 个寄存器(通用定时器则只需要 3 个),来控制 PWM 的输出。这四个寄存器分别是:捕获/比较模式寄存器( TIMx_CCMR1/2)、捕获/比较使能寄存器( TIMx_CCER)、捕获/比较寄存器( TIMx_CCR1~4) 以及刹车和死区寄存器( TIMx_BDTR)。$ j0 K- z. {" y( c: q2 B
6 S4 S1 ], f5 p% Y& W! r& d5 f(1)捕获/比较模式寄存器( TIMx_CCMR1/2)( h' ~( p/ R' {' ?; w1 s7 m& c
5 B$ e7 ?6 F. _/ u3 i3 B+ r6 W3 v该寄存器总共有 2 个, TIMx _CCMR1 和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制 CH3 和 CH4。
0 z% v: W, l3 ~7 D; Y9 d. z) G3 }" A4 p* b) x( U+ X6 C
寄存器分了 2 层,上面一层对应输出时的设置而下面的则对应输入时的设置。模式设置位 OCxM,此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是 PWM 模式,这 3 位必须设置为110/111。这两种 PWM 模式的区别就是输出电平的极性相反。 另外 CCxS 用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。
. P0 g% R3 |1 J/ r2 |) ^4 K- o' a1 N6 |, y2 j- E0 I w. s, k
- /**
" _ R4 r' N) _+ c y9 z - * 没有重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PA6, PA7, PB0, PB1* C' C0 T# \0 q5 O1 o1 w7 i
- * 部分重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PB4, PB5, PB0, PB1
) \8 `1 G/ _+ K9 G4 O - * 完全重映射时,TIM3的四个通道CH1, CH2, CH3, CH4分别对应PC6, PC7, PC8, PC9
. @2 f# C8 @; ] - *
1 H6 y( a" D6 s5 U: U' `9 F4 _ - * 110:PWM模式1 - 在向上计数时,一旦TIMx_CNT<TIMx_CRRx时,通道x为有效电平,3 p( t, w9 g. y |" o' H
- * 否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CRRx时,通道x为无效电平,/ f0 F0 ?0 D' Y2 N: Q& S
- * 否则为有效电平。5 `! L/ I9 C4 F& D0 s
- *% k, M6 P; y. g; F6 k
- * 111:PWM模式2 - 在向上计数时,一旦TIMx_CNT<TIMx_CRRx时,通道x为无效电平,
4 D) O$ Y; K6 A8 m! Z5 D - * 否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CRRx时,通道x为有效电平,0 ^9 c3 y& F) F" E- y2 s0 |
- * 否则为无效电平。
. W. M) h1 D3 B( ^ - */
复制代码 9 I$ `1 F+ J* x6 D0 i1 ?+ D! F1 p
(2)捕获/比较使能寄存器( TIMx_CCER)! C0 a( \6 I# H# ^: D! S
! ]3 r0 h. Y( z% U( G
这里只用到了 CC1E 位,该位是输入/捕获 1 输出使能位,要想PWM 从 IO 口输出,这个位必须设置为 1。
5 ~0 h( q* ~+ T' ?0 X$ c
6 j# K7 U) W8 r5 J8 X; j& ~* r7 R" O# {& ~7 i: Z4 _
+ v, M8 a9 J) K, L3 ], ?(3)捕获/比较寄存器( TIMx_CCR1~4)% M8 K( ?/ {: n* |4 ~& F
3 ~* h- W1 a* y) u) D
该寄存器总共有 4 个,对应 4 个输通道 CH1~CH4。在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。
: e: |# {# |( ^5 y! I
, Y2 W/ i. {7 H2 c( k
8 x7 u; j2 }$ @2 G. f V" m0 a0 M9 c% y' t
(4)刹车和死区寄存器( TIMx_BDTR)
3 A9 D+ u( R& C2 e% T6 R& I. q0 `% {, G) k3 n
如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器( TIMx_BDTR)。该寄存器,我们只需要关注最高位: MOE 位,要想高级定时器的 PWM 正常输出,则必须设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。% X0 o$ {! A' h4 L# Y
! I$ a7 ]2 W- Z7 X* m5 Q
0 Y% C1 B0 ?4 [. f12.3. PWM波形产生原理
# L- l A: a; i1 P& K
: U# K6 T# c3 ^6 L* V通用定时器可以利用 GPIO 引脚进行脉冲输出,在配置为比较输出、PWM 输出功能时,捕获/比较寄存器 TIMx_CCR 被用作比较功能,下面把它简称为比较寄存器。
( j- b# H' p$ ]5 f4 E6 s
+ }2 s8 g2 b4 B$ Z1 f; K/ H! Q这里直接举例说明定时器的PWM输出工作过程:若配置脉冲计数器TIMx_CNT 为向上计数,而重载寄存器 TIMx_ARR 被配置为 N,即 TIMx_CNT 的当前计数值数值X在 TIMxCLK 时钟源的驱动下不断累加,当 TIMx_CNT 的数值X大于 N 时,会重置 TIMx_CNT 数值为 0 重新计数。* [3 n5 b0 c4 p* u* F" b
8 J' V& N$ n0 _" I/ Y) k
而在 TIMxCNT 计数的同时,TIMxCNT 的计数值X会与比较寄存器TIMx_CCR 预先存储了的数值 A 进行比较,当脉冲计数器 TIMx_CNT 的数值X小于比较寄存器 TIMx_CCR 的值A时,输出高电平(或低电平),相反地,当脉冲计数器的数值X大于或等于比较寄存器的值 A 时,输出低电平(或高电平)。
2 E( _: `, Y% F+ m4 D! \0 Y
7 Y8 z: ^% V% S9 w如此循环,得到的输出脉冲周期就为重载寄存器 TIMx_ARR 存储的数值 (N+1) 乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器 TIMx_CCR 的值 A 乘以触发脉冲的时钟周期,即输出PWM的占空比为 A/(N+1) 。
/ \3 K; \% [. ^% b& G8 z9 [( f- R/ o/ a' h" V+ l0 S
% M% o& i! i' `, ~ z$ j8 E6 i12.4 PWM配置步骤6 E- R- N( ~' M. W5 n2 G; B
5 q0 z x: N$ q3 Z5 [4 Z
1.开启TIM3时钟、GPIOB时钟和复用功能时钟( q7 [2 _; ]$ N- }3 R5 S
2.配置GPIOB5为复用输出 x) u6 t( \3 _0 k
3.设置TIM3_CH2重映射到PB5上% C8 o- w5 M5 L
4.初始化TIM3,设置ARR和PSC# M$ ?" }" m! R# y, {# \0 K8 i
5.设置TIM3_CH2的PWM模式' R& r' B2 T4 c: R1 E
6.使能TIM3的CH2输出
) l% R m) O/ {0 p: Z7.使能TIM3- [$ L# {# |9 n1 C( `
8.在主函数中改变占空比完成呼吸灯/ p/ f$ n& a1 j, U+ c; e: e4 s" S
/ G0 a- B0 s7 G6 Q9 e" l. g
8 C3 g2 ]+ x# {$ M0 N. H% M12.5 定时器引脚复用功能映射8 v6 f+ s' g, s( `7 I! ^7 Y
' F. D3 \( V- e( F0 x
6 ^( ]3 |7 [; d) S. H- c$ j S$ m, ~+ M
, n) g! l4 M9 u2 B
4 }8 A+ d5 C5 w% J
+ S, p( f1 P# Q5 e# j3 V
+ d! ]' l8 y: U8 B- v根据以上重映像表,我们使用定时器3的通道2作为PWM的输出引脚,所以需要对PB5引脚进行配置。注意:如果使用PB4当做TIM3部分重映射的CH1输出,除了要进行部分重映射配置外,还需要禁用JTAG!并在开启复用时钟后禁用JTAG!
4 E0 a2 T- j. r0 M% [5 |+ O3 f. Q. W0 o/ K$ j
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //禁用JTAG,在开启复用时钟后禁用+ i8 j. ]1 N6 F8 O. d w: n3 r/ |
! b5 }6 j/ d9 c8 p, h) A
+ C! J/ H$ z. x; r
1. 新建两个文件,pwm.c 和 pwm.h1 H2 V2 Q, m/ g% S& T# ?3 h; r
[% l: H, P& \" b+ W# n! Z s
) c0 f) H. S. F
8 d" L* f/ F7 I0 a8 n) d6 L7 w; {5 e8 x4 }6 u2 i+ v
2. 在头文件 pwm.h 添加下面代码:
6 J3 W! e* a6 U* P c6 A% B+ O( ?3 E5 c
- #ifndef _PWM_H6 K6 E. O3 {8 l/ J# l
- #define _PWM_H1 ^+ {, p! R' w9 |
- #include "stm32f10x.h"
( A( } ~4 n, A# |( r+ h - : Q7 L7 K9 s3 R9 V0 v
- void PWM_Init(u16 arr, u16 psc);4 ~) F* h, T, F; [1 x7 C1 C
- 7 g: A3 f8 [. R1 A4 i9 `0 q+ r
- #endif;
复制代码
+ W+ Z/ r( s+ H
' y" @" d ?. @1 e' y- A& |
3 Y" W5 E* K1 \3 [% I, g4 m3 ?. a& S% a1 y6 S; T, z* l& y1 Y
$ A8 b6 O; `$ ~' Z) Z9 ^! `+ A X9 _3. 把 pwm.c 添加到工程中, d l X. z7 D3 S; O' x
* N& b1 h8 \/ E# Y1 k* L
. N8 c: g# Q9 K) ^
& e% _& `4 z7 O+ t
* u& v6 i9 y5 P# |) b3 e6 _4. 在 pwm.c 中添加以下代码:% [+ U" {/ w! X- o% |+ v0 h! K
9 C6 S: F1 ^+ ^( K4 j: e& J
- #include "pwm.h"
5 Q+ N* t& I$ b2 |% N/ l/ K' l - 5 _, s# V3 V; l0 C
- void PWM_Init(u16 arr,u16 psc)# M+ l* o; r# |8 y; @+ w" Q0 C
- {
* [% e! d+ F6 I - GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体* b5 b8 y/ x- W5 m! @
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIMx定时器结构体5 D5 U3 O* g' ?& O) ]
- TIM_OCInitTypeDef TIM_OCInitStructure; //定义定时器脉宽调制结构体 ^/ ]6 d1 P# Z/ d5 J0 D9 A
- + C3 n9 k# ~' i+ l' r
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能TIM3时钟; l7 b p& J' Q q! w% b2 Z+ d. `
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);//使能GPIOB时钟和AFIO复用时钟
. U( m4 p& W* D3 E - 4 B' A. {9 t+ z
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //TIM3部分重映射 TIM3_CH2->PB5
, H; T7 N1 k6 l& O$ I7 p0 o7 V
; d `2 i o' O4 K6 X3 E- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
) x4 g+ W' |' x: Y# l: l$ Q - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
" Z6 @; ~& }7 l& u - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //配置输出速率
1 N5 h* `. _" N0 Y' U2 |: i - GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB( }: ?' J O$ Y5 x9 g" j) u
- : w) ?+ f3 [/ H; E# I
- TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值 arr=value-1$ N* G; }0 m& y& p& d
- TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置预分频值 psc=value-16 l$ U$ ~# k% m+ P& T
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
% g" e" T3 y, P7 c# r* m* `$ \: o0 I - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式. ^7 X7 `7 e, q4 |6 S) B
- TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //初始化TIMx时间基数& U) U$ m U+ w' P& i
- / {% y8 o& N3 A( x! S, q4 \; ?9 u7 q. W
- //初始化TIM3 Channel2 PWM模式 3 u# j' _3 h' T) C5 s: f
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式14 f! H) l# V, p1 _" [
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能比较输出
9 \8 j4 ]& b& P& [- J - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高; @) q- Y: N3 M- y
- TIM_OC2Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2' _. T9 D1 ?! |* p P; j& B
- ; o) E9 ]4 B2 N7 R; ^2 E& {
- TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
$ Q8 I* t- w- s7 f4 H; ? - TIM_Cmd(TIM3, ENABLE); //使能TIM3
" a! N( w; E& }" Q: g6 @" ?6 e - }
复制代码 & a, L4 | q4 ?6 c- A: u
# f# N& t4 [$ l( D0 m( s ^
: S' ^4 L# O) s; X( k' ^& E' W9 p
1 F6 ]$ x& d! x& L4 Z, n
8 u1 _) Y% D5 A9 d% m+ q
, O" }" t& W5 n0 ^ v' V4 t5. 实现PWM呼吸灯功能; p4 I* A- G- ]/ ~7 F: _: K2 E
* H ]- a* f2 P- #include "stm32f10x.h"
' O( J, z8 T6 Z - #include "delay.h". V, \( c1 {7 ]1 I' q' ^, F: H
- #include "led.h") P2 a o" @/ ]1 |/ ~! W' s2 h; k
- #include "tim.h"
' V. @1 }! L& ] x, S& k, E9 @ - #include "key.h"
@: v' P7 R% x S9 i - #include "pwm.h"! \: E- w: |8 ~4 m1 V6 u
- 3 W9 ?6 \1 k6 S3 l6 `$ m3 X |2 x
- int main(void)# o1 X/ U1 Z. z0 a, t0 q
- {
. c5 Z1 w- M1 _ ` Q/ ] - u16 pwmValue = 0;
& C0 { A& P& l3 c - u8 dir = 0;
* @; Q$ q' H# X( P m3 I - 7 \& p- r j4 I; J, x* {
- delay_init();6 I9 x) ?; p, [0 m: C% G; O
- PWM_Init(999, 719); {9 F5 U# Z4 A. `
# J4 G( H& F, j. m0 Y+ F- while(1)& v" H0 q4 S3 C# _( p$ y7 ^
- {: {$ V- @' G8 A) G& q" S' N. h
- if(dir) {3 ?# e+ p: C% l4 t j
- if(pwmValue > 550) TIM_SetCompare2(TIM3, --pwmValue);. y; B0 [5 C1 k7 J& {1 f a2 a
- else dir = 0;+ J2 M" ~- d9 ?* j+ `, V) t; u$ c% r
- }
( I3 [# f/ r$ ^: [1 C5 @) Z; e - else {7 |% k5 h/ Y/ K! g7 t
- if(pwmValue < 990) TIM_SetCompare2(TIM3, ++pwmValue);
# }2 t& V- @- { - else dir = 1;
' f; n2 W% m; t4 U - }
4 t- U. H5 G; j' S1 \3 U - delay_ms(3);! v/ q& ]$ V3 }3 Z
- }' m; t1 p: }& g1 v$ A0 s
- }
复制代码
0 n; Q0 @7 ~: T" V2 C: F) ^7 X3 A9 j$ m, l' T
小提示:如果身边没有LED电阻面包板搭电路,可以用杜邦线连接PB5和PC13,同时PC13设置成开漏输出(GPIO_Mode_Out_OD),这样就能完成呼吸灯实验了(不瞒你说我也是这样干的)。& {, y8 Z3 b) B0 Z4 Y
( q/ h9 p$ Y. `1 Z3 e" e
! B5 [+ j* u" [4 s& l3 R |