1. 为什么我要折腾六路互补PWM这件事
搞电机驱动的朋友大概率都经历过这个阶段:能让电机转起来不难,难的是让它转得稳、转得安静、转得有效率。我最近在调一块基于STM32G431的无刷电机驱动板,卡在了一个看似基础但极其关键的环节上——六路互补PWM的验证。说白了,就是让TIM1的CH1/CH1N、CH2/CH2N、CH3/CH3N这六路信号按照预期输出,带死区、能互补、不打架。
为什么这件事值得单独拿出来做一次实验?因为六路互补PWM是整个三相逆变桥的“指挥系统”。你后面所有的FOC算法、SVPWM调制、电流环调节,最终都要落到这六路信号的时序上。如果这六路信号本身有问题——比如死区时间不对、互补关系反了、中心对齐模式没配对——那后面的代码写得再漂亮也是空中楼阁。我见过太多人直接抄一份驱动代码就往里灌,结果MOS管发热严重甚至炸管,回头查半天才发现是PWM配置的锅。
这篇文章适合谁看?如果你正在用STM32G431或者其他带高级定时器的MCU做电机驱动,如果你对“互补PWM”“死区时间”“中心对齐”这些概念还停留在“知道有这么回事”的阶段,如果你想找一份能直接上手复现的验证方案,那这篇内容应该能帮你省下不少调试时间。我会从设计思路讲到具体配置,再到实测波形和踩过的坑,尽量把每个参数背后的“为什么”说清楚。
2. 六路互补PWM的整体设计思路拆解
2.1 为什么非得是“六路”而不是“三路”
先把这个最基本的问题讲透。三相无刷电机的逆变桥有六个开关管,分成三个桥臂,每个桥臂有上下两个管子。所谓“六路PWM”,就是每个管子单独一路控制信号,总共六路。那为什么不用三路加反相器呢?理论上三路PWM加三个反相器也能得到六路信号,但问题在于死区时间没法精确控制。反相器带来的延迟是固定的、不可调的,而不同MOS管的开关速度不一样,需要的死区时间也不同。用高级定时器的互补输出功能,死区时间可以通过寄存器精确配置到纳秒级,这才是正道。
STM32G431的TIM1是一个高级控制定时器,天生就是为电机控制设计的。它有三个通道,每个通道都能输出一对互补信号(CHx和CHxN),加上刹车输入和死区发生器,正好满足三相逆变桥的需求。我选它不是因为别的,就是因为它把这套硬件机制做得很完整,你只需要配置寄存器就能得到想要的波形,不用在外围电路上动脑筋。
2.2 中心对齐模式的选择逻辑
PWM模式有边沿对齐和中心对齐两种。边沿对齐模式下,计数器从0数到ARR然后归零,波形不对称;中心对齐模式下,计数器从0数到ARR再倒数回0,波形关于中心对称。对于电机驱动来说,中心对齐模式有个巨大的好处:它产生的PWM谐波频谱更干净,对电流采样的干扰更小。因为中心对齐时,每个PWM周期的中点是对称的,ADC采样可以安排在计数器过零点或者峰值点,避开开关噪声。
具体到STM32G431的TIM1,中心对齐模式有几种子模式,区别在于中断标志产生的时机。我一般用模式1,也就是计数器在向上计数和向下计数时都产生比较中断。这样每个PWM周期有两个采样窗口,对于FOC的双电阻采样或者单电阻采样都更灵活。当然代价是中断频率翻倍,CPU负载会高一些,但对于G431这种带FPU的Cortex-M4来说,完全扛得住。
2.3 死区时间的计算与取舍
死区时间是互补PWM里最关键的参数之一。它的作用是确保同一个桥臂的上管关断之后,下管才开通,反之亦然。如果没有死区,上下管同时导通,那就是电源直接对地短路,瞬间大电流,管子必炸。
死区时间设多少?这取决于MOS管的开关特性。以我手头这块板子用的MOS管为例,开通延迟约20ns,关断延迟约50ns,上升时间约30ns,下降时间约20ns。最坏情况是上管关断慢、下管开通快,所以死区时间至少要覆盖“关断延迟+下降时间-开通延迟”这个差值。算下来大概需要50ns左右。但实际中我会留一倍余量,设100ns左右。STM32G431的TIM1死区发生器可以配置为步进值乘以时钟周期,假设定时器时钟是170MHz,一个周期约5.88ns,那100ns大约对应17个步进。
这里有个经验:死区时间不是越大越好。死区越大,输出电压的失真越严重,因为死区期间桥臂输出电压取决于电流方向,会产生额外的谐波。所以要在安全和性能之间找平衡。我一般先用示波器看波形,确认没有直通风险后,再逐步减小死区时间,直到波形边缘刚好不重叠为止。
3. 核心配置细节与CubeMX实操要点
3.1 时钟树配置:别让定时器饿着
STM32G431的最高主频是170MHz,TIM1挂在APB2总线上。CubeMX里配置时钟树的时候,要把PLL配到170MHz,然后确保APB2的分频系数是1,这样TIM1的时钟就是170MHz。如果你不小心把APB2分频设成了2,定时器时钟就变成85MHz,死区时间的计算全得重来。
我习惯在Clock Configuration页面直接输入170,让工具自动算PLL参数。然后检查一下APB2 Prescaler是不是1。这一步看着简单,但我第一次做的时候就是在这里翻的车——定时器时钟不对,导致PWM频率死活算不对,查了半天才发现是分频器的问题。
3.2 TIM1的Mode配置:中心对齐加PWM模式1
在CubeMX的TIM1配置页面,Mode部分要选“PWM Generation CH1 CH1N”这类组合。具体操作是:Channel1选“PWM Generation CH1 CH1N”,Channel2和Channel3同理。这样三个通道的互补输出就都使能了。
下面有个“Counter Settings”区域,Counter Mode要选“Center Aligned mode 1”。Prescaler设为0,Counter Period先设一个值,比如850,这样PWM频率就是170MHz/(2850) = 100kHz。为什么是2850?因为中心对齐模式下,一个完整的PWM周期包含向上计数和向下计数两段,所以实际频率是时钟除以(2*ARR)。
PWM Mode选“PWM mode 1”,也就是计数器小于比较值时输出有效电平。这个“有效电平”是高还是低,后面在通道配置里可以单独设。我一般让CHx输出高有效,CHxN输出低有效,这样上下管的驱动逻辑刚好互补。
3.3 死区配置:BreakAndDeadTime的设置
在TIM1的配置页面往下翻,有个“Break And Dead Time”区域。Dead Time要勾上,然后设置Dead Time Value。CubeMX里给的是一个0到255的值,实际死区时间等于这个值乘以定时器时钟周期,但具体公式还跟DTG寄存器的位域有关。简单来说,当DTG值在0到127之间时,死区时间 = DTG * Tdtg,其中Tdtg = 1/定时器时钟。所以如果定时器时钟170MHz,DTG设17,死区就是17/170MHz = 100ns。
Break Input我暂时没启用,因为实验阶段不需要刹车功能。但实际产品里一定要配,用来在过流时快速关断PWM。Automatic Output State我也没开,这个功能是在刹车后自动恢复输出,调试阶段容易掩盖问题。
3.4 通道极性:别把上下管搞反了
每个通道的配置里有个“PWM Generation Channel”区域,里面有“Mode”和“Pulse”以及“Output Compare Polarity”。CH1的Polarity设为“High”,CH1N的Polarity设为“Low”。这意味着CH1有效时输出高电平,CH1N有效时输出低电平。如果你的驱动电路用的是高电平开通的MOS管驱动芯片,那CH1接上管、CH1N接下管,逻辑就对了。
这里有个容易搞混的地方:CH1N的极性设成Low,并不是说它一直输出低,而是说当比较匹配时它输出低电平。在死区期间,CH1和CH1N都输出无效电平,也就是CH1低、CH1N高,这样上下管都关断。这个逻辑一定要在脑子里过清楚,不然波形出来你会以为配错了。
4. 代码实现与波形验证的完整过程
4.1 生成代码后的初始化检查
CubeMX生成代码后,先别急着写业务逻辑。打开main.c,找到MX_TIM1_Init函数,逐行看一下配置是否和预期一致。重点检查这几个寄存器:CR1的CMS位是不是01(中心对齐模式1),CCMR1和CCMR2的OCxM位是不是110(PWM模式1),CCER的CCxE和CCxNE是不是都置位了,BDTR的DTG是不是你设的值。
我习惯在初始化之后加一句HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)以及对应的CH2、CH3,还有HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1)等。注意互补通道的启动函数是HAL_TIMEx_PWMN_Start,不是HAL_TIM_PWM_Start。这个我踩过坑,当时只启动了主通道,互补通道没输出,示波器上只有三路波形,查了半天才发现是启动函数用错了。
4.2 设置占空比的正确姿势
设置占空比用__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, value)。value的范围是0到ARR。在中心对齐模式下,占空比 = value/ARR。比如ARR=850,value=425,占空比就是50%。
但要注意,中心对齐模式下,比较值决定了脉冲的宽度,而脉冲的位置是关于中心对称的。如果你想要50%占空比,value设ARR/2就行。如果想要更小的占空比,value就设小一点。实际调试时我会先设50%,看波形是否对称,然后再改。
4.3 示波器实测:四通道同时抓波形
验证六路互补PWM,光看代码没用,必须上示波器。我用的是四通道示波器,先抓CH1、CH1N、CH2、CH2N这四路。触发方式设为CH1上升沿触发,时基调到20us/div左右,应该能看到完整的PWM周期。
正常的波形应该是这样的:CH1和CH1N互补,CH1高的时候CH1N低,中间有一段两者都低的区域,那就是死区。CH2和CH1有120度的相位差,CH2N和CH2互补。如果你看到CH1和CH1N同时高,那就是死区没生效或者极性配反了,赶紧断电检查。
我实测下来,死区时间100ns的时候,波形边缘很干净,没有明显的振铃。把死区降到50ns,边缘开始出现轻微的重叠,虽然还没直通,但已经不安全了。所以最终定在100ns。
4.4 用定时器中断监控PWM状态
为了验证PWM在运行过程中是否稳定,我开了TIM1的更新中断。在中断里翻转一个GPIO,然后用示波器看这个GPIO的频率。如果PWM频率是100kHz,中心对齐模式下更新中断的频率也是100kHz(因为向上和向下都产生中断,但更新事件只在溢出时产生一次)。实际上中心对齐模式1的更新事件是在计数器达到ARR时产生的,所以中断频率等于PWM频率。
在中断里我还会读一下CCR1的值,通过串口打印出来,确认占空比没有意外变化。这个监控手段在调试FOC的时候特别有用,因为FOC会动态改CCR值,如果中断里读到的值和预期不符,说明算法有问题。
5. 常见问题与排查技巧实录
5.1 波形出来了但互补关系不对
这是最常见的问题。现象是CH1和CH1N同时高或者同时低,没有互补。原因通常有三个:一是CCER寄存器的CCxNE位没置位,互补输出没使能;二是CH1N的极性配错了,应该设Low却设成了High;三是启动函数用错了,互补通道没启动。
排查方法很简单,在调试器里直接看TIM1->CCER的值。如果CC1E和CC1NE都是1,说明使能没问题。再看TIM1->CCMR1的OC1M和OC1MN,确认都是110。最后看TIM1->BDTR的DTG,确认死区值不是0。这三步走完,基本能定位问题。
5.2 死区时间实测和计算值对不上
有时候你按公式算出来死区应该是100ns,但示波器上量出来是150ns。这通常是因为示波器探头的地线太长,引入了额外的电感,导致边沿变缓,看起来死区变大了。换用短地线弹簧或者直接焊接同轴探头,测量结果会准确很多。
另一个原因是MOS管驱动芯片的传输延迟不一致。上管驱动和下管驱动的延迟差会叠加到死区上。这种情况下,你需要在驱动芯片的输入端测量,而不是在MOS管栅极测量。如果输入端死区是对的,那问题就在驱动电路上。
5.3 中心对齐模式下PWM频率算不对
前面提过,中心对齐模式的PWM频率 = 定时器时钟/(2*ARR)。但如果你开了重复计数器(RCR),频率还要再除以(RCR+1)。CubeMX里有个“Repetition Counter”参数,默认是0,也就是不额外分频。如果你不小心设成了1,频率就减半了。
我建议在调试阶段把RCR设为0,等PWM频率验证正确后再根据需要调整。另外,如果你用的是HAL库的HAL_TIM_PWM_Init函数,它会自动把RCR设为你配置的值,所以CubeMX里设多少就是多少,不会丢。
5.4 互补通道没有输出但主通道正常
这个问题我遇到过两次。第一次是因为我只调用了HAL_TIM_PWM_Start,没有调用HAL_TIMEx_PWMN_Start。第二次是因为我在CubeMX里没有勾选“CH1N”的选项,只勾了“CH1”。这两个原因都很隐蔽,因为主通道有输出,你会以为整个通道都配好了。
检查方法:在CubeMX的TIM1配置页面,看Channel1的下拉菜单是不是“PWM Generation CH1 CH1N”。如果只是“PWM Generation CH1”,那CH1N就没使能。改过来重新生成代码就行。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| CH1和CH1N同时高 | 极性配反或死区为0 | 检查CCER和BDTR寄存器 |
| 互补通道无输出 | 启动函数用错或CubeMX未使能 | 检查代码和CubeMX配置 |
| PWM频率不对 | 时钟树或ARR或RCR配错 | 逐项核对时钟和计数器设置 |
| 死区时间偏大 | 探头地线过长或驱动延迟 | 换短地线,在驱动输入端测量 |
| 波形有振铃 | 驱动电阻不合适或布局问题 | 调整栅极电阻,检查PCB布局 |
6. 几个容易被忽略的实操心得
6.1 先验证再优化,别一上来就调死区
很多人一上来就想把死区调到最优值,结果波形还没看明白就开始改参数,越改越乱。我的做法是先把死区设一个明显偏大的值,比如500ns,确保绝对不会直通。然后看波形,确认互补关系、频率、相位都对了,再逐步减小死区。这样每一步都有明确的参照,不会迷失。
6.2 用LED做最直观的状态指示
在调试PWM的时候,我习惯在更新中断里翻转一个LED。如果LED在闪,说明中断在跑,PWM在输出。如果LED不闪,那要么是定时器没启动,要么是中断没使能。这个手段虽然简单,但能快速区分“代码没跑起来”和“波形不对”这两类问题。
6.3 保存一份“黄金配置”
调通之后,把CubeMX的配置文件单独存一份,命名为“TIM1_6PWM_100kHz_100nsDeadTime.ioc”。以后再做类似项目,直接拿这份配置改,能省很多时间。我现在的习惯是每调通一个关键外设就存一份配置,积累下来就是一个自己的“配置库”。
6.4 注意G431的TIM1和TIM8差异
STM32G431有两个高级定时器:TIM1和TIM8。它们的功能基本一样,但引脚分配不同。TIM1的CH1在PA8,CH1N在PA7(或者PB13,取决于复用)。TIM8的CH1在PC6,CH1N在PA7。如果你在PCB上已经把引脚固定了,选定时器的时候就要注意别选错。我一般优先用TIM1,因为它的引脚更常用,资料也更多。
6.5 刹车功能早晚要加,别等到炸管
实验阶段可以不开刹车,但实际产品里一定要配。G431的TIM1有刹车输入引脚(BKIN),可以接到过流比较器的输出。一旦过流,硬件自动关断PWM,不用等软件响应。这个响应速度是纳秒级的,软件中断根本比不了。我建议在画板子的时候就把BKIN引脚预留出来,哪怕第一版不用,后面升级也方便。
7. 从六路PWM到实际电机驱动的延伸思考
六路互补PWM验证通过,只是电机驱动的第一步。接下来你要面对的是:如何根据转子位置动态调整六路PWM的占空比,也就是SVPWM或者FOC。这时候你会发现,PWM的频率、死区时间、中心对齐模式的选择,都会直接影响电流环的性能。比如PWM频率太低,电流纹波大,FOC的采样精度就上不去;死区太大,低速时转矩脉动明显。
我个人的经验是,对于G431这种170MHz的M4,PWM频率设在20kHz到50kHz之间比较合适。20kHz是听觉阈值,低于这个值电机会有啸叫;50kHz以上开关损耗又太大。死区时间在100ns左右,既能保证安全,又不会明显影响波形质量。中心对齐模式配合ADC的注入通道,在计数器过零点采样,能避开开关噪声,得到比较干净的电流信号。
这套配置我后来用在了一个小功率无刷电机上,跑FOC的时候电流波形很平滑,低速也没有明显的抖动。当然,这是后话了。眼下先把六路PWM的波形调好,后面的路才好走。