第4章 软件PWM模拟:IO口模拟PWM原理、定时器中断实现、软件PWM精度分析
各位同学,咱们今天聊点硬核的——软件PWM。说实话,很多新手一上来就怼硬件PWM,觉得软件模拟是“野路子”。但我得说,在实际项目中,软件PWM往往能救你于水火之中。我自己就遇到过MCU硬件PWM通道不够用的情况,那时候真是抓耳挠腮,最后靠软件模拟硬是撑了下来。
4.1 IO口模拟PWM的原理
先说说最基础的东西。PWM的本质是什么?说白了就是让一个IO口在高低电平之间来回切换,通过控制高电平持续的时间(占空比)来模拟不同的电压值。
你想想看,一个LED灯,如果给它1ms的高电平、9ms的低电平,它看起来就是10%的亮度。如果反过来,9ms高、1ms低,那就是90%的亮度。这就是人眼的视觉暂留效应在帮我们“作弊”。
IO口模拟PWM的核心就两个参数:
- 周期(Period):一个完整的PWM波形的时间长度
- 占空比(Duty Cycle):高电平时间占整个周期的比例
举个例子,假设周期是10ms,占空比30%,那高电平就是3ms,低电平就是7ms。代码实现起来其实很直接:
// 伪代码示例
while(1) {
GPIO_SetHigh(); // 拉高IO
delay_us(3000); // 保持3ms
GPIO_SetLow(); // 拉低IO
delay_us(7000); // 保持7ms
}
嗯,这里要注意——这种纯延时的方式有个致命问题:CPU被完全占死了。你啥也干不了,只能在那傻等。所以实际项目中,我们很少这么干。
4.2 定时器中断实现——这才是正经做法
我个人习惯用定时器中断来做软件PWM。为什么?因为中断是“不打扰”的。CPU该干嘛干嘛,到了时间点,中断来了,我切换一下IO状态,然后继续干我的活。
实现思路其实很简单:
- 设置一个定时器,让它以固定的频率产生中断
- 在中断服务函数里,维护一个计数器
- 计数器到了某个阈值,就翻转IO口
我给大家画个图(用文字描述):
- 定时器每100us中断一次
- 一个PWM周期需要100次中断,也就是10ms
- 如果占空比是30%,那前30次中断IO为高,后70次为低
代码实现大概是这样的:
// 全局变量
volatile uint16_t pwm_counter = 0;
volatile uint16_t pwm_duty = 30; // 占空比 0-100
// 定时器中断服务函数(假设每100us触发一次)
void TIMER_IRQHandler(void) {
pwm_counter++;
if(pwm_counter <= pwm_duty) {
GPIO_SetHigh(); // 占空比内,输出高
} else {
GPIO_SetLow(); // 占空比外,输出低
}
if(pwm_counter >= 100) { // 一个周期结束
pwm_counter = 0;
}
}
你看,这样CPU就解放了。主循环里可以跑别的逻辑,中断里只做最轻量的事情——比较和赋值。
4.3 软件PWM精度分析——别被数字骗了
好,现在咱们来聊聊精度。很多同学看到“软件PWM”就觉得精度不行。其实不然,精度取决于你的定时器分辨率和中断频率。
咱们算一笔账:
- 假设MCU主频是72MHz
- 定时器预分频后,得到1MHz的计数频率(每1us计一次)
- 如果PWM周期是10ms(100Hz),那一个周期就是10000个计数
- 理论上占空比分辨率可以达到1/10000,也就是0.01%
听起来很牛对吧?但别高兴太早。实际中你会遇到几个坑:
| 影响因素 | 影响程度 | 说明 |
|---|---|---|
| 中断响应延迟 | 中等 | 中断嵌套、其他中断优先级更高时,会延迟PWM切换 |
| 定时器分辨率 | 高 | 决定了你能达到的最小步进 |
| CPU负载 | 高 | 中断频率越高,CPU被占用的时间越多 |
| 代码执行时间 | 低 | 中断服务函数里的指令数直接影响最小周期 |
我曾经在一个项目中,把PWM频率设到了20kHz,结果发现LED亮度不均匀,有肉眼可见的闪烁。查了半天,原来是中断响应延迟导致的。因为那个MCU同时还在处理USB通信,USB中断优先级更高,时不时就把PWM中断给挤掉了。
4.4 避坑指南——我踩过的那些雷
做软件PWM这些年,我踩过的坑真不少。给大家列几个典型的:
- 中断频率过高导致系统卡死:我曾经把PWM中断频率设到100kHz,结果CPU99%的时间都在处理中断,主循环根本跑不动。后来降到1kHz,一切正常。
- 占空比突变导致LED闪烁:如果你在中断里直接修改占空比变量,可能会在周期中间切换,导致一个不完整的脉冲。解决方案是加一个“影子寄存器”,在周期结束时刻才更新。
- 多路PWM相位不同步:多路软件PWM如果各自独立计数,它们的相位会随机漂移。我习惯用一个全局计数器,所有通道共用,这样相位就一致了。
4.5 实战建议——什么时候用软件PWM
说了这么多,总结一下我的经验:
- 通道数多但精度要求不高:比如你要驱动16路LED做氛围灯,硬件PWM只有4路,那就用软件模拟,每路8位分辨率足够了
- 频率要求不高:LED调光一般100Hz-1kHz就够,这个频率范围内软件PWM完全胜任
- 临时调试用:我经常在调试阶段先用软件PWM验证算法,等稳定了再切换到硬件PWM
- 成本敏感项目:有些低端MCU根本没有硬件PWM,软件模拟是唯一选择
嗯,今天就聊到这儿。下一章咱们会讲硬件PWM的实现,到时候你会看到,硬件方案虽然好,但软件方案也有它不可替代的灵活性。记住一句话:没有最好的方案,只有最合适的方案。