首页 > 技术文章 > 在STM32上利用PWM原理实现呼吸灯效果

magicduan 2021-12-16 16:31 原文

在ST32项目中第一次接触到PWM这个概念,PWM是Plus width modulation的英文缩写,百度百科有详细介绍。

因为介绍的太详细了,对于做软件开发的人员来说看着还是有些晕乎,知道了一个大概。最后我简化理解为高中物理中的方波,

将一个方波周期分解问n份,1份代表一个高电平,这样我们就可以得到n+1个值,0个高电平,1个高电平,2个高电平,...,n个高电平。

不能将高电平理解为计算机软件中的1,低电平为0,如果按照这个理解n份代表的是2^n个数。作为软件开发人员比较容易理解为2^n. 

现在想想开始不太理解PWM的原因可能就是2^n的搞得鬼。

大概理解PWM概念后,就是关于频率和Duty Circle的概念。频率容易理解就是高中物理的平路,周期数/秒 。 T = 1/f. Duty Circle说的就是一个周期内高电平份数。

在嵌入式开发中PWM能用来做什么呢?这也是我个人的理解,应该是很不全面的。

  1. 控制风扇,灯等设备的档位:由于我们将方波一个周期分为n个Duty Circle(高电平),这样就将GPIO的有效电压输出分为0...n(n+1)份,再通过一些放大电路就可以将风扇、灯等设备分为n挡进行数字化控制了。呼吸灯也是这个原理,将灯分为n挡,然后在周期性的调整Duty Circle的值达到呼吸灯的效果。
  2. 用PWM波模拟数字化的0,1对灯带等设备进行控制,这样做的优点是仅仅需要两根线就可以控制灯带设备了。例如将PWM周期分为三份,1个Duty Circle表示二进制的0,2个Duty Circle表示二进制的1,这样就可以发送24bit的色彩值给灯带控制芯片控制灯带颜色了,也可以做跟复杂的事情了。

 

这里开始介绍如何在STM32上实现呼吸灯效果,参考英文的教程 https://breiteneder.me/?p=424&amp=1&lang=en

这个英文教程用的是Timer中断来控制LED灯值的变化,实现呼吸灯的效果。由于仅仅是呼吸灯效果,

不需要严格控制每个周期的Duty Circle值(上文的PWM第二种作用),我这里使用的freertos的task来控制Duty Circle的数值,相对简单一些。

我将代码放到Github上了,有兴趣的可以自取。https://github.com/magicduan/demo_pwm

开发板:STM32G431Rb 开发环境:STM32 Cube IDE 1.8

  • Step1:将开发板配置为freertos. 可以参考这篇英文文章.
  • Step2:STM32G431Rb板上的LED等对应的GPIO为PA5,将PA5管脚配置为TIM2_CH1

            

  • Step3:配置Timer2,将 Clock Source 设置为“Internal Clock”,Channel1 设置为“PWM Generation CH1”. 

**注意:板子的不同你能选择的TIMER和Channel都可能不同,上面给的英文的文章配置的就是TIM2_CH2,如果配置的是TIM2_CH2就需要将Channel 2设为PWM

  • Step4: 设置TIM2的周期,Duty Circle等参数。“Parameter Setting”
    1. Prescale -->就是对Timer的频率进行降频处理,在Clock Configuration中可以看到APB1, APB2的timer频率值为170MHZ(每个板子可能不同),这里我将Prescale的值设置为68,这样我们我们得到频率为170MHZ/68 = 2MHZ的频率。
    2. Counter Period-> 就是上面说的高电平的份数(Duty Circle)设置为100,也就是将LED灯的亮度切割为0...100个值
    3. PWM Generation Channel 1中"Fast Mode” 设置为Enable(为什么要设置为Enable,还没有去深究), Plus(32bits value)设置为100,这个值无所谓,就是初始的Duty Circle 值,0 ~ 100之间都可以。

         

         

 

  • Step5 配置完成,利用STM32 Cube IDE生成代码。
  • Step6 生成代码后,进行最后的编程处理
    • main.c 中main函数中启动PWM波 HAL_TIM_PWM_Start(&html2,TIM_CHANNEL_1)
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);

  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();
    • main.c中加入全局变量pwm_plus_value就是Duty Circle的值,pwm_dir表示呼吸灯值是变大还是变小,其实也可以不用全局变量的。
    • /* USER CODE BEGIN PFP */
      uint8_t pwm_plus_value = 50;
      uint8_t pwm_dir = 0; // 0 for UP, 1 for Down
      
      /* USER CODE END PFP */

      main.c中加入修改pwm_plus_value的处理函数,就是达到最大值100后,改为递减,达到最小值0时,变为递增。设置TIM2的PWM的Duty Circle值的寄存器

    • 我们这里用的是Channel1,设置的就是CCR1,前面英文的文章中用的是Channel 2,设置的是CCR2,以此类推。
    • /* USER CODE BEGIN 4 */
      void update_pwm_value()
      {
        if (pwm_dir == 0){
          pwm_plus_value++;
        }else{
          pwm_plus_value--;
        }
        if (pwm_plus_value >= 100){
          pwm_dir = 1;
        }else if (pwm_plus_value <= 0){
          pwm_dir =0;
        }
        htim2.Instance->CCR1 = (htim2.Init.Period*pwm_plus_value)/100u;
      
      }
      /* USER CODE END 4 */

      在defaultTask中周期调用update_pwm_value()函数就可以实现效果了

    • void StartDefaultTask(void *argument)
      {
        /* USER CODE BEGIN 5 */
        /* Infinite loop */
        for(;;)
        {
          update_pwm_value();
      //    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
          osDelay(5);
        }
        /* USER CODE END 5 */
      }

       

 

推荐阅读