在使用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个通道的占空比。3 ?" q `& T) }# L. Q/ b. @
) S, s1 Q( \6 Z& H/ ~0 Q 如果使用中央对齐模式计数的话,那么定时器的计数波形如下:& Y$ s% k3 r. J: v
! t$ c! _" Z" ]; @6 c* `4 r5 y- R
, X; m7 v$ k2 S 三角波就是计数器计数的方式,从0开始逐渐增大到装载值ARR,然后在减小到0。蓝色的波形就是比较寄存器的值CCR,计数器在计数的时候,每次都会将计数值和比较寄存器的值CCR进行比较,当计数器值小于CCR的值时,输出低电平。当计数器的值大于CCR的值时,输出高电平。
/ ? N7 Z+ U9 P1 L7 V: P3 H4 E% Q0 y/ }+ z" r: f( m: ] Q
, Y7 h9 a( N& L& o# T4 P: j
) j$ c/ a' }: F8 `0 V8 b 当需要改变占空比的时候,只需要改变比较寄存器CCR的值就行了。6 C) O. V' S( J% h+ {; j7 [
2 H+ g! R7 B4 D& |$ l! D
, j: a; p+ \( I! L$ ~7 t. C* [5 J# `
( z' Z: o8 |" D; J- n 通过改变ARR的值设置输出PWM波形的频率,通过改变CCR的值改变PWM波的占空比。但是用这种方式输出PWM波时,一个定时器的4个通道输出的PWM波频率都是一样的,那么能不能用一个定时器输出4路不同频率的PWM波呢?' L4 i. Z* z; ?: l4 @, B+ N
通过观察捕获/ 比较模式寄存器 1(TIMx_CCMR1)中的OC1M位时可以发现,除了PWM模式外还有一个翻转模式,在翻转模式下,当计数器CNT的值等于比较寄存器CCR的值时,输出的电平就会翻转。
8 j9 S7 {" d9 Y6 K ~. j/ @8 d1 B1 u& m$ {# }2 V
3 q9 V0 W' R8 q" c$ A
( K, d+ h. r q3 f
1 e% R. [ d" f% X2 j. h; M, j9 ~
那么就可以通过这个功能,在输出PWM波的过程中不停的去改变CCR的值,那么输出的电平就会不停的翻转,这样就可以手动改变PWM波的频率了。
6 E, J9 Q6 A1 w- T2 f5 g. Q
, w! z7 N2 Y5 T8 c
$ i# O5 z) }' P$ o5 r( ?
( M" } `3 o4 A) l; K x1 U5 v& U5 [ 比如第一次将CCR的值设置为CCR1,然后使能比较中断,当计数器的值CNT等于CCR1时,就会触发中断,然后在中断中将CCR的值修改为CCR2,。接下来当计数器的值等于CCR2时,又会触发中断,在中断中继续设置下一次CCR的值。这样每触发一次中断后,输出的PWM波形电平就会翻转一次。在计数器从0到ARR计数期间,PWM的波形可以翻转好多次。在PWM模式中,每一个计数周期波形只能翻转一次,但是在翻转模式下,一个计数周期可以翻转好多次,这样就可以改变输出PWM波形的频率了,同样每次设置不同的CCR值时,也可以改变占空比。
4 F( r9 W6 k# s0 I- g- d% r- k
) `; y v, c) u# S2 D 下面就通过代码来实现。
) R; \% R# x/ g% F! d; Y- \! ]' U5 n: a* j0 L% {
- #include "timer2.h"
! C Q) p0 g1 X, o" d - //比较值
& N" e2 ? @" K9 M: _" y- o - u16 CCR1_Val = 32768;0 k! e3 x- C* Z/ D L* h
- u16 CCR2_Val = 16384;
/ p7 L U! H+ v# y5 A - u16 CCR3_Val = 8192;
2 U- z L" {3 j/ ^ - u16 CCR4_Val = 4096;
5 B1 V8 p6 P) R3 d; h - $ p9 ]8 v) ]; N2 T, q- i- t
- void TIM2_PWM_Init( u16 arr, u16 psc )$ C9 F0 d6 C- }5 W
- {
+ Z+ A5 h. ]- X - 4 j- O/ O! ]' O7 ?% Z @
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;+ P, U( m6 n( Y0 l
- NVIC_InitTypeDef NVIC_InitStructure;
) z' q5 z: b: a4 W6 g: e) { - TIM_OCInitTypeDef TIM_OCInitStructure;
, T- x1 h) s+ F* x" p% I - GPIO_InitTypeDef GPIO_InitStructure;7 {8 R0 m7 c: D$ ^& @2 J
- , {* k! K2 M4 y- `0 ]) Z" Q& I
- //使能时钟7 o1 }% b/ `- g# f. V; I# q4 T4 t
- RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );
' ~3 k0 y# P; O - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
4 \2 T( H+ B- c9 q: t - ; X! |) w! R" Z3 y& g
- //设置IO口# s4 b' I! ]7 j
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
; _# D6 i: i8 P8 P - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- X; a2 M$ i+ w3 \6 Y' N3 k - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;4 T# j3 L4 U3 _$ x$ @* y7 K3 ]7 q
- GPIO_Init( GPIOA, &GPIO_InitStructure );/ x! P1 x y: I0 c' Y/ E! Q% O
- b4 b: X0 ^; a
- //设置中断优先级% f) L+ v; L. P
- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;9 z9 k9 S& e6 A4 k$ V( j* R; L7 d. x. c8 [
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;( i4 _% u' ]" t7 E m/ y4 ^
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;' t. {. ^/ Z" U0 J9 N1 |
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;" V# `8 n5 d* w M6 l+ o: N
- NVIC_Init( &NVIC_InitStructure );3 A6 X# A; ^- O& L5 ?, j
- ! \3 N0 d( l' h' v2 s! i
- //设置定时器基本参数
$ y8 t9 @& M) ^, V9 J - TIM_TimeBaseInitStructure.TIM_Period = arr;
5 `1 _% i2 ~- F% ]6 W) \: M - TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
9 `5 Q" N0 `9 V* l. x* q - TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。
4 n# j/ U5 E$ | u - TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;0 f6 f( d* G" `( C# A$ M& O
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- _1 c S: A( j5 X, I+ p5 f - TIM_TimeBaseInit( TIM2, &TIM_TimeBaseInitStructure );
& ?) p% r2 W7 o; X8 A, U: E& q
' {2 V- p9 j6 Q/ f* c7 E, a- //设置工作模式
: A) T# [! I p4 p! W% n* H - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //设置定时器工作在翻转模式1 b7 x$ F. S7 c3 N) Q3 W
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;' _$ D" J* E# b+ r9 l- J: E' q" J( K
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
" z5 S" o7 M E' v2 L - * i4 x. E& d# M; i
- //设置4个通道 比较寄存器值
+ M, l* P% n$ ]( S( a {6 R& a9 f& ^- i - TIM_OCInitStructure.TIM_Pulse = CCR1_Val;* R9 s! p1 y! U2 f( {
- TIM_OC1Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA0
% ]+ ~# w$ N+ K' |2 N" Z' C/ H
3 A2 L6 V; n+ S6 f- TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
7 j# u- h& r R( Y/ j* q - TIM_OC2Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC2 PA1 X( W) B, K* j, F/ Z
# |8 w5 C L5 M$ x/ m+ j0 f- TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
% `0 F' @. `7 C z3 P! C' [ - TIM_OC3Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA2 D2 i9 d7 j' i+ S$ [- E
2 w3 Z2 I8 X. w) `" T! M- TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
9 |. M4 F# `' A8 [- m8 O9 `" W - TIM_OC4Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA3$ K& f1 f9 O+ |% t. J4 ]7 K
- , F/ f# V% b) D2 S* d
- //禁止自动预装载* M/ ~& q' e& w H3 V1 P
- TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );
; [0 J9 }2 n6 {" v - TIM_OC2PreloadConfig( TIM2, TIM_OCPreload_Disable );
9 f; N/ r0 [8 p1 X - TIM_OC3PreloadConfig( TIM2, TIM_OCPreload_Disable ); M. m, [ X. S
- TIM_OC4PreloadConfig( TIM2, TIM_OCPreload_Disable );
- Y9 H, u+ I6 V, f4 c1 E - & w# w) c* O; f& ~& ]
- //使能定时器$ K/ ?. H) ^" p( \# r
- TIM_Cmd( TIM2, ENABLE );
( K7 g6 y/ q3 r' y- L - TIM_ITConfig( TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE );( ~9 ?# r, [3 V! y, F" z
- }
复制代码 6 F4 {1 a5 h( U- {% ?
首先初始化定时器,初始化的两个参数arr和psc分别设置定时器的自动重装载值和分频系数。这里将arr的自动装载值设置为最大0xFFFF,也就是65535,相当于计数器从0开始计数,直到65535才结束。将分频系数psc设置为0,也就是不分频,定时器按照72MHz的频率工作。
. v" \/ G7 W, Y# ~+ B* c; e+ |% k6 m/ f( N; Y* S v
将定时器的工作模式设置为翻转模式,然后依次给4个通道设置比较值。这里要将自动重装载功能关闭,否则就不能手动的更改比较值了。最后使能定时器和4个通道的比较中断。2 L4 x. C0 o. G: q
7 W( ^' Z/ e6 `0 P# O1 r 接下来就是最重要的环节了,在中断中改变每个通道的频率和占空比。
p! L; R" W8 v: e, h
( \9 u' I$ Q5 o, \- Y- //占空比 0 --- 100
' S. D$ r5 H8 g# n, H% L - u16 CCR1_dc = 20;
' w5 g8 d$ V& B5 Q5 y. O - u16 CCR2_dc = 40;* s, Y* v+ d/ I4 B- ~* b
- u16 CCR3_dc = 60;
5 W* e8 l& y, g) ` - u16 CCR4_dc = 80;
, L i. ]; e2 h; L$ Q, c: g3 ]
+ Y H' t& u6 E6 j" ?- u32 capture = 0;
" d; q! s+ f7 T - u8 flag1 = 0, flag2 = 0, flag3 = 0, flag4 = 0;. a" {: Q2 ?: t8 I& b3 ]% p, S
- u16 setcap = 0;" R% p5 r# B$ x& Q
- l; h6 }4 l8 d8 w3 V: z( k
- void TIM2_IRQHandler( void )
: l Z: [; _% k$ h/ ?0 ~9 p; d - {& A8 c5 E- r t4 T7 L& ]( [
- if( TIM_GetITStatus( TIM2, TIM_IT_CC1 ) != RESET )
0 O4 u O. ?" A. m0 e- N( J - { D( N2 J1 d* h O y( B
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC1 );
8 P! r+ r' t/ P4 v - capture = TIM_GetCapture1( TIM2 );2 d" @5 Y- b4 {3 K2 P" Q' [, J
- //设置占空比6 [% W9 C8 P1 w7 f% J, k: h
- if( flag1 == 0 )
' K) m& U) Z/ f0 W - {7 l8 C7 G. q1 h/ T' A* ^
- flag1 = 1;
) \- J- Z: F# O- Y7 f1 z. X - setcap = capture + ( u32 )CCR1_Val * CCR1_dc / 100;
. ^5 R6 g$ A6 H: w% k) { - }
- W% {# \7 F. P/ O& Y6 `6 N' q. v$ k" Z - else' E* a) o- Z, |3 }; ?3 b
- {
B; y+ R1 D' @* `7 ]5 w - flag1 = 0;
2 y( s0 Y' i1 y8 f2 U0 Q - setcap = capture + ( u32 )CCR1_Val * ( 100 - CCR1_dc ) / 100;# I3 u8 \! G0 z5 Y% n$ J* f# k
- }$ k4 g0 u3 Q- h' a' G7 W
- TIM_SetCompare1( TIM2, setcap );
* a4 H" O, H+ y7 E& g - }
+ P& l1 w) S1 C" P
, F6 Y( x( F( X- x w- if( TIM_GetITStatus( TIM2, TIM_IT_CC2 ) != RESET )
+ }) O4 ]" f6 K0 x$ T: j |: e - {( u1 o }7 F3 t& n- m4 X6 @: C) B
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC2 );" O7 s) k8 y7 n! m
- capture = TIM_GetCapture2( TIM2 );. P% `. U* q, L" Y
- if( flag2 == 0 )+ h& k4 z$ Z% W+ _1 P3 Y7 H
- {
9 L" l) c; O* d9 u - flag2 = 1;
% t6 e( ^5 v& O& [ X6 O% m8 @ - setcap = capture + CCR2_Val * CCR2_dc / 100;
, |& t; j: Z) ]! [9 D - }4 B. O6 i( Q: q- D4 W! y
- else0 [9 R+ k2 N) y1 v! z
- {
0 ~/ C) V) |/ A) V# ]2 M - flag2 = 0;/ @/ p1 N" D' G, ]
- setcap = capture + CCR2_Val * ( 100 - CCR2_dc ) / 100;
1 a* @ @" J. B1 r - }. ^; C( \' ?4 P' ^: ?
- TIM_SetCompare2( TIM2, setcap );# G/ t; V& k) d) F+ ? _
- }
. z: ^& W" D) ] o2 J) B8 p" t - 6 J& t2 v. Z6 U( a0 |% G
' f( A$ j, Z! h- V- if( TIM_GetITStatus( TIM2, TIM_IT_CC3 ) != RESET ); h8 F, w$ o" B: ~$ H; p
- {
$ @' m8 ]6 e. X7 ^' y - TIM_ClearITPendingBit( TIM2, TIM_IT_CC3 ); K' R7 `4 J2 q# s6 n
- capture = TIM_GetCapture3( TIM2 );# h8 j8 A6 b: C0 @* z9 h. x9 F" ^+ }/ u7 h
- ' P* ^2 h! _# ?) m
- if( flag3 == 0 ). V& K |, L3 a% Q1 c0 n7 n
- {
" N* ~9 [1 \( p, K- ^ - flag3 = 1;
, C) ?& k# j! `% o - setcap = capture + CCR3_Val * CCR3_dc / 100;4 ~3 {, [' d8 A
- }6 e6 Z4 C, X: ~4 T9 M
- else
/ Y0 H1 j. K3 J- l% } - {
& y$ \$ n6 ^) j5 ]/ [, m - flag3 = 0;
3 `% e/ X0 W2 o - setcap = capture + CCR3_Val * ( 100 - CCR3_dc ) / 100;
- V* }$ `/ V/ n3 p# a - }
5 [7 W% V+ m+ | - TIM_SetCompare3( TIM2, setcap ); T7 |7 [6 H. P1 r
- }7 g4 v& \' O" K$ K0 f: M
- # s/ \" G* M% }, \( k0 b0 T3 e
- if( TIM_GetITStatus( TIM2, TIM_IT_CC4 ) != RESET )
/ V }2 d0 X( Q5 `% F. i6 T& `5 @8 d - {
4 Q$ }7 h$ o; s" w( H - TIM_ClearITPendingBit( TIM2, TIM_IT_CC4 );
( X z. A) Y! S! b! e2 b - capture = TIM_GetCapture4( TIM2 );
; u. ~; H' F: D2 h0 O - ' N: P& X) I" `* ]* ~9 ~
- if( flag4 == 0 )& W" ~; N0 K/ L- I6 H. y5 l' O
- {
R2 K1 K8 t/ N1 D - flag4 = 1;
* R* i# j' B. M( U - setcap = capture + CCR4_Val * CCR4_dc / 100;
9 Y# B; a0 T% k3 n: ] - }
1 A( i5 [' u3 q2 ? - else
' J. R: \/ V2 U - {
) ^( O/ h1 b3 t. g$ n - flag4 = 0;% p( ~6 f- N4 U7 [5 C# u2 S
- setcap = capture + CCR4_Val * ( 100 - CCR4_dc ) / 100;. T2 A/ Q. n% w2 e
- }
6 o8 T+ b$ K1 N" M! ?% y A& f" H - TIM_SetCompare4( TIM2, setcap );; n1 `' t4 ? i8 X
- }! _: X" ?+ h$ Q& S1 K" [( L e% N
- : M5 I i5 F: {6 D* K
- }
复制代码 ) C; ]9 N% [; E) }% H, `
进入中断后,首先读取当前比较寄存器的值,因为比较寄存器的值是会一直累加的,直到比较寄存器的值等于ARR的时,才会清零。所以每次中断的时候,需要将现在的比较值读出来,然后加上一个值,作为下一次的比较值。读出当前的比较值存放在capture中,接下来需要在加一个值,作为下一次的比较值。因为要改变占空比,所以增加的值有两种情况,分别是高电平持续时间值和低电平持续时间值。为了区分高低电平,这里使用的一个标志位来判断,标志位的值只有0和1两种情况,将0作为高电平的时间,将1作为低电平的时间。CCRx_Val 中存放整个周期的计数值,当标志位为0时,CCRx_Val 乘以高电平的百分比,计算出高电平的累加时间。当标志位为1时,CCRx_Val 乘以低电平的百分比,计算出低电平的累加时间。CCRx_dc 中存放占空比的值,范围是0到100,表示占空比从0%到100%。 当需要改变周期时,就修改每个通道的 CCRx_Val 值,当需要改变占空比时,就修改每个通道的 CCRx_dc 的值。
8 h, `+ p6 Y3 j3 {( `/ B3 F+ K: l5 a9 V9 y6 {
- int main( void )
0 ?- f) ^; @; }" V* U - {; H1 V* T6 W1 T, R( ~" I
- u16 i = 0; _: N% [$ H5 \7 Q) S, Y
- delay_init();
( `- p4 J" a( }0 v1 ~ - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );7 f9 }# R7 B6 v8 c+ o
- TIM2_PWM_Init( 65535, 0 );
0 f& I0 N% d2 ]- c4 ~. @! Q# Y7 _) } - while( 1 )$ i" y; Z# {1 @# } k7 X
- {
5 a( f: L0 c* q: e, K' v) n r - }
+ V6 ~* O% ^# _6 f - }
复制代码 ' y0 A! x' l; N5 \3 F
接下来在主函数中初始化定时器,定时器2的4个通道输出波形如下:
& C. K/ l1 c ~# ~" y6 ~6 L
1 |2 ?2 Y% R* @) V* g
$ m9 W" C: q. U0 C& m/ f- J' r5 `
6 P: _9 [) @/ b$ P' q( O! x r& ^
通过波形可以看到,定时器2的四个通道频率和占空比都是不一样的。说明通过定时器中的比较模式,是可以实现同一个定时器输出不同频率PWM波的。
+ e4 O- H! @' J' n7 u- F* N5 I
. n+ U& G: @; t* }2 \$ H. |; A- Q( D1 o ~
; K8 T4 |3 G3 w/ [) E7 S2 _ |