前言
- W- b6 C* Z5 y6 }在本章中,演示如何使用计算法得到的呼吸曲线 PWM 波和 SPWM 波,并使用 STM32定时器 TIM的 PWM功能输出波形控制 LED 灯,实现三色LED的呼吸效果。
& U0 S" o( n7 p: f. q+ B1 a6 h" C' L% n/ X* M' a
一、相关知识介绍
; W9 u: }7 A) ~0 A7 L1.呼吸灯简介! y" C6 [ J, V% ?
呼吸灯,就是指灯光设备的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减,8 \4 I8 j( L" i2 {* f9 X' F3 [
很有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的指示灯中4 J, z5 O6 n) o' Q4 z( C3 t/ C
( l4 V6 w! o3 @8 ]$ G2 H要控制 LED 灯达到呼吸灯的效果,实际上就是要控制 LED 灯的亮度拟合呼吸特性曲线。前面控制全彩 LED 灯时,通过控制脉冲的占空比来调整各个通道 LED 灯的亮度,从而达到混色的效果。若控制脉冲的占空比在 3 秒的时间周期内按呼吸特性曲线变化,那么就可以实现呼吸灯的效果了。
7 c7 h8 B6 M0 y: w6 ]6 G这种使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术—— 通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。
1 ?: Y! B% C$ \2 {* c
0 w/ b! U4 s0 J% }' w) p: e$ NPWM 控制的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。: j0 l2 J4 X R) ]
SPWM 波形——脉冲宽度按正弦规律变化而和正弦波等效的PWM 波形。SPWM 是一种非常典型的 PWM 波形,它在数字电路控制中应用非常广泛,如果使用低通滤波器,可以由 SPWM 波得到其等效的连续正弦半波。% I. O7 c& {. u, W8 S4 i& u; S r
; ^% \* e$ g: l7 ~# _- a4 S, n
若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成拟合的 PWM 波形,通常使用计算法和调制法:
1 R# G, |6 ~8 z" I(1) 计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形;; u# R& {+ ^; h) d8 I$ I. [+ Q
(2) 调制法:拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断,就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求 。相对于计算法,其处理过程计算简单。
2 ` s8 y0 m& y) M1 l# u- S i) ^- P6 N8 \4 ^5 q) ^6 A
% ]0 n* V! ~3 V# q2. 硬件简介
5 C8 G) _9 d5 ]( @9 ~/ CTIM—单色呼吸灯 使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种+ a3 O5 O9 J3 T7 v) `. k
颜色的单色呼吸灯。; H, p S( I3 N& ?: h) n
RGB 灯,里面由红蓝绿三个小灯构成, 使用 PWM控制时可以混合成 256 不同的颜色
7 v' W( c/ J0 O/ g( V+ S) N) I+ K. Q* k v2 A' d) m
/ t% c4 h9 O/ y$ c
( S7 U/ f5 N5 t: Q
本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为使用STM32 的定时器控制输出 PWM脉冲,然而并不是任意 GPIO都具有 STM32 定时器的输出通道功能,所以 在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯,如图* b# _! m1 o$ [! B, o
1 d: y& q3 @5 J! J( M
4 w) C2 o% K1 ~" Y# V6 E" V+ n
! q2 Y. q6 p; j7 Y2 r3 c本实训中的 RGB 灯使用阴极分别连接到了 PB5、PB0 及 PB1,它们分别是定时器! [" O7 {+ R5 S8 E, B; j
TIM3 的通道 2、3、4,其中 PB5用于定时器输出通道时,需要使用重定义功能。
) Z U' B: a" s+ }5 l& V8 L, s! N. a3 M% l0 o) h
( m7 ]* M# ^+ f二、实现呼吸灯7 i1 U2 A( \& q: K, R# t- t
1.单色呼吸灯. C4 @% T+ G: M* M4 U$ \2 E
要点
/ G3 x8 q7 F8 Z m: |) y初始化 PWM 输出通道,初始化 PWM 工作模式;6 f+ n0 D' [# z( E
计算获取 PWM数据表;% p2 s' D" U7 I5 V+ Q
编写中断服务函数,在中断服务函数根据 PWM 数据表切换比较寄存器的值;* E! f# s6 [1 y. _) T9 B* t6 @# k
' `, h! {( _' `1 l0 }LED灯硬件相关宏定义
* p; t; q G V& `: E* L1 g- {* V5 Jbreathing.h
# k( n% o1 P/ M( ]8 @
1 N V8 e1 s2 J2 |/ S- #define RED_LIGHT5 J& y' C9 b% T- z( c. U
- #define GREEN_LIGHT % k# a2 s5 B! C3 l9 c5 q
- #define BLUE_LIGHT1 o5 e* C2 M* h
0 z2 l+ v2 `1 U% T- /*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLOE_LIGHT*/2 ~. j$ T2 h( B K% W) R
- #define LIGHT_COLOR_RED_LIGHT
4 ~& h n, U1 n3 O - /********定时器通道*******/' \) {2 x. Q' \! L+ M
- #if LIGHT_COLOR == RED_LIGHT
, [7 Z+ L0 h9 S/ M* w# l - /************红灯***********/
; ~: D( _( A9 ]3 L3 g: Z- s v2 O9 q. E+ W - #define BRE_TIMx TIM34 L( C3 V) U+ s) M2 w/ b
- ( d# l* q# U2 p: B2 K
- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd9 n+ z+ b# u7 p3 [. k+ x, n1 Y
- #define BRE_TIM_CLK RCC_APB1Periph_TIM3
9 ^& c( \7 P2 z% b( t7 L- k" K - #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
3 [: J/ o. z& s+ M X6 u - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO) ~7 j2 b/ m5 K c
- //红灯的引脚需要重映射
) N& O( o! f: Y o D - #define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPI0_PartialRemap_TIM3,ENABLE);0 a: O( f% [# `% h( ]
- " X1 E* x: D7 I2 T: U$ D' }
- #define BRE_TIM_LED_PORT GPIOB
# H! ~ A2 R6 h- J' @. ` - #define BRE_TIM_LED_PIN GPIO_Pin_5
i5 _. v5 _2 \# v8 A& l - 2 l3 n0 u6 H$ |
- #define BRE_TIM_OCxInit TIM_0C2Init //通道选择,1~4
. d% L. L2 e7 o/ W9 b4 ~3 l - #define BRE_TIM_OCxPreloadconfig TIM_OC2Preloadconfig
: P; j) e7 T) x% l# q5 h* A - #define BRE_CCRx CCR2
7 Z8 F& v: v/ X) B5 ^5 p - #define BRE_TIMx_IRQn TIM3_IRQn //中断
" E' p+ g6 ~. A, J! ]; ~ U - #define BRE_TIMx_IROHandler TIM3_IRQHandler
# z1 q: ?. j M" P1 G - ! g) {: ]5 [3 o1 ?; J) t
- #elif LIGHT_COLOR == GREEN_LIGHT
1 y0 X: g' L# e! Z2 \$ E
; u9 W ~3 A; E$ i) p0 o/ q. M- /************绿灯***********// V4 s, a4 T! l- F& {
- #define BRE_TIMx TIM3) v" L8 G# o! c9 j0 j+ D7 {8 h7 B- u
- ) _. X! ~0 }6 e8 h& f. M" O
- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
4 A9 L% f6 ^! X3 k! M, P - #define BRE_TIM_CLK RCC_APB1Periph_TIM3
- ~ Q# H/ z P" r - #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
, W9 R+ H$ w5 O4 ^6 Z2 I - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB): G ^4 b% L, n7 \
- //绿灯的引脚不需要重映射
1 G& V) ] u" ^. z0 Q - #define BRE_GPIO_REMAP_FUN()
2 h1 j/ g; q9 R- y- u - ! E' {0 T* A; W/ D
- #define BRE_TIM_LED_PORT GPIOB. y5 l- G% E3 e/ E! Y, ^, S! {, ?: N
- #define BRE_TIM_LED_PIN GPIO_Pin_05 e& {0 x b* U2 N$ u
- ; z/ W: L& u. ?; m G
- #define BRE_TIM_OCxInit TIM_0C3Init //通道选择,1~4: _) l6 T/ w" l, l
- #define BRE_TIM_OCxPreloadconfig TIM_OC3Preloadconfig) r4 m! K8 C: s
- #define BRE_CCRx CCR3
2 {, N# d& i% Y6 b - #define BRE_TIMx_IRQn TIM3_IRQn //中断& d: ~9 \( n! B% Z
- #define BRE_TIMx_IROHandler TIM3_IRQHandler
5 i+ N M8 n( m C$ [ - - z% Q1 U4 G$ e5 Y3 D H
- #elif LIGHT_COLOR == BLUE_LIGHT
* I7 D% d2 L$ O0 |9 I* H# d3 J - /************蓝灯***********/
! w3 V: [% \2 ~" y0 V. w. |4 \# L; z4 v - #define BRE_TIMx TIM3
9 O+ ]1 l- l( e7 q4 D Q* r3 Z
/ m5 `7 U( H6 D C- #define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd$ J' R. j. D L" Z2 o
- #define BRE_TIMCLK RCC_APB1Periph_TIM30 O5 [& D, W# i; z
- #define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
m: N8 {6 \( v/ i b; ~; D - #define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
2 u. w# u0 d* t - //蓝灯的引脚不需要重映射
5 U/ ]. s; a5 V! s - #define BRE_GPIO_REMAP_FUN() " f9 j, K) I. h" ]
' a) A0 M% W$ _* q, [1 C) n" }- #define BRE_TIM_LED_PORT GPIOB8 r# x) c; u0 c5 o, b4 P6 H# L
- #define BRE_TIM_LED_PIN GPIO_Pin_1
6 [2 v' k S- m# j& s# P, S# p2 M
S. s2 t" Y3 O0 X# a3 p- E2 z7 W- #define BRE_TIM_OCxInit TIM_0C4Init //通道选择,1~4
5 M1 d5 k/ i m8 z) X - #define BRE_TIM_OCxPreloadconfig TIM_OC4Preloadconfig0 c; j# I6 P, h" q; P" q+ K
- #define BRE_CCRx CCR4
X) m4 S$ {* `. @& b& W$ o - #define BRE_TIMx_IRQn TIM3_IRQn //中断
; o8 X8 {2 k8 Z+ r/ p! E% e- O - #define BRE_TIMx_IROHandler TIM3_IRQHandler( P+ i" r% D" ~0 ~
- ' s) L; S$ \2 m9 m2 [) J
- #endif 4 [6 ?7 Q) ^$ d& c6 ]( q- I
- - r/ r0 T- r1 d7 B$ s3 V+ l9 j
复制代码
( j& }1 S3 g8 j. `7 ?' p为方便切换 LED 灯的颜色,定义了三组宏,通过修改代码中的 #define LIGHT_COLOR RED_LIGHT语句,可以切换使用红、绿、蓝三种颜色的呼吸灯。在每组宏定义中,与全彩 LED 灯类似,定义了定时器编号、定时器时钟使能库函数、引脚重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名。不同的是,这个定时器的比较寄存器 CCRx 在控制呼吸灯的单个周期内需要切换为 PWM 表中不同的数值,所以需要利用定时器中断。* x* O. V! N. P4 a7 B8 a! ?
6 s& n6 k, F( D/ p5 D+ n
: c3 N; E ]9 n0 X! X2.初始化GPIO
0 _4 ?5 Z7 H3 n- z' q3 @ p初始化用于定时器输出通道的 GPIO
1 m4 d- P! S% w0 a% A/ g0 obreathing.c 文件:& g: k; i x X1 M' u
- static void TIMx_ _GPIO_ Config (void)+ I5 K5 v" Z6 [7 i+ G# O& ~
- {
% \8 t6 G% J4 t - GPIO_InitTypeDef GPIO_InitStructure;
3 M, R* D" J3 ?$ H( p; z' r -
5 h. O' ?6 {- E0 Y - /* clock enable */
2 z N4 P' O3 t2 x) N - RCC_APB2PeriphClockCmd(BRE_TIM_GPIO_CLK, ENABLE);( e X( }! e" Y& @
- BRE_TIM_GPIO_APBxClock_FUN( BRE_TIM_GPIO_CLK,ENABLE );
" y0 r4 m7 p$ x$ e) r. D$ ^: w. i* g2 h - BRE_GPIO_REMAP_FUN();
. |, Y$ z5 h! z* O
- G/ ?# s# c* L- /*配置呼吸灯用到的引脚*/; E: b( O/ I4 Z7 x- S
- GPIO_InitStructure.GPIO_Pin = BRE_TIM_LED_PIN ;; B. w- E3 i& _
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
6 n3 G8 s' ^' f0 V" [ - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
2 b1 e* z- g5 ~- k4 F5 } -
2 q; A% `6 ^7 O. G4 s - GPIO_Init( BRE_TIM_LED_PORT,&GPIO_InitStructure ) ;, [5 C& e$ }: S! \# ^
- }
: W8 x* q. u4 z3 D6 ~ - Y. l! a. ]: c9 l7 k
复制代码 " S1 r& e' ?+ n( d1 o
由于直接使用定时器输出通道的脉冲信号控制 LED 灯,此处把 GPIO 相关的引脚配置成了复用推挽输出模式。其中由于红灯使用的引脚需要用到第二功能,本代码使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作。/ v" ^. T6 S' F5 h
; m: ]4 R- n2 \; R6 h" d
: K1 N) T( ~0 V& R) Y+ l& i3.定义 PWM表
3 U' l# z, T2 {/ f- /*******breathig.c文件*********/6 h1 z& z. o9 r7 y- k f7 k: \
- /*LED亮度等级PWM表,指数曲线,
/ ? _- f- o6 {1 Z; v3 w - 此表使用工程目录下的python脚本index_wave.py生成*/# R, c5 F* J: c0 y: x
- * S) }. }7 U' m8 h# [1 @5 g
- uint16_t indexWave[] = {
& F5 w9 L5 U) ~" S - 1,1,1,1,2,2,2,2,3,3,4,4,5,5,
5 |3 O) u# c+ D - 6,7,8,9,10,11,13,15,17,19,22,
- z5 W( d f6 J: e8 A5 F - 25,28,32,36,41,47,53,61,69,79,# \- r& [6 u, D: s
- 89,102,116,131,149,170,193,219,
# m: a4 D, S# ] q. t - 250,284,323,367,417,474,539,613,
, J G8 Y% \, C" U1 j - 697,792,901,1024,1024,901,792,697,* f) ^ p' e' {
- 613,539,474,417,367,323,284,250,
- {4 C* j$ o( X4 F3 ] - 219,193,170,149,131,116,102,89,
5 }( o9 N$ c1 f2 r0 i! Y - 79,69,61,53,47,41,36,32,28,25,
+ ~6 P6 F; k/ T8 p - 22,19,17,15,13,11,10,9,8,7,6,8 @+ q, e: y: R+ f% Z) \0 x8 b# D
- 5,5,4,4,3,3,2,2,2,2,1,1,1,1( m5 w" F! t. h; q6 z+ \
- };) m6 _' p8 L# ]+ e* `
- //计算PWM表有多少个元素
1 U6 A& F5 l. R8 R7 c - uint16_t POINT_NUM = sizeof (indexWave)/sizeof (indexWave[0]);$ J2 A9 L5 u$ x- h7 t
复制代码 * A' p2 }& Z! g# M
$ O3 Y& M# E" j. Y. B6 a; Q% q: Z" k1 O
该 PWM 表是利用本工程目录下的 python 脚本 index_wave.py 生成的
' R9 _, ^; f, m5 o0 d& t/ Y: e2 Z g
8 u w- r$ e* `: u; v5 c- l; O
+ h5 j5 r# D: |6 o! D- B该脚本运行后会在工程目录下生成一个包含该 PWM 表的 py_index_Wave.c文件,复制这些数据到 breathing.c 文件,即可得到本工程中的 indexWave 数组。实际上使用 C 语言也可以编写这样的脚本制作出 PWM 表,只是使用 python 脚本比较方便绘制图形而已。0 z) n! C& j5 ^( ?1 b1 j4 G1 X3 A1 T% r
+ G, C* k+ m: R! P K该 python 脚本生成 PWM表数据的原理,实质是按照如下函数曲线进行采样:
1 Q" f1 {4 E: ]# V$ a; T: m9 X% ~' e$ s+ ]) C% s& M( V2 Y
/ V" a4 o) } ~$ b; m; j6 |
# d5 V) S0 u$ v/ r# `4 O Tpython 脚本在这样的函数曲线上取 110 个点,即可得到上述代码中 PWM 表数组indexWave。1 a0 O, O9 Z% H- @' ], {$ l( t
这个 PWM 表记录了呼吸特性曲线,PWM 表的数据将会被赋值到定时器的 CCRx 比较寄存器,从而控制输出占空比呈呼吸特性曲线变化的 PWM波。至于计算 PWM 表时,为什么选择采样 110 个点以及为什么表中的最大元素为 1024,需要结合下面定时器和中断服务函数中的配置来理解。+ |2 M5 L' r( z% L$ E0 b" z
y& p, c$ s. @ ?
( G. F" `' S |- w5 t
4.定时器 PWM配置2 A! J6 i. \# }% ^" n- ~" C: Y
% a) o; ]% S$ G# X, K2 |" ^' X
* p0 B% d7 K- K# k8 E$ F* c6 p1 |! W
初始化了控制 RGB 灯用的定时器,被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT 的值小于输出比较寄存器CCRx 的值时,PWM 通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较寄存器的值。- {9 b+ l/ z+ k- a, T- k
代码中的 TIM_Period和 TIM_Prescaler是关键配置。其中 TIMPeriod 被配置为(1024-1),它控制定时器的定时周期,定时器的计数寄存器 CNT 从 0 开始,每个时钟会对计数器加 1,计数至 1023 时完成一次计数,产生中断,也就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器 CCRx 被赋值为 PWM 表中的元素,即可改变输出对应占空比的 PWM 波,控制 LED灯,如: i9 D+ Y5 N0 c! p+ Q
* \' o; B3 n$ f+ A5 S+ R8 E
6 ~+ N8 P! O6 S9 m$ V# l
h; \* t; Z" \3 i4 ?
根据本工程中的 PWM 表更新 CCRx 的值,即可输出占空比呈呼吸特性曲线变化的PWM波形,达到呼吸灯的效果。最终,拟合曲线的周期由 TIMPeriod、PWM表的点数、TIM_Prescaler以及下面中断服务函数的 period_cnt比较值共同决定,本工程需要调整这些参数使得拟合曲线的周期约为 3秒,从而达到较平缓的呼吸效果。& ~, ?* N$ x4 j2 h
& j3 J, S7 Q9 [; D# C: [0 O `! `9 d5 b8 K! ?5 j: d
5.定时器中断服务函数% S5 |" l9 `0 k$ c) {
stm32f10x_it.c 文件
& s/ ^$ }" Y$ v, A0 g- //控制输出波形的频率1 F6 X6 T, \4 _0 a' u
- _IO uint16_t period_class = 10;
" s9 S" [0 _+ {; d6 a+ x - /*PWM 表*/
' a4 @( |0 \0 h/ }1 _& M6 q - extern uint16_t indexWave[] ;
* [1 c' x9 u5 d8 Y( d - /*PWM表中的点数*/2 ]1 @ ]! `9 w% J# \7 \
- extern uint16_t POINT_NUM ;
8 @+ ?1 z d6 b$ P8 G; O R5 g5 L - /*******stm32f10x_ it.c文件*******/# O7 y2 I" r" M2 q1 a9 M& C
- /*呼吸灯中断服务函数*/6 ~# M3 a" v I, k, {5 S
- void BRE_TIMx_IRQHandler (void)$ v# `+ ^! J0 P" s
- {+ ?6 s5 L" J* L
- static uint16_t pwm_index = 0; //用于PWM查表7 e+ N' r+ Z8 E
- static uint16_t period_cnt = 0; //用于计算周期数
( `- m; b7 Q' G( }3 l - //TIM_ IT_ Update
- L$ M5 s% d/ ]! F1 D1 H( X - if (TIM_GetITStatus (BRE_TIMx, TIM_IT_Update) != RESET)
$ G: J# d5 d! I. A+ Q a - {
0 M( b; b8 l' y7 c, w, [ - period_cnt++;: g; U% ]! ?/ K
- //根据PWM表修改定时器的比较寄存器值
9 I+ {" c/ I$ ^% B - BRE_TIMx->BRE_CCRx = indexWave [pwm_ index] ;
9 ?* P% a9 I: l4 T: s1 n+ g2 W - //每个PWM表中的每个元素使用period_ class 次
2 D% U, c9 ~5 {; z! |* v6 k - if (period_cnt > period_class)
, F" r& P8 h! K7 M5 C" F9 x. c - {
4 ]! | l; H: h0 `% ^+ }3 z; b R - pwm_index++; //标志PWM表指向下一个元素
# P9 q/ h2 n" @( ^ - //若PWM表已到达结尾,重新指向表头
% E; M2 h4 w6 A0 W7 F' a, G - if ( pwm_index >= POINT_NUM)
# q V# ?7 T4 d7 U - {
+ k& Y4 Z0 \2 ]* r1 I, T - pwm_index=0;5 f5 V0 P& Q* p' t! U) j: t
- }
% L$ ^, e7 P" K - period_ cnt=0;//重置周期计数标志
- F! r3 P% w% M9 h- X - }
: C( _/ G" d' [$ X! x2 d+ f5 T - TIM_ClearITPendingBit (BRE_TIMx, TIM_IT_Update); //必须要清除中断标志位
4 ^) `( t* L Z; \' q - }7 m2 j3 @; r& z7 j8 M+ L* i' h; F' }
- }
. \; ?$ H1 h4 D
复制代码
7 S7 O' Z1 o- j在中断服务函数中,包含两个静态变量 period_cnt和 pwm_index。其中 pwm_index 比较容易理解,它用于指示当前要使用 PWM 表中的哪个元素,从而在“BRE_TIMx->BRE_CCRx = indexWave[pwm_index];‖语句中可以给 CCRx 赋予正确的数值,而且当 PWM 表中的数据都使用一遍时,pwm_index 将重新指向 PWM 表的开头,开始下一次呼吸循环。! z. S9 D' ]/ c- [+ ~
在本工程的单次呼吸循环中,每个 PWM 表元素都会使用 10 次,代码中利用period_cnt 变量指示当前使用的次数,当 period_cnt> period_class 时(即period_cnt>10 时),pwm_index 才会指向下一个元素。每个 PWM表元素使用多次,主要是为了在 TIMPeriod、PWM 表的点数、TIM_Prescaler 都固定的情况下,通过调整每个元素的重复次数可以调整整个拟合波形的周期。如把代码中的比较值 period_class 改为 100,每个 PWM 表遍历一次的时间就变为原来配置的 10 倍,其拟合的呼吸周期也就相应地改变了。" f# h. [& l, _' r8 p7 h
$ H* h: ~! F) }% |( J3 L/ O7 W! ?* B4 I6 _$ z
6.计算拟合波形的周期
! j6 e- y4 S2 u7 ~3 V) i8 A在本工程中,TIMPeriod、PWM 表的点数、TIM_Prescaler 以及 period_cnt 都会影响到拟合曲线的周期,而在实际应用中又有如下要求:
' P/ _% M/ ?6 A1 P! }7 g: T' l; }
TIMPeriod:定时器的计数周期,它的值必须与 PWM 表中的极大值相等(应用中赋值需要减 1),而 PWM 表的极大值决定了控制的分辨率。例如极大值为 10 时,PWM 占空比只有 10 个等级,精确到 0.1,当极大值为 1000 时,PWM 占空比有1000个等级,精确到 0.001。
7 A' v! Q7 C% ^: O* L! }: X9 _TIM_Prescaler:定时器时钟分频因子,它控制定时器计数器 CNT 计数加 1 所需要的时间,它的值太大会导致输出的单个 PWM 波周期过长,影响控制的动态特性。如控制 LED 灯时,该值太大会导致 LED 灯开关时间变长,闪烁明显。一般来说,该值越小越好。5 c9 _- U8 ?2 i
PWM 表的点数:PWM 表的点数即对拟合曲线的采样点数,采样点越多,能更好地还原拟合曲线,采样点太少,可能会导致失真,: @$ G0 u, _6 ~
period_class:周期倍数,即 PWM 表中每个元素的循环次数,它影响拟合曲线的3 P( I+ W/ `) z" V+ O6 F' g
周期。当 period_class=1时,可以输出本配置中周期最短的拟合曲线。+ L1 G/ d5 t% S
amplitude_class:幅值分级,可以把拟合曲线的幅值分成 N 个等级,控制时可以选择按某个幅值等级进行输出。本工程没有配置该参数,所以只能输出最大的等级,即amplitude_class=1。
) X( Q" x" s$ r8 g2 y
! ]- j; H+ A; V! T以上各个参数虽然侧重点不同,但若修改其中的任何一个,最终都会影响到所拟合曲线的周期,所以在实际应用中,通常先设定好 TIMPeriod、TIM_Prescaler、PWM 表的点数以及幅值等级数 amplitude_class,得到适合的控制精度、动态特性拟合度以及幅值等级后,然后再调整 period_class 控制拟合曲线的周期,而且 period_class 在程序中动态修改非常方便,不需要重置定时器和 PWM表。
- \1 G9 _- v/ ~1 B- E/ j2 `1 f, ~9 N7 m' ~" N
最终,我们把本工程配置中的拟合曲线周期计算公式概括如下:5 Z) x9 n7 c6 H9 C, L
' i' N& n L; X' A
9 }4 p& J5 y8 n: |/ s
4 p0 t' L9 e: k. M
7 y/ o; j3 `0 o9 R* E& _5 H9 m通过公式的计算可知本工程的配置可使得输出的拟合曲线周期为 3.128 秒,是比较平缓的呼吸周期。
* x1 x4 j! c" w# a1 |+ _
3 t9 J* w" C; U. w- X. k
8 i: J8 r# C6 e; ]7.主函数+ j( a1 P. L# `* f/ l/ [. _! d7 `4 \& O
- int main(void)
0 q* P% O7 C2 W1 u2 n( L U - {
, y/ j4 a* {3 e* W+ g) L - /*初始化呼吸灯*/; z9 s' u+ T2 v
- TIMx_ Breathing_ Init() ;; X2 z: @+ O. q$ L5 V8 ^
- while (1)
$ v+ }4 M% Q; \ - {
, b8 @, q4 H' \- W9 W -
. w1 J- C, V* I; G w) J - }
* s7 u$ h0 x9 E - ! b, T6 \; o, H0 D( y
- }
- _& s( f0 F- t1 B3 W1 B4 }
复制代码 3 z( i( z1 _' A* x: w
( ^" w8 [: o; T
main 函数中直接调用了 TIMx_Breathing_Init 函数,而该函数内部又直接调用了前面讲解的 GPIO 和 PWM 配置函数:TIMx_GPIO_Config 和 TIMx_Mode_Config。初始化完成后,定时器开始工作,然后它会在中断服务函数中切换 PWM 数据,控制 LED 灯显示呼吸效果。
" O# _+ {( q8 \5 H9 U
+ q7 t, V E9 c* P
! J/ a3 D/ z+ u4 C5 I总结
, r# G M# l9 U本工程通过LED灯实现了呼吸灯效果,整体不难,使用呼吸曲线 PWM 波控制 LED 灯,可实现红、绿、蓝三种颜色的单色呼吸灯。实现全彩呼吸灯需要在单色呼吸灯的基础上,添加对 PWM 波的幅值控制,分为256 个等级,从而实现RGB888 全色彩的呼吸灯。
! Y0 S7 s/ _2 B$ {) ~; _: G————————————————! M4 c: \: B! e7 R% N4 q5 ^
版权声明:清道 夫
' z+ L: G3 g) N4 b2 I4 J& V
3 w' w% O0 L' y+ m7 c/ E H, u
& r( B5 j. Q- P6 X3 z |