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 基本配置流程

  1. 选择定时器:高级定时器(TIM1/TIM8)支持互补输出和死区,通用定时器(TIM2-TIM5)只支持单通道。
  2. 设置时钟源:内部时钟(CK_INT)还是外部时钟?电机控制一般用内部时钟。
  3. 配置时基:预分频器(PSC)+ 自动重装载值(ARR)决定PWM频率。
  4. 设置输出模式:PWM模式1还是模式2?这决定有效电平是高点还是低点。
  5. 配置比较值:CCR决定占空比。
  6. 使能输出:别忘了开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的完整链路画出来了,你看完应该能对整个体系有个全局认识。

PWM生成方式对比与配置体系 PWM生成方式 硬件PWM(推荐) 软件PWM(不推荐) 定时器配置(PSC+ARR+CCR) 死区时间设置(BDTR寄存器) 互补输出 + 刹车保护 定时器中断 + GPIO翻转 软件模拟死区(精度差) CPU占用高,抖动大 核心原则 电机控制 → 硬件PWM + 高级定时器 + 死区插入

3.5 实际项目中的配置建议

说了这么多理论,最后给几条实战建议:

  • 优先用高级定时器:别省那点资源,电机控制不是点LED。
  • 死区时间从保守开始:先设大一点(比如1μs),确认不烧管子再慢慢往下调。
  • 注意时钟树:定时器时钟可能和系统时钟不同步,配置前先看时钟树。
  • 用示波器验证:别信寄存器里的值,实际波形才是真理。

我的调试习惯:先用低频(1kHz)和大死区(2μs)验证基本功能,确认上下管不会直通后,再逐步提高频率、缩小死区。每一步都用示波器盯着波形看。

嗯,关于PWM生成方式,核心就这些。硬件PWM是正道,定时器配置是基本功,死区时间是保命符。这三样拿下了,电机控制的PWM部分你就已经入门了。