2. PWM信号生成方法:硬件定时器与软件模拟
生成PWM信号,说白了就两条路:用硬件定时器,或者用软件模拟。两条路我都走过,踩过不少坑。今天跟你聊聊我的体会。
2.1 硬件定时器生成PWM
硬件定时器是MCU内部专门干这活的模块。你配置好周期和占空比,它自己就干活了,CPU几乎不用管。
我最早用STM32的TIM做PWM驱动舵机。配置起来其实不复杂,核心就三步:
- 设置定时器的时钟源和预分频,决定计数频率
- 配置自动重装载值(ARR),决定PWM周期
- 设置比较值(CCR),决定占空比
下面是一个典型的配置代码,我用的是STM32 HAL库:
// 假设定时器时钟为72MHz,预分频72-1,计数频率1MHz
// 目标PWM频率 = 1MHz / (ARR+1)
// 目标占空比 = CCR / (ARR+1)
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72分频,计数频率1MHz
htim2.Init.Period = 999; // ARR=999,PWM频率=1kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // CCR=500,占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
配置完,TIM2_CH1引脚上就有稳定的1kHz、50%占空比的PWM信号了。CPU可以去做别的事,比如处理传感器数据、跑通信协议。
硬件PWM的核心优势:零CPU开销、精度高、抖动小。适合对时序要求严苛的场景,比如电机控制、LED调光、开关电源。
2.2 软件模拟PWM
有时候MCU的硬件定时器不够用,或者引脚被占用了。这时候就得用软件模拟。
软件模拟的原理很简单:用通用定时器产生固定频率的中断,在中断服务函数里翻转GPIO电平。我举个最基础的例子:
// 使用SysTick定时器,每100us中断一次
// 目标PWM频率 = 1 / (100us × 周期计数值)
// 假设周期计数值=100,则PWM频率=100Hz
volatile uint16_t pwm_counter = 0;
volatile uint16_t pwm_period = 100; // 周期
volatile uint16_t pwm_duty = 50; // 占空比
void SysTick_Handler(void) {
pwm_counter++;
if (pwm_counter >= pwm_period) {
pwm_counter = 0;
}
if (pwm_counter < pwm_duty) {
GPIO_SetPin(HIGH); // 输出高电平
} else {
GPIO_SetPin(LOW); // 输出低电平
}
}
这段代码能工作,但有个致命问题:中断里不能做复杂运算,否则会影响PWM的精度。我曾经在一个项目里用软件模拟同时控制4路PWM,结果中断服务函数越来越长,最后PWM波形抖得没法看。
注意:软件模拟PWM的精度受限于中断响应时间和CPU负载。中断里别做浮点运算、别调用延时函数、别做阻塞式操作。否则波形会变形,电机可能会发出刺耳的噪音。
2.3 两种方法的对比
我整理了一个对比表,方便你选型时参考:
| 对比项 | 硬件定时器 | 软件模拟 |
|---|---|---|
| CPU占用 | 几乎为零 | 高(每个周期都要进中断) |
| 精度 | 高(ns级) | 低(受中断延迟影响) |
| 抖动 | 极小 | 较大(中断嵌套时更明显) |
| 通道数 | 受硬件限制(通常2~4路) | 理论上无限(但CPU扛不住) |
| 频率范围 | 宽(从几Hz到MHz) | 窄(通常几百Hz以内) |
| 实现难度 | 中等(需配置寄存器) | 低(几行代码就能跑) |
| 适用场景 | 电机控制、电源、精密调光 | LED闪烁、蜂鸣器、简单调速 |
我的建议:能用硬件就别用软件。如果硬件定时器不够用,可以考虑用DMA+定时器的方式模拟多路PWM,效果比纯中断好很多。这个后面章节会细讲。
2.4 混合方案:硬件定时器+软件扩展
有些场景下,硬件定时器数量有限,但你又需要多路PWM。这时候可以试试混合方案。
我记得有个项目需要控制6个舵机,但MCU只有4路硬件PWM。我的做法是:
- 4路关键舵机用硬件PWM,保证精度
- 2路非关键舵机用软件模拟,频率降低到50Hz(舵机标准频率)
这样既保证了核心功能,又节省了硬件资源。代码上只需要把软件模拟的中断优先级设低一些,避免影响硬件PWM的时序。
2.5 知识结构图
下面这张图总结了PWM生成的三种路径,以及各自的适用边界:
2.6 避坑指南
最后分享几个我踩过的坑:
- 中断优先级设置不当:软件模拟PWM的中断优先级如果设得太高,会阻塞其他关键中断(比如通信、ADC)。我曾经把PWM中断优先级设成最高,结果UART接收丢包严重。后来把PWM中断优先级降到中等,问题解决。
- 忘记考虑GPIO翻转速度:有些MCU的GPIO翻转速度有限,比如50MHz的芯片,GPIO翻转可能只有10MHz。如果你要生成1MHz以上的PWM,得确认GPIO能不能跟上。
- 硬件PWM的互补输出:做H桥驱动时,需要互补PWM带死区。硬件定时器通常支持这个功能,但软件模拟实现起来非常麻烦。我建议这种场景别省硬件资源。
一个小技巧:调试PWM时,先用示波器看波形,别光靠代码里打印占空比。波形能告诉你很多信息:上升沿斜率、抖动情况、死区时间是否足够。我每次调PWM,示波器都是第一个上场的工具。