3. PWM生成方式:硬件PWM vs 软件PWM、定时器配置、死区时间设置
说到PWM生成,很多刚入行的朋友第一反应就是——用定时器翻转IO嘛。没错,这确实是最直接的办法。但实际项目中,选硬件还是软件,差别大了去了。我见过不少工程师在软件PWM上栽跟头,也见过有人把硬件定时器用出花来。今天咱们就把这事彻底聊透。
3.1 硬件PWM vs 软件PWM:到底差在哪?
先给个结论:能用硬件就别用软件。这不是我偏激,是血的教训换来的。
| 对比项 | 硬件PWM | 软件PWM |
|---|---|---|
| 实现方式 | 定时器硬件自动输出波形 | CPU中断+GPIO翻转 |
| CPU占用 | 几乎为0 | 高(每个周期都要进中断) |
| 最高频率 | 可达MHz级别 | 受限于中断响应速度 |
| 抖动(Jitter) | 极小(ns级) | 大(μs级,受中断嵌套影响) |
| 多通道同步 | 天然同步 | 需要额外同步机制 |
| 死区插入 | 硬件自动完成 | 软件模拟,精度差 |
核心结论:电机控制这种对实时性要求极高的场景,硬件PWM是唯一选择。软件PWM只适合LED调光、简易风扇控制这类低频应用。
我早期做过一个项目,用STM32的软件PWM驱动直流电机,频率设到20kHz。结果电机一启动,主控就卡死——中断太频繁,连串口都来不及响应。后来换成硬件PWM,CPU占用直接从85%降到3%。你说这差距大不大?
3.2 定时器配置:从入门到精通
硬件PWM的核心就是定时器。不管你是用STM32、TI的C2000还是英飞凌的AURIX,配置思路都差不多。我拿最常用的STM32高级定时器举例。
3.2.1 基本配置流程
- 选择定时器:高级定时器(TIM1/TIM8)支持互补输出和死区,通用定时器(TIM2-TIM5)只支持单通道。
- 设置时钟源:内部时钟(CK_INT)还是外部时钟?电机控制一般用内部时钟。
- 配置时基:预分频器(PSC)+ 自动重装载值(ARR)决定PWM频率。
- 设置输出模式:PWM模式1还是模式2?这决定有效电平是高点还是低点。
- 配置比较值:CCR决定占空比。
- 使能输出:别忘了开MOE位(主输出使能)。
// STM32 TIM1 硬件PWM配置示例(HAL库)
void HAL_TIM_PWM_Config(TIM_HandleTypeDef *htim)
{
TIM_OC_InitTypeDef sConfigOC = {0};
// 配置PWM模式1:向上计数时,CNT < CCR时输出有效电平
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%(假设ARR=1000)
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
// 配置互补通道(用于H桥下管)
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(htim, &sConfigOC, TIM_CHANNEL_1);
}
我的习惯:初始化时把占空比设到50%,等电机启动后再动态调整。这样能避免上电瞬间的电流冲击。
3.2.2 频率与分辨率的选择
这里有个经典矛盾:频率越高,分辨率越低。为什么?
假设系统时钟72MHz,预分频器设为0(不分频),那么定时器计数频率就是72MHz。如果PWM频率设为20kHz,ARR = 72MHz / 20kHz = 3600。分辨率就是log2(3600) ≈ 11.8位。
如果PWM频率提高到100kHz,ARR = 720,分辨率降到9.5位。说白了,鱼和熊掌不可兼得。
| PWM频率 | ARR值(72MHz时钟) | 分辨率(位) | 适用场景 |
|---|---|---|---|
| 1kHz | 72000 | 16.1 | 伺服电机、精密控制 |
| 20kHz | 3600 | 11.8 | 通用电机控制 |
| 100kHz | 720 | 9.5 | 高速电机、低电感电机 |
注意:分辨率低于10位时,电机低速运行会出现明显的步进感。我曾经在无人机电调上踩过这个坑,100kHz下电机低速抖动得厉害,后来降到40kHz才解决。
3.3 死区时间设置:保命的关键
死区时间,说白了就是防止H桥上下管同时导通的那段空白期。上下管同时导通是什么后果?短路,烧管子。我见过不止一次,工程师忘了设死区,一上电MOS管直接冒烟。
3.3.1 死区时间怎么算?
死区时间取决于三个因素:
- MOS管关断延迟(t_off):数据手册上查,一般几十ns到几百ns。
- MOS管开通延迟(t_on):通常比关断快,但也要考虑。
- 驱动芯片延迟:隔离驱动芯片本身也有延迟。
经验公式:死区时间 ≥ t_off(max) - t_on(min) + 安全裕量
安全裕量我一般留50%~100%。比如MOS管关断延迟200ns,开通延迟100ns,那死区时间至少设200ns - 100ns + 100ns = 200ns。稳妥起见,我会设到300ns~500ns。
3.3.2 硬件死区 vs 软件死区
高级定时器(如STM32的TIM1/TIM8)自带死区插入功能,你只需要往寄存器里写一个值。软件死区?嗯,我劝你别碰。
// STM32 TIM1 死区配置示例
void HAL_TIM_DeadTime_Config(TIM_HandleTypeDef *htim)
{
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
// 死区时间 = DTG[7:0] × t_dts
// 假设 t_dts = 13.89ns(72MHz时钟不分频)
// DTG = 36 → 死区 ≈ 500ns
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1;
sBreakDeadTimeConfig.DeadTime = 36; // 500ns
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
HAL_TIMEx_ConfigBreakDeadTime(htim, &sBreakDeadTimeConfig);
}
我曾经犯过的错:死区时间设得太大(2μs),结果电机在高速运行时波形畸变严重,效率直接掉了15%。死区不是越大越好,够用就行。
3.4 一张图看懂PWM生成体系
下面这张图把硬件PWM和软件PWM的完整链路画出来了,你看完应该能对整个体系有个全局认识。
3.5 实际项目中的配置建议
说了这么多理论,最后给几条实战建议:
- 优先用高级定时器:别省那点资源,电机控制不是点LED。
- 死区时间从保守开始:先设大一点(比如1μs),确认不烧管子再慢慢往下调。
- 注意时钟树:定时器时钟可能和系统时钟不同步,配置前先看时钟树。
- 用示波器验证:别信寄存器里的值,实际波形才是真理。
我的调试习惯:先用低频(1kHz)和大死区(2μs)验证基本功能,确认上下管不会直通后,再逐步提高频率、缩小死区。每一步都用示波器盯着波形看。
嗯,关于PWM生成方式,核心就这些。硬件PWM是正道,定时器配置是基本功,死区时间是保命符。这三样拿下了,电机控制的PWM部分你就已经入门了。