PWM驱动LED灯
之前是使用标准库函数配置引脚输出PWM控制呼吸灯,因为开发板上的蜂鸣器是有源的,所以这次还是用来确定LED灯,这次使用的是HAL库,用CubeMX软件初始化PWM功能
PWM输出原理
Period:周期,单位是秒
Duty:占空比
CubeMX配置
因为PB0引脚是定时器3的通道3,定时器3是通用定时器,也有PWM输出功能,所以在软件中对TIM3进行初始化
主要就是要设定定时器时钟的分频值PSC,自动重载值ARR,因为定时器时钟确定后,每计数一次的时间也定了,从0向上计数到ARR的值,就是一个PWM周期
在PB0引脚选择TIM3_CH3
开启通用定时器3的时钟源,选择内部时钟,通道3的PWM功能
配置时基单元和PWM输出通道
预分频系数PSC设置为71,也可以写为72000000/1000000-1,设置定时器的时钟为1MHz,因为72MHz的晶振振72次是1us(72分频),1/1us = 1/0.000001s = 1000000Hz = 1Mz
自动重载值ARR为999,也就是从0计数到999,每计数一次是1us,共1000次,所以1000 * 1us = 1ms,所以PWM信号的周期就是1ms,换算成频率就是1KHz
配置PWM输出通道时,Pulse设置的就是CCR的值,当CNT计数值小于CRR时,会输出一个有效电平,是高电平有效还是低电平有效要看CH Polarity(CH通道极性)选择,如果选择为高电平,则该有效电平就是高电平,如果选择低电平则该有效电平就是低电平;(一定要看你的硬件电路是高电平有效,还是低电平有效,这里我使用的小灯是低电平有效,但是我和UP配置的极性高电平,所以占空比是相反的,这点大家一定要注意)
Pulse的值也就是占空比的值,因为PWM周期已经在设置自动重载值ARR时确定了,为1000,所以Pulse设置为500的话,表示PWM信号的占空比就为50%,如果Pulse为250,则占空比就是25%
ARR的值控制的是PWM信号的周期,CCR的值控制的是PWM信号的占空比;ARR不变,改变CCR的值,则会改变占空比;CCR不变,改变ARR,则会改变PWM的周期
注意:在大多数情况下,选择开启CCR寄存器的预装载功能,让占空比的变化在下一个PWM信号周期才生效
打开定时器3中断和机械按键1的外部中断,定时器回调函数中可以改变定时器3输出PWM的频率,达到输出不同的LED频闪效果,机械按键可以开启和关闭LED灯。
代码1
实现功能:机械按键1可以打开或关闭LED灯,开发板上电后LED灯会发出不同频率的光
Led.c
主要是编写LED的开启和关闭函数
/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"
/* Private define-------------------------------------------------------------*/
/* Private variables----------------------------------------------------------*/
static void Led_ON(void);
static void Led_OFF(void);
/* Public variables-----------------------------------------------------------*/
Led_t Led =
{
OFF_Status,
Led_ON,
Led_OFF
};
/* Private function prototypes------------------------------------------------*/
/*
* @name Led_ON
* @brief 打开Led
* @param None
* @retval None
*/
static void Led_ON()
{
//只需开启PWM输出功能,即可让Led亮
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
Led.Status = ON_Status;
}
/*
* @name Led_OFF
* @brief 关闭Led
* @param None
* @retval None
*/
static void Led_OFF()
{
//关闭PWM输出,即关闭Led
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_1);
Led.Status = OFF_Status;
}
MyInit.c
要在初始化函数中打开LED,上电后才会亮
/*
* @name Peripheral_Set
* @brief 外设设置
* @param None
* @retval None
*/
static void Peripheral_Set()
{
Timer6.Timer6_Start_IT(); //启动定时器6
Led.ON(); //打开Led
}
CallBack.c
外部中断回调函数中,机械按键1按下翻转LED2灯电平,同时控制LED1的开关
定时器6中断回调函数中,用LED2显示定时器运行状态,改变定时器3ARR的值,不断改变PWM信号的频率
/*
* @name HAL_GPIO_EXTI_Callback
* @brief 外部中断回调函数
* @param GPIO_Pin:触发中断的GPIO引脚
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY1_Pin) //如果触摸按键1被按下
{
LED.LED_Fun(LED1,LED_Flip);
//控制Led开关
if(Led.Status == ON_Status)
{
Led.Led_OFF(); //关闭Led
}
else
{
Led.Led_ON(); //打开Led
}
}
}
/*
* @name HAL_TIM_PeriodElapsedCallback
* @brief 定时器中断回调函数
* @param *htim:处理定时器的结构体指针
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t Fre_Cnt = 0;
if(htim->Instance == htim6.Instance)
{
//定时器6每隔5ms进入一次中断,usMCU_Run_Timer就加1,当计时时间达到1s时,翻转LED灯的状态
if(++Timer6.usMCU_Run_Timer >= TIMER_1s)
{
Timer6.usMCU_Run_Timer = 0;
LED.LED_Fun(LED2,LED_Flip);
}
//控制PWM的频率长度与大小
if(Fre_Cnt++ >= 3) //0,1,2不执行
{
Fre_Cnt = 0;
//定时器1时钟:1MHz
//PWM周期:(1/1000000Hz) * ARR
//PWM频率:1/(1/1000000Hz)*ARR = 1000000/ARR
//ARR = 250,PWM周期为:250us = 0.25ms = 0.00025s,PWM频率为:1/0.00025s = 4000Hz = 4KHz
//ARR = 500,PWM周期为:500us = 0.5ms = 0.0005s,PWM频率为:1/0.0005s = 2000Hz = 2KHz
//ARR = 1000,PWM周期为:1000us = 1ms = 0.001s,PWM频率为:1/0.001s = 1000Hz = 1KHz
//ARR = 2000,PWM周期为:2000us = 2ms = 0.002s,PWM频率为:1/0.002s = 500Hz = 0.5KHz
/*定时器6每隔5ms执行一次回调函数,在Fre_Cnt为0,1,2时,不改变ARR的值,所以PWM周期不会变,维持15ms
当Fre_Cnt为3时,进行一次改变ARR值的操作,把ARR的值减少,当减到500时再让ARR的值为2000,
如此反复改变ARR的值,改变PWM的频率,但占空比始终是50%*/
TIM1->ARR -= 10;
if(TIM1->ARR < 500)
{
TIM1->ARR = 2000;
}
//设置占空比为50%
TIM1->CCR1 = TIM1->ARR/2;
}
}
}
(因为前面配置的代码是用来控制蜂鸣器的,所以对LED灯的闪烁来说,频率太快,我们人眼根本无法看出来,所以我们需要将周期变大,频率更小,才能看出来)
注意——HAL_Delay函数中断优先级的问题
在LED发出不同频率光的函数中可以看到,使用了HAL库的延时函数HAL_Delay(),而频率的函数又在外部中断的回调函数中被调用,同时在定时器回调函数也有调用HAL_Delay函数;
因为HAL_Delay延时函数是使用到了System tick time(系统滴答定时器),也是通过中断定时,所以这三个中断就要考虑中断优先级的关系了
因为外部中断或者定时器中断都是在中断处理过程中被HAL_Delay的中断打断的,说明HAL_Delay的中断优先级是比这两者高的,不然HAL_Delay的延时中断打断不了外部中断或者定时器中断,就没有延时的作用
打开CubeMX的NVIC配置界面可以看到,System tick timer的默认抢占优先级是0,自己配置的外部中断是1,定时器6中断是2,数字越小则优先级越大,所以HAL_Delay延时函数在外部中断或定时器中断中能起作用
拓展