在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能用來做什麼呢?這也是我個人的理解,應該是很不全面的。
- 控制風扇,燈等裝置的檔位:由於我們將方波一個週期分為n個Duty Circle(高電平),這樣就將GPIO的有效電壓輸出分為0...n(n+1)份,再通過一些放大電路就可以將風扇、燈等裝置分為n擋進行數字化控制了。呼吸燈也是這個原理,將燈分為n擋,然後在週期性的調整Duty Circle的值達到呼吸燈的效果。
- 用PWM波模擬數字化的0,1對燈帶等裝置進行控制,這樣做的優點是僅僅需要兩根線就可以控制燈帶裝置了。例如將PWM週期分為三份,1個Duty Circle表示二進位制的0,2個Duty Circle表示二進位制的1,這樣就可以傳送24bit的色彩值給燈帶控制晶片控制燈帶顏色了,也可以做跟複雜的事情了。
這裡開始介紹如何在STM32上實現呼吸燈效果,參考英文的教程https://breiteneder.me/?p=424&=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”
- Prescale -->就是對Timer的頻率進行降頻處理,在Clock Configuration中可以看到APB1, APB2的timer頻率值為170MHZ(每個板子可能不同),這裡我將Prescale的值設定為68,這樣我們我們得到頻率為170MHZ/68 = 2MHZ的頻率。
- Counter Period-> 就是上面說的高電平的份數(Duty Circle)設定為100,也就是將LED燈的亮度切割為0...100個值
- 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 */ }