前言+ E& d2 }( V$ A0 M
在本章中,演示如何使用计算法得到的呼吸曲线 PWM 波和 SPWM 波,并使用 STM32定时器 TIM的 PWM功能输出波形控制 LED 灯,实现三色LED的呼吸效果。0 a3 c) D& b8 z! V9 I
. I Y5 n$ R) ?/ ^ a
一、相关知识介绍
" S [ f9 w% ?& X2 O8 O1.呼吸灯简介- V2 g& I1 ]6 m( k3 I
呼吸灯,就是指灯光设备的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减,- q% o* ~: r& t T( e: X3 T; i
很有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的指示灯中4 r. @/ N$ f- }& S7 q- ]
& f0 Z. K6 k0 G, S& p' P0 ]
要控制 LED 灯达到呼吸灯的效果,实际上就是要控制 LED 灯的亮度拟合呼吸特性曲线。前面控制全彩 LED 灯时,通过控制脉冲的占空比来调整各个通道 LED 灯的亮度,从而达到混色的效果。若控制脉冲的占空比在 3 秒的时间周期内按呼吸特性曲线变化,那么就可以实现呼吸灯的效果了。
2 P; G r) D5 }这种使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术—— 通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。; Z9 ^5 H" x! d; V, s+ C- }3 ~8 W( T9 q2 c) v
. V0 X* C9 v$ \" @( BPWM 控制的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。
5 A7 T+ G$ z5 c- r( @- }SPWM 波形——脉冲宽度按正弦规律变化而和正弦波等效的PWM 波形。SPWM 是一种非常典型的 PWM 波形,它在数字电路控制中应用非常广泛,如果使用低通滤波器,可以由 SPWM 波得到其等效的连续正弦半波。) K; M) K* n! \5 A
5 r, y) J' Z9 i1 @" I# ]若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成拟合的 PWM 波形,通常使用计算法和调制法:
0 Z0 c1 i9 O, {- \! H0 S(1) 计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形;3 k8 V0 v) f( S; Q0 _8 Z# Q3 g: l
(2) 调制法:拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断,就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求 。相对于计算法,其处理过程计算简单。6 U( k/ q+ T$ G$ V( r7 w+ w2 u. Q
/ \9 V( ^. F$ n5 R
/ }( k/ e! T# K+ p
2. 硬件简介7 C8 |" x: ~6 T+ [6 }8 ^# ]4 K% S' l
TIM—单色呼吸灯 使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种
( ]4 c! Z1 W8 @颜色的单色呼吸灯。
9 H5 |. u: g5 h3 Z# w/ q/ uRGB 灯,里面由红蓝绿三个小灯构成, 使用 PWM控制时可以混合成 256 不同的颜色
3 e1 Z; ~; x+ @: @
* D |3 S( U; k9 W8 h+ U7 V9 b
% K; q% s- M6 }
9 B/ P ~; G5 b) x% ~& C
本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为使用STM32 的定时器控制输出 PWM脉冲,然而并不是任意 GPIO都具有 STM32 定时器的输出通道功能,所以 在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯,如图
) Y9 ?& d/ o7 d3 Q
5 N8 K+ y9 L% D5 t; a" m
7 L# u2 x& E0 Y: p' U, F' G5 \$ H+ I: |3 U$ g
本实训中的 RGB 灯使用阴极分别连接到了 PB5、PB0 及 PB1,它们分别是定时器
- s* V8 i6 k* k1 c1 p$ G# CTIM3 的通道 2、3、4,其中 PB5用于定时器输出通道时,需要使用重定义功能。
! k7 B; h8 J9 {& O& P+ [ Q& R5 M4 W7 B
2 A4 l. v* p$ F' q) [7 l% }
二、实现呼吸灯
5 J. u, F& _9 e) m( D4 ~" R: o1.单色呼吸灯6 L& }" R% {: s" R0 ^9 t' h
要点# g1 i$ C. R% B, e* ?( Q
初始化 PWM 输出通道,初始化 PWM 工作模式;5 a3 z, v3 y0 {0 m; I1 B8 \# @
计算获取 PWM数据表;6 k5 u! I2 d$ r2 g- q5 j
编写中断服务函数,在中断服务函数根据 PWM 数据表切换比较寄存器的值;
3 M8 V8 p* o/ X, [8 M# C2 \6 Y; C6 N& Z$ K! P6 H5 ~
LED灯硬件相关宏定义0 u) S. u8 G0 t L$ ]
breathing.h
+ V7 X& U3 N2 v$ K2 r) o" O8 z- ' k! D& |7 f: d% Z# [
- #define RED_LIGHT
2 E- E, u8 A1 }6 m( z/ J7 v' D - #define GREEN_LIGHT
* ]: Q2 V% d5 v! V) y - #define BLUE_LIGHT& _( P2 U" H4 z
- , j4 _* Q0 w$ P) F. c# h# J
- /*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLOE_LIGHT*/1 L7 S$ e7 d& X: _
- #define LIGHT_COLOR_RED_LIGHT
9 M2 x ]* N4 Z, f - /********定时器通道*******/
- {8 D% U7 E b9 G) d# S0 M - #if LIGHT_COLOR == RED_LIGHT
, {3 k/ }' q$ R- F, W' w - /************红灯***********/: S1 b/ l4 a1 d3 ~' g7 z! }
- #define BRE_TIMx TIM3# ] H) Q$ o: w* m8 k/ T
% D5 k5 h) c: S- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd1 j: v& e0 \0 X
- #define BRE_TIM_CLK RCC_APB1Periph_TIM3: c" C: l. ? g0 V
- #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
- l9 P9 J" ]4 B4 f" c8 X4 U - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO)
* b H1 d- \' C$ ? - //红灯的引脚需要重映射
6 P; x5 S# `2 @9 p - #define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPI0_PartialRemap_TIM3,ENABLE);
3 H2 p) r8 b8 w1 g7 `: s9 ?3 V$ o - 7 q4 P/ W. a, @: }
- #define BRE_TIM_LED_PORT GPIOB
& v4 m! v5 K r" d" M8 u! L - #define BRE_TIM_LED_PIN GPIO_Pin_5 T0 J0 \6 ?* t- l% W
- , i# f0 x, V3 B9 Q- S2 F2 d
- #define BRE_TIM_OCxInit TIM_0C2Init //通道选择,1~4
6 v$ A% I( N( I1 p. |2 y6 {$ ` - #define BRE_TIM_OCxPreloadconfig TIM_OC2Preloadconfig
, h0 ^- C" V g! T- s - #define BRE_CCRx CCR21 U4 r0 M* {, F& V+ _/ i$ _% Q. c
- #define BRE_TIMx_IRQn TIM3_IRQn //中断0 z8 m/ i, H) \( P+ C: E7 [
- #define BRE_TIMx_IROHandler TIM3_IRQHandler
* b$ Q9 q" O* q! ~4 T7 w - ' h, v1 A% T4 J: M5 H% s% ~% ^4 q
- #elif LIGHT_COLOR == GREEN_LIGHT
* y. ~/ `: \* N, C" ` - ! O, @ x5 n z2 r- b, s3 I
- /************绿灯***********/$ ]) \0 q* H# F& \! F
- #define BRE_TIMx TIM3
6 u0 L) M3 M: u/ e
$ D7 W" U6 }/ j- I8 K0 d+ X. v4 Q- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
; c0 c1 F( L3 ], [/ D& r) ` - #define BRE_TIM_CLK RCC_APB1Periph_TIM38 r) }: y* |& T
- #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
$ }8 [ N9 x3 K8 }/ Q# O: _. p - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)4 {, s9 c6 y8 K |5 A) V l
- //绿灯的引脚不需要重映射* h, v: k# J8 c& G) c9 n
- #define BRE_GPIO_REMAP_FUN()
4 h) ^" {& F4 [& Z
, Z3 I' `$ U7 b [- #define BRE_TIM_LED_PORT GPIOB
" z1 _* C1 J8 Q9 r* Z - #define BRE_TIM_LED_PIN GPIO_Pin_0
9 t* K2 J v: Y, u7 Q0 Q - ; d; N; P, t+ B+ h
- #define BRE_TIM_OCxInit TIM_0C3Init //通道选择,1~40 E6 P2 z5 \; p+ ]+ O
- #define BRE_TIM_OCxPreloadconfig TIM_OC3Preloadconfig
4 [; E4 }$ {% p n4 o - #define BRE_CCRx CCR3
& G& C5 x$ ^1 f# F% h% U" ?' T - #define BRE_TIMx_IRQn TIM3_IRQn //中断
; a: t: V. z7 ?2 l* t; } - #define BRE_TIMx_IROHandler TIM3_IRQHandler" a3 G! L' R1 }$ q9 O
- / U# K( n7 l) l' U
- #elif LIGHT_COLOR == BLUE_LIGHT
6 K: L# c [) J- R5 p4 N - /************蓝灯***********/
) j: p' R) S; \% { - #define BRE_TIMx TIM3/ K" t$ Z5 }: {2 M$ K/ {
% W0 T/ M H4 T6 H5 h9 O/ ~- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd8 Z- L: e; X# |( X
- #define BRE_TIMCLK RCC_APB1Periph_TIM3! b, ]1 Y3 r$ \$ E4 C- Q, Z/ X
- #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd# x% @7 t7 I/ F5 |" H! T# ~
- #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
/ L* g A6 d$ C- G" [! }- B - //蓝灯的引脚不需要重映射
( \! {9 J4 v5 z" Y - #define BRE_GPIO_REMAP_FUN()
! n1 G2 q/ Y) c6 ^- G( L/ Z" j* v - ( W: v! d* R% {& E
- #define BRE_TIM_LED_PORT GPIOB* z) X d4 q- Y$ G, }
- #define BRE_TIM_LED_PIN GPIO_Pin_1
+ Q/ ?' L7 w' T! m) j
7 P& u: `4 h" y0 _; ^: h- b- #define BRE_TIM_OCxInit TIM_0C4Init //通道选择,1~43 ^: C9 i& b5 b9 i% j% B
- #define BRE_TIM_OCxPreloadconfig TIM_OC4Preloadconfig
y+ @. }; A; u" Q- g' [' L. S' U+ z - #define BRE_CCRx CCR4
9 x) G6 V- K, R( w - #define BRE_TIMx_IRQn TIM3_IRQn //中断8 Y, w2 V: B7 v6 k- r* |
- #define BRE_TIMx_IROHandler TIM3_IRQHandler
8 H9 w' M/ A9 [& ?+ P( C' h, W& ^ - 0 f9 _9 R: _7 ~7 ^/ T
- #endif # f3 m* L ?' {
- 7 G5 Z) F$ g; V& A& t! U! I: _
复制代码 : K [& |" H5 ^: |# [
为方便切换 LED 灯的颜色,定义了三组宏,通过修改代码中的 #define LIGHT_COLOR RED_LIGHT语句,可以切换使用红、绿、蓝三种颜色的呼吸灯。在每组宏定义中,与全彩 LED 灯类似,定义了定时器编号、定时器时钟使能库函数、引脚重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名。不同的是,这个定时器的比较寄存器 CCRx 在控制呼吸灯的单个周期内需要切换为 PWM 表中不同的数值,所以需要利用定时器中断。1 z$ ~% r1 }6 I( J
, r, J! o$ Q9 r+ p$ A5 m
/ r) V- C' z& {8 j
2.初始化GPIO
. ~* D; G2 V4 v+ I: }, g初始化用于定时器输出通道的 GPIO
! D3 h1 Q* K: l' A9 E2 v4 `/ z0 l4 s' s ybreathing.c 文件:) f2 P3 ?/ r6 M- D1 p$ p) z
- static void TIMx_ _GPIO_ Config (void)
7 {8 I" p7 g: L5 n/ I - {
: \! r3 p2 c7 e7 ?/ C - GPIO_InitTypeDef GPIO_InitStructure;
" p: p% u( r5 I -
) G6 p# x1 R# \& ^ - /* clock enable */
0 A. ^* i, d0 E' d( |' m; J - RCC_APB2PeriphClockCmd(BRE_TIM_GPIO_CLK, ENABLE);
3 L; F x5 u" N7 F - BRE_TIM_GPIO_APBxClock_FUN( BRE_TIM_GPIO_CLK,ENABLE );
1 ~0 g1 p6 S! w* M6 | - BRE_GPIO_REMAP_FUN();& U# N2 }+ J$ ?8 i2 z" J' K
- & P1 C( ~9 V P4 c0 M7 X4 z0 K
- /*配置呼吸灯用到的引脚*/
, M6 p1 R- C: ?1 S; a. d6 y" X - GPIO_InitStructure.GPIO_Pin = BRE_TIM_LED_PIN ;
$ F8 m1 P. |- v* d8 _ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出3 T8 j) s# J& z
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;- V- o" u8 s* f0 A
-
% c/ @# z; N. F( m% A% r - GPIO_Init( BRE_TIM_LED_PORT,&GPIO_InitStructure ) ;- H* j* D/ v3 M6 \3 E
- }
5 y% \" V( a. p( c# \6 M+ S4 D
4 ?) r$ M" D u6 c# Z4 Z* G+ Z
复制代码 ) [/ }/ y8 k* g$ _9 b) Y
由于直接使用定时器输出通道的脉冲信号控制 LED 灯,此处把 GPIO 相关的引脚配置成了复用推挽输出模式。其中由于红灯使用的引脚需要用到第二功能,本代码使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作。3 g& ~/ b8 n" V8 @' b! k
- F- U2 A6 D, Q- j) B
" z+ ?& e0 w+ j5 W) {5 e: Z% u s3 M3.定义 PWM表& h7 w3 ~! q( g8 H3 k# m
- /*******breathig.c文件*********/
0 t J) O5 J W' d1 x) ?0 M - /*LED亮度等级PWM表,指数曲线,. e/ L/ U8 y ^* m9 p
- 此表使用工程目录下的python脚本index_wave.py生成*/. d+ Y1 u0 f; F' K9 e1 S
' p; N- M$ l" q0 m% M- H6 V- uint16_t indexWave[] = {
9 u5 p; F3 ?) O; @ - 1,1,1,1,2,2,2,2,3,3,4,4,5,5,% h$ v" ]9 p; N# ~9 x3 H: v( _' g+ |
- 6,7,8,9,10,11,13,15,17,19,22,+ \; c! i* h. a8 ^4 g8 ?
- 25,28,32,36,41,47,53,61,69,79,# s {9 j: Q" I1 w
- 89,102,116,131,149,170,193,219,
/ P: G+ ?( z% H3 b( J* t7 V0 r - 250,284,323,367,417,474,539,613,
) A7 B2 {: H3 t - 697,792,901,1024,1024,901,792,697,; n# i! p# _+ i( U
- 613,539,474,417,367,323,284,250,6 e: Y) L p* W& Q
- 219,193,170,149,131,116,102,89,
8 Q: o: P9 B7 f% S: ^! d - 79,69,61,53,47,41,36,32,28,25,* ~5 o9 U3 `6 P4 X# h8 k2 j2 r
- 22,19,17,15,13,11,10,9,8,7,6,: M5 o" J; q6 ]1 k
- 5,5,4,4,3,3,2,2,2,2,1,1,1,1; S- m* U6 o N5 w
- };5 w, Q# m* L0 i2 n; N( e4 a
- //计算PWM表有多少个元素+ p. w5 @6 g' b n1 n5 k2 S
- uint16_t POINT_NUM = sizeof (indexWave)/sizeof (indexWave[0]);3 b6 d T7 D+ b6 }8 s# l. o4 @
复制代码
# }! i4 Q/ h0 ~7 _. H8 n5 V# e$ `/ e" C S
该 PWM 表是利用本工程目录下的 python 脚本 index_wave.py 生成的' T. }8 {8 k4 m- t
% b! Q: r& M. u" Q
* h9 z: x$ P j; ~5 E: H8 T; W( X
( s2 F) H: o, h3 q ~该脚本运行后会在工程目录下生成一个包含该 PWM 表的 py_index_Wave.c文件,复制这些数据到 breathing.c 文件,即可得到本工程中的 indexWave 数组。实际上使用 C 语言也可以编写这样的脚本制作出 PWM 表,只是使用 python 脚本比较方便绘制图形而已。$ x6 o+ \/ A$ d7 J( L+ L+ I# ?
7 Z+ J. [7 g* e. {0 T
该 python 脚本生成 PWM表数据的原理,实质是按照如下函数曲线进行采样:
2 w4 k6 J5 ]& e& k( Q# `* H) R
% x3 H) y" j) r% ? y' k
: O- N' N& q; T7 z' D* O* C
F4 y a& _( m+ a: n# bpython 脚本在这样的函数曲线上取 110 个点,即可得到上述代码中 PWM 表数组indexWave。
: a4 M- S& ~) g8 u5 a) E这个 PWM 表记录了呼吸特性曲线,PWM 表的数据将会被赋值到定时器的 CCRx 比较寄存器,从而控制输出占空比呈呼吸特性曲线变化的 PWM波。至于计算 PWM 表时,为什么选择采样 110 个点以及为什么表中的最大元素为 1024,需要结合下面定时器和中断服务函数中的配置来理解。
Y6 k; w) V) {9 I3 d! ^4 ^# a/ f( [) u7 B! a2 e" d
+ ?" e! w5 v$ V2 s3 P- Q
4.定时器 PWM配置
1 C. q) d( O/ T+ K
- Q8 Z' Q' T( Y
8 B* h/ F; V# ` s }- D
6 Q- T% p6 X5 V) w
初始化了控制 RGB 灯用的定时器,被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT 的值小于输出比较寄存器CCRx 的值时,PWM 通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较寄存器的值。% \2 e' s; q4 d7 W. F) t
代码中的 TIM_Period和 TIM_Prescaler是关键配置。其中 TIMPeriod 被配置为(1024-1),它控制定时器的定时周期,定时器的计数寄存器 CNT 从 0 开始,每个时钟会对计数器加 1,计数至 1023 时完成一次计数,产生中断,也就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器 CCRx 被赋值为 PWM 表中的元素,即可改变输出对应占空比的 PWM 波,控制 LED灯,如:
/ n O6 N* z3 V% [& ` N
" O& q* m$ n$ F* ]2 M8 Z
' M! I# Q# Q' u2 B3 {
" h }' f( w1 h% L) c3 i
根据本工程中的 PWM 表更新 CCRx 的值,即可输出占空比呈呼吸特性曲线变化的PWM波形,达到呼吸灯的效果。最终,拟合曲线的周期由 TIMPeriod、PWM表的点数、TIM_Prescaler以及下面中断服务函数的 period_cnt比较值共同决定,本工程需要调整这些参数使得拟合曲线的周期约为 3秒,从而达到较平缓的呼吸效果。2 \; E4 s0 w# g: Q; _5 W
. s; o6 l5 V8 l* q1 e2 l6 C x
0 z) M9 r: E, T# A5 F; [& N6 R5.定时器中断服务函数
" N! R( g) u0 _stm32f10x_it.c 文件, Y) m s3 y0 R* C
- //控制输出波形的频率
# ?2 z9 J+ ] Q4 v - _IO uint16_t period_class = 10;
" c1 A% K' \- C; I - /*PWM 表*/$ e `! E" i$ h# a; a
- extern uint16_t indexWave[] ;
f* y6 w( j0 l; N1 N- [ - /*PWM表中的点数*/
2 K* Y7 ]3 w/ }( q! P/ a0 X - extern uint16_t POINT_NUM ;
; G) i4 T# ~0 u9 n% { L - /*******stm32f10x_ it.c文件*******/+ \4 f+ j$ a' M4 G0 G
- /*呼吸灯中断服务函数*/
; H2 Z. v( m7 T - void BRE_TIMx_IRQHandler (void)
+ r" c* F/ b# f - {& E, Q: G! D* }- P) J, q- v7 k
- static uint16_t pwm_index = 0; //用于PWM查表' |. _0 y- a! a1 e z# c* K) I
- static uint16_t period_cnt = 0; //用于计算周期数
/ g% R! m3 O) r1 p7 ^, w - //TIM_ IT_ Update3 i1 J1 Y9 I! V' P0 r
- if (TIM_GetITStatus (BRE_TIMx, TIM_IT_Update) != RESET)
5 U1 {( S8 I6 V2 e" [+ G: l5 N p- e - {
% |& S0 X, J: a" ]/ ~9 Q$ S - period_cnt++;! B7 P/ k2 }7 `1 \
- //根据PWM表修改定时器的比较寄存器值
. X O/ a9 i, f% R - BRE_TIMx->BRE_CCRx = indexWave [pwm_ index] ;% \2 [7 v$ a8 U$ G2 f- u
- //每个PWM表中的每个元素使用period_ class 次* ?. U" p7 k0 `0 r
- if (period_cnt > period_class) x) |2 l9 u% ^6 d
- {
1 S- j# E* ]# r0 M- I `) C - pwm_index++; //标志PWM表指向下一个元素
, |( V# m% o" y; S# ? - //若PWM表已到达结尾,重新指向表头2 w0 |9 d0 M& [% R
- if ( pwm_index >= POINT_NUM) I( N. G! K% a
- {
' D" ^' ^2 y2 _1 c+ U. @# |9 I; R - pwm_index=0;
|! S+ g N* D! w5 }& s# N - }
# G* Y. d7 q. f) z6 K5 G - period_ cnt=0;//重置周期计数标志3 T5 {5 b5 {8 b$ B& Y' c
- }$ z8 o2 N/ y7 r. n2 z' Q* d9 x" q
- TIM_ClearITPendingBit (BRE_TIMx, TIM_IT_Update); //必须要清除中断标志位5 z6 l4 a$ Z+ J! n; j) f& r- E8 L! K$ Q
- }
! _3 |) F' U# @. i - }+ D5 {1 X0 j( h1 x9 O- L
复制代码
5 N! x0 O6 [1 F2 V: c在中断服务函数中,包含两个静态变量 period_cnt和 pwm_index。其中 pwm_index 比较容易理解,它用于指示当前要使用 PWM 表中的哪个元素,从而在“BRE_TIMx->BRE_CCRx = indexWave[pwm_index];‖语句中可以给 CCRx 赋予正确的数值,而且当 PWM 表中的数据都使用一遍时,pwm_index 将重新指向 PWM 表的开头,开始下一次呼吸循环。7 R" u( H" b# r! q7 M j0 L/ n" F
在本工程的单次呼吸循环中,每个 PWM 表元素都会使用 10 次,代码中利用period_cnt 变量指示当前使用的次数,当 period_cnt> period_class 时(即period_cnt>10 时),pwm_index 才会指向下一个元素。每个 PWM表元素使用多次,主要是为了在 TIMPeriod、PWM 表的点数、TIM_Prescaler 都固定的情况下,通过调整每个元素的重复次数可以调整整个拟合波形的周期。如把代码中的比较值 period_class 改为 100,每个 PWM 表遍历一次的时间就变为原来配置的 10 倍,其拟合的呼吸周期也就相应地改变了。* f/ P* O6 h1 @" Q3 p& k& ^9 z
) O. C0 D4 i. L+ c
1 t$ k. H9 {1 _; K+ R$ ~/ m7 N& |6.计算拟合波形的周期
Z4 A) ~5 L' [8 ^在本工程中,TIMPeriod、PWM 表的点数、TIM_Prescaler 以及 period_cnt 都会影响到拟合曲线的周期,而在实际应用中又有如下要求:: S- R$ I& T" x6 q) v
. ?! i" j- S& F/ i4 X
TIMPeriod:定时器的计数周期,它的值必须与 PWM 表中的极大值相等(应用中赋值需要减 1),而 PWM 表的极大值决定了控制的分辨率。例如极大值为 10 时,PWM 占空比只有 10 个等级,精确到 0.1,当极大值为 1000 时,PWM 占空比有1000个等级,精确到 0.001。& Z& d3 L) @) t. |5 D' L' L4 k) [
TIM_Prescaler:定时器时钟分频因子,它控制定时器计数器 CNT 计数加 1 所需要的时间,它的值太大会导致输出的单个 PWM 波周期过长,影响控制的动态特性。如控制 LED 灯时,该值太大会导致 LED 灯开关时间变长,闪烁明显。一般来说,该值越小越好。
0 d: r2 V: K$ I6 i. s$ R1 HPWM 表的点数:PWM 表的点数即对拟合曲线的采样点数,采样点越多,能更好地还原拟合曲线,采样点太少,可能会导致失真,
( R1 k- `' p2 ~) X6 P2 hperiod_class:周期倍数,即 PWM 表中每个元素的循环次数,它影响拟合曲线的4 p- j5 h: [: i: f
周期。当 period_class=1时,可以输出本配置中周期最短的拟合曲线。
! G1 ? ]$ h; o0 l R8 Tamplitude_class:幅值分级,可以把拟合曲线的幅值分成 N 个等级,控制时可以选择按某个幅值等级进行输出。本工程没有配置该参数,所以只能输出最大的等级,即amplitude_class=1。
+ K- W4 e! p' { Q" A$ @
* `9 v- e, L! @以上各个参数虽然侧重点不同,但若修改其中的任何一个,最终都会影响到所拟合曲线的周期,所以在实际应用中,通常先设定好 TIMPeriod、TIM_Prescaler、PWM 表的点数以及幅值等级数 amplitude_class,得到适合的控制精度、动态特性拟合度以及幅值等级后,然后再调整 period_class 控制拟合曲线的周期,而且 period_class 在程序中动态修改非常方便,不需要重置定时器和 PWM表。
' O* {3 N: j0 l- [/ f: z* a. p2 \- J' {2 T! X. h( i
最终,我们把本工程配置中的拟合曲线周期计算公式概括如下:
! A2 W Z9 o1 r, c3 y" j# a% |, k! ~
2 w0 F- Q( N) ]' L# {$ q, C
2 ^- ?8 [) a) U% w4 L$ V
, ]! a, N/ v( s. Y通过公式的计算可知本工程的配置可使得输出的拟合曲线周期为 3.128 秒,是比较平缓的呼吸周期。8 S+ k2 Q! o# Q0 W' s
& A) o8 w1 _9 S
8 R5 ~/ N1 j0 L' U# [7.主函数. S, ?+ y* f5 V" A
- int main(void)0 V- e! }6 z2 Y& Y/ q* X- Z
- {4 ~2 x* z0 o4 m; a D6 b
- /*初始化呼吸灯*/
( n& c$ G: E7 l; `7 I' U# W - TIMx_ Breathing_ Init() ;
0 F3 k, ?1 n% e9 C6 A' n - while (1)
* R) J+ N$ e" `% m - {
$ E/ H8 Q! c! L8 X4 q4 ~1 R0 U -
# F* v* T7 p1 H, M - }
: G2 u) i ~* a* X% s4 U$ ]% d - 6 Q7 n3 X4 L8 u
- }
! W' |/ U$ S- u; G* G! r2 s7 r' T
复制代码 4 |; a7 `5 U v* Z
! o4 ]4 g1 p5 @8 f8 Y1 [
main 函数中直接调用了 TIMx_Breathing_Init 函数,而该函数内部又直接调用了前面讲解的 GPIO 和 PWM 配置函数:TIMx_GPIO_Config 和 TIMx_Mode_Config。初始化完成后,定时器开始工作,然后它会在中断服务函数中切换 PWM 数据,控制 LED 灯显示呼吸效果。
3 H0 T* ^6 E* _% f% O4 h, a' L' N8 F
+ J7 y' u# {$ R+ \总结
, l- S# ]7 o0 o Q本工程通过LED灯实现了呼吸灯效果,整体不难,使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种颜色的单色呼吸灯。实现全彩呼吸灯需要在单色呼吸灯的基础上,添加对 PWM 波的幅值控制,分为256 个等级,从而实现RGB888 全色彩的呼吸灯。
% Z0 M) e; s" @& j* s! U% w————————————————
0 n& |0 g f1 S k4 H2 K版权声明:清道 夫, k% M& n- M' L
. {: y& A" t( J! p% d
3 g" f! `5 j) R6 C( c- [$ Q |