C语言实现呼吸灯(HAL库)

1. 呼吸灯原理

呼吸灯的实现可以通过控制灯的亮度连续变化,当变化的频率大于24帧时,肉眼看上去就会逐渐变暗,逐渐变亮。

2. PWM控制亮度

PWM通过设置亮度在一段时间内的占空比,亮的百分比多,人眼看到的就亮,反之就是暗。
关于PWM的块不打算展开说,这里针对呼吸灯的PWM详细说明。

/*
 * 描述  :呼吸灯PWM初始化
 * 参数  :
 *        无
 * 返回  :
 *        无
 */
void bspBreathLedTIMInit(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  g_breathled_tim_handle.Instance = TIM2;
  g_breathled_tim_handle.Init.Prescaler = 83;
  g_breathled_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  g_breathled_tim_handle.Init.Period = (BREATHLED_PWM_VALUE - 1);
  g_breathled_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  g_breathled_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&g_breathled_tim_handle) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&g_breathled_tim_handle, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&g_breathled_tim_handle) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&g_breathled_tim_handle, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&g_breathled_tim_handle, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }

  GPIO_InitTypeDef GPIO_InitStruct;
  BREATHLED_GPIO_CLK_ENABLE();
  /* TIM2 GPIO Configuration
   * PA3     ------> TIM2_CH4
   */
  GPIO_InitStruct.Pin = BREATHLED_GPIO_GREEN_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
  HAL_GPIO_Init(BREATHLED_GPIO_TYPE, &GPIO_InitStruct);

  HAL_TIM_Base_Start_IT(&g_breathled_tim_handle);
}

我这里使用了PA3引脚,定时器2做实验。时钟频率是84MHz,Prescaler设置(84 - 1) = 83,Period这里设置是(10000 - 1) = 9999,所以定时器是 (83 + 1) * (9999 + 1) / 84000000 = 0.01s = 10ms一个周期。
这个频率各位可以根据实际情况去计算,尽量设置成看上去平滑,而且对系统也不会造成产生过多中断。

/*
 * 描述  :定时器底层回调初始化
 * 参数  :
 *        无
 * 返回  :
 *        无
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim_base)
{
  if (htim_base->Instance == TIM2)
  {
    __HAL_RCC_TIM2_CLK_ENABLE();

    HAL_NVIC_SetPriority(TIM2_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
  }
}

/*
 * 描述  :定时器中断处理函数
 * 参数  :
 *        无
 * 返回  :
 *        无
 */
void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&g_breathled_tim_handle);	
  HAL_TIM_PeriodElapsedCallback(&g_breathled_tim_handle);	
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM11) {
    HAL_IncTick();
  }
  //呼吸灯
  else if (htim->Instance == TIM2)
  {
    //注意由于产生中断过快,反转实现不了
    //LED电平反转
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_3);
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

上面代码是中断处理函数,对呼吸灯的引脚电平反转。是通过设置占空比的参数,定时器自动触发中断后反转电平。

3. 呼吸灯亮度曲线

//PWM设置的Period值
#define BREATHLED_PWM_VALUE                10000
//呼吸灯x轴变暗或变亮步数
#define BREATHLED_X_DIVIDE                 100
//呼吸灯x轴总步数
#define BREATHLED_X_TOTAL                 (BREATHLED_X_DIVIDE * 2)
//呼吸灯y轴比例
#define BREATHLED_Y_RATIO                 (BREATHLED_PWM_VALUE / BREATHLED_X_DIVIDE)
//亮度变化频率,也就是帧数,单位毫秒
#define BREATHLED_FRAMES_MS											10

一些宏定义,BREATHLED_PWM_VALUE值是上面PWM设置的Period值。
BREATHLED_X_DIVIDE是x轴,变暗或变亮时,x轴的步数。
BREATHLED_X_TOTAL是x轴变暗加上变亮一个周期的总步数。
BREATHLED_Y_RATIO是y轴比例。
BREATHLED_FRAMES_MS亮度变化频率,注意这个值必须大于24帧,也就是小于40毫秒的时间,为了更加平滑,可以适量提高帧数,这样看起来呼吸灯更加流畅。

3.1 线性折线

在这里插入图片描述

y = 10000时代表占空比100%,x轴为时间。

/*
 * 描述  :呼吸灯函数,由x轴得出y轴值
 * 参数  :
 *        [in]  x     x轴,时间
 * 返回  :
 *        y轴值,PWM值
 */
static unsigned int breathLedCurve(unsigned int x)
{
  unsigned int y;
  if (x < BREATHLED_X_DIVIDE)
  {
    y = BREATHLED_Y_RATIO * x;
  }
  else
  {
    y = -BREATHLED_Y_RATIO * (x - BREATHLED_X_TOTAL);
  }
  return y;
}

