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