十八、PWM呼吸灯
1. 配置流程
我们外接一个LED灯进行PWM实现,注意一下要选择合适规格的LED。
DANGER
📌 连接天空星开发板的PA02引脚!!以PA02为Timer2的PWM输出引脚。
一般使用定时器PWM功能,都需要有以下几个步骤。
- 关闭寄存器保护
- 使能时钟
- 配置GPIO复用
- 配置定时器
- 配置PWM
- 使能TIMER和PWM
- 调整定时器比较值
1.1 关闭寄存器保护
HC32F4A0芯片有很多寄存器无法直接修改写入,要关闭一些相关寄存器的写保护。
// 关闭相关的寄存器写保护
LL_PERIPH_WE(LL_PERIPH_GPIO | LL_PERIPH_FCG | LL_PERIPH_PWC_CLK_RMU);
2
1.2 使能时钟
首先LED灯连接在PA2引脚上,查找数据手册的第43页可知,PA2有好几个定时器通道的复用功能,如图1-1-1所示。
这里选择PA2的复用功能,也就是使用TimerA_5_CH1进行PWM输出。
使能时钟:
// 使能 TimerA 时钟
FCG_Fcg2PeriphClockCmd(FCG2_PERIPH_TMRA_5, ENABLE);
2
1.3 配置GPIO复用
前面介绍过PWM输出是依赖于定时器的,所以要对定时器进行配置,但是我们不使用定时器的中断功能,顾不用对定时器的中断进行配置。
又因为我们使用的PA2是定时器A_5的通道1,所以我们要配置GPIO复用。
使用复用功能:
// 引脚复用
GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_02, GPIO_FUNC_5);
2
第一个参数是GPIO端口,第二个参数是引脚。第三个参数是功能复用编号。 例如: 我想复用PA4引脚的Timer6_7的通道A,输出PWM,那我们的复用编号就是上面对应的编号:
1.4 配置定时器
要使用定时器参数配置有一个结构体,如图1-3-1所示。
这个结构体定义了 TimerA 的初始化参数,用于配置 TimerA 模块的工作模式、计数源、计数方式等。让我逐个解释这个结构体的各个成员:
u8CountSrc
:计数源。这个参数指定了计数器的时钟源。可以是软件时钟源或者硬件时钟源,具体取决于你的硬件设计。通常,软件时钟源是来自内部时钟,而硬件时钟源则是外部引脚信号。sw_count
:软件计数器配置。这个结构体包含了软件计数器的相关参数,包括时钟分频、计数模式和计数方向。
u8ClockDiv
:软件时钟源的分频系数。用于设置软件时钟源的分频,以减小计数器的计数速度。u8CountMode
:计数模式。这个参数指定了计数器的工作模式。u8CountDir
:计数方向。指定了计数器的计数方向,可以是向上计数、向下计数或者向上/向下循环计数。
hw_count
:硬件计数器配置。这个结构体包含了硬件计数器的相关参数,包括硬件计数器的上升条件和下降条件。这些条件通常用于配置外部引脚信号触发计数。
u16CountUpCond
:硬件计数器的上升条件。用于指定触发计数器上升的外部条件。u16CountDownCond
:硬件计数器的下降条件。用于指定触发计数器下降的外部条件。
u32PeriodValue
:周期参考值。这个参数指定了计数器的周期值。具体的含义取决于计数器的工作模式和计数方式。通常情况下,这个值用于指定计数器计数到多少值后重新加载初始值。u8CountReload
:计数重新加载使能。这个参数指定了计数器在溢出或者下溢时是继续计数还是停止计数。
结构体定义:
stc_tmra_init_t stcTmraInit;
相关配置如下:
// 使用默认初始化参数,初始化 TimerA 结构体
(void)TMRA_StructInit(&stcTmraInit);
// 设定必要的 TimerA 结构体参数
stcTmraInit.sw_count.u8CountMode = TMRA_MD_SAWTOOTH; // 锯齿波
stcTmraInit.u8CountReload = TMRA_CNT_RELOAD_ENABLE; // 自动重新装填值
stcTmraInit.u8CountSrc = TMRA_CNT_SRC_SW; // 时钟源 PCLK
stcTmraInit.sw_count.u8CountDir = TMRA_DIR_UP; // 向上计数模式
stcTmraInit.u32PeriodValue = 10000; // 周期
2
3
4
5
6
7
8
9
初始化配置:
(void)TMRA_Init(CM_TMRA_5, &stcTmraInit);
1.5 配置PWM
配置好TIMERA参数之后,需要配置PWM输出结构体。
u32CompareValue
:指定 TimerA 通道的比较值。这个值确定了 PWM 的占空比,通常是一个介于 0 和计数器最大值之间的值。在 32 位 TimerA 单元中,可以是介于 0 和 0xFFFFFFFF 之间的任意值;在 16 位 TimerA 单元中,可以是介于 0 和 0xFFFF 之间的任意值。u16StartPolarity
:指定当计数器开始计数时的极性。这个参数用于确定计数器启动时输出的电平极性。可以是 TMRA_PWM_LOW(低电平有效)或者 TMRA_PWM_HIGH(高电平有效)。u16StopPolarity
:指定当计数器停止计数时的极性。这个参数用于确定计数器停止时输出的电平极性。u16CompareMatchPolarity
:指定当计数器匹配比较寄存器时的极性。u16PeriodMatchPolarity
:指定当计数器匹配周期寄存器时的极性。
结构体定义如下:
stc_tmra_pwm_init_t stcPwmInit;
结构体配置如下:
结构体默认配置如上图
// 使用默认参数初始化 PWM 结构体
(void)TMRA_PWM_StructInit(&stcPwmInit);
// 设置占空比对应的比较值
// 占空比的百分比为 stcPwmInit.u32CompareValue / stcTmraInit.u32PeriodValue * 100%
stcPwmInit.u32CompareValue = 2000;
2
3
4
5
6
初始化配置:
// 初始化 TimerA_5 CH1 PWM
(void)TMRA_PWM_Init(CM_TMRA_5, TMRA_CH1, &stcPwmInit);
2
1.6 开启通道缓存
HC32F4A0芯片中,有一种PWM通道缓存功能,TimerA 的通道1和通道2之间的缓存关系是通过缓存控制寄存器(BCONRm)来实现的。具体来说,TimerA 共有四个比较基准寄存器(CMPARn),分为两组,每组包含两个寄存器。这些寄存器可以实现成对的缓存功能,即第二个寄存器作为第一个寄存器的缓存基准值。其中,第一个组是CMPAR1和CMPAR2,第二个组是CMPAR3和CMPAR4。
当缓存控制寄存器(BCONRm)的BEN位被置位时,缓存功能生效,即第二个寄存器的值会随着第一个寄存器的变化而变化。
在TimerA中,奇数通道和偶数通道之间的缓存关系是这样的:
偶数通道的比较基准寄存器(CMPARn)可以作为奇数通道相邻的比较基准寄存器的缓存基准值。
举个例子:
假设有通道1和通道2,其中通道2是偶数通道。如果通道2的比较基准寄存器(CMPAR2)作为通道1比较基准寄存器(CMPAR1)的缓存基准值,那么通道1的输出会跟随通道2的设置而变化。通道1的设置会实时地从通道2的缓存值中获取,从而实现两个通道之间的同步控制。
因此,当你控制通道2的PWM占空比时,通道1的PWM占空比会随之改变。
WARNING
📌 再举一个生活的小例子:
想象你在家里有两个灯泡,一个是台灯(通道1),另一个是房间的主灯(通道2)。你使用一个遥控器来控制这两个灯泡的亮度。遥控器上有两个调光按钮,分别对应台灯和主灯。
现在,你按下了遥控器上的台灯调光按钮(控制通道1),将台灯的亮度调高。这时,房间的主灯(通道2)也会跟着变亮。为什么会这样呢?
原因是,家里使用了智能调光系统,台灯和主灯之间有一种特殊的连接。当你调节台灯的亮度时,这个系统会自动将主灯的亮度同步调整,使得整个房间的亮度保持一致。这就好像 TimerA 中的通道1和通道2之间的缓存关系一样,通道1的亮度变化会影响到通道2,实现了整体亮度的同步调节。
设定缓存基准值功能开启:
//配置TimerA_5 CH1 PWM 占空比缓存
TMRA_SetCompareBufCond(CM_TMRA_5, TMRA_CH1, TMRA_BUF_TRANS_COND_VALLEY);
//使能TimerA_5 CH1 PWM 占空比缓存
TMRA_CompareBufCmd(CM_TMRA_5, TMRA_CH1, ENABLE);
2
3
4
1.7 使能TIMER和PWM
我们使用初始化函数:
// PWM 输出使能
TMRA_PWM_OutputCmd(CM_TMRA_5, TMRA_CH1, ENABLE);
// TimerA_5 开始工作
TMRA_Start(CM_TMRA_5);
2
3
4
5
1.8 调整定时器比较值
void TMRA_SetCompareValue(CM_TMRA_TypeDef *TMRAx, uint32_t u32Ch, uint32_t u32Value) 这个函数用于设置 TimerA 的比较值,即设置 PWM 波形的比较值,用来控制 PWM 的占空比。让我解释一下这个函数的参数和功能:
TMRAx
:指向 TimerA 实例寄存器基址的指针。这个参数用于指定要设置比较值的 TimerA 实例。u32Ch
:TimerA 通道。指定要设置比较值的 TimerA 通道。TimerA 可能有多个通道,每个通道可以独立设置比较值。u32Value
:要设置的比较值。这个值确定了 PWM 波形的高电平持续时间,即在一个周期内的高电平持续时间。 根据函数说明,u32Value
的取值范围取决于 TimerA 实例的位数:- 如果 TimerA 实例是 32 位的,则
u32Value
可以是 0 到 0xFFFFFFFF 之间的任意值。 - 如果 TimerA 实例是 16 位的,则
u32Value
可以是 0 到 0xFFFF 之间的任意值。
通过调用这个函数,并传入相应的参数,你可以设置 TimerA 的比较值,从而控制 PWM 波形的占空比。
在PWM(脉冲宽度调制)应用中,可以通过改变Compare2的值来调整PWM信号的占空比。例如,增加Compare2的值会增加PWM高电平的持续时间,从而增加占空比。
占空比的百分比可以通过以下公式计算:
- 比较值(Compare Value)是指 PWM 波形的高电平持续时间,即在一个周期内的高电平持续时间。
- 周期值(Period Value)是指 PWM 波形的一个完整周期的总时间,包括高电平和低电平的持续时间。 在之前的代码中,stcPwmInit.u32CompareValue 表示比较值,而 stcTmraInit.u32PeriodValue 表示周期值。因此,你可以使用上述公式来计算占空比的百分比。
2. 呼吸灯函数
要实现一个呼吸灯的效果,首先我们来看呼吸灯产生的原理。呼吸灯产生的原理就是LED灯逐渐变亮再逐渐变暗,然后一直循环下去。控制LED灯的亮暗是通过改变PWM的占空比,占空比越大,LED灯越亮,占空比越小,LED灯越暗。所以,我们只需要调节PWM的占空比就可以实现呼吸灯的效果。设置PWM的占空比的函数在之前介绍过,编写呼吸灯代码如下:
/******************************************************************
* 函 数 名 称:pwm_breathing_lamp
* 函 数 说 明:呼吸灯函数,每调用一次就会呼吸一次
* 函 数 形 参:
* 函 数 返 回:无
* 作 者:LCKFB-LP
* 备 注:
******************************************************************/
void pwm_breathing_lamp(void)
{
static uint32_t brightness;
uint32_t step = 10; // 亮度改变的步长
uint32_t delayTime = 1; // 延时时间
TMRA_SetCompareValue(CM_TMRA_5, TMRA_CH2, 0xFFFF);
// 逐渐增加亮度
for(brightness = 1000; brightness < 9000; brightness += step)
{
TMRA_SetCompareValue(CM_TMRA_5, TMRA_CH2, brightness);
printf("brightness UP : %d\r\n",brightness);
delay_ms(delayTime);
}
delay_ms(50);
// 逐渐减少亮度
for(brightness = 9000; brightness > 1000; brightness -= step)
{
TMRA_SetCompareValue(CM_TMRA_5, TMRA_CH2, brightness);
printf("brightness D : %d\r\n",brightness);
delay_ms(delayTime);
}
delay_ms(50);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 初始化亮度和步长 :亮度
brightness
从0开始,表示灯光最暗。步长step
设置为10,表示每次亮度变化的量。 - 逐渐增加亮度 :通过一个for循环,从0逐渐增加亮度到1000(假设这是最大亮度值)。在每次循环中,使用
TMRA_SetCompareValue(CM_TMRA_5, TMRA_CH1, brightness)
来设置PWM的占空比,从而调整亮度。每次亮度调整后,通过delay_ms(delayTime)
函数暂停10毫秒,以便观察到亮度的逐渐变化。 - 逐渐减少亮度 :亮度达到最大值后,通过另一个for循环逐渐减少亮度回到最暗。逻辑与增加亮度时相同,只是这次是减少亮度值。
3. 实验现象
烧写我们的代码之后,会观察到LED灯由暗变亮,继而由亮变暗的效果。