1. 六路互补PWM到底在做什么
先把概念说清楚。六路互补PWM,指的是三对、共六路PWM信号,每一对里两路波形在理想情况下完全反相,也就是常说的“互补”。这三对信号通常对应三相桥式电路的三个桥臂,每个桥臂有上下两个开关管,上管开的时候下管必须关,下管开的时候上管必须关。如果这两路信号同时导通,那就是直通,电源正负极之间被短路,轻则烧管子,重则炸板子。
所以六路互补PWM的核心价值,不是“输出六路波形”这么简单,而是用硬件机制保证同一桥臂上下管不会同时导通。这个硬件机制就是死区时间——在上下管切换的瞬间,强制插入一段两路都关断的空白期,等一个管子彻底关断之后,再让另一个管子开通。
我这次实验用的是STM32G431,高级定时器TIM1。选它的原因很直接:TIM1是高级控制定时器,自带互补输出和死区插入功能,还有刹车输入,是做电机驱动、数字电源这类应用的标配。通用定时器虽然也能输出PWM,但互补和死区要靠软件或者外部逻辑去凑,既占CPU又不可靠。
这个实验适合谁看?如果你正在做无刷电机驱动、三相逆变、数字电源,或者单纯想搞明白STM32的高级定时器互补输出怎么配,那这篇内容可以直接拿去参考。如果你只是想让一个LED呼吸灯闪起来,那用不到六路互补,普通PWM就够了,不必杀鸡用牛刀。
我先把这次实验的目标列出来,后面所有配置都围绕这几个目标展开:
- 用TIM1的CH1/CH1N、CH2/CH2N、CH3/CH3N输出三对互补PWM;
- 中心对齐模式,方便后续做电机FOC控制;
- 死区时间可调,先设一个保守值,实测波形后再优化;
- 用示波器验证六路波形的互补关系和死区是否真实存在;
- 留出刹车输入接口,为后续故障保护做准备。
2. 方案选型与整体设计思路
2.1 为什么是TIM1而不是普通定时器
STM32G431属于G4系列,主打混合信号和电机控制。它内部有几个定时器,TIM1和TIM8是高级控制定时器,TIM2/3/4是通用定时器,TIM6/7是基本定时器。高级定时器和通用定时器最大的区别就在互补输出这块。
通用定时器每个通道只有一个输出引脚,你要做互补,得自己再找一个引脚,用软件在中断里翻转,或者外挂反相器。软件翻转的问题在于延迟不确定,中断响应有抖动,死区时间根本没法精确控制。外挂反相器倒是硬件反相了,但死区还得另外加RC延迟或者逻辑门,电路复杂不说,温度漂移也会影响死区精度。
TIM1就不一样了。它每个通道自带一个互补输出引脚,比如CH1对应PA8,CH1N对应PB13(具体引脚要看封装和复用表)。死区时间由BDTR寄存器里的DTG位段控制,纯硬件生成,精度是定时器时钟周期级别,不受中断和软件影响。刹车输入也是硬件直接封波,响应速度是纳秒级,软件根本追不上。
所以做六路互补,只要芯片里有高级定时器,就优先用它。这是最省事、最可靠的路子。
2.2 中心对齐模式的选择理由
PWM有两种常见对齐方式:边沿对齐和中心对齐。
边沿对齐就是计数器从0数到ARR,然后归零重新开始。PWM的跳变都发生在计数器的起点或者终点,波形不对称。这种模式配置简单,适合LED调光、简单电机调速这类对波形对称性没要求的场景。
中心对齐是计数器先从0数到ARR,再从ARR倒数回0,形成一个三角波。PWM的跳变发生在三角波的上升沿和下降沿,波形关于中心对称。这种模式有两个好处:一是谐波分布更优,电机运行更平稳,噪音更小;二是配合ADC采样时,可以在计数器过零或者峰值时触发采样,避开开关噪声。
我做这个实验是为了后续的无刷电机驱动,所以直接上中心对齐。虽然配置稍微麻烦一点,但后面省事。
2.3 死区时间的计算逻辑
死区时间不是随便填一个数就完事,它跟你的开关管特性、驱动电路、母线电压都有关系。算少了,直通风险还在;算多了,输出波形畸变,电机效率下降。
死区时间的下限由开关管的关断时间决定。比如一个MOS管,数据手册里写关断延迟时间td(off)是100ns,下降时间是50ns,那从栅极信号撤去到漏极电流真正截止,大概需要150ns。这150ns就是死区时间的理论下限。实际取值要留余量,通常取1.5到2倍,也就是225ns到300ns。
死区时间的上限由输出波形质量决定。死区期间,桥臂中点电压由负载电流方向决定,如果死区太长,输出电压和理想值偏差就大,低速时尤其明显,电机会有转矩脉动。
STM32的BDTR寄存器里,DTG是一个8位字段,分几种编码方式。以G431为例,定时器时钟假设是170MHz,死区时间计算公式分几段:
- DTG[7:5]=0xx时,死区时间 = DTG[7:0] × tDTS;
- DTG[7:5]=10x时,死区时间 = (64 + DTG[5:0]) × 2 × tDTS;
- DTG[7:5]=110时,死区时间 = (32 + DTG[4:0]) × 8 × tDTS;
- DTG[7:5]=111时,死区时间 = (32 + DTG[4:0]) × 16 × tDTS。
tDTS是死区时钟周期,通常等于定时器时钟周期。170MHz下,tDTS约5.88ns。如果我要300ns死区,用第一段公式,DTG值取300/5.88≈51,也就是0x33。这个值后面在代码里会体现。
3. 核心配置细节与实操要点
3.1 时钟与定时器基础配置
G431的主频可以跑到170MHz。我用的是外部晶振加PLL,配置成170MHz。TIM1挂在APB2总线上,APB2的时钟也是170MHz,所以定时器时钟就是170MHz,不需要额外倍频。
这里有个细节要注意:STM32的定时器时钟并不总是等于APB总线时钟。如果APB预分频系数是1,定时器时钟等于APB时钟;如果APB预分频系数大于1,定时器时钟是APB时钟的2倍。G431的APB2预分频通常设为1,所以TIM1时钟就是170MHz。这个在CubeMX里会自动算好,但你要心里有数,不然算死区时间的时候会差一倍。
PWM频率我设的是20kHz。这个频率高于人耳听觉范围,电机运行不会有啸叫。计算ARR值:中心对齐模式下,PWM频率 = 定时器时钟 / (2 × ARR × (PSC+1))。PSC取0,则ARR = 170MHz / (2 × 20kHz) = 4250。所以ARR设为4250-1=4249。
3.2 互补输出与死区配置
在CubeMX里,TIM1的Channel1、Channel2、Channel3都设为“PWM Generation CHx CHxN”模式。这个选项就是同时使能主输出和互补输出。CH1对应PA8,CH1N对应PB13;CH2对应PA9,CH2N对应PB14;CH3对应PA10,CH3N对应PB15。具体引脚以你用的封装为准,G431的引脚复用比较灵活,CubeMX会自动分配。
死区时间在CubeMX的Break and Dead-Time Settings里配置。Dead Time设为51,对应前面算的约300ns。Automatic Output(自动输出)要开启,否则需要软件使能MOE位才能输出。Off State Selection选“When inactive, CHxN is driven to inactive level”,这样刹车时互补输出会被强制到安全电平。
刹车功能我暂时没接实际信号,但配置里把刹车输入使能了,刹车极性设为高电平有效,滤波设为0。后面做故障保护时,只要把刹车引脚接到过流比较器的输出上,一旦过流,硬件立刻封波,不需要软件干预。
3.3 中心对齐模式的寄存器设置
CubeMX里Counter Mode选“Center Aligned mode 1”。模式1和模式2的区别在于输出比较中断的触发时机:模式1在计数器向上计数和向下计数都触发,模式2只在向下计数触发。对于PWM输出本身,两者波形一样,只是中断行为不同。我用模式1,方便后续在中断里做电流采样。
中心对齐模式下,比较值CCR决定占空比。占空比 = CCR / ARR。初始我设CCR为ARR的一半,也就是2125,输出50%占空比。这样示波器上看上下管波形对称,容易判断死区。
3.4 输出使能与保护机制
TIM1有个MOE(Main Output Enable)位,在BDTR寄存器里。这个位不清零,PWM不会输出。CubeMX生成的代码里,HAL_TIM_PWM_Start之后还需要调用__HAL_TIM_MOE_ENABLE,或者用HAL_TIMEx_PWMN_Start这类函数。我踩过的坑是:只调了HAL_TIM_PWM_Start,结果主输出有波形,互补输出没波形,查了半天才发现MOE没开。
刹车输入触发后,MOE会被硬件清零,PWM立刻停止输出。要恢复输出,得先清除刹车标志,再重新置位MOE。这个流程在刹车中断里做,后面做保护时会详细说。
4. 完整实操流程与波形验证
4.1 CubeMX配置步骤
打开CubeMX,选STM32G431CBU6(我用的芯片型号)。系统时钟配置成170MHz,具体是HSE 8MHz经过PLL倍频。TIM1配置如下:
- Clock Source: Internal Clock
- Channel1: PWM Generation CH1 CH1N
- Channel2: PWM Generation CH2 CH2N
- Channel3: PWM Generation CH3 CH3N
- Counter Settings: Prescaler=0, Counter Mode=Center Aligned mode 1, Period=4249
- PWM Generation Channel1: Mode=PWM mode 1, Pulse=2125, Output compare preload=Enable, Fast Mode=Disable, CH Polarity=High, CHN Polarity=High, Idle State=Reset
- Break and Dead-Time: Dead Time=51, Automatic Output=Enable, Off State Selection=Inactive, Break Input=Enable, Break Polarity=High, Break Filter=0
生成代码后,在main函数里加入启动代码:
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3); __HAL_TIM_MOE_ENABLE(&htim1);注意最后那句MOE使能,少了它互补输出不会出来。
4.2 示波器实测波形分析
我用的是四通道示波器,先看CH1和CH1N这一对。探头接PA8和PB13,地夹接板子地。触发设在CH1上升沿,时基调到5us/div。
实测波形:CH1高电平期间,CH1N是低电平;CH1下降沿之后,CH1N并没有立刻上升,而是等了一段时间才上升。这段两路都是低电平的时间,就是死区。用光标测量,大约300ns,和设计值吻合。
再看CH1和CH2的关系。中心对齐模式下,CH1和CH2的相位差是120度。20kHz对应周期50us,120度就是16.67us。示波器上CH2的上升沿确实比CH1滞后约16.7us。CH3再滞后16.7us。三路波形均匀分布,说明中心对齐和比较值设置正确。
有个现象要注意:死区期间,桥臂中点电压不是零,而是由负载电流方向决定的。如果负载是感性,电流不能突变,会通过续流二极管续流,中点电压被钳位到母线正极或者负极。所以死区不仅影响波形,还影响输出电压。做电机控制时,这个非线性要在算法里补偿。
4.3 死区时间的实测与调整
前面算的300ns是理论值,实际电路里驱动芯片也有延迟。我用的栅极驱动芯片开通延迟约200ns,关断延迟约150ns。这意味着即使MCU输出没有死区,驱动芯片本身也会引入不对称延迟。所以实际死区要加上这个差值。
我把死区从300ns逐步加大到500ns,观察波形。300ns时,上下管切换处有轻微重叠,说明驱动芯片的关断延迟比开通延迟长,需要额外死区。加到500ns后,重叠消失,波形干净。但再加大到800ns,输出波形明显畸变,占空比偏离50%较多。所以最终定在500ns,对应DTG值约85,也就是0x55。
这个调整过程说明一个道理:死区时间不能只按MCU手册算,要把驱动电路和开关管的延迟一起考虑。最可靠的方法就是示波器实测,看切换点有没有重叠或者畸变。
4.4 刹车功能验证
刹车输入我暂时用按键模拟。把刹车引脚配置成上拉输入,按键按下时拉低。在刹车中断里翻转一个LED,表示刹车触发。
实测:按下按键,六路PWM立刻停止输出,所有输出引脚变成配置的Idle State。松开按键,清除刹车标志,重新使能MOE,PWM恢复输出。整个过程硬件响应,没有软件延迟。
这里有个细节:刹车中断里必须清除刹车标志位,否则MOE无法重新置位。清除标志用__HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_BREAK)。这个标志在SR寄存器里,不清的话下次刹车中断不会触发。
5. 常见问题与排查技巧实录
5.1 互补输出没有波形怎么办
这是最常见的问题。排查顺序如下:
- 先确认MOE位是否置位。用调试器看BDTR寄存器的bit15,如果是0,说明主输出没使能。调用__HAL_TIM_MOE_ENABLE即可。
- 确认互补输出引脚是否配置正确。有些引脚默认功能不是TIM1_CH1N,需要在CubeMX里检查引脚复用。
- 确认HAL_TIMEx_PWMN_Start是否调用。只调HAL_TIM_PWM_Start不会启动互补输出。
- 如果主输出有波形但互补没有,检查CHN Polarity设置。如果设成Low,互补输出极性反了,示波器上可能看不到预期波形。
5.2 死区时间不准确怎么排查
死区时间偏差大,通常有几个原因:
- 定时器时钟不是预期的频率。检查APB预分频和PLL配置,确认TIM1时钟。
- DTG值算错。对照参考手册的公式重新算,注意不同DTG[7:5]区间的公式不一样。
- 驱动芯片延迟没算进去。用示波器实测切换点,根据重叠情况调整DTG值。
- 探头接地不良。高频下探头地线太长会引入振铃,影响测量。用弹簧地针代替鳄鱼夹。
5.3 中心对齐模式下占空比不对
中心对齐模式下,占空比 = CCR / ARR。但要注意,如果CCR大于ARR,输出会一直是高电平;如果CCR为0,输出一直是低电平。我一开始把CCR设成ARR+1,结果波形一直是高,查了半天才发现是CCR超了。
另外,中心对齐模式下,PWM频率 = 定时器时钟 / (2 × ARR × (PSC+1))。这个2是因为计数器上下都数。如果你按边沿对齐的公式算,频率会差一倍。
5.4 刹车触发后无法恢复输出
刹车触发后,MOE被硬件清零,同时刹车标志置位。要恢复输出,必须:
- 清除刹车标志;
- 重新置位MOE;
- 如果刹车输入还在有效电平,MOE无法置位,必须先解除刹车信号。
我遇到过一次,按键松开后PWM没恢复,原因是刹车标志没清。在中断里加上清除标志的代码后正常。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 互补输出无波形 | MOE未使能 | 检查BDTR bit15 |
| 互补输出无波形 | 未调用PWMN_Start | 检查启动代码 |
| 死区时间偏差大 | 定时器时钟不对 | 确认APB预分频 |
| 死区时间偏差大 | DTG计算错误 | 对照手册重算 |
| 占空比不对 | CCR超过ARR | 检查CCR值 |
| 刹车后不恢复 | 刹车标志未清 | 清除TIM_FLAG_BREAK |
| 波形有振铃 | 探头接地不良 | 用弹簧地针 |
6. 实操心得与后续扩展
这个实验做完,我最深的体会是:六路互补PWM的难点不在配置,而在验证。CubeMX点几下就能生成代码,但波形对不对、死区够不够、刹车灵不灵,必须用示波器一处处看。我见过太多人代码跑通了就以为万事大吉,结果上电一跑就炸管子,就是因为没实测死区。
另一个心得是死区时间要留余量但不能过头。余量不够,直通风险还在;余量太大,输出畸变。我的做法是先按理论值算,然后实测切换点,逐步加大到重叠消失,再稍微加一点余量,最后定值。这个过程花不了多少时间,但能避免很多返工。
后续这个平台可以往几个方向扩展。一是把刹车输入接到过流比较器上,做过流保护;二是加入ADC采样,在中心对齐的过零点触发,做电流环控制;三是把PWM频率提高到40kHz,配合更快的驱动芯片,减小电流纹波。这些扩展都建立在六路互补PWM稳定输出的基础上,所以先把这一步做扎实,后面才走得顺。
最后分享一个小技巧:调试PWM时,把示波器的余辉模式打开,可以同时看到波形的长期稳定性和瞬态抖动。如果余辉模式下波形边缘模糊,说明有抖动,可能是时钟不稳定或者中断干扰。这个技巧在调电机驱动时特别有用,能提前发现很多隐藏问题。