在使用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个通道的占空比。5 T# }8 C4 [3 z: H1 x
8 |$ m% I) ]6 {$ Y X u 如果使用中央对齐模式计数的话,那么定时器的计数波形如下:
- ~( T5 ]: W) ]) d2 x
; d5 j; t) {4 i- u7 ~6 ^6 `& {
) _3 ?: x3 K X 三角波就是计数器计数的方式,从0开始逐渐增大到装载值ARR,然后在减小到0。蓝色的波形就是比较寄存器的值CCR,计数器在计数的时候,每次都会将计数值和比较寄存器的值CCR进行比较,当计数器值小于CCR的值时,输出低电平。当计数器的值大于CCR的值时,输出高电平。
3 Q, h# o ]' }5 w |6 T
2 H" d* [* F% N
* C. L: o, p& }: |' o' t
/ G, \1 D; B5 q; C# V7 l2 P, T 当需要改变占空比的时候,只需要改变比较寄存器CCR的值就行了。
. p7 o! P% J% ?2 v: m5 X# U" w3 x
5 |7 s3 r* g5 k* m
1 r8 ~ N1 v- a# B8 p# t
* L! s0 j* z+ }. g 通过改变ARR的值设置输出PWM波形的频率,通过改变CCR的值改变PWM波的占空比。但是用这种方式输出PWM波时,一个定时器的4个通道输出的PWM波频率都是一样的,那么能不能用一个定时器输出4路不同频率的PWM波呢?
* f/ {5 v6 U+ I( t X 通过观察捕获/ 比较模式寄存器 1(TIMx_CCMR1)中的OC1M位时可以发现,除了PWM模式外还有一个翻转模式,在翻转模式下,当计数器CNT的值等于比较寄存器CCR的值时,输出的电平就会翻转。& d" Z9 I, J6 {
6 a# e9 w- x7 \1 W; a: l
' w" ^2 E7 V& Q8 i1 h, S+ ^& w9 D5 l" O' R% L
% j! D# G) o3 ?" e) I7 k; U
那么就可以通过这个功能,在输出PWM波的过程中不停的去改变CCR的值,那么输出的电平就会不停的翻转,这样就可以手动改变PWM波的频率了。1 x. v3 ] h; s: b+ A
5 F) C: m0 {1 _8 i, U) b
( V' c `: s# {& s/ U
; p- a' q8 `) H
比如第一次将CCR的值设置为CCR1,然后使能比较中断,当计数器的值CNT等于CCR1时,就会触发中断,然后在中断中将CCR的值修改为CCR2,。接下来当计数器的值等于CCR2时,又会触发中断,在中断中继续设置下一次CCR的值。这样每触发一次中断后,输出的PWM波形电平就会翻转一次。在计数器从0到ARR计数期间,PWM的波形可以翻转好多次。在PWM模式中,每一个计数周期波形只能翻转一次,但是在翻转模式下,一个计数周期可以翻转好多次,这样就可以改变输出PWM波形的频率了,同样每次设置不同的CCR值时,也可以改变占空比。/ \+ M# e# o$ s) D4 b
, v! ^+ f! r$ V5 R5 l/ x6 [ 下面就通过代码来实现。% f0 H5 b' n a1 {$ H; K9 d! \
5 E" i+ g8 M% f9 Z5 n5 n- #include "timer2.h"
- \3 b; Y/ E1 V - //比较值- m0 q3 a: K2 p$ R( @1 ~) N& q
- u16 CCR1_Val = 32768;& }) X5 H# M4 J
- u16 CCR2_Val = 16384;! E1 x! o$ {" V" T7 p, ^
- u16 CCR3_Val = 8192;# y8 K7 l8 f3 F
- u16 CCR4_Val = 4096;) t/ f7 j3 N, |( I$ A
- ' `5 G+ Z- a2 X/ b0 a5 K
- void TIM2_PWM_Init( u16 arr, u16 psc )7 ~9 Q" m$ N$ x6 h
- {/ s' d4 i" k/ Y: u
* B& S# [, r* \8 W6 j: R) J+ o- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
4 E! D' r$ w$ d - NVIC_InitTypeDef NVIC_InitStructure;
) G3 q, y9 b ?! L7 [3 f4 K/ V ? - TIM_OCInitTypeDef TIM_OCInitStructure;/ R- Y! j e/ d+ k2 X4 F) _
- GPIO_InitTypeDef GPIO_InitStructure;' |2 }, x H5 v% p/ z p
- 0 V6 \0 k% O* [4 d2 u5 o
- //使能时钟& j0 M- c# h; L
- RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );9 x k! T/ l, {) y- J+ c
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
7 x6 E" l( e5 u
+ D, t6 x: w5 E- //设置IO口
F; _* R8 C; {6 ~4 x- g - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
) \ P/ Y; g2 X; c) O - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
: W: s# R; A7 i% { - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;$ [0 s1 R% m7 N5 {
- GPIO_Init( GPIOA, &GPIO_InitStructure );
, f, \5 D2 O$ C/ q" a4 Q: j
; M5 B4 U# K6 q, \4 b- //设置中断优先级" W6 d7 I' a3 M' H
- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
; [+ g2 Z& A, N8 u1 `- d% Q - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
- j1 h! H6 a( `7 ]8 O# d$ h2 ~ - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;$ x/ {% g- e2 k
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;" B4 U, U+ p( T
- NVIC_Init( &NVIC_InitStructure );
( `% h, D) \1 B& D2 l
3 S \+ b5 A, l; q- //设置定时器基本参数
; a( s: l, g# d6 L3 z* y - TIM_TimeBaseInitStructure.TIM_Period = arr;
6 ^+ d( W3 B3 S - TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
. {% ^' L% d4 F6 E& w - TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。
$ W8 V, K2 ]- b- O7 A# O - TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;2 p3 X z4 m3 _: `; `# t. ~' {
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;* { m G; b. m0 H2 T
- TIM_TimeBaseInit( TIM2, &TIM_TimeBaseInitStructure );7 a: a/ ]1 V, s, I" s5 [
- 5 f: M$ m- w1 C; U
- //设置工作模式4 U8 e! F% u6 s- F5 l8 E
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //设置定时器工作在翻转模式
5 t% R% V- g6 p - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
5 e2 c( K$ I H$ M, h' i6 a5 I - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;! T$ @; [) g6 c. y7 |
& E* f) T. b" B8 K- //设置4个通道 比较寄存器值
, d2 G2 A& i' m& O# b- t - TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
1 j: Y- _& F' H; W0 R: }% r8 R$ K - TIM_OC1Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA0
* O5 Z% L7 R* i& k
& s/ B4 [- X$ W( [- R2 z- TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
/ G/ I/ V& `0 _4 h) f - TIM_OC2Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC2 PA1
% T& u! P6 `8 T2 r. M+ E C; l
+ r4 Y4 @! h4 Y2 [7 i- TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
% @8 ]) K+ o, P, I - TIM_OC3Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA2
) b: ~4 B, {& d9 H1 U. @- `
6 X5 E1 [( \ k: |- TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
. D" i1 t; O7 o( R& T; }% M: O - TIM_OC4Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1 PA3
( p( A' b( }3 b, D; g
" V) Z/ w" {) C" C7 b& m0 P& U- //禁止自动预装载4 p9 ^ a9 V* U
- TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );
. s+ p* Z* @( g9 q( ? - TIM_OC2PreloadConfig( TIM2, TIM_OCPreload_Disable );
; Z7 [9 D# c# g- i4 Y - TIM_OC3PreloadConfig( TIM2, TIM_OCPreload_Disable );7 S3 l h( P% o
- TIM_OC4PreloadConfig( TIM2, TIM_OCPreload_Disable );
) g v% ^* D/ x - ; [: i, e. z$ z* A+ n
- //使能定时器' q% m2 M6 C$ U$ _6 Y3 X( v
- TIM_Cmd( TIM2, ENABLE );
k! s Z0 s `( s - TIM_ITConfig( TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE );
: u) [4 c- w- V" I! M - }
复制代码 6 U S& x3 l; e3 P) }) l
首先初始化定时器,初始化的两个参数arr和psc分别设置定时器的自动重装载值和分频系数。这里将arr的自动装载值设置为最大0xFFFF,也就是65535,相当于计数器从0开始计数,直到65535才结束。将分频系数psc设置为0,也就是不分频,定时器按照72MHz的频率工作。6 \) ~ }! }5 V" }
/ |4 D8 ]* ` a6 W2 d/ j, R3 d! s
将定时器的工作模式设置为翻转模式,然后依次给4个通道设置比较值。这里要将自动重装载功能关闭,否则就不能手动的更改比较值了。最后使能定时器和4个通道的比较中断。 v; a& o3 Z6 }" }8 T
( E6 `9 z' U: ~% p. O; |4 q
接下来就是最重要的环节了,在中断中改变每个通道的频率和占空比。* K- b9 B' H1 Y# S' I5 I
) F5 Q! E5 \% }0 M/ Y; M, N- //占空比 0 --- 1000 u7 Y5 m' e4 s
- u16 CCR1_dc = 20;
' i2 W0 {5 x3 q$ b - u16 CCR2_dc = 40;0 D; {9 q9 T8 v, Q. d4 j! u
- u16 CCR3_dc = 60;, V2 G8 Z0 c' N
- u16 CCR4_dc = 80; x# i4 e4 X4 i% ?
- 4 ^( T# I. j7 H' G" z
- u32 capture = 0;
_1 N* n3 u* L7 N9 R8 ? - u8 flag1 = 0, flag2 = 0, flag3 = 0, flag4 = 0;+ @" `" W& r( b6 I' |9 v
- u16 setcap = 0;
$ P: B- b$ ]! E - * S1 ]1 t! S! a
- void TIM2_IRQHandler( void )
0 M2 a2 I* u) m. G2 r - {
& V! R7 w6 b5 i9 h - if( TIM_GetITStatus( TIM2, TIM_IT_CC1 ) != RESET )
8 Q) n, Q3 z# ~ y - {
+ p* ?/ y/ r, N6 [ - TIM_ClearITPendingBit( TIM2, TIM_IT_CC1 );; X" w( z) y: b
- capture = TIM_GetCapture1( TIM2 );
6 j3 [+ ~+ O0 g9 `" _* K' E - //设置占空比+ }5 K7 z9 o/ {3 S
- if( flag1 == 0 )4 |) T- ?' s- R
- {
/ D% ]6 O: D7 h7 Q- Z: Y2 _ - flag1 = 1;
9 P) v# \; }% w/ i. ` - setcap = capture + ( u32 )CCR1_Val * CCR1_dc / 100;, f/ Z7 t( g7 k/ `: g' }0 n; e
- }3 ^3 F9 W& C1 S2 ]$ V' i$ ^
- else
2 v8 |- b0 z. f0 }0 X+ G - {: K# ^5 E$ D. ^6 M& ^
- flag1 = 0;* n$ f$ t [1 _
- setcap = capture + ( u32 )CCR1_Val * ( 100 - CCR1_dc ) / 100;8 D2 I. I8 N) t! Q
- }- k) X' A; K+ F
- TIM_SetCompare1( TIM2, setcap );+ q B- A- b* A, r
- }
0 P, R) ?' }/ u
4 P5 o( a' h; E1 i) T+ O- if( TIM_GetITStatus( TIM2, TIM_IT_CC2 ) != RESET )
$ ?, A3 n. C9 @) v - {$ z/ E: E, G! _. ~7 p7 r, \7 v# q, P
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC2 );6 g5 U. u* P# q" k) L
- capture = TIM_GetCapture2( TIM2 );
7 n8 \4 a5 |/ E' q" f9 u6 ]* x - if( flag2 == 0 )
% ?, v$ O ^8 I2 `% N - {
; u0 C$ ]1 x; ?5 G; V7 p0 l: U - flag2 = 1;( y& h. F. u, Z
- setcap = capture + CCR2_Val * CCR2_dc / 100;
+ c3 [9 I7 `4 {, k) Q& x - }
7 B/ i/ }/ ^& u; F - else
' T/ G- C! M# S- b/ Z1 S: k) \9 q0 |( t - {) w$ k9 R+ B) ~* Z8 i* F5 @
- flag2 = 0;9 b7 i h! s8 P0 z% U
- setcap = capture + CCR2_Val * ( 100 - CCR2_dc ) / 100;
+ C% v9 x3 c r' D* |+ j; W9 O - }" x& H0 l4 R/ N- F
- TIM_SetCompare2( TIM2, setcap ); U0 h4 c; m6 ^
- }( ?% j# V, d( i. O# ~8 R
( M$ k" L- I. f3 p! q
! o- J3 ?) z0 g- if( TIM_GetITStatus( TIM2, TIM_IT_CC3 ) != RESET )
. J( f% |* p6 P* R# t- F' D! t0 y - {
: d3 M- e( ~, k; V. { - TIM_ClearITPendingBit( TIM2, TIM_IT_CC3 );4 i* {9 \9 m, m, V! u7 ^" V8 \
- capture = TIM_GetCapture3( TIM2 );
2 `8 c* N) g: y" X- z% _ - 5 O3 ?) y. V2 v! S7 M
- if( flag3 == 0 )6 l' e6 j; ]5 b) M: ?. e0 c
- {
, l6 S( }8 B8 g. p" z - flag3 = 1;
+ q& r e$ A8 O0 R: } - setcap = capture + CCR3_Val * CCR3_dc / 100;! p$ D2 |, a6 j$ E* R7 z
- }
7 T( w! B% B+ V5 D4 Q1 Z4 v - else
& M# m9 A0 x7 g K( h* K - {% b7 r' u/ S* h. [: k
- flag3 = 0; }8 z; r8 H" f+ ]3 w1 C. `* n( y. ^
- setcap = capture + CCR3_Val * ( 100 - CCR3_dc ) / 100;' o1 W- ?3 n$ _1 H' c
- }
, l# ]. P* J2 P/ a- t% w U; k - TIM_SetCompare3( TIM2, setcap );
4 ?8 l$ M0 R) l& a" P - }& T6 j+ |1 ^2 C' `' i
- - ?8 V( y+ z& X& h/ A# @
- if( TIM_GetITStatus( TIM2, TIM_IT_CC4 ) != RESET )
: B3 ]) ]+ [4 B |8 h - {' l, r8 E" I$ t1 u" E
- TIM_ClearITPendingBit( TIM2, TIM_IT_CC4 );% h4 E7 A, P2 w" b2 a; g5 E0 L
- capture = TIM_GetCapture4( TIM2 );3 U' J: }# c: I8 ^ n% j2 N
1 m. E) y; i3 X0 ]- if( flag4 == 0 )
0 U$ U- S5 t; w) v; _4 I - {
1 w/ T y9 F, B* n" {# Q - flag4 = 1;1 K3 X0 J) E) Y: E+ d1 X
- setcap = capture + CCR4_Val * CCR4_dc / 100;
! W) b. S! t$ a; a) }4 @3 i1 F! ?9 t - }$ D0 I0 o7 A( `
- else$ U, e8 W/ c$ S5 B V/ Q
- {
7 b3 ^+ z; Y. a - flag4 = 0;; w/ c/ K* A2 D
- setcap = capture + CCR4_Val * ( 100 - CCR4_dc ) / 100;
" D' C. c4 R) |! Y3 E8 T - }
/ G7 u- ?" P/ K - TIM_SetCompare4( TIM2, setcap );
! o5 M% Z- j9 y - }' y" V( S) q* P+ I) E; W; b
$ s. m. X& C+ q% Y- }
复制代码
0 m- o% ^2 X, ?( E 进入中断后,首先读取当前比较寄存器的值,因为比较寄存器的值是会一直累加的,直到比较寄存器的值等于ARR的时,才会清零。所以每次中断的时候,需要将现在的比较值读出来,然后加上一个值,作为下一次的比较值。读出当前的比较值存放在capture中,接下来需要在加一个值,作为下一次的比较值。因为要改变占空比,所以增加的值有两种情况,分别是高电平持续时间值和低电平持续时间值。为了区分高低电平,这里使用的一个标志位来判断,标志位的值只有0和1两种情况,将0作为高电平的时间,将1作为低电平的时间。CCRx_Val 中存放整个周期的计数值,当标志位为0时,CCRx_Val 乘以高电平的百分比,计算出高电平的累加时间。当标志位为1时,CCRx_Val 乘以低电平的百分比,计算出低电平的累加时间。CCRx_dc 中存放占空比的值,范围是0到100,表示占空比从0%到100%。 当需要改变周期时,就修改每个通道的 CCRx_Val 值,当需要改变占空比时,就修改每个通道的 CCRx_dc 的值。( Q8 m* F! E8 I8 H2 E3 _8 E, H
% X7 Y, N) U2 w6 e- int main( void )
/ s; z6 v$ U& T$ t. W - {' H" ^8 a) j3 P4 O4 [
- u16 i = 0;5 \' E/ z6 e% a0 [# o" q9 p0 x
- delay_init();
0 b+ H' y: z" } - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 ); ?$ ]( Z- p5 k3 R" W
- TIM2_PWM_Init( 65535, 0 );- {6 r/ U! K! l) n
- while( 1 )+ y! T7 k6 ]3 a' Y4 |1 |
- {
# e9 g) }0 H2 }1 x0 v! A. H - }" X c1 b9 M* a. q
- }
复制代码
; N: S# s( }) \ 接下来在主函数中初始化定时器,定时器2的4个通道输出波形如下:6 j; @/ }5 Y! a* s- D
H* D* i8 J4 U& \3 ]. X" |: g
9 b y+ \+ @$ v/ r! C
5 |! r! r3 l3 W2 k' E* _; l7 I! U
通过波形可以看到,定时器2的四个通道频率和占空比都是不一样的。说明通过定时器中的比较模式,是可以实现同一个定时器输出不同频率PWM波的。
9 u E" Q2 t2 F8 }2 F5 Z1 [9 ^- }2 l/ n. j: L+ ]$ z, a1 y
. D; B+ q1 c6 B5 [- h- A B- O& h& D3 \# H
|