4. PWM 生成与配置:高级定时器与通用定时器的 PWM 模式、死区时间设置、互补输出
好,咱们今天聊点硬核的。PWM 生成,说白了就是让定时器输出一串可调宽度的方波。这玩意儿在电机控制里有多重要?我这么说吧,没有 PWM,你连个直流电机都转不起来。我个人习惯把 PWM 看作是「数字世界控制模拟世界的桥梁」——你想想看,一个 0 或 1 的方波,通过调节占空比,就能控制电机转速、舵机角度、LED 亮度,是不是很神奇?
4.1 高级定时器 vs 通用定时器:到底差在哪?
很多初学者会问:「我直接用通用定时器输出 PWM 不行吗?」行,当然行。但你要做电机控制,尤其是三相无刷电机或者伺服驱动,通用定时器就有点力不从心了。
我直接给你列个对比表,一目了然:
| 特性 | 通用定时器(如 TIM2~TIM5) | 高级定时器(如 TIM1、TIM8) |
|---|---|---|
| PWM 通道数 | 4 个独立通道 | 6 个通道(3 组互补对) |
| 互补输出 | 不支持 | 支持,带死区插入 |
| 刹车功能 | 无 | 有,紧急停止用 |
| 重复计数 | 无 | 有,适合多脉冲场景 |
| 应用场景 | 普通 PWM、输入捕获 | 电机控制、逆变器、电源 |
嗯,这里要注意:高级定时器本质上是一个「增强版」的通用定时器。它把通用定时器的功能全包了,还额外加了死区、互补、刹车这些电机控制必备的特性。所以,如果你做的是 FOC(磁场定向控制)或者 BLDC(无刷直流电机)驱动,请直接上高级定时器,别犹豫。
核心结论:通用定时器适合做「单端 PWM」,比如控制一个 LED 或者一个普通直流电机。高级定时器适合做「互补 PWM + 死区」,比如驱动 H 桥或者三相逆变器。
4.2 PWM 模式:边沿对齐 vs 中央对齐
PWM 模式其实就两种:边沿对齐模式和中央对齐模式。我刚开始做电机控制时,一直用边沿对齐,觉得简单。后来做 FOC 时发现电流纹波大得离谱,查了半天才发现是 PWM 模式选错了。
边沿对齐模式:计数器从 0 数到 ARR(自动重装载值),然后跳回 0。输出电平在计数值达到 CCR(捕获比较寄存器)时翻转。说白了,就是「单边调制」。
中央对齐模式:计数器先向上数到 ARR,再向下数到 0,形成一个三角波。输出电平在向上和向下过程中各翻转一次。这就是「双边调制」。
我建议你记住这个原则:
- 边沿对齐:适合对噪声不敏感的场景,比如灯光调光、普通电机调速。优点是计算简单,缺点是电流纹波大。
- 中央对齐:适合对电流质量要求高的场景,比如 FOC、伺服驱动。优点是电流纹波小、谐波少,缺点是开关损耗略高。
我的经验:做 FOC 时,我习惯用中央对齐模式。虽然开关次数翻倍,但电流波形漂亮很多,尤其是低速运行时,电机噪音明显降低。
4.3 死区时间设置:别让上下管「打架」
死区时间,英文叫 Dead Time。这玩意儿是电机控制里最容易忽略、也最容易出问题的地方。
为什么会这样?你想,H 桥或者三相逆变器的上下两个开关管,如果同时导通,那就是短路——电流瞬间飙升,管子直接烧掉。所以,必须保证上管关断后,下管才能导通。这个「等待时间」就是死区。
高级定时器里,死区时间是通过 BDTR 寄存器设置的。具体来说,是 TIMx_BDTR 里的 DTG[7:0] 位。我直接给你一个配置示例:
// 以 STM32F4 为例,TIM1 配置死区时间
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
// 时基配置
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // 中央对齐
TIM_TimeBaseStructure.TIM_Period = 1000; // PWM 频率 = 168MHz / 1000 = 168kHz
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
// 通道配置(以通道1为例)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // 使能互补输出
TIM_OCInitStructure.TIM_Pulse = 500; // 占空比 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
// 死区时间配置
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
TIM_BDTRInitStructure.TIM_DeadTime = 50; // 死区时间 = 50 个时钟周期
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
// 使能输出
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
警告:死区时间不是越大越好。死区太大,会导致输出波形失真,电机效率下降;死区太小,又可能发生上下管直通。我一般建议死区时间设置为 PWM 周期的 1%~3%。比如 PWM 频率 20kHz(周期 50μs),死区取 0.5μs~1.5μs 比较合适。
我曾经在一个项目中,因为死区时间设得太小(只有 100ns),结果上电瞬间 MOSFET 直接冒烟。嗯,从那以后,我每次配置死区都会用示波器实测一下,确保上下管之间有明显的「空隙」。
4.4 互补输出:一对「好兄弟」
互补输出,就是同一个定时器通道,同时输出两个极性相反的 PWM 信号。一个叫 OCx(主输出),一个叫 OCxN(互补输出)。
你想想看,H 桥的上管需要高电平导通,下管需要低电平导通——这不正好是一对互补信号吗?高级定时器就是为这个设计的。
配置互补输出时,有几个关键点:
- 极性设置:
TIM_OCPolarity和TIM_OCNPolarity可以分别设置。我习惯把主输出设成高电平有效,互补输出也设成高电平有效——这样死区插入后,上下管都是高电平关断,逻辑更清晰。 - 空闲状态:
TIM_OSSRState和TIM_OSSIState控制定时器停止或空闲时,输出引脚的电平状态。我建议使能这两个选项,防止电机失控。 - 刹车功能:
TIM_Break是紧急停止用的。一旦刹车引脚被触发,所有 PWM 输出立即进入空闲状态。这个在电机控制里是保命用的,一定要接。
一句话总结:互补输出 + 死区时间 = 安全的电机驱动。没有死区的互补输出,就是定时炸弹。
4.5 避坑指南:我踩过的那些坑
做 PWM 配置这么多年,我踩过的坑真不少。挑几个典型的跟你说说:
- 坑一:忘记使能主输出——配置完所有寄存器,PWM 就是不出来。查了半天,发现
TIM_CtrlPWMOutputs()没调用。高级定时器有个「主输出使能」位,必须手动打开。 - 坑二:死区时间单位搞错——有些芯片的死区时间是以系统时钟周期为单位的,有些是以定时器时钟为单位的。我建议你直接看数据手册,别靠猜。
- 坑三:互补输出极性反了——上管和下管的导通逻辑搞反,结果电机反转。我曾经因为这个,浪费了整整一个下午。
- 坑四:刹车引脚没处理——刹车引脚默认是低电平有效,如果你没接外部电路,它可能一直处于触发状态,导致 PWM 输出被强制关闭。
调试小技巧:每次配置完 PWM,先用示波器看波形。看三点:频率对不对、占空比对不对、死区时间对不对。这三样没问题,再上电接电机。安全第一。
4.6 小结
PWM 生成与配置,是电机控制软件框架的基石。高级定时器提供了互补输出和死区时间,这是通用定时器做不到的。我个人觉得,做电机控制的人,一定要把高级定时器的每个寄存器都吃透——因为一旦 PWM 出问题,电机要么不转,要么烧管子。
下一章,咱们聊聊 ADC 采样与电流检测。嗯,那又是另一个有意思的话题了。