前言9 B' C! W& s% G" M9 o
在本章中,演示如何使用计算法得到的呼吸曲线 PWM 波和 SPWM 波,并使用 STM32定时器 TIM的 PWM功能输出波形控制 LED 灯,实现三色LED的呼吸效果。
% L/ _7 T z, O/ f! v* U* E$ E. }0 \# D C
一、相关知识介绍4 D2 v s6 n- |# `
1.呼吸灯简介
6 G) ?: g* t- ]+ O- c: O呼吸灯,就是指灯光设备的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减," v9 M8 q8 G8 \
很有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的指示灯中 ~, v0 h2 |8 ^3 g( o
0 _% H! k- Y# |9 U" f" Z' J3 E
要控制 LED 灯达到呼吸灯的效果,实际上就是要控制 LED 灯的亮度拟合呼吸特性曲线。前面控制全彩 LED 灯时,通过控制脉冲的占空比来调整各个通道 LED 灯的亮度,从而达到混色的效果。若控制脉冲的占空比在 3 秒的时间周期内按呼吸特性曲线变化,那么就可以实现呼吸灯的效果了。
3 _; m; H$ W) H8 U这种使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术—— 通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。
S8 }/ p3 F( f5 t2 h& f* M7 L( }% ~
PWM 控制的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。
G; Q, C1 N3 S6 y! h, tSPWM 波形——脉冲宽度按正弦规律变化而和正弦波等效的PWM 波形。SPWM 是一种非常典型的 PWM 波形,它在数字电路控制中应用非常广泛,如果使用低通滤波器,可以由 SPWM 波得到其等效的连续正弦半波。
! N) p3 M, y8 I' Q9 F: v
" v( ^$ i2 ?7 X. K若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成拟合的 PWM 波形,通常使用计算法和调制法:; N5 R% m/ ] D- [
(1) 计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形;1 r' _, M' ?3 r/ t0 E
(2) 调制法:拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断,就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求 。相对于计算法,其处理过程计算简单。
; \4 A# Y n t) s* ^( [8 _2 ] ~2 W7 N, D" t
6 T; U/ R4 _1 }- e2. 硬件简介
5 |0 T% Z, v% d9 C" g- ATIM—单色呼吸灯 使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种+ G9 R( ?" m( T3 t3 q( Y) c
颜色的单色呼吸灯。! ^- a; q5 x1 X4 L: }! E
RGB 灯,里面由红蓝绿三个小灯构成, 使用 PWM控制时可以混合成 256 不同的颜色
; E/ M1 J8 o/ V ^' g0 A# Z6 e( y) d& u; |! N0 h* x( l
0 ?* a; f4 F0 f( S! J! `
3 O0 n( m0 k2 w
本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为使用STM32 的定时器控制输出 PWM脉冲,然而并不是任意 GPIO都具有 STM32 定时器的输出通道功能,所以 在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯,如图
6 H; ~+ k8 C" m J* A$ A; X* {
! d5 o. {0 ?0 n8 Q& N7 j
. z+ l8 Q' G' ]( ~" q
8 J. Q; `6 i) w% ~6 i; ?2 u3 N- _本实训中的 RGB 灯使用阴极分别连接到了 PB5、PB0 及 PB1,它们分别是定时器% {, _( }+ Q3 O% D4 [
TIM3 的通道 2、3、4,其中 PB5用于定时器输出通道时,需要使用重定义功能。
% ]! Y& M$ K& T% J
; I5 o( V3 Q+ |# Z* z5 ^) c1 {9 Y3 P. Z
二、实现呼吸灯
. K9 V7 J9 G" K, S1.单色呼吸灯. M9 e1 i+ F3 G+ X1 L0 L! _% ?& U
要点7 G4 j* s2 p7 o( j) M8 n
初始化 PWM 输出通道,初始化 PWM 工作模式;
$ x' I! h- S b! x计算获取 PWM数据表;! L. P3 g3 I$ O8 K# A( G T
编写中断服务函数,在中断服务函数根据 PWM 数据表切换比较寄存器的值;
. E0 N2 a# x C0 s: N6 s4 l
6 |! J8 d( l+ J% b7 v/ J8 ?. o2 MLED灯硬件相关宏定义7 F0 v Y# T' @
breathing.h2 j9 F0 z2 l& B9 \' Z# ]7 N
5 ?* S, h$ v5 [! b j6 R- #define RED_LIGHT/ }0 G) ?5 `$ i' s4 q3 K( z4 l
- #define GREEN_LIGHT 3 @$ P' W% b8 }! u
- #define BLUE_LIGHT
3 R T6 h; R } ?6 u
+ r( o1 B0 f9 l$ D; i- /*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLOE_LIGHT*/1 G1 M( T3 s8 G. W
- #define LIGHT_COLOR_RED_LIGHT
& `) u8 _3 U* [& R2 r. @5 u. z2 b - /********定时器通道*******/2 Z. D5 D1 i' A) `" X8 u
- #if LIGHT_COLOR == RED_LIGHT
) Y+ P5 T- W: R - /************红灯***********/
: C* @+ X7 g# ?( s2 ~" j+ V. m - #define BRE_TIMx TIM3; s7 {/ K% @. P' a% k
- ) R2 b4 ?' |" x! w
- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
3 l$ g3 |- w* m, M; r" c, f3 D - #define BRE_TIM_CLK RCC_APB1Periph_TIM3
9 L. W* K' ~- |8 O - #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
( \ v+ W2 l$ F6 l7 @ - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO)
( I. k: ]& K8 H/ c4 D - //红灯的引脚需要重映射1 u, p% \2 `" j& F, |3 l6 n/ c
- #define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPI0_PartialRemap_TIM3,ENABLE);: j; r" q/ Q" O4 |" h$ @
- " ]& d: N1 o9 w. R# r
- #define BRE_TIM_LED_PORT GPIOB- G* ~( y3 W+ S. D* K! }: p
- #define BRE_TIM_LED_PIN GPIO_Pin_5
: t) C, x* A8 n3 l" ` - 7 C; ^& `2 n: o# E( e3 n# b1 W: L
- #define BRE_TIM_OCxInit TIM_0C2Init //通道选择,1~4( |- e- F& p# i* t
- #define BRE_TIM_OCxPreloadconfig TIM_OC2Preloadconfig) W3 J% R6 D% K
- #define BRE_CCRx CCR2
( A/ X8 U# ^& c! a5 F - #define BRE_TIMx_IRQn TIM3_IRQn //中断
( i) s: c( B9 `- b+ r - #define BRE_TIMx_IROHandler TIM3_IRQHandler
* v' `: u4 ~ T/ b
4 X# Z* N8 b; L# l9 c" v- #elif LIGHT_COLOR == GREEN_LIGHT
% V. k1 b# @) C2 @3 e8 w0 _# U
6 J, \' z R/ T- ^6 c. T+ z' w- /************绿灯***********/
- q6 z! Z3 ~; E5 V- [% q3 j - #define BRE_TIMx TIM3
' H) D* j' T5 ]8 A% D6 e' a S c$ Z - * R% U% E# i: O# ?9 _, g" `' g8 H) ~2 G0 a
- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
1 l2 a: Y* g. ]5 Y3 n" E% U" S* D% Y q - #define BRE_TIM_CLK RCC_APB1Periph_TIM33 y! c3 v% X8 a& L/ n) q
- #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
& q# B6 K7 z. |( K0 S6 d# o- u - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
) g( C; k4 {, q& _8 J3 O" j1 Z - //绿灯的引脚不需要重映射% i/ N" B; }6 A$ M9 P) U* }
- #define BRE_GPIO_REMAP_FUN()3 @3 `7 k. P! i, o$ `
- " N* `- m! v- B
- #define BRE_TIM_LED_PORT GPIOB
0 O/ v' b, W8 E7 S' u - #define BRE_TIM_LED_PIN GPIO_Pin_0
. b0 S7 Q& K( J& }" O9 {7 t' m - $ H9 z! s5 R0 ]( I, w: \
- #define BRE_TIM_OCxInit TIM_0C3Init //通道选择,1~4
$ J0 K; A1 @; l! z' i. L - #define BRE_TIM_OCxPreloadconfig TIM_OC3Preloadconfig% ~/ A2 ^3 `( V: m* \4 Y8 x
- #define BRE_CCRx CCR3
% x% Z F; [' `$ G O: X - #define BRE_TIMx_IRQn TIM3_IRQn //中断
! R2 \# G8 P) ?) ]: ^ - #define BRE_TIMx_IROHandler TIM3_IRQHandler3 @4 w# s5 i, M* x# R- ~) Z
- ! J5 M ~# U1 n/ V1 k6 s' d
- #elif LIGHT_COLOR == BLUE_LIGHT
1 E# n! Z: K7 W0 p | - /************蓝灯***********/: G/ d0 f7 D3 b- `
- #define BRE_TIMx TIM3: ~' ~# N+ T& T. E1 K; u9 J7 q* i
* V: |2 }" w- d: i3 J1 g+ ]# ^- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd, Q' v) U4 Q! o9 j
- #define BRE_TIMCLK RCC_APB1Periph_TIM3
+ X# _0 e# H c/ U, d# I - #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd% O# J* E+ I; z8 M. h# r4 ]" g% C- |
- #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)$ N4 n) I! P! K4 D/ i2 m
- //蓝灯的引脚不需要重映射0 T9 |; D! M- J
- #define BRE_GPIO_REMAP_FUN() # Q- f0 A7 O6 c' ]4 ]8 v
v6 t ~* ?* X9 e- @- #define BRE_TIM_LED_PORT GPIOB
: {7 t# z& @' f - #define BRE_TIM_LED_PIN GPIO_Pin_17 B; @! U0 e! M# A! ]1 K6 p$ l
- $ h8 W5 g/ `: N2 W8 f* I( a
- #define BRE_TIM_OCxInit TIM_0C4Init //通道选择,1~45 v& j8 k* B1 c( e" y4 d, j9 A& p
- #define BRE_TIM_OCxPreloadconfig TIM_OC4Preloadconfig1 C$ Y- k; v& q/ Z/ E3 D, `* w
- #define BRE_CCRx CCR45 n2 @% Z3 r, C8 ~
- #define BRE_TIMx_IRQn TIM3_IRQn //中断
0 O4 r1 U/ M" F' V* p3 u( L# x" t7 } - #define BRE_TIMx_IROHandler TIM3_IRQHandler
% p" `8 ^+ _ Z+ N6 [* E9 t - ( q( U7 v; y$ C$ |
- #endif + K* ^! n; q7 _: O, U5 i- p* {
4 u* z* t$ W2 t q
复制代码 1 R! X* y3 W7 b; ~( N
为方便切换 LED 灯的颜色,定义了三组宏,通过修改代码中的 #define LIGHT_COLOR RED_LIGHT语句,可以切换使用红、绿、蓝三种颜色的呼吸灯。在每组宏定义中,与全彩 LED 灯类似,定义了定时器编号、定时器时钟使能库函数、引脚重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名。不同的是,这个定时器的比较寄存器 CCRx 在控制呼吸灯的单个周期内需要切换为 PWM 表中不同的数值,所以需要利用定时器中断。/ R, b7 `9 o2 {: ~4 T+ h+ J
( {2 t$ c9 e. [2 v
6 \7 p: @& ^6 J6 M, T$ R2.初始化GPIO
9 }0 _; ~5 f* G; y" b! H初始化用于定时器输出通道的 GPIO
+ m/ ^9 y$ G$ \8 nbreathing.c 文件:
, V8 o$ H" ~ f2 a0 s% S0 N5 h- static void TIMx_ _GPIO_ Config (void)6 ?2 k Y5 i3 `% B/ P# k9 E% n! T
- {
; u* K( c9 D; t - GPIO_InitTypeDef GPIO_InitStructure;! Z8 ]! t5 ?+ H& V; i$ s* @# }
- ) z$ n. z: c7 E: F4 R+ d6 `7 I
- /* clock enable */8 q* x* o8 J7 k
- RCC_APB2PeriphClockCmd(BRE_TIM_GPIO_CLK, ENABLE);! \! F% v$ k! Y6 e3 u. o5 u% u
- BRE_TIM_GPIO_APBxClock_FUN( BRE_TIM_GPIO_CLK,ENABLE );3 }4 S' F& R, o# b0 b' W, n
- BRE_GPIO_REMAP_FUN();: X( y. @. \% B t$ C1 L: X
' }/ h- c- E/ y0 p- /*配置呼吸灯用到的引脚*/, _ h: N# }" T: q
- GPIO_InitStructure.GPIO_Pin = BRE_TIM_LED_PIN ;6 C' {3 q, S- @1 m% r
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
1 ?4 B+ z) U3 J. Y I7 G0 W - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
, l* x! y# w( U3 B - " C o, Y0 K& `* d p$ f
- GPIO_Init( BRE_TIM_LED_PORT,&GPIO_InitStructure ) ;
0 g$ D: V: i5 W' V" S# P - }
9 }' a. }6 F+ ?2 N - " b$ r2 H$ Y) O; ]8 E1 M4 e
复制代码
$ D+ g' S8 T: C \由于直接使用定时器输出通道的脉冲信号控制 LED 灯,此处把 GPIO 相关的引脚配置成了复用推挽输出模式。其中由于红灯使用的引脚需要用到第二功能,本代码使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作。! `- i" w# h3 m- R* i, J
! p2 [3 h4 t/ Q/ P) u
4 H9 e# ~9 T: x4 ]
3.定义 PWM表
5 K! A! `# r; T" t- /*******breathig.c文件*********/
+ e8 w `- T* [) `. G6 d( P" \9 z - /*LED亮度等级PWM表,指数曲线,6 H& i+ ~ a$ s0 d
- 此表使用工程目录下的python脚本index_wave.py生成*/
$ f% @1 i" `" [5 S% q7 I1 _ - 0 U. y; Q y, V& U& O
- uint16_t indexWave[] = {
+ ^ l$ n/ J* p) A7 C- P - 1,1,1,1,2,2,2,2,3,3,4,4,5,5,
* o- g, R# N9 {5 ~' S" H% ?, V - 6,7,8,9,10,11,13,15,17,19,22,0 c3 s$ i. c" q8 T/ u% h5 ^
- 25,28,32,36,41,47,53,61,69,79,! @- d; u# F2 B; i# A
- 89,102,116,131,149,170,193,219,
% _7 S: L' H& b6 y$ C - 250,284,323,367,417,474,539,613,
0 `2 }8 i3 ?4 w5 j8 } - 697,792,901,1024,1024,901,792,697,
. L0 I- G6 D0 `9 k: W+ z7 T) D2 I - 613,539,474,417,367,323,284,250,0 u( U$ |# [( `2 \4 j: q
- 219,193,170,149,131,116,102,89,7 ~& ]! @# @* ~4 g, P) d/ N
- 79,69,61,53,47,41,36,32,28,25,
$ c& `- D2 O' y - 22,19,17,15,13,11,10,9,8,7,6,
; [& q* F9 W9 F0 v" F* G) L - 5,5,4,4,3,3,2,2,2,2,1,1,1,1
& {& j' o) ], N0 U7 |6 j! L6 S - };
) Z6 q0 U2 b& g- f/ \ - //计算PWM表有多少个元素
7 Q+ _8 C2 {& n - uint16_t POINT_NUM = sizeof (indexWave)/sizeof (indexWave[0]);1 V2 j* ?2 m3 T y7 B1 C
复制代码
5 A7 z5 l. }3 n7 @% Q- `5 ?& f2 `/ x3 c3 }' i, Y0 R
该 PWM 表是利用本工程目录下的 python 脚本 index_wave.py 生成的- @6 N: R. A3 s& x) T r
, \- D) G3 o5 q4 v
% y4 `$ f! p* B5 m: C$ s
% o0 u' f8 T( J2 W& b' |该脚本运行后会在工程目录下生成一个包含该 PWM 表的 py_index_Wave.c文件,复制这些数据到 breathing.c 文件,即可得到本工程中的 indexWave 数组。实际上使用 C 语言也可以编写这样的脚本制作出 PWM 表,只是使用 python 脚本比较方便绘制图形而已。
" @6 A6 ^5 ?6 @; S3 D5 V$ _5 F6 j9 j* [
该 python 脚本生成 PWM表数据的原理,实质是按照如下函数曲线进行采样:3 j0 d" i) y7 `& \* p; l. D
6 e1 ^: p( v. n. M
- F' @& Y1 @, x' d( x8 v5 Y4 ]. f: [; o1 `! o
python 脚本在这样的函数曲线上取 110 个点,即可得到上述代码中 PWM 表数组indexWave。
' f: h7 d. h& a& E& D这个 PWM 表记录了呼吸特性曲线,PWM 表的数据将会被赋值到定时器的 CCRx 比较寄存器,从而控制输出占空比呈呼吸特性曲线变化的 PWM波。至于计算 PWM 表时,为什么选择采样 110 个点以及为什么表中的最大元素为 1024,需要结合下面定时器和中断服务函数中的配置来理解。
8 k% H. o& H7 Y. v4 \. f4 \1 D# m3 `$ `1 E/ G/ l. f$ `
7 |# y3 q: l8 n: g* ]4.定时器 PWM配置. t. T w M# h: _0 X
2 O5 h p$ D5 T. a( K
, K- Q# Y( q* n& a* T% d9 |
5 o$ @5 n, p' g% d: ^7 e7 a; B
初始化了控制 RGB 灯用的定时器,被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT 的值小于输出比较寄存器CCRx 的值时,PWM 通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较寄存器的值。
& f |: H+ }/ y t+ Q代码中的 TIM_Period和 TIM_Prescaler是关键配置。其中 TIMPeriod 被配置为(1024-1),它控制定时器的定时周期,定时器的计数寄存器 CNT 从 0 开始,每个时钟会对计数器加 1,计数至 1023 时完成一次计数,产生中断,也就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器 CCRx 被赋值为 PWM 表中的元素,即可改变输出对应占空比的 PWM 波,控制 LED灯,如:
4 S+ y7 m5 k2 E9 G L" I1 {& Z+ h Q+ S/ P3 g- ~8 Q
/ m* ~1 V) C5 K( \- y, D
/ Z* h6 }3 M7 U$ e* _! ]4 z" u# ]根据本工程中的 PWM 表更新 CCRx 的值,即可输出占空比呈呼吸特性曲线变化的PWM波形,达到呼吸灯的效果。最终,拟合曲线的周期由 TIMPeriod、PWM表的点数、TIM_Prescaler以及下面中断服务函数的 period_cnt比较值共同决定,本工程需要调整这些参数使得拟合曲线的周期约为 3秒,从而达到较平缓的呼吸效果。, E" p9 F4 g# b3 ^+ P0 N
0 W4 d! p2 C( b: y: {& o
7 i/ k) h- f' G# W8 k
5.定时器中断服务函数* O7 P: G+ W( b# @" x/ n
stm32f10x_it.c 文件7 n Z6 n! V/ o# O$ j. q
- //控制输出波形的频率, I- i$ h- E! R6 ~, D
- _IO uint16_t period_class = 10;4 b4 x- _' R/ C6 L5 N
- /*PWM 表*/: s# ?9 X9 E U1 y& p! g4 b
- extern uint16_t indexWave[] ;8 Y j3 X5 [& n
- /*PWM表中的点数*/
& y8 u" Y+ c% X' g( [ - extern uint16_t POINT_NUM ;
" L* o& W6 ~/ X - /*******stm32f10x_ it.c文件*******/
2 H. {) { h: @# P" D5 D - /*呼吸灯中断服务函数*/* Z! j' U4 s$ S3 z: g7 F9 r. v" i
- void BRE_TIMx_IRQHandler (void)9 G' h6 z/ I8 x8 W
- {
3 w) k5 S6 W: G$ D& B - static uint16_t pwm_index = 0; //用于PWM查表
- ^2 h0 _, c; L | - static uint16_t period_cnt = 0; //用于计算周期数+ @0 L- d; s/ Z3 O
- //TIM_ IT_ Update
7 P% |" b3 v; ?8 D - if (TIM_GetITStatus (BRE_TIMx, TIM_IT_Update) != RESET)
' @% [& Z8 d4 I1 w& Q$ e" x# {9 O - { q* Z& k, s! ?+ S5 ^; t; Q
- period_cnt++;: b9 n; \" f! b9 H: F. X/ [
- //根据PWM表修改定时器的比较寄存器值2 ]( i+ M3 ?2 r* d2 r
- BRE_TIMx->BRE_CCRx = indexWave [pwm_ index] ;; E1 e% d6 G$ m# ]
- //每个PWM表中的每个元素使用period_ class 次1 [) o9 |0 h+ U% d2 w# g' w
- if (period_cnt > period_class)
2 R8 S" n- Y, t5 i4 a! W - {
1 I/ N8 ^) O) l( k @: H6 O4 }) V - pwm_index++; //标志PWM表指向下一个元素
, a2 I7 P4 d% u6 ~6 i - //若PWM表已到达结尾,重新指向表头
3 V, b, c) f! s; _ - if ( pwm_index >= POINT_NUM) - L: K; S& g* U6 W; u. u" l) E
- {( \$ I% K1 A1 L& G
- pwm_index=0;0 V) O; u9 T+ v% m1 N
- }% F! T" T( [5 W* e/ q- @ J
- period_ cnt=0;//重置周期计数标志: S! t9 W+ B8 k3 Z( f# w* _
- }2 J; ^& b0 u8 h; |# l0 `+ g
- TIM_ClearITPendingBit (BRE_TIMx, TIM_IT_Update); //必须要清除中断标志位
/ n: d3 |* E$ k, h, G. |! l - }
) z2 |. W0 \( I& q* N. D/ ~' S$ E - }
( q! r" V) R0 E
复制代码 1 ]2 u7 v$ Q! m Y
在中断服务函数中,包含两个静态变量 period_cnt和 pwm_index。其中 pwm_index 比较容易理解,它用于指示当前要使用 PWM 表中的哪个元素,从而在“BRE_TIMx->BRE_CCRx = indexWave[pwm_index];‖语句中可以给 CCRx 赋予正确的数值,而且当 PWM 表中的数据都使用一遍时,pwm_index 将重新指向 PWM 表的开头,开始下一次呼吸循环。5 R. P* x4 |! ^, M2 u3 ?& p
在本工程的单次呼吸循环中,每个 PWM 表元素都会使用 10 次,代码中利用period_cnt 变量指示当前使用的次数,当 period_cnt> period_class 时(即period_cnt>10 时),pwm_index 才会指向下一个元素。每个 PWM表元素使用多次,主要是为了在 TIMPeriod、PWM 表的点数、TIM_Prescaler 都固定的情况下,通过调整每个元素的重复次数可以调整整个拟合波形的周期。如把代码中的比较值 period_class 改为 100,每个 PWM 表遍历一次的时间就变为原来配置的 10 倍,其拟合的呼吸周期也就相应地改变了。
5 T% f2 L ]; J) ]5 w) F! K7 N- P" [
8 z7 Z _; Z3 L$ w- d& ?6.计算拟合波形的周期1 A; b& X# L8 S
在本工程中,TIMPeriod、PWM 表的点数、TIM_Prescaler 以及 period_cnt 都会影响到拟合曲线的周期,而在实际应用中又有如下要求:3 k g4 B& ]( ~! [. J+ r' u2 t+ V
% W; W- `4 X) j' S {+ I6 p9 dTIMPeriod:定时器的计数周期,它的值必须与 PWM 表中的极大值相等(应用中赋值需要减 1),而 PWM 表的极大值决定了控制的分辨率。例如极大值为 10 时,PWM 占空比只有 10 个等级,精确到 0.1,当极大值为 1000 时,PWM 占空比有1000个等级,精确到 0.001。
! g* @! o( d9 r) S4 wTIM_Prescaler:定时器时钟分频因子,它控制定时器计数器 CNT 计数加 1 所需要的时间,它的值太大会导致输出的单个 PWM 波周期过长,影响控制的动态特性。如控制 LED 灯时,该值太大会导致 LED 灯开关时间变长,闪烁明显。一般来说,该值越小越好。4 ^5 k$ [% |3 k
PWM 表的点数:PWM 表的点数即对拟合曲线的采样点数,采样点越多,能更好地还原拟合曲线,采样点太少,可能会导致失真,
5 ]2 ~2 D0 d9 q* V, \+ Nperiod_class:周期倍数,即 PWM 表中每个元素的循环次数,它影响拟合曲线的4 M% l. ~) y; n! u% n5 P' t8 l
周期。当 period_class=1时,可以输出本配置中周期最短的拟合曲线。
) q" x% z/ W$ \, Bamplitude_class:幅值分级,可以把拟合曲线的幅值分成 N 个等级,控制时可以选择按某个幅值等级进行输出。本工程没有配置该参数,所以只能输出最大的等级,即amplitude_class=1。
3 [# s. K5 ^6 ?) j. o5 I% S& [! A9 ?# {! R
以上各个参数虽然侧重点不同,但若修改其中的任何一个,最终都会影响到所拟合曲线的周期,所以在实际应用中,通常先设定好 TIMPeriod、TIM_Prescaler、PWM 表的点数以及幅值等级数 amplitude_class,得到适合的控制精度、动态特性拟合度以及幅值等级后,然后再调整 period_class 控制拟合曲线的周期,而且 period_class 在程序中动态修改非常方便,不需要重置定时器和 PWM表。8 Z* d4 ]% l. n
9 }$ j# I/ z& R
最终,我们把本工程配置中的拟合曲线周期计算公式概括如下:" v. D$ \+ P4 m6 s/ E: k* `
6 p' E4 A% V0 f- f2 N( [6 Q9 o p
9 I% u2 X6 X# K" K
! L6 f1 K2 b4 F# I5 h
' f. Q% g! l/ @. W& h, \7 u通过公式的计算可知本工程的配置可使得输出的拟合曲线周期为 3.128 秒,是比较平缓的呼吸周期。
8 z. b# @8 l+ n# \4 c; V9 C+ ~3 t% P% Q% n% b
2 b* T" K( V J+ S6 ]7.主函数6 _6 [$ D/ u8 o, } G
- int main(void)$ p4 n: [0 x- `0 V& L
- {& R! B2 X) \' D7 |7 H" J
- /*初始化呼吸灯*/
" g1 I4 }6 R( L" h3 @ - TIMx_ Breathing_ Init() ;5 p9 D9 P% L( H. M
- while (1) 1 q. U# i* y* Y. p4 i
- {" Y8 W; X9 \4 }0 F- G& d1 _
- : _: [1 D a9 G/ C B
- }# {9 G. a; b: d/ ?9 _" e! A
- 2 b+ x' g8 C- j6 M! ]
- }- N% O# j2 O$ l. l+ I1 ^
复制代码 $ n. _0 _( s' \7 S* Z* V; Y
% v3 z- B( ~, p5 W$ M9 E9 Bmain 函数中直接调用了 TIMx_Breathing_Init 函数,而该函数内部又直接调用了前面讲解的 GPIO 和 PWM 配置函数:TIMx_GPIO_Config 和 TIMx_Mode_Config。初始化完成后,定时器开始工作,然后它会在中断服务函数中切换 PWM 数据,控制 LED 灯显示呼吸效果。
; h1 C# r2 l4 g0 |3 I
& X+ P/ U* r$ Z1 u4 q9 j* ~
. z7 _( D, R1 Q# K" L总结& N1 e* |; d" d, u: G7 [
本工程通过LED灯实现了呼吸灯效果,整体不难,使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种颜色的单色呼吸灯。实现全彩呼吸灯需要在单色呼吸灯的基础上,添加对 PWM 波的幅值控制,分为256 个等级,从而实现RGB888 全色彩的呼吸灯。
8 Y; u3 [$ c7 ~9 w7 y \/ J————————————————
( j. m. s- }" U版权声明:清道 夫
) o8 \% e9 y v2 |
" A3 K1 |2 N0 c( T4 r6 u0 `
/ K9 i& {% d2 a4 C( R. t |