1. 程式人生 > 其它 >在STM32上利用PWM原理實現呼吸燈效果

在STM32上利用PWM原理實現呼吸燈效果

在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 */
      }