01 PWM介绍 PWM定义:脉冲宽度调制(PulseWidthModulation,PWM)简称脉宽调制。通俗讲,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。
" G) `- B% {: L+ w* t
电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。 / |3 k7 e$ X5 N- X+ i
占空比定义:占空比就是高电平所占整个周期的时间,如下图所示:
6 X5 I. t3 w1 b! B& S/ k
+ W- [- y6 V/ y& q& O( I$ G第一个PWM波,周期为10ms,高电平的时间为4ms,所以占空比为40%,同理第二个PWM波为60%,第三个为80%。
( H6 O9 Y& A1 E' Q3 o; ]" _
PWM的频率: PWM的频率的整个周期的倒数,所以说上图PWM的周期为1/0.01,也就是100HZ。改变PWM的频率是通过改变整个的周期实现的。所以通过改变高低电平总共的时间、改变高电平占总周期的比例就可以实现任意频率、任意占空比的PWM波。 3 B5 \* E1 Q+ ~0 W& c, u
PWM的用途和优点:电机调速、功率调制、PID调节、通信等等,配置简单、抗干扰能力强,从处理器到被控系统信号都是数字形式的,无需进行数模转换。并且让信号保持为数字形式可将噪声影响降到最小,噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响,这是PWM用于通信的主要原因。 " f% `" u6 o( {! ~$ _+ j3 Q
02 STM32的管脚复用STM32没有专门的PWM引脚,所以使用IO口的复用模式。首先确认PWM功能的输出管脚,使用定时器9。从下面的框图中得知,timer9只有两个输出通道,所以timer9只能输出两路PWM。 + E9 u% g) N' _$ p$ `( n# ?$ W
. n3 i# M2 b& ^# Q3 G0 c- }, J' D在STM32F207数据手册中的Alternatefunction mapping图片中,timer9的两个通道分别可以复用为PA2,PA3,PE5和PE6。 & [1 Q+ O. @4 O/ C
03 STM32输出PWM原理
1 Y1 H) J' N/ q/ P \" R下图中的①部分,在《[color=var(--weui-LINK)]STM32基础定时器详解》讲解过了,关于影子寄存器,也在《[color=var(--weui-LINK)]STM32影子寄存器》中讲述,下文不再赘述了。本文将重点在②部分,捕获/对比通道讲解,其中STM32的PWM就是利用对比通道实现的。 % ^% S4 o) a8 J/ ?5 [
0 V: m1 A( N) i0 G. GPulse Width Modulation mode allows you to generate a signal with afrequency determined by the value of the TIMx_ARR register and a dutycycle determined by the value of the TIMx_CCRx register。 节选自STM32F207 Reference manual手册 ' Z! R4 t% d: P) j3 t! G
# K( Z, q1 H" [% ?# _" d
脉冲宽度调制模式可以生成一个信号,该信号频率由TIMx_ARR 寄存器值决定,其占空比则由TIMx_CCRx 寄存器值决定。9 e( G$ ?7 o x3 R& R
1 N$ a* U8 x' C0 o8 }
从下图可以看出,当CCR寄存器和CNT计数器数值一样时,会产生动作(改变通道对应的GPIO电平)。由于CNT溢出时,重载值由TIMx_ARR寄存器值决定的。所以说TIMx_ARR寄存器值决定周期,而TIMx_CCRx寄存器值决定CNT溢出时,经过多久会产生动作(改变通道对应的GPIO电平),也就是决定了占空比。 7 T& \1 x y/ Q3 x
" `) j5 l7 O7 A3 J0 N
以向上计数为例,重载值为ARR,比较值为CRRx
$ w/ b8 z: s/ |$ \. Y
; \% U" w8 Z6 I$ u' c. X
5 K* R; i( _7 m上图可以看出:' o: _6 i1 \; }% i% w
7 F- M+ f( e; \. @8 O
0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平。 t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平。
3 s; U2 E% s+ t: A) M+ @6 `) `6 X! g3 n
当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程至此一个PWM周期完成。
" \4 [- e/ l- V- s+ F) W上图更加形象的说明了 信号频率由 TIMx_ARR 寄存器值决定。 占空比则由 TIMx_CCRx 寄存器值决定。 + ~( h% L) C/ B; F! F: ]1 ~' T% U6 Z
* L' L. F3 V* ? o5 j+ g( H
0 @4 `5 F, a h9 r* uSTM32输出PWM的过程: 1、首先配置GPIO,配置定时器,具体参考一下代码。定时器配置参考《STM32基础定时器详解》。 2、捕获/比较通道使能比较通道。 ~9 ?8 Z* h9 A3 r7 h* Y) {
9 ]3 r4 N' U( R" i# w. M
上图看到,①寄存器名字为:Capture/Compare1register。可以选择从②处输入捕获,也可以选择从从③中输出,也就是我们需要的PWM输出功能。选择捕获通道,还是选择比较通道,在框图中没有找到具体的说明,但在TIMx_CCMR1寄存器CC1S[1:0]控制位使能。 $ m* w& Z- O8 V5 e S1 D1 `# f
& h) L6 n2 n, }' b4 P& ?& Y
3、使能完输出,就要配置PWM输出了 ; x; e4 ~0 I6 w
/ H1 o L: c" Z0 ~
①TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式控制器 ! D7 t/ H a! M. |' p
110:PWM模式1,111:PWM模式2。
/ V6 R* n/ Q: K
6 X4 w- r: @3 r/ Y
②计数器值TIMx_CNT与通道1捕获比较寄存器CCR1进行比较,通过比较结果输出有效电平和无效电平。 8 c! O K" P5 l4 W4 i6 b, L
+ q$ V% `8 k" x/ x$ {& M
OC1REF=0 无效电平,OC1REF=1无效电平。 * M) }8 ?7 I. k9 p! ? G( L
③通过输出模式控制器产生的信号。TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性。 2 X* A g7 d$ ?8 j+ ~. q
0:高电平有效,1:低电平有效。
) D; k3 V2 T& Y# ~
④TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚。
6 y' f( ?3 |' U
0:关闭,1:打开。 : }# q K9 ~ F& X8 i: \' i- o- M5 ~" m
首先对PWM模式1和PWM模式2进行介绍: 01 模式1
, }/ `" E2 X' o. o% b. M% T n/ `7 h N在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向上计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。 02 模式2# n( k/ ~0 Q. J% \; c7 d7 I; i( g
在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。
* `5 C9 A& e7 t9 Z) k s
TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。% m, d8 h) m1 }* Q- T
7 `# k9 d1 l6 @: ^
PWM输出高低电平由TIMx_CCMR1:OC1M位和TIMx_CCER:CC1P位共同决定。 " G- E) [" k9 a# q5 g
总结下来: % Z2 X8 E' }% |# v5 U' g
模式1:
0 Y7 ~& k+ F& L/ ]( h+ m
CNT<CCR为有效电平//(OC1REF =1) CNT>CCR为无效电平//(OC1REF =0) * _" ~, _) ~+ D/ m3 I6 l7 a
模式2:
* i i! P( T7 Q4 i! O
CNT<CCR为无效电平//(OC1REF =0) CNT>CCR为有效电平//(OC1REF =1)
" K2 E# m' G# G7 a
CC1P: 0:高电平有效 1:低电平有效
$ q5 k& q2 L0 P5 A% ?+ h0 h04 STM32输出PWM配置
6 m, m2 ~, E( w1 I/ ?分析了原理,那么下面就分析STM32生成PWM的过程。
& @: n- t1 ~. {. ^7 S& e
1、首先要将GPIO设置为复用输出 0 ~1 F# f- |2 y3 ?
- , b( F8 T5 v! Y& N( _
- /* GPIOE clock enable */3 s$ d% }+ P# i- Z. z( K
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
) |! H( y. x4 b* L$ F
O1 K0 J( X4 ~, `$ g4 d* w2 ~, r- /* GPIOE Configuration: TIM9 CH2(PE6)*/
a8 l! V9 b+ n) b. Z% U - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 |GPIO_Pin_6;
& p* @: K |" v - GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF;% e6 d- H. `- P: ^" S
- GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;
7 G& H4 W; r" f% S7 L& x! z" J - GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;
; B# n* i5 S% j) r - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
! A0 j; f. C" C4 E% E) b& P' k4 o - GPIO_Init(GPIOE, &GPIO_InitStructure);, k5 |3 h7 \4 y9 t5 G2 z' x
- 1 b, K+ [4 O2 u$ p) r; I! b
- /*Connect TIM9 pins to AF3 */
! H, R2 q; N' A$ T, D w( t3 l - GPIO_PinAFConfig(GPIOE,GPIO_PinSource5, GPIO_AF_TIM9);) K3 @3 x3 X# L, T
- GPIO_PinAFConfig(GPIOE,GPIO_PinSource6, GPIO_AF_TIM9);
复制代码 0 T8 X- n1 T, U; ~* j! d
2、配置定时器向上计数,配置定时器频率
" F% w' m9 G7 V2 h/ W$ L9 B" I
/ W# l K* I& y
9 `$ e5 k7 @/ y& K9 _- /* TIM9 clock enable */* x: t% h. t6 ^, Q
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9,ENABLE);
; K( ]2 ]5 f$ ~ - / Y% H% q0 F( h, n# d
- /* Compute the prescaler value */1 z& {8 h6 k' _1 @
- PrescalerValue= (uint16_t) ((SystemCoreClock) / 2000000) - 1;# C! @' @- Z: q- l1 G3 x
$ T- m% ^8 N8 }* k# u- /* Timebase configuration */
J* l" j' J5 C# e" U - TIM_TimeBaseStructure.TIM_Period =1000-1;
3 ~6 [, w/ r, _" H+ ?. V: B9 Y - TIM_TimeBaseStructure.TIM_Prescaler =PrescalerValue;& Q2 x' ^8 q# v2 D: u. W3 j
- TIM_TimeBaseStructure.TIM_ClockDivision =0;. }, H: ]7 A& O
- TIM_TimeBaseStructure.TIM_CounterMode =TIM_CounterMode_Up;% l+ i9 w! b: _3 h
- : w& k& O2 X. v0 p9 `. n' S7 d
- TIM_TimeBaseInit(TIM9,&TIM_TimeBaseStructure);
复制代码 . e# B( t: f1 f! R5 v
/ h# t, T2 n+ L/ x( t
# ]0 ]; ~7 y$ B5 ?
1 E8 Y7 }0 v+ Z' N; i$ R
0 n5 ?! |5 e1 R# j/ c+ ~( H! \( I3、配置PWM输出上面分析过程较为麻烦,ST提供了标准外设库,我们只需要配置TIM_OCInitTypeDef结构体即可。
4 w t+ D+ o+ {1 r/ ~ ^2 q
' _5 B& i; K7 f# j- TIM_OCInitTypeDef TIM_OCInitStructure;
3 O7 y2 g% ?7 c G4 U! y" w
+ n- }! c [7 ~. Z2 N- /* PWM Modeconfiguration: Channel1 */
2 K, z# e& G7 l, y8 j; i8 j! ] - TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_PWM1;
( x$ Y: N% }& b. h7 K9 u - TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;! w# W0 i) I& r- a, X& q& L9 u
- TIM_OCInitStructure.TIM_Pulse =100-1;
9 h+ s* m; o# o2 L3 @* V2 o - TIM_OCInitStructure.TIM_OCPolarity =TIM_OCPolarity_High;
1 b7 x6 Q8 ~" f- L
% p* @5 X5 C% N0 [- TIM_OC1Init(TIM9,&TIM_OCInitStructure);! d; Q9 U' _- f+ L
- TIM_OC1PreloadConfig(TIM9,TIM_OCPreload_Enable);
复制代码 9 K9 v% z* x, j. J7 l+ |
; N7 g7 B/ n/ q8 \ }
5 D$ @$ F! Y- w# U( i% n
TIM_OCInitTypeDef结构体解析
! a: T( Z- O7 L, }" @+ r9 ~
$ L8 @' i4 w% _1 Q- b- typedef struct
# ?* p% L3 p$ O6 ~. G5 \/ d, ?% v - {0 l G i, P! t9 `7 e1 S, ?
- uint16_t TIM_OCMode; //PWM模式1或者模式2
* J; ?6 _7 o( W. ]: t - uint16_t TIM_OutputState; // 输出使能OR失能
8 I# Q$ }* u/ @- J: e# x - uint16_t TIM_OutputNState; // PWM输出不需要1 n. k/ Z7 K, [" D! {% T
- uint32_t TIM_Pulse; // 比较值
& Q: N3 v2 u7 d - uint16_t TIM_OCPolarity;// 比较输出极性: ^8 {; E' ^' m& W9 o9 ^
- uint16_t TIM_OCNPolarity; // PWM输出不需要$ K A/ q) V, m$ E0 J
- uint16_t TIM_OCIdleState;// PWM输出不需要$ }0 n% t, r6 ?& u* M5 z5 W; {
- uint16_t TIM_OCNIdleState; // PWM输出不需要
' i: p0 Y w2 Y" X: z - }TIM_OCInitTypeDef;
复制代码 / @! `5 V' K7 o# t
其中TIM_Pulse可以在初始化时设置,设置完毕后,也可以通过以下接口再次更新。
( k3 t2 ~) U# H9 n) Y: |% |: Q
- : H4 R+ O' o4 x5 N; ?
- void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1)
复制代码
. n% V g9 D! P G2 i& A! b/ ~
7 r( R$ O3 ]) B3 N4、使能定时器
: I- m2 r& h. o$ y) G5 `6 f& y8 f
- + H9 N8 B3 H& t- q! Y0 C+ P
- TIM_ARRPreloadConfig(TIM9, ENABLE);
; h1 S9 Y/ K y A
+ N/ Y+ _* }5 v" s) I6 M- /* TIM9 enable counter*/
& Y+ N; |. o6 k - TIM_Cmd(TIM9, ENABLE);
复制代码 : I% W2 [; U9 g6 O+ v' r4 M! x
使用timer9输出PWM的波形。 5 \, L: Z6 ^+ \/ w0 B; I
, j% B N1 D y% ]+ i. k# r* n
6 z+ V9 y2 K$ c/ Y) y1 q
|