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