在使用STM32单片机输出PWM波形的时候,通常可以直接使用定时器提供的PWM模式。可以通过自动重装载寄存器(TIMx_ARR)来设置定时器的输出频率,然后通过捕获/ 比较寄存器 1(TIMx_CCRx)来设置占空比。一个定时器只有一个自动重装载寄存器(TIMx_ARR),但是有4个通道的捕获/ 比较寄存器 1(TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4)。所以使用一个定时器输出PWM波形的时候,频率是统一调整的,4个通道的频率是相同的,但是占空比每个通道可以独立设置。比较寄存器TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4分别设置4个通道的占空比。. {0 b, E! T# T2 |
: Y9 P1 |9 _8 C9 c4 z) r! B% ?- `9 q
如果使用中央对齐模式计数的话,那么定时器的计数波形如下:
& N& P7 j: T5 U0 A) X. c5 D6 t
2 `7 V0 N/ E7 V$ p+ I& E3 x: _' L0 X
三角波就是计数器计数的方式,从0开始逐渐增大到装载值ARR,然后在减小到0。蓝色的波形就是比较寄存器的值CCR,计数器在计数的时候,每次都会将计数值和比较寄存器的值CCR进行比较,当计数器值小于CCR的值时,输出低电平。当计数器的值大于CCR的值时,输出高电平。
# x# w) D3 I+ c/ B# t R
5 q! x! e# q( L
: |/ T3 Q9 o5 W+ v
, \% t8 J# E- o- m$ F 当需要改变占空比的时候,只需要改变比较寄存器CCR的值就行了。9 b( ~; e3 o5 p3 Q# Y4 \0 b+ `
, h$ w$ }5 Y3 h8 d# d' O
# l$ {% L$ E$ \
6 m' f0 X# N9 V' A
通过改变ARR的值设置输出PWM波形的频率,通过改变CCR的值改变PWM波的占空比。但是用这种方式输出PWM波时,一个定时器的4个通道输出的PWM波频率都是一样的,那么能不能用一个定时器输出4路不同频率的PWM波呢?
" A7 c O' W# {8 e# W9 v 通过观察捕获/ 比较模式寄存器 1(TIMx_CCMR1)中的OC1M位时可以发现,除了PWM模式外还有一个翻转模式,在翻转模式下,当计数器CNT的值等于比较寄存器CCR的值时,输出的电平就会翻转。
1 ~, a4 h- y2 t; ?- `- P
7 C2 k' D# d- {# Y) P9 V& f3 S$ y
8 n* e" N m2 C
9 j& `- w/ Z# b$ N; e8 r
/ L' W8 Y: g0 `' C! {
那么就可以通过这个功能,在输出PWM波的过程中不停的去改变CCR的值,那么输出的电平就会不停的翻转,这样就可以手动改变PWM波的频率了。! n( x0 b B/ N3 q; z4 g
% c* {: |% F$ T4 X) P& G8 G
( D; S3 z) a1 g- ~! q# e4 C
) u4 F& G; {; K" J n 比如第一次将CCR的值设置为CCR1,然后使能比较中断,当计数器的值CNT等于CCR1时,就会触发中断,然后在中断中将CCR的值修改为CCR2,。接下来当计数器的值等于CCR2时,又会触发中断,在中断中继续设置下一次CCR的值。这样每触发一次中断后,输出的PWM波形电平就会翻转一次。在计数器从0到ARR计数期间,PWM的波形可以翻转好多次。在PWM模式中,每一个计数周期波形只能翻转一次,但是在翻转模式下,一个计数周期可以翻转好多次,这样就可以改变输出PWM波形的频率了,同样每次设置不同的CCR值时,也可以改变占空比。
& m% m) E8 m# Z, u/ M/ g+ G
: Z* c, j1 }2 q) z+ s e; D4 f 下面就通过代码来实现。, O1 c% l& H% {, U, V9 M
" P) z( v* H% v( l+ H
- #include "timer2.h". l* T" f# ?6 s3 R
- //比较值% o& v2 V* U# m* o" E/ h& w
- u16 CCR1_Val = 32768;9 C$ W' d* X; c0 G9 I0 u
- u16 CCR2_Val = 16384;
. W5 a7 Y: t m- e' @ k& y - u16 CCR3_Val = 8192;
8 i( \, U9 @" m) K3 J- U# H V - u16 CCR4_Val = 4096;5 k* r! e9 u5 \4 O% l
7 J/ O, D$ k% O/ |) s- void TIM2_PWM_Init( u16 arr, u16 psc )
* f0 P$ r9 ~5 m0 F: c1 f* k/ H - {
: b+ s3 Q+ Z, } X, o: d - " `$ [/ @" g& i5 \
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;7 ^7 j' e9 l, a" F
- NVIC_InitTypeDef NVIC_InitStructure;
l1 E( C6 E4 N9 `9 {$ d3 x - TIM_OCInitTypeDef TIM_OCInitStructure; J6 Y, j9 P" [3 I9 Q: D$ `* E- R
- GPIO_InitTypeDef GPIO_InitStructure;( h2 f7 v8 E' o+ V- G' _) w% |
- ) B# y/ |1 q3 \+ n( D6 d
- //使能时钟9 O3 A4 u. U8 s; f' ?. H' j: W
- RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );0 d. y4 G$ ~' X9 P6 o9 ~" H
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );- D+ g0 ^+ D7 }/ G$ Q' F; f
- s9 }" ]. Z' e- o5 e- //设置IO口
: B7 ^2 k5 K' ]4 w# T - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
5 N. t8 |1 W% z* m9 O - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;7 f6 S3 U. q0 \2 b9 ]" J- c0 }% a
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
: r4 h, T4 x5 m - GPIO_Init( GPIOA, &GPIO_InitStructure );6 C7 p9 @' ~" Y2 k% M
- . R0 A/ e# a! i' x |' N
- //设置中断优先级
6 W0 y3 R# ~( | - NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;9 A9 X. y' S( z G2 [5 n5 v2 t3 c
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;4 h( T) |' d. d8 d
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;& q ?+ `8 j, P9 R8 @
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
. n& n, [8 d9 @) D3 x - NVIC_Init( &NVIC_InitStructure );5 z, x6 h O3 p7 J2 n1 D/ W) X) m
% \% W f# s" ?% [- //设置定时器基本参数
9 q: e* o8 l& N: h% B A - TIM_TimeBaseInitStructure.TIM_Period = arr;
. s' p1 j) r3 I+ `; O3 Z: H7 D* |, v - TIM_TimeBaseInitStructure.TIM_Prescaler = psc;8 |1 |: H5 @$ @. l' E( g1 M L
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。% I. j- W% {; y" ]/ ^9 X/ p
- TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;& T0 I4 ?& h6 B4 m2 @
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;+ O% W' q' d8 |: E
- TIM_TimeBaseInit( TIM2, &TIM_TimeBaseInitStructure );+ w+ |8 N/ L% [
- " `" B6 i8 u; t1 P% b" _, V
- //设置工作模式
1 y: z3 n: U Q - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //设置定时器工作在翻转模式
# W: Z8 z X: I' [3 u$ [ - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;2 y9 O& z8 `3 {0 r1 b' }7 _: O
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
" z9 ]: ?7 R# ?9 v+ U
3 J, A5 K" D! W1 L. x- //设置4个通道 比较寄存器值# E( a1 d! @; l
- TIM_OCInitStructure.TIM_Pulse = CCR1_Val;% {# Z' _. |& z+ t \$ z, D
- TIM_OC1Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA0* A0 w" C0 n( j% I0 k9 M- P
- 8 o* q, `4 m1 C' u* {: W! N! A
- TIM_OCInitStructure.TIM_Pulse = CCR2_Val;) F/ u: [; |' J7 {6 L, `
- TIM_OC2Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC2 PA12 }1 [* w* f1 h( h
- 9 g- a: Y. R( p
- TIM_OCInitStructure.TIM_Pulse = CCR3_Val;( |* J! w- S/ i. e
- TIM_OC3Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA2
9 C# F/ b4 w8 F; }; w( w7 ^
# @" f8 G% K* x% B5 \4 h: G- TIM_OCInitStructure.TIM_Pulse = CCR4_Val;* D8 @0 c/ v a( }1 a# d1 Q+ X
- TIM_OC4Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA3; Z; v P6 c$ z0 r! n
. s3 j4 g0 N m* K1 L- //禁止自动预装载% N/ Z% i5 U& O- A, C$ S1 F
- TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );
, W" \' R- |" P: C1 u7 b5 k - TIM_OC2PreloadConfig( TIM2, TIM_OCPreload_Disable );8 r0 L7 ?& O( Z% P" l( l; j' b
- TIM_OC3PreloadConfig( TIM2, TIM_OCPreload_Disable );
. y% G( @8 ?$ A B8 S7 v1 V! i6 Y; u - TIM_OC4PreloadConfig( TIM2, TIM_OCPreload_Disable );
: \" ~' m9 p( J7 o M8 f2 U
" {0 s% w n" O: x/ u; `- //使能定时器
/ o# N; g, ~3 B - TIM_Cmd( TIM2, ENABLE );: B" Z4 r) z: ]7 H* b# T) x
- TIM_ITConfig( TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE );
: t1 x1 p! V) ^3 p$ n1 i/ d - }
复制代码 ; ]7 Q1 \4 A0 Z
首先初始化定时器,初始化的两个参数arr和psc分别设置定时器的自动重装载值和分频系数。这里将arr的自动装载值设置为最大0xFFFF,也就是65535,相当于计数器从0开始计数,直到65535才结束。将分频系数psc设置为0,也就是不分频,定时器按照72MHz的频率工作。4 w8 z: ~$ k5 s; C2 l l: }
$ J. N) [! K; G- k" e: K
将定时器的工作模式设置为翻转模式,然后依次给4个通道设置比较值。这里要将自动重装载功能关闭,否则就不能手动的更改比较值了。最后使能定时器和4个通道的比较中断。
3 j& h, R" g+ x# {. z; w4 X, f& ~. x ^( R8 n _9 y
接下来就是最重要的环节了,在中断中改变每个通道的频率和占空比。
* |2 e9 `4 j7 J: g) p1 a1 o Y- m% y8 [2 ~# w: R# q
- //占空比 0 --- 100
+ ] l9 `4 v# _% ]! A - u16 CCR1_dc = 20;( J; _8 b H$ u$ v
- u16 CCR2_dc = 40;
5 u4 y( V- ]% e _5 r C4 h, _, P - u16 CCR3_dc = 60;
# W" N. {7 x7 m/ W/ A - u16 CCR4_dc = 80;
, t T/ Y, L+ W - - q! r. n0 V) }. @, q" B2 E) _; i" x# c
- u32 capture = 0;3 m0 f, s5 v0 `, u6 @" ~
- u8 flag1 = 0, flag2 = 0, flag3 = 0, flag4 = 0;9 G# w' I4 ^- f$ i, E" l
- u16 setcap = 0;
) f. [% _7 C, P. n" [! U - + d1 p$ v( a' F6 B! L
- void TIM2_IRQHandler( void )3 B, n' I3 b I! d6 x
- {0 S! R6 H: d" N6 w% V* c6 G
- if( TIM_GetITStatus( TIM2, TIM_IT_CC1 ) != RESET )
5 |: M# U v9 y4 ]+ Z0 w4 Y - {* b- U1 Q+ {7 @" I: z. e2 _* m* O. z
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC1 );
' x% T6 r1 j" A - capture = TIM_GetCapture1( TIM2 );! U& F% ]: Q. M: h# g4 o
- //设置占空比8 g. x. J9 H3 R* p8 m0 A `/ t
- if( flag1 == 0 )& _8 S) z7 W# k# H
- {
% Z1 U0 E l( G- L- W; r - flag1 = 1;
0 T; b- ~! ]# T - setcap = capture + ( u32 )CCR1_Val * CCR1_dc / 100;
7 D# L6 x2 s: H - }2 X) N# o" u& X2 v1 [- W
- else
# m6 u4 f0 v/ m+ k6 r+ W) J - {- r$ A0 l( \( o$ @' R2 Z1 R& \
- flag1 = 0;
+ R# v2 y T1 x4 n$ {' Y S: u) L - setcap = capture + ( u32 )CCR1_Val * ( 100 - CCR1_dc ) / 100;$ O& t8 Z* Z5 i; X' P
- }
3 J; q) b# v" _' w - TIM_SetCompare1( TIM2, setcap );
7 y7 I- }. p* b; l/ } - }( ]$ j% A& B! m% k: F+ I/ }1 ?
7 C3 z E. q5 b5 P& F$ R9 e8 B- if( TIM_GetITStatus( TIM2, TIM_IT_CC2 ) != RESET )
5 e( P) Q" B8 \7 K s: W3 k - {
/ L ^( b% P# _9 H1 r - TIM_ClearITPendingBit( TIM2, TIM_IT_CC2 );; J7 t$ g( m" [+ |$ e9 }
- capture = TIM_GetCapture2( TIM2 );
5 w. y9 C- M+ g - if( flag2 == 0 )% j4 A# Q$ L$ ~4 a* y& T
- {
: @3 u; a% \6 F+ H! G - flag2 = 1;: Q; c( i+ J- [0 D
- setcap = capture + CCR2_Val * CCR2_dc / 100;; x% ^! ~7 u; h+ Z! I7 P4 r
- }3 l1 u Y$ q0 |4 y9 u2 i
- else7 |6 i( s3 r, _
- {
0 w5 C' b5 ^9 ~& z* k - flag2 = 0;
1 G- K& A3 s0 x4 m - setcap = capture + CCR2_Val * ( 100 - CCR2_dc ) / 100;4 d* M l' q$ P5 ^ ?7 N, |
- }! ^2 L: _/ j* O: G/ A1 q, C
- TIM_SetCompare2( TIM2, setcap );6 T+ A! h3 \8 p
- }
7 K* j8 G) b6 V! ]7 }' `
( J+ S, f& a/ E+ R6 {4 p
' C# X) i( j9 Z1 C# i1 g2 h- if( TIM_GetITStatus( TIM2, TIM_IT_CC3 ) != RESET )
; k* Q E# v: d3 ~3 i E' g2 w - {' Y$ o+ s7 ~' p0 k2 j/ N+ X
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC3 );
! |% V% c7 H; t - capture = TIM_GetCapture3( TIM2 );* L( c. A, v+ G$ j
- , s& X$ t4 Y4 S: }. m t" a$ t7 s
- if( flag3 == 0 )$ G. z) Q8 |% |; H" e; O0 `% n6 i
- {7 K6 L$ E8 `! h+ |
- flag3 = 1;
& B3 k L0 s# S0 ?& v4 B7 c& x - setcap = capture + CCR3_Val * CCR3_dc / 100;
, }, u: m9 ?8 T, I8 D3 v& ]( a - }/ c k- f" C5 N y4 G% F% z
- else
1 H( m2 S. z; i) w9 @ - {
9 D( L8 `8 E# Z. r& h i. ] - flag3 = 0;
4 {+ I& u! G: V% e# `' o - setcap = capture + CCR3_Val * ( 100 - CCR3_dc ) / 100; T- G9 `- n/ B0 I Y3 o! H- ~4 Q7 I
- }
/ j- R5 f" n; I9 t) ]" P% B! a - TIM_SetCompare3( TIM2, setcap );
# L' P F9 M) P - }' A o0 b5 i+ k( H l' u9 G
" p. \/ a; m- n4 b- if( TIM_GetITStatus( TIM2, TIM_IT_CC4 ) != RESET )1 J: K3 ~* M. a: d: A; E y3 F k
- {4 O, r$ C4 V2 }. ~' q8 `
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC4 );- Q' a8 G# L3 M$ `
- capture = TIM_GetCapture4( TIM2 );& ]8 C& e7 \( D* q
- ) ^4 M# t/ m! `3 d# ]
- if( flag4 == 0 )
& D- M7 O8 ~6 V1 E$ k - {, v' J, K% R3 ~! z0 J
- flag4 = 1;
) h5 X' E3 V/ R - setcap = capture + CCR4_Val * CCR4_dc / 100;9 h: u1 o0 O# \4 L+ S' j
- }
" D( x' }2 E/ K: f5 ~' N - else
: g+ o% l" M' k+ c4 O, \# e' J5 F; ] - {2 k. M0 X& b; E A" \" Q
- flag4 = 0;! Z! l1 X5 o ^' v7 G4 X
- setcap = capture + CCR4_Val * ( 100 - CCR4_dc ) / 100;
' N- f7 o% o7 c- j- ~) }" ` - }& k( f) e3 y0 k, W9 ]* h- z3 d
- TIM_SetCompare4( TIM2, setcap );
! ?0 @" p3 y: A' z3 R% Q) K: { - }
! K4 v4 P# ]5 e5 g - ) R6 w- ~ H8 |/ t
- }
复制代码
/ C4 }( s4 [# U 进入中断后,首先读取当前比较寄存器的值,因为比较寄存器的值是会一直累加的,直到比较寄存器的值等于ARR的时,才会清零。所以每次中断的时候,需要将现在的比较值读出来,然后加上一个值,作为下一次的比较值。读出当前的比较值存放在capture中,接下来需要在加一个值,作为下一次的比较值。因为要改变占空比,所以增加的值有两种情况,分别是高电平持续时间值和低电平持续时间值。为了区分高低电平,这里使用的一个标志位来判断,标志位的值只有0和1两种情况,将0作为高电平的时间,将1作为低电平的时间。CCRx_Val 中存放整个周期的计数值,当标志位为0时,CCRx_Val 乘以高电平的百分比,计算出高电平的累加时间。当标志位为1时,CCRx_Val 乘以低电平的百分比,计算出低电平的累加时间。CCRx_dc 中存放占空比的值,范围是0到100,表示占空比从0%到100%。 当需要改变周期时,就修改每个通道的 CCRx_Val 值,当需要改变占空比时,就修改每个通道的 CCRx_dc 的值。
3 l$ |# Q" t2 O! t( [ e8 W1 h
) D V2 h/ }9 n7 Q5 o( f3 a- int main( void )5 [- H3 P$ A# T( I
- {2 B5 t/ s+ k( w% ~3 P/ K0 g
- u16 i = 0;+ s. f' {# u" W
- delay_init();) ^* F4 }/ \( q4 X
- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
4 c3 ^9 {" w& q: M; X5 H5 k - TIM2_PWM_Init( 65535, 0 );
7 l5 H, g- O, A/ G/ Q: V - while( 1 )2 Y& K$ ^5 ^( c* O4 G* _7 F4 W
- {4 `( E. S, p- o" l3 \! ~# ?8 j
- }2 q: W: |) g1 @* ^' L
- }
复制代码 8 C3 X6 v* \; e* g$ `- G/ p' B" J
接下来在主函数中初始化定时器,定时器2的4个通道输出波形如下:* ]! M% T1 @& K- j9 n
+ j) P3 J1 M' W/ c+ v; S
+ q+ G- x ?' U1 i4 B
# V* [+ [' M2 F% n 通过波形可以看到,定时器2的四个通道频率和占空比都是不一样的。说明通过定时器中的比较模式,是可以实现同一个定时器输出不同频率PWM波的。
- U C; W; z/ |: T" B c2 G
9 r' A3 P; C. j3 O2 U
) z# c9 R$ D2 D
: G3 i+ Z6 z& Q0 |* g4 V |