01. PWM简介( E W( s) Y( X! N U' U* D
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图 14.1.1 所示:, f; p+ F* Y9 m2 Y9 E" L- X
3 d: M1 _, H0 e. [6 ?% c& U
2 C6 O5 z8 ^5 m8 J2 l0 N# [( Q
: _+ j# a, R# Z, ^! g4 S图 14.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。
" f/ R% x" S: L" ]/ S8 Y9 j, `6 g7 v! Z& I n+ N- i
STM32F4 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出!这里我们仅使用 TIM14 的 CH1 产生一路 PWM 输出。7 r4 Y( F2 B/ N" t
" w2 ~ C) E* u0 ~
02. 硬件设计
+ e2 w+ v% q i- n+ c: L; g用到的硬件资源有:3 [# p/ `$ v+ X/ a1 @7 S# v
/ B R- h1 b! r* p& Q1) 指示灯 DS0
9 O+ h3 X' W* }4 d4 K2 V3 l* A# |4 D# J2) 定时器 TIM14: D/ Q; e0 o! P% a+ ]" F0 Q$ z2 S
这两个我们前面都已经介绍了,因为 TIM14_CH1 可以通过 PF9 输出 PWM,而 DS0 就是直接在 PF9 上面的,所以电路上并没有任何变化。
* G# _3 c: R- H2 S- J' X$ E# J; I' f, e' B* G# i
03. 配置步骤
0 [ {, ]8 J8 X% k" D/ A. m0 m; u3.1 开启 TIM14 和 和 GPIO 时钟,配置 PF9 选择复用功能 AF9 (TIM14)输出。( z9 P8 A$ b% @* p. l# e% E
6 M; U9 D7 @! _; c8 |# D/ k
要使用 TIM14,我们必须先开启 TIM14 的时钟。这里我们还要配置 PF9 为复用(AF9)输出,才可以实现 TIM14_CH1 的 PWM 经过 PF9输出。 库函数使能 TIM14 时钟的方法是:+ t2 g6 {/ C& ]9 P( O
1 F$ A( ^ |5 _0 U% m- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //TIM14 时钟使能
复制代码 4 X' m: G, m! ^7 s! @
我们还要使能 GPIOF 的时钟。然后我们要配置 PF9 引脚映射至 AF9,复用为定时器 14,调用的函数为:- GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GPIOF9 复用为定时器 14
复制代码 + j& p |$ w6 p8 W9 ]
这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看 STM32F4 对应的数据手册,比如我们 PWM 实验,我们使用的是定时器 14 的通道 1,对应的引脚 PF9 可以从数据手册表中查看:
3 v1 j+ w: f2 t! i$ t& i/ n. c' w) @
d6 r" {# ]3 \) F3 v- m( w3.2 初始化 TIM14, 设置 TIM14 的 ARR 和 和 PSC 等参数。- o" `* ^! m& ?$ i1 P ^
0 h: l0 s: E3 I: A: K* A# ?* C3 i
在开启了 TIM14 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM的周期。当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM周期在这里不宜设置的太小。这在库函数是通过 TIM_TimeBaseInit 函数实现的,4 A% Y9 J: G. y A9 D' n
, `2 ~2 R$ Q: i. K( {5 u3 g- TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值. [, \$ G# c& N$ K' E' }
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值9 G3 {) C# ~; H8 ~
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim! x7 ?& ~6 C i8 |! B: b: J
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
; R3 u# [+ z n$ |- k - TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx 的
复制代码 $ ?3 J! D1 \ Y- J# ^/ J) Z& c
3.3 设置 TIM14_CH1 的 PWM 模式 ,能 使能 TIM14 的 CH1 输出。
; x7 V, G( F$ e
. s& _. ~ o5 X$ T/ Q2 O. B我们要设置 TIM14_CH1 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电平亮,而我们希望当 CCR1 的值小的时候,DS0 就暗,CCR1 值大的时候,DS0 就亮,所以我们要通过配置 TIM14_CCMR1 的相关位来控制 TIM14_CH1 的模式。在库函数中,PWM 通道设置是通过函数 TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道 1,所以使用的函数是 TIM_OC1Init()。1 e) i/ m- o$ J9 o7 Q0 b/ [
# W2 H& z4 ^, H. @; @
- void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
复制代码
2 ~2 F* N9 b9 H, W; iTIM_OCInitTypeDef类型结构体
) L3 Z6 j( O; `( X
# }( c3 H; y* u1 X" u; u% Z- typedef struct
q1 ~/ L* j. _ T5 X! _ @ J - { K3 h9 t3 _6 l! |
- uint16_t TIM_OCMode;
. z/ _+ b4 I+ q. Z8 E6 ] - uint16_t TIM_OutputState;
, X: J: Q/ H7 V1 C - uint16_t TIM_OutputNState; */
& ]8 r4 c3 O0 {+ k8 X' f' x4 e - uint16_t TIM_Pulse;$ f5 B; B, G1 n* F/ v; ?$ ?
- uint16_t TIM_OCPolarity;7 I, g* d# P$ g; U* [
- uint16_t TIM_OCNPolarity;
; l& w$ Q1 M' Q. x! X$ | - uint16_t TIM_OCIdleState;
; Q2 g1 c7 m u" W1 D0 _8 N - uint16_t TIM_OCNIdleState;
4 V& Q9 o5 }; V - } TIM_OCInitTypeDef;
复制代码 % i9 z; I4 `9 w
这里我们讲解一下与我们要求相关的几个成员变量:
% s& i v/ D( r参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。. Z0 t% C: P; t n
参数 TIM_OutputState 用来设置比较输出使能,也就是使能 PWM 输出到端口。2 X3 W7 U+ O+ Y3 }- y
参数 TIM_OCPolarity 用来设置极性是高还是低。) @4 l8 A' k9 \- b0 r( E
其他的参数 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。% s9 O% _6 W8 m3 Y" K$ }
' g7 q' J/ C7 J. Y
- TIM_OCInitTypeDef TIM_OCInitStructure; 9 @8 M }: A5 \5 V4 O
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择模式 PWM+ E7 O' F! Z4 _& I, g" q9 N
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能+ E5 {! _4 E1 J& W- G
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低7 F9 V2 G; z2 l9 K
- TIM_OC1Init(TIM14, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC1
复制代码 / Y# z) `6 O# r5 n0 o
3.4 使能 TIM14 。: m3 M7 n8 ~1 }: _6 H$ e0 F, ~/ ?6 g1 E
& k9 S' N0 b- ^, _7 e
在完成以上设置了之后,我们需要使能 TIM14。使能 TIM14 的方法前面已经讲解过:- TIM_Cmd(TIM14, ENABLE); //使能 TIM14
复制代码
8 Z! Y8 u. o4 Y9 Y; |: O3.5 修改 TIM14_CCR1 来控制占空比。
! B4 X! D/ @* H) {. F3 h8 t! V7 Q: Y: N* q, ?8 z& ^# s
在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIM14_CCR1 则可以控制 CH1 的输出占空比。继而控制 DS0 的亮度。在库函数中,修改 TIM14_CCR1 占空比的函数是:$ ]- ?5 }6 X- e4 y
8 Z9 s: D- o: b4 k V- void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2);
复制代码
4 T M* ^, K' |9 @3 l' ^( ]04. 程序示例
7 S; m( F. s% f* [, S7 n! Mpwm.h
% c8 L3 V5 m) ^2 F. H1 Q% G* H, s* ~- z; L4 d% C
- #ifndef __PWM_H__
* ]" Z; _6 S' f* P: u - #define __PWM_H__$ s( ?0 h" F! \+ _
7 e4 V8 C4 {; O/ h! D8 u- #include "sys.h"
; m# c$ I# k \ - 4 d. a" o' d( x! X3 D. _7 i
- //定时器初始化6 h* K$ a5 R% g. M
- void TIM14_PWM_Init(u32 arr, u16 psc);
6 ]0 r2 b$ g6 ]
# m7 X5 p0 N" m/ w+ i1 `2 z/ L- #endif /*__PWM_H__*/
复制代码
- u4 Z' Y7 e- }. ~pwm.c
( R' N$ W, ]# `. x% v8 e
1 [& A, H' I4 a5 Y" ?! w: `- #include "pwm.h"+ ?% O1 b& Y. K3 U1 q7 v, c: N
6 {! v3 B( X L# l8 W4 z$ y: w+ n: f
, m2 y9 }. B7 p2 @3 C1 F# u/ L- //定时器初始化
/ G! n5 U0 |% [2 G y, l, U - void TIM14_PWM_Init(u32 arr, u16 psc)
5 ]# E, W& M- r - {
2 k8 j8 S0 D) { - & F1 C% l8 s1 J( [/ k
- GPIO_InitTypeDef GPIO_Init_Struct;
8 ?) ~0 s" P3 B; |6 G -
) Z3 g* G+ ^1 F/ {* L" p6 w+ s - TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
( ^) `1 \ ?! \) F7 g; k0 ? - 1 f4 }( L3 w5 L5 c# B' U, J
- TIM_OCInitTypeDef TIM_OCIInitStruct;
, {; q9 d3 |3 `1 z - ) a& g6 h2 B& f3 D
- //使能定时器TIM14时钟2 u+ E. D s$ R$ R6 e+ \6 V" N
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);
5 ~* p) _3 w+ ?5 [/ ^, n - b4 j( V2 W4 }( ^$ t
- //使能GPIO时钟 GPIOF
- o6 j& D) u$ Q - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);8 \+ e5 N; s- Z
- , X" X1 e/ j2 V4 }: [& a& k
- //GPIOF选择位复用功能 GPIOF PF99 n! {! L) Z3 R" ~
- GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);: C2 G/ [# S, e3 `! z; e. C
-
8 w0 z# J. m% P1 L& t i6 s - //初始化GPIO
4 `# g/ s5 G4 @$ Q5 W - GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_9;& A& X7 I- N- q: v' j% _
- GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_AF;
" t% Q' z1 t. i9 j" \ - GPIO_Init_Struct.GPIO_OType = GPIO_OType_PP;
% R6 G Z6 I e6 {& A8 X1 ? - GPIO_Init_Struct.GPIO_PuPd = GPIO_PuPd_UP;
- V% f- H5 `. k2 D& i+ Y - GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz;: H4 B5 g# x( y
- , S3 X- C6 e4 b: R2 f! z
- GPIO_Init(GPIOF, &GPIO_Init_Struct);5 {7 M6 ~, s! Q5 i( |6 O3 {+ b0 r
-
9 \# Y1 z9 w/ ~& H! ^; ?4 ] u( W - //初始化定时器14
, J. h" E3 q$ q - TIM_TimeBaseInitStruct.TIM_Period = arr;6 }; ^/ d( [; G* l' ]+ b
- TIM_TimeBaseInitStruct.TIM_Prescaler = psc;3 Z9 j" x' I; |7 u0 d
- TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
/ p! \3 U! w9 V& Q; U' J' j3 \! i: L - TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;- [3 w1 K7 a! a8 K) G8 Q
-
. y9 Y/ S! l0 G& D" W - TIM_TimeBaseInit(TIM14, &TIM_TimeBaseInitStruct);4 d1 F4 { g) i3 K: ]
- ( ~6 x t. \ C% T3 k
- //设置TIM14_CH1为PWM模式
, M+ q$ B0 \' t0 [! {) _ - 0 K4 c2 E, g) o% o' M5 j
- TIM_OCIInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
3 G7 [; \* z# o w4 m: \3 g - TIM_OCIInitStruct.TIM_OutputState = TIM_OutputState_Enable;
6 t: K+ F) e7 w/ w( i7 ` - TIM_OCIInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
8 F! G: S- z& w1 p - TIM_OC1Init(TIM14, &TIM_OCIInitStruct);+ R( K& s. N; J5 Y" O
-
) g2 S0 C1 K6 ~ - //使能TIM14在CCR1上的预装载寄存器
, U+ x8 F1 `8 r7 i N - TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
- v0 }( r1 T/ V1 Y2 m+ ?) }4 V -
; g3 J1 }) y7 a% _! m W, ~ - //ARPE使能
1 r2 x4 ~% R' W {! z - TIM_ARRPreloadConfig(TIM14, ENABLE);
7 L8 F2 O+ v5 P+ p& u( j5 S - - l0 Y7 c* `" B6 k7 S/ s' i* V( Y0 Y
- //使能TIM14' H4 E0 x' t2 D" Z
- TIM_Cmd(TIM14, ENABLE);+ W/ v, E x) a ?: X/ t( |
- ' n6 _& H. d. z3 d( ?
- }
7 m. @! |5 ^$ [% r9 A) G: j
复制代码
1 d9 j* ~5 P! O0 _' \main.c
2 @+ K% ]( d# B7 Z
2 m1 c+ H2 y( F% s- #include "sys.h"# \* K4 h: s( c. D3 v, n3 _
- #include "delay.h"" P8 I# w( @* L8 E
- #include "usart.h"
' Z5 t: g: \! w( \! m - #include "led.h"
" `3 ]4 E1 Q# s+ ~ O0 Y$ \& l( j - #include "beep.h" }4 u5 O% B1 O; H5 [# o7 c
- #include "key.h", }# k" E# S( N3 N0 V1 w
- #include "exti.h"
1 o1 I0 q$ w2 |( R. {7 j - #include "iwdg.h"
8 u/ N F1 E$ g& j- `0 B - #include "wwdg.h"; n7 f8 k8 H* k0 e8 Z
- #include "timer.h"
+ ? |% j0 V; N! Y$ g5 N - #include "pwm.h"& H5 x! ^1 I3 I7 C' I
8 U6 \7 h/ j) F, J- int main(void)
' Q: r4 {( n6 }8 v - { 9 e7 _) U% F9 h n0 K; z8 j
- u16 led0Value = 0;% x/ ?/ l+ g( r9 a5 Y
- u8 dir = 1;% B4 @6 V$ x. s8 g
-
5 b2 K) X- W- |( { - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
6 v; P9 W2 G6 _; x& n5 q - 5 S' Z& H6 Q* n0 Y1 O
- delay_init(168);# B Z/ A& ~: c( W
- 4 F- Y- C* `7 p" g
- TIM14_PWM_Init(500 - 1, 84 - 1);+ r; K& a0 v5 U' C& u9 @
% y' c# |: p* o/ r2 E- % m1 y4 k1 u- \! \& T. e
- while(1) }# z! I9 e7 ]: Y
- {
0 S9 p" O5 ^/ F x! W0 z6 |, F - delay_ms(10);# |; m# |4 p2 D* B. ^$ k4 i2 l
- if (dir)2 v' N. n$ U- X5 H4 J" T# A
- { k+ s: o+ w. z v
- led0Value++;
4 Y, ^! j0 x5 v; A - }4 q* U/ I# [2 N1 o9 U
- else% i7 W7 L, Z9 A, I" a* \2 T, }% b- n
- {
" `# J$ \) o: B A/ x! U - led0Value--;- O( x2 d4 ]4 x# N6 U
- }
9 [0 O" {# Y a8 K! z3 |# ]! x -
1 B% k! D- B) y( g, g$ [ - if (led0Value > 300), d! |$ T" T0 C: v% { M7 _
- {) w4 W! t) O$ j) \( ?9 C+ O
- dir = 0;
4 d$ U& p5 @0 o' B/ R! c s - }8 w* l- u3 P" V1 W
-
% C4 \) z+ R& ^ d/ i O - if (0 == led0Value)& F3 S6 U( m5 E2 b
- {2 ?5 g- Z1 W7 F" o5 d
- dir = 1;* y8 Q1 \/ y1 r* ?) l
- }
/ ^- A. G* I3 O3 N0 H0 K -
! ?$ w; `) c7 t0 z - TIM_SetCompare1(TIM14, led0Value);* _: w8 p9 y+ [
- }
1 m1 q0 K$ R+ u3 ~, H - ) J" R( @% f( J/ @' c* w& g. i
- }
复制代码
# Y+ G& X7 ?, p }05. 结果验证0 L; `) y1 F' B M2 U- T( c* m
我们将看 DS0 不停的由暗变到亮,然后又从亮变到暗。
3 }4 V5 |( g z/ R! C# P$ Y) v' u7 q2 Y. c
8 l0 o/ z" Q$ j; D E6 r" H! D
|