- /* STM32 嵌入式学习入门(5)——PWM的实现% S- p$ |" U5 P" _
, b; J# C, f: Z: q! F1 M! E- 上一篇博文介绍了定时器和PWM的基本的原理,本篇博文从代码层面来介绍PWM的具体实现。同样,还是以博主所用的开发板——正点原子开发板STM32F103ZET6为例。
% J& e9 K! |* A, P/ ~& p4 L9 N - ! q4 h; Z0 Q$ `+ `
- 一、基于STM32的PWM输出配置步骤(初始化操作):/ c$ j& M. v9 \! P! Z4 j
+ k8 Q& W; T* j( s' Q- 1. 操作步骤(基于STM32固件库、使用定时器3的PWM功能):3 n4 H* H* s0 K4 c/ m+ i
- (1)使能相关时钟(定时器3和相关IO口时钟。):% [$ B; I; b9 t; Y. n0 m2 o b
- //要使用什么外设就要先使能相关外设所挂载的时钟,这些内容在最开始GPIO那块就有提到STM32的GPIO介绍, s" f7 J% m9 @- a2 d5 T
- ①使能定时器3时钟:RCC_APB1PeriphClockCmd();) _2 G- i: S$ U; r# B) H
- ②使能GPIOB时钟:RCC_APB2PeriphClockCmd();0 j' u' ]: T3 ^) F8 h/ u! K/ a
( d+ x; t" K+ ?) G5 y5 ^- (2)初始化IO口为复用功能输出。函数:GPIO_Init();
7 S9 L7 \4 ^. x( M - //同样,IO口初始化的操作在STM32的GPIO介绍那篇文章里也说到了。
7 W7 S1 u9 f3 E" X4 P3 s9 D8 H5 n - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; , \0 ^ ~) J' e: f7 B/ Q
- //这里我们是要把引脚用作定时器的PWM输出引脚,因此要重映射配置。所以需要开启AFIO时钟。同时设置重映射。
1 W) N& W# L4 `- L- w& O7 c - RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);$ u# t4 g; b0 O$ O8 f
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); 5 H) F3 l; _8 Z" _+ y! B* K3 B
* s: h8 C1 _9 [/ Z; ~, s- (3)初始化定时器:ARR,PSC等:TIM_TimeBaseInit();ARR寄存器在上一篇文章讲原理时候已经说到了。PWM原理) q b9 d2 d# w, s, d0 L
- $ A) h6 v$ d; ?5 U; ~- z
- (4)初始化输出比较参数:TIM_OC2Init();/ Z3 U% K8 T1 _5 o+ `6 z$ y
- (5)使能预装载寄存器: TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); . i B; q8 U& `5 C/ c
- (6)使能定时器。TIM_Cmd();
. q3 L. e8 z& q3 M& G& a6 E% v1 x# n - (7)不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare2();, c) l, A" w& P) r) x4 W" l9 c
- " Y1 t7 F+ w1 Q$ T. Z, }+ E( c( c" {5 b
- 2.初始化源代码:5 G+ x; p2 n' T# U
- (1)以STM32F103ZET6为芯片的开发板的PWM初始化,这里只是初始化一个通道用作PWM输出 */9 h5 q0 k" n1 V' t9 U8 R6 C
- //TIM3 PWM部分初始化
# S1 y" k: G; ?' Q5 {: K* t - //PWM输出初始化
( ^2 B, Q( b# Y( c: i0 z - //arr:自动重装值) y( l9 [8 k& r. o; l
- //psc:时钟预分频数
4 o7 w/ _6 C) G4 x% n - void TIM3_PWM_Init(u16 arr,u16 psc)//STM32F103ZET6- v/ Z. }* R: x
- {
5 S9 v+ G4 Z7 v5 X" Z& H. q - GPIO_InitTypeDef GPIO_InitStructure;
7 I- r- j4 R0 d" I* h - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
N$ S$ F. s2 H' [; p - TIM_OCInitTypeDef TIM_OCInitStructure;
9 M' g! x( ~7 q/ g1 c - \% E1 n2 ~) I, F$ N, N
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟% N* U) v. H6 W! A- P! R
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟0 w. Y2 u* u: u& f: c! x
- 0 V' P2 [3 f) K
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5 & a& p! L5 X9 i' e8 n& J+ q
- 0 J: Q, u* S6 m8 v+ Y
- //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
/ g, g$ A: y& H1 L- o ?2 ~3 L/ M - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2- B; I5 v7 Q# w1 L3 c8 I
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出( a0 q5 J: a4 C. v( H$ i
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;! v/ n& |5 z* k+ g6 p
- GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
" y3 H1 q5 K* R f: C7 q9 V - / x7 F7 `' R5 w- J* A- H
- //初始化TIM3! S- X1 ?5 r1 @ ?& G Z& {
- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值( D* l$ n3 x4 N* ?7 L* R# S
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 u- J* Z0 e! d% r
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim: S* T* o' I0 p% [
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式) S/ ?0 a/ @; f) A( w. w/ ?. O
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
7 @! `. `5 s; \" J" e) y% q% ~! h -
3 z) N: M' `/ \( Q7 i2 h - //初始化TIM3 CH2 PWM模式 * g+ G0 z/ ?5 |" i* q- [1 l- G- P
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
! R# _1 t. s$ R$ W( c' ~% t+ | - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
# t+ i- T$ A6 M- p0 P5 }) d2 v - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
4 R+ t* j/ C1 D% M - TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC28 u4 | q$ l+ D! B- s% P$ e, ?
- 3 G) K2 l$ P. g* C' J8 t* C |
- TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
E# a" s& }0 E6 h8 e3 [4 d- R -
+ H' h- F! w! m" p4 P" u" }/ l+ ? - TIM_Cmd(TIM3, ENABLE); //使能TIM3# j; S2 V0 l: M2 P6 C
- }
2 t# ]& K0 ]0 l ^ - /* 8 R1 ^9 b5 r) j {& r0 U3 z; r
- (2)下面的代码是博主做嵌入式循迹小车的项目中的PWM初始化,用了TIM1的两个通道(通道1和通道4)去分别控制两个驱动轮的转速,从而实现让小车转向的功能。和上面的初始化代码的不同主要在于小车项目中用了两个通道,同时项目使用的开发板的芯片是STM32F103RCT6。在这里贴出源代码,做一比较。 */! p( a+ C& k+ ?/ U- A5 S
- //PWM输出初始化$ a$ ?, [) {9 l
- //arr:自动重装值
1 Q% _2 {( o! x5 j. L* w$ R) } - //psc:时钟预分频数
" Y% u, ~/ j3 P* G. G - void TIM1_PWM_Init(u16 arr,u16 psc)//STM32F103RCT6
7 O2 u+ Y: y; O4 W3 g1 B$ `! A - {
( c t8 }2 B. U% \: Q9 M9 Y - GPIO_InitTypeDef GPIO_InitStructure;) z# ^. G8 Z" F3 h" U* k
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
9 a9 X8 o& |) `" `. a* j) C7 q* c - TIM_OCInitTypeDef TIM_OCInitStructure;! s' f3 W4 H5 j) P
-
1 T+ r0 l$ Y, r3 ]7 T: | - RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
& ^& D! ^8 C, `) v h% ~. e - 3 D2 R' w( O, R7 F+ E3 |
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA外设时钟使能% d" h- r. J0 h4 D
-
- N' R' t; W H - //设置该引脚为复用输出功能,输出TIM1 CH1和CH4的PWM脉冲波形, N9 r7 ~; i8 g4 K( T; D) j3 p
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11;//TIM_CH1
7 _: U/ Z% r' X ^8 F5 l - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
( M' [) c5 R( m" ]% u" S; c; g - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;! q$ b6 J$ N- l- x5 l
- GPIO_Init(GPIOA,&GPIO_InitStructure);' R' ]6 A9 b' @1 O2 Q
-
$ t. N4 t9 |: s7 U3 f& a - TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K4 L4 K9 t$ R! b, l2 @3 B7 T
- TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
* _( x" G# t2 d `8 l - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
$ T/ w! f, Y" m7 T& W Y: ^ - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
7 ]+ C- ~- Y% D/ \ - TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位3 O$ Y; {8 \# k/ @$ r
-
4 Y; F% V2 z$ }3 ^' i" I - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
; S2 o4 C3 t6 j: R4 q+ c( N: X5 z - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
- h* s. g# U; U4 x - TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值# k) z2 a7 K8 i/ W% B
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
$ [+ M, x. y& @) s -
1 F; Y: S$ G* S! [0 i - TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OC1InitStruct中指定的参数初始化外设TIM1的通道1. w. p4 R+ {9 W5 g( P" R {( c
- TIM_OC4Init(TIM1, &TIM_OCInitStructure); //根据TIM_OC2InitStruct中指定的参数初始化外设TIM1的通道4" J+ k; N; m3 x8 a" D: f( G- [
- $ o6 x0 c j% Z* F# t% ^. |: X: ^
- TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能 3 {9 ~( a3 ^) }" z! S
-
. U* f2 ?( s5 u2 P/ T& \ - TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH1预装载使能 通道1
/ S6 Z% m2 I" W, {1 \7 |6 d: I - TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH4预装载使能 通道4
& ~6 }7 u6 `. n0 ` - + L) g0 P' C/ A: M6 {" }
- TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIMx在ARR上的预装载寄存器(x==1)
' f- v# x) z0 k" c* l) w1 w - 8 Y6 a. E" c. K6 A Z7 w
- TIM_Cmd(TIM1,ENABLE); //使能TIM1
; f9 \$ i( l* d; g$ y4 _4 I; ]* q - - O/ ?) @ u. a
- }
/ p2 i5 Q: u n3 v) }. L - ! A7 _7 t; B& X
- /* 初始化函数在主函数的开头处调用,调用函数的两个参数根据自己需要去设定。7 b8 C! i+ @6 z j
7 Z' n+ N9 _; @& i- ' H! K: c# g: l" d& h& v/ C
- 二、PWM的操作:
0 M9 @, q4 p" X8 q' u
0 r" T9 o* P1 h- `: s- 上面的初始化结束后就可以利用相关引脚输出PWM波了。这个输出过程简单说就是改变比较值CCRx的操作,就是往CCR寄存器里写值。这一点上一篇文章里有分析到。下面先介绍一下往CCRx寄存器里写值操作的函数(来自官方库函数):TIM_SetCompare1(); */
& S" S- l1 R8 e/ X# |, C* Q - /**+ _/ p4 }" L, N$ E) w3 `
- * @brief Sets the TIMx Capture Compare1 Register value4 a1 Q/ S# V8 r {
- * @param TIMx: where x can be 1 to 17 except 6 and 7 to select the TIM peripheral.1 r- x; u' ]' `
- * @param Compare1: specifies the Capture Compare1 register new value. j! i/ ^5 V9 ~% I9 m; K' o
- * @retval None. M9 h8 Q* T) n% c8 A* \0 k, q
- */4 ^) s) O7 E8 ^+ [" g8 K
- void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)
! y l, Z8 B; H- @ - {
/ A* U4 W* a1 F7 ]; m1 z1 D& j5 P - /* Check the parameters */
$ T8 M' E2 n$ t" N2 }9 G* z; C0 ] - assert_param(IS_TIM_LIST8_PERIPH(TIMx));//有效性判断8 C) K) p! f/ w1 r9 W
- /* Set the Capture Compare1 Register value */
6 O) L2 B" q1 t/ o5 N* a - TIMx->CCR1 = Compare1;# ?+ t- Y# J& e
- }
: f( L e0 s. a- Y1 L+ q5 e - /* 说明:和这个函数类似的一共有四个:TIM_SetCompare1(); TIM_SetCompare2(); TIM_SetCompare3(); TIM_SetCompare4();其实现框架都是一样的,四个函数分别对应四个通道,使用哪个通道调用哪个函数,要对应起来。
$ Z6 \4 W( T, O5 b7 L, p - : r" |% Z4 T% C: _, g6 c6 @3 Y
- - k) Q, Q: ^9 x$ u5 G- d! R
- 下面先介绍还是以自己嵌入式循迹小车的项目为例,比如小车直行时就是在执行下面这个函数。 */
% K5 T. y! G, w0 q z* Q - u8 Case_F_SIGN(void)//直行
- s" j6 X7 ]) `; T6 `3 Q6 A8 O - {
) m' R# Q R2 ^! Z6 z9 R - while(1)
- R3 D8 w( }$ s# u; n6 e/ @ - {* @7 b% a8 M# U9 ?5 ^$ ?
- delay_ms(10);( C# ~) S1 [- a4 v
- pwmvalR=413;
7 S$ g1 L0 C7 g$ D& ? - pwmvalL=413;
% Y5 ^* Q+ G# L# y4 _8 O - TIM_SetCompare1(TIM1,pwmvalR);
5 ?4 B) K. F; l" ^* y' M - TIM_SetCompare4(TIM1,pwmvalL);
0 R$ W- Q4 Q& @, j* q - t=SensorScan();( V. R& s2 A1 p. v* B: L) e9 d
- if(t!=F_SIGN)
: }% j. m2 X# U/ { - return t;2 I& |1 { W3 ^1 Y; |
- }: f2 z! x- ^; v7 Z
- }3 V- w" O! M/ ], g$ w* Z' D
- /* & O) s+ d, g9 M' ~ R% k
- pwmvalR和pwmvalL是两个全局变量,它们的值是根据项目需要和小车硬件情况实际测出来的,然后调用两个函数,写入相关寄存器,,下面是调用SensorScan();看小车是不是还沿着轨迹在行驶,如果不是,那么跳出while(1)循环,否则,返回主调函数当前小车的状态。这段代码现在看写得不是特别好了,但是说明这个函数的用法还是没问题的。
# h' @( j! e7 d6 s: n* R8 I
7 U* T( ~# o1 A) f7 W
$ Q& l/ |) P- ^8 s2 h9 Z- 高级控制定时器(TIM1 和TIM8)
n1 R/ I: D! ~5 U5 x {$ |$ a5 b
' I: l1 J- p% Z# m- TIM1和TIM8定时器的功能包括:
# m& E3 h; N* Q1 E( Y - ● 16位向上、向下、向上/ 下自动装载计数器 & t2 z* y/ R( P- [1 Z
- ● 16位可编程( 可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535 之间的任意数值
. O1 c, q! e3 U1 N2 { - ● 多达4个独立通道: 7 N7 y0 I( H" h* S+ J4 u, ~
- ─ 输入捕获 ) c* H) Z3 f3 Y4 ?" o# {& m) w
- ─ 输出比较 ; {) @% ?; p' S5 ?6 Q; a5 n
- ─ PWM生成(边缘或中间对齐模式)
" N+ h; Y# p7 n - ─ 单脉冲模式输出 ( I, |- B4 i* i: s
- ● 死区时间可编程的互补输出
p# ^7 U; S" w, O& q - ● 使用外部信号控制定时器和定时器互联的同步电路
4 o; R, R) C w# j - ● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
& [, `$ p6 U9 d - ● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
3 q x# S( \# F8 A; {9 [ - ● 如下事件发生时产生中断/DMA :
p3 h Z! W0 C( v7 r - ─ 更新:计数器向上溢出/ 向下溢出,计数器初始化(通过软件或者内部/ 外部触发)
8 Y! j) s) A% K; P1 C- ` - ─ 触发事件(计数器启动、停止、初始化或者由内部/ 外部触发计数)
+ q' {3 A; A1 ]1 v& o" e - ─ 输入捕获
$ r9 h" A: l3 [+ w; y! }& N5 M- B* E - ─ 输出比较
# m5 r, x0 K3 C1 y - ─ 刹车信号输入
( O5 Y% ~! d5 b5 Q - ● 支持针对定位的增量(正交)编码器和霍尔传感器电路 . n2 U% \: K9 _: K) g: `3 s
- ● 触发输入作为外部时钟或者按周期的电流管理
% J: {# a& ?; s4 l
8 E2 R7 S1 m/ m, p. P) J: ^. m- 通用定时器(TIMx)
( g5 f) w9 U j - + T* Y; ?+ j* C( ]; W
- 通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括: # L: f9 f1 b$ o! L+ M+ C
- ● 16位向上、向下、向上/ 向下自动装载计数器 . m% |, Q0 }1 u
- ● 16位可编程( 可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536 之间的任意数值 . H: w* s( B: p m4 W' c8 u7 N
- ● 4个独立通道: & B* Q B+ c1 [+ G2 X
- ─ 输入捕获 9 ] w4 w; x* i6 ]/ E, t
- ─ 输出比较 # S$ ]9 @: J* w
- ─ PWM生成(边缘或中间对齐模式)
& W/ U1 F8 [, j1 J - ─ 单脉冲模式输出 9 Y# j/ J* _1 c- ^3 O4 \
- ● 使用外部信号控制定时器和定时器互连的同步电路 * v- x/ p6 X: f& O6 n
- ● 如下事件发生时产生中断/DMA : % g3 {! z/ i# l; Y; K7 |0 G
- ─ 更新:计数器向上溢出/ 向下溢出,计数器初始化(通过软件或者内部/ 外部触发)
8 A) s9 s% A8 V( C/ s* v - ─ 触发事件(计数器启动、停止、初始化或者由内部/ 外部触发计数) ( ^+ r+ |6 X. G, ^( t8 D
- ─ 输入捕获
# x" `6 a* l6 [9 Q4 K8 V - ─ 输出比较
( p z7 d3 t1 H9 I% m) U - ● 支持针对定位的增量(正交)编码器和霍尔传感器电路 + d9 W) t( i# j. d0 Q* u# a
- ● 触发输入作为外部时钟或者按周期的电流管理- X5 [0 U3 f0 L! ]" P4 H% e" h% C
" t: t" N' M% `( F/ T6 b4 B% m3 [- 基本定时器(TIM6 和TIM7)3 e* O0 e; b- C8 [" E( G
- 5 y' R( j0 c- C+ e4 D/ H" z
- TIM6和TIM7定时器的主要功能包括:
# o- R; A7 q7 `0 N& t g( V* D$ @ - ● 16位自动重装载累加计数器 $ r0 _% r3 H% p, ~- G- |1 R! S
- ● 16位可编程( 可实时修改)预分频器,用于对输入的时钟按系数为1~65536 之间的任意数值, M4 ~- S0 O- C$ {
- 分频 8 z$ z! I# M: o/ g3 H! J$ `
- ● 触发DAC的同步电路
6 H8 C) {" @$ m - ● 在更新事件(计数器溢出)时产生中断/DMA 请求
3 Z" b/ n& T+ H1 N8 P- n
# r% F5 r" I: e' ~( x( W: n- STM32的通用定时器是一个通过可编程预分频器(PSC)驱动的16 位自动装载计数器(CNT)构成。STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。
% W! p4 R$ I5 W( f+ O - 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。STM32的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以同时产生30路PWM输出!* J) i" A2 s. h5 f9 G2 Z; Q' n
( E9 ]' B" m# I9 p) D1 N- 要使STM32的通用定时器TIMx产生PWM输出,除了定时器介绍的寄存器外,我们还会用到3 个寄存器,来控制PWM 的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。7 n9 h9 N: e. q+ O
$ Q) k" C; x/ U! f& d& i- TIM3_CH2默认是接在PA7面的,而我们的DS0接在PB5上面,如果普通MCU,可能就只能用飞线把PA7飞到PB5上来实现了,不过,我们用的是STM32,它比较高级,可以通过重映射功能,把TIM3_CH2映射到PB5上。 STM32的重映射控制是由复用重映射和调试IO 配置寄存器(AFIO_MAPR)控制的。
1 P8 S7 Q3 l4 Q4 n$ r
$ c' x* a* v+ y- 3 M# Q6 O @8 X" |9 M8 v6 p: ?2 y
- 1)开启TIM3时钟以及复用功能时钟,配置PB5为复用输出。1 _# q) N3 P n+ g! W. u7 |& u4 e" s" v
& }) y% H, a7 O0 n5 {8 \( F; ]- 要使用TIM3,我们必须先开启TIM3的时钟,还要配置PB5为复用输出,这是因为TIM3_CH2通道将重映射到PB5上,此时,PB5属于复用功能输出。
/ D0 j& F: X2 [6 ^7 G - ' D% v- `! {) l- S3 L' ^$ c' H
- 库函数使能TIM3时钟的方法是: , X: }4 Z8 K, R1 {3 X( C( f1 K! Q
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
* Z1 g% N+ \1 O" a3 i$ E! B - # a% d% j: j( G' U0 L
- 库函数设置AFIO时钟的方法是: $ y& Q9 }6 ?8 S
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟使能$ q1 V3 _! B2 M- V, [9 T( |& N: Y
- 3 v. Z- f& \" a$ Y! d
- 设置PB5为复用功能输出的方法:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
9 A, e. r8 M/ T$ P0 S - + k) J d% ^$ ^) N! J
- 2)设置TIM3_CH2重映射到PB5上。
7 S9 K# Z9 a8 o) C5 P) V - 7 Q+ p" j& c* C' D8 I7 L5 C. F3 | z
- 因为TIM3_CH2默认是接在PA7上的,所以我们需要设置TIM3_REMAP为部分重映射(通过AFIO_MAPR配置),让TIM3_CH2重映射到PB5上面。在库函数函数里面设置重映射的函数是:
3 C3 @0 d8 [9 k D' T; P - void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
Z) Q/ K, i! z8 z - & ~9 |1 ~' a; o5 I9 j# Y6 l
- STM32重映射只能重映射到特定的端口。第一个入口参数可以理解为设置重映射的类型,比如TIM3部分重映射入口参数为 GPIO_PartialRemap_TIM3,这点可以顾名思义了。所以TIM3部分重映射的库函数实现方法是: GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
4 }/ F, `$ B: K$ P z+ o - 4 d+ o7 Z' g k# l$ _. ?, f7 L
- 3)初始化TIM3,设置TIM3的ARR和PSC。
* A* a, ?+ y% C0 D0 u+ f
8 y5 o( @6 q8 G( ~' k0 t& X- 在开启了TIM3的时钟之后,我们要设置ARR和PSC两个寄存器的值来控制输出PWM的周期。当PWM周期太慢(低于50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM周期在这里不宜设置的太小。这在库函数是通过TIM_TimeBaseInit函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:
& [, o4 w' l- i% ~3 u6 C, e3 C - TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值
7 x0 y4 Z0 y: c4 b9 Y2 S$ } - TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 + W; E8 x* \+ f# G1 R) W
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
8 e& q( `( v6 C8 G6 f - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
/ M; H: G N; @3 p - TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的
1 M/ C3 k# F, y4 ~ - 1 R$ b- f" f1 m. q) ]
- 4)设置TIM3_CH2的PWM模式,使能TIM3的CH2输出。
/ K7 s. B' H7 z) X, M0 q- s
7 R) F/ @9 W# B4 S$ }- 接下来,我们要设置TIM3_CH2为PWM模式(默认是冻结的),因为我们的DS0是低电平亮,而我们希望当CCR2的值小的时候,DS0就暗,CCR2值大的时候,DS0就亮,所以我们要通过配置TIM3_CCMR1的相关位来控制TIM3_CH2的模式。在库函数中,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道2,所以使用的函数是TIM_OC2Init()。 6 _9 B( \/ j( v9 @) b: A2 f/ W
- void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
6 W' ]9 K7 w: j2 g6 O
4 A9 i% I7 m, m9 H! [* d- 5)使能TIM3。
0 z7 g- _* Y( Y* g& Q - 9 _" T& I5 p$ l6 R+ R6 G( ]( C
- 在完成以上设置了之后,我们需要使能TIM3。使能TIM3的方法前面已经讲解过: TIM_Cmd(TIM3, ENABLE); //使能TIM3
% u) j. f B/ e6 ?0 Z6 y& O - 6)修改TIM3_CCR2来控制占空比。
3 @9 M" ?1 }/ e( w& _% T& S - 最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM3_CCR2则可以控制CH2的输出占空比。继而控制DS0的亮度。 在库函数中,修改TIM3_CCR2占空比的函数是: 2 M% |& i1 L" g+ s% D5 |" p
- void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2); 理所当然,对于其他通道,分别有一个函数名字,函数格式为TIM_SetComparex(x=1,2,3,4)。
* ~) x3 x4 T& j, z8 ` - 通过以上6个步骤,我们就可以控制TIM3的CH2输出PWM波了。
7 x5 u8 u! J& l, B x3 i+ U* `/ ` p - */
复制代码
9 m8 M4 K( Y# Z k% \ |