/*
 * 描述  :呼吸灯过程
 * 参数  :
 *        无
 * 返回  :
 *        无
 */
void breathLedProgress(void)
{
  static unsigned int time_x = 0;
  unsigned int pwm_value_y = 0;

  while (1)
  {
    time_x = (time_x + 1) % BREATHLED_X_TOTAL;
    pwm_value_y = breathLedCurve(time_x);

    //修改占空比
    __HAL_TIM_SetCompare(&g_breathled_tim_handle, TIM_CHANNEL_4, pwm_value_y);
			
     sysDelay(BREATHLED_FRAMES_MS);

    if (time_x % BREATHLED_X_DIVIDE == 0)
    {
      sysDelay(500);
    }
  }
}

实现方式可以参考上面的,但是人眼感受的却不是线性的,是由于在灯光微亮区,很小的光通量改变也让人眼感到光通量变化很大,而在光通量比较大的区域,很大的光通量跳跃,人眼感觉到的光通量变化不大,简单理解为,人眼对亮度暗的比较敏感,而对亮度量的不敏感。
所以你尝试后发现,当由亮变暗时感觉时间长,由暗变亮时时间短,会有突然变亮的感觉。这个曲线是有缺陷的。

3.2 抛物线曲线

在这里插入图片描述
由上图可知,当亮度由暗变亮时,程序的呼吸灯曲线需要如同图3。
为了方便我这里使用一元二次曲线,有条件的可以使用对数函数,但是效果或许看起来会差不多。但是对数函数计算量会比一元二次方程大的多,所以衡量之下,选择通过一元二次曲线来实现。
在这里插入图片描述
通过这个曲线,你可能感觉到和线性折线差不多的效果。所以还需要降低由暗变亮时的亮度变化频率,即可让人眼看上去平滑。

3.3 伽马曲线

人眼的特征曲线大致为对数。伽马校正的目的是用来对人类视觉的特性进行补偿。在实践中,这意味着我们必须在亮度上呈现出来的现象满足我们的眼睛,以便我们能够感觉到这是亮度是线性增加的。更科学地说,这意味着我们必须通过将眼睛的对数特征曲线与指数特征曲线连接起来,实现感觉上的线性亮度变化。
关于LED伽马曲线
有兴趣的可以点击这链接研究。
我这里说明一下代码,我选择的使用了PWM值为1024,分成64步,所以PWM的Period值10000改成1024。一开始我使用了65536的Period值,但是实际上因为中断频率太低,导致LED灯会闪烁的问题。

static const unsigned short breath_led_pwmtable[BREATH_LED_X_TOTAL] =
{
  0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10,
  11, 12, 13, 15, 17, 19, 21, 23, 26, 29, 32, 36, 40, 44, 49, 55,
  61, 68, 76, 85, 94, 105, 117, 131, 146, 162, 181, 202, 225, 250,
  279, 311, 346, 386, 430, 479, 534, 595, 663, 739, 824, 918, 1023
};

PWM值是使用了公式计算出来,为了程序能够减少计算量,我们把计算出来的值用数组来存储。公式想要了解的也可以点击上面的连接,里面有说明。我们只需要在呼吸灯过程遍历这个数组,从0开始到63,然后63又到0,重新设置PWM的占空比,即可实现呼吸灯效果。
想要完整代码的也可以移步呼吸灯伽马曲线代码下载。

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现呼吸灯效果,可以使用PWM输出控制LED的亮度。具体实现步骤如下: 1. 初始化TIM定时器和GPIO引脚,将GPIO引脚配置为PWM输出模式。 2. 配置TIM定时器的自动重载寄存器ARR和占空比寄存器CCR,使得PWM输出的频率和占空比符合呼吸灯效果的要求。 3. 启动TIM定时器,开始PWM输出。 下面是一个简单的示例代码: ```c #include "stm32f4xx_hal.h" TIM_HandleTypeDef htim; TIM_OC_InitTypeDef sConfig; void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_TIM2_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF1_TIM2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); htim.Instance = TIM2; htim.Init.Prescaler = 0; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = 1000; htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim); sConfig.OCMode = TIM_OCMODE_PWM1; sConfig.Pulse = 500; sConfig.OCPolarity = TIM_OCPOLARITY_HIGH; sConfig.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1); } int main(void) { HAL_Init(); PWM_Init(); while (1) { for (int i = 0; i <= 1000; i++) { __HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, i); HAL_Delay(5); } for (int i = 1000; i >= 0; i--) { __HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, i); HAL_Delay(5); } } } ``` 在上面的代码中,TIM2的时钟频率为84MHz,ARR设置为1000,占空比寄存器CCR的值从0到1000递增和递减,每次递增或递减的时间间隔为5ms,这样就可以实现呼吸灯效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值