通过PWM波实现LED呼吸灯(本文硬件环境基于板载STM32F303RE的Nucleo开发板,程序使用HAL库进行开发)

原理

  1. 对于LED,流过它的电流越大(加在其两端的电压越大),其亮度越高,反之亮度越低
  2. 对于方脉冲波,其每个周期的电压有效值公式如下:

$$ 电压有效值=\sqrt {t \over T}U_m \quad (U_m为电压峰值、t为U_m持续的周期、T为该方脉冲波的周期) $$

由以上两点可得出:

通过改变方脉冲波的占空比来改变方脉冲波在某个周期的电压有效值从而改变LED的亮度以实现呼吸灯的效果

环境

硬件环境

  • 板载STM32F303RE的Nucleo开发板
  • 蓝色LED灯珠,正极接开发板的PA0引脚,负极接地

软件环境

  • 使用STM32CubeMX完成外设的配置以及工程代码的初始化
  • 开发环境为VScode + PlatformIO
  • 使用STM32Cube框架开发

前置知识

PWM(脉冲宽度调制)

摘自百度百科

脉宽调制(PWM)基本原理:控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率

简而言之,PWM就是通过改变脉冲的工作周期来近似生成模拟信号的一种技术

相关链接:

STM32——PWM基本知识及配置过程

百度百科:PWM

代码部分

除去使用STM32CubeMX生成的代码外,需要自己实现的部分:

  1. main函数内启用定时器输出PWM波,并开启定时器中断
  2. 在定时器中断回调函数内改变PWM波的输出比较值

第一部分

几个简单的HAL库函数

  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

第二部分

在本文中有两个重要的周期,一个是PWM波的周期,即在STM32CubeMX配置的定时器的溢出值,一个是呼吸灯的“呼吸”周期,即呼吸灯由亮到暗再到亮的的时间。

根据上述的原理可得在PWM为输出为低电平有效时,呼吸灯的亮度与PWM的输出比较值有如下关系:

PWM输出比较值大,呼吸灯亮度高

PWM输出比较值小,呼吸灯亮度低

那么在定时器的中断回调函数中应该将PWM的输出比较值增大,在半个呼吸灯周期时将PWM的输出比较值减小

宏定义

定时器溢出值与半个呼吸灯周期

/* USER CODE BEGIN PD */
#define LED_PWM_MAX 1000
#define LED_PWM_TIME 3000
/* USER CODE END PD */

定时器回调

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim == (&htim2))
  {
    if (LED_TimC == LED_PWM_TIME)
    {
      LED_TimC = LED_PWM_TIME;
    }
    if ((++LED_TimC) < LED_PWM_TIME * 2)
    {
      __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ((LED_TimC > LED_PWM_TIME) ? LED_PWM_TIME * 2 - LED_TimC : LED_TimC) / (LED_PWM_TIME / LED_PWM_MAX));
    }
    else
    {
      LED_TimC = 0;
    }
  }
}
/* USER CODE END 4 */

进阶部分

算法介绍

通过引入三次贝塞尔曲线,让呼吸灯的亮度变化更加平滑

三次贝塞尔曲线公式:

$$ B(t)=P_0(1-t)^3+3P_1t(1-t)^2+3P_2t^2(1-t)+P_3t^3,t\in[0,1] 注:P_0、P_1、P_2、P_3均为坐标 $$

我们需要对该公式进行处理优化以便于其更好的在STM32上运行

$$ 对于时间函数,其P_0取(0,0),P_3值取(1,1)则有B(t)=3P_1t(1-t)^2+3P_2t^2(1-t)+t^3 $$

我们使用CSS过渡效果中的ease-in-out效果参数

$$ P_1值取(0.5,0)P_2值取(0.5,1)将公式再次处理优化得到B(t)=>\begin{cases}x=t^3+1.5t(1-t)^2+1.5t^2(1-t)\\y=t^3+3t(1-t)^2\end{cases} $$

对该公式再次处理得到

$$ B(t)=>\begin{cases}x = -1t^3+3t^2-3t+1\\y=2t^3-3t^2+1\end{cases} $$

当给定x时对于上述三次方程可求出一个实数根t,将实数根t带入y的相关方程即可解得y

算法实现

新增红色LED,正极接开发板的PA1,负极接地

插入PWM输出代码

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);

引入优化后的三次贝塞尔时间函数的声明和定义

/* USER CODE BEGIN PFP */
float s1v3rd(float a, float b, float c, float d);
float bezier_conv(float x);
/* USER CODE END PFP */
/* USER CODE BEGIN 4 */
// 根据给定系数求解三次方程
float s1v3rd(float a, float b, float c, float d)
{
    float p, q, qp, s1, s2, s3;

    if (a)
    {
        p = (3 * a * c - b * b) / (3 * a * a);
        q = (27 * a * a * d - 9 * a * b * c + 2 * b * b * b) / (27 * a * a * a);
        s1 = -b / (3 * a);
        //------------------------------
        qp = q * q / 4 + p * p * p / 27;
        qp = (qp < 0) ? -qp : qp;
        
        s2 = (-q / 2) + sqrt(qp);
        s2 = (s2 > 0) ? cbrt(s2) : -cbrt(-s2);

        s3 = (-q / 2) - sqrt(qp);
        s3 = (s3 > 0) ? cbrt(s3) : -cbrt(-s3);
    }
    else
    {
        return 0;
    }

    return s1 + s2 + s3;
}

float bezier_conv(float x)
{
  float t=s1v3rd(A3,A2,A1,A0-x);
  return B3 * (t * t * t) + B2 * (t * t) + B1 * t + B0;
}
/* USER CODE END 4 */

在中断回调函数内插入设置经过转换的定时器输出比较值的函数

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, bezier_conv(((LED_TimC > LED_PWM_TIME) ? LED_PWM_TIME * 2 - LED_TimC : LED_TimC) / LED_PWM_TIME) * LED_PWM_MAX);

参考链接

cubic-bezier三次贝塞尔时间函数

cubic-bezier

Last modification:December 19th, 2020 at 01:39 pm