先说个丢人的事:我第一次给自制的三相逆变器上电,用的还是低压24V电源,结果板子上最大的那颗MOSFET先冒了烟。查了半天,问题不在驱动电路,也不在PCB走线,而是MCU输出的PWM波形本身就存在严重缺陷——上下桥臂的互补信号在切换瞬间同时导通了好几个微秒。用示波器一抓,一切真相大白。
从那次之后,我立了个规矩:功率电路必须等互补PWM波形验证完才能碰。这个“验证六路互补PWM”的实验,核心目的就是用示波器证明:三对互补信号频率正确、占空比正确、死区时间正确、相序关系正确。把这四点测透了,再上功率级心里才算有底。这篇文章写给正在做BLDC驱动、FOC控制器、逆变器或数字电源的朋友,如果你正在从“能点灯”转向“能转电机”的阶段,这篇内容很适合当一份操作参考。
1. 为什么“互补”这件事值得单独做个实验
1.1 三对信号焊死了电机驱动的物理结构
三相逆变器的功率级,本质上是三个半桥电路。一个半桥由上下两个开关管串联在母线正负之间,中间抽头接电机的一相。要让电机正常工作,上管打开时下管必须关闭,下管打开时上管必须关闭——这两路控制信号天然需要互为反相。
单片机的定时器外围电路里,每个定时器通道可以同时输出两路信号:普通通道和互补通道。普通通道输出PWM,互补通道输出它的逻辑反相信号。以STM32的TIM1为例,CH1/CH1N、CH2/CH2N、CH3/CH3N正好组成三对互补输出,对应三相逆变器的U、V、W三组桥臂。所谓“六路互补PWM”,就是这三对信号合在一起,一共六根线,接管功率级的全部桥臂。
有的人可能会问:既然只是反相,我在中断里翻转IO口不就行了?理论可以,实际不行。软件翻转存在延迟,你无法保证上下管切换的间隔精确到纳秒级。而且一旦你在主循环里放了别的事,波形就会漂移。定时器硬件自动生成的互补输出,才是工程上唯一可靠的做法。
1.2 死区不是保守主义,是保命符
完美的互补信号,上下管一个关一个开,逻辑上毫无问题。但实际功率器件有关断延迟:MOSFET的栅极电荷需要时间泄放,IGBT有拖尾电流。如果上下桥臂的驱动信号严格无缝切换,上管还没完全关断,下管已经导通了,那电源正负极之间就形成一条低阻直通路径,工程上叫“shoot-through”,直通短路。轻则芯片发热异常,重则秒烧功率管。
死区时间就是在这种切换瞬间主动插入的一段“双双关断”间隔。上管关闭后,等几百纳秒或者几微秒,确认它确实关了,再去开下管。死区时间要在安全性和波形质量之间取平衡:太短,器件容易直通;太长,输出波形畸变,电机电流谐波变大。
这个实验要验证的第一优先项,就是死区时间存在且数值符合预期。没有死区的波形,哪怕看起来再完美,也不能用于真实功率级。
1.3 硬件定时器做这件事到底好在哪
硬件生成互补PWM还有一个软件方案做不到的优势:定时器内部生成的互补信号天然同步。三对信号的相位关系由同一个时基产生,不会因为代码执行路径不同而产生误差。而且一旦发生刹车事件(比如过流信号触发BKIN引脚),硬件能立刻把所有输出拉到一个预设的安全状态,不需要等CPU进中断,这个响应时间是微秒级以下。高级定时器的“高级”,恰恰就体现在管脚输出级联的安全逻辑链路上,而不是单纯的PWM频率更高。
2. 用TIM1同时拉出三对互补信号——配置顺序决定成败
2.1 引脚复用先于定时器初始化
我默认使用STM32F407系列做演示,TIM1在PE口上映射了一组很规整的引脚:PE9是CH1、PE8是CH1N,PE11是CH2、PE10是CH2N,PE13是CH3、PE12是CH3N。用PE8到PE13这连续六个引脚接功率级驱动芯片的六路输入,板上走线很顺。
很多人写代码习惯先配定时器再配GPIO,但高级定时器的输出引脚如果复用功能没选对,配置完定时器波形根本出不来。正确的顺序是:先使能GPIO和TIM1的时钟,然后配置GPIO复用为AF1(TIM1对应的复用功能编号),最后再初始化定时器。否则在初始化过程中会出现输出状态抖动,严重时会让后级驱动一脚踩进错误的逻辑状态。
2.2 五步配置,每一步都有具体原因
初始化代码的骨架大致是下面这个过程,我用HAL库的描述方式写,你换成标准外设库或者寄存器操作也都是同一个逻辑顺序。
// 1. 时钟和GPIO复用(先做) RCC_ClkInitTypeDef…… // 使能GPIOE和TIM1时钟 GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; // 复用推挽 GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Alternate = GPIO_AF1_TIM1; // 映射到TIM1 HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); // 2. 时基:20kHz开关频率 TIM_TimeBaseInitStructure.Period = 8400 - 1; // 168MHz / 8400 = 20kHz TIM_TimeBaseInitStructure.Prescaler = 0; TIM_TimeBaseInitStructure.ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.CounterMode = TIM_COUNTERMODE_UP; HAL_TIM_Base_Init(&htim1);这里有个容易忽略的点:Period = 8400 - 1,计数器从0数到8399后归零,一个周期占用8400个时钟周期。168MHz除以8400,正好得到20kHz的PWM频率。这个频率适合绝大多数MOSFET驱动,IGBT一般建议10kHz到15kHz,你把Period调到16800或者21000对应关系就出来了。
// 3. PWM模式和通道配置(CH1/CH2/CH3) sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 4200; // 50%占空比 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 互补通道极性 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2); HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);这一步同时配了六路输出:CH1/CH1N、CH2/CH2N、CH3/CH3N。占空比统一设成50%,是为了验证方便。一旦要跑电机,只需要在每个PWM周期更新三路Pulse,让三路占空比按正弦规律变化,这就是SPWM;按SVPWM扇区计算切换,就是空间矢量调制。
// 4. 死区时间和刹车配置(BDTR寄存器) sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_1; sBreakDeadTimeConfig.DeadTime = 84; // 具体解算见下节 sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIM_DeadTimeConfig(&htim1, &sBreakDeadTimeConfig); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2); HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);注意第3步和第4步的顺序是有讲究的:先配好PWM波形参数,最后再开主输出使能位MOE。如果顺序反了,定时器一开始就把一串尚未配好死区的信号放出引脚,做实验时可能整板起飞。HAL库把MOE的开启藏在HAL_TIM_PWM_Start内部,所以它的执行顺序天然靠后,这是个好设计。
2.3 死区值怎么算到具体寄存器位
BDTR寄存器里的DTG位只有8位,软件填进去的数值不是直接等于死区时间,需要查参考手册里的死区时间公式。以F407在TIM1内部时钟DTS为168MHz时为例,公式是这样的:
Tdts = 1 / 168MHz ≈ 5.95ns 若 DTG[7:0] 在 0 ~ 127 范围内: 死区时间 = DTG[7:0] × Tdts 死区时间 = 84 × 5.95ns ≈ 500ns实验中我常用500ns作为起步值,对大多数快速MOSFET的驱动电路是安全的。如果你的功率管的关断延迟比较大,或者驱动IC本身内部加了互锁延迟,死区可以放宽到1us到2us。千万不要死区设成零试运气,第一次上电很可能就是青烟仪式。
我一直强调一个细节:死区时间换算表在不同芯片上不同。比如STM32F1的TIM1死区计算公式带DTS预分频选择,参数表和F4不完全一样。写代码前一定要查对应型号参考手册中“BDTR寄存器”那一章的死区配置表,别把F4的配置思路照搬到F1上。
3. 上示波器前的准备:低压台、探头、上电顺序
3.1 一套低成本的验证平台就够了
这个实验不需要完整的电机驱动板。我通常用一个100元以内的最小系统板,加上一个半桥驱动芯片的空板(只焊驱动芯片,不焊MOSFET),用跳线把六路信号引到测试点。如果你有现成的电机驱动板,可以先不接电机、不接母线电压,只给控制板供电,输出信号自然就送到了驱动芯片输入端。这样测到的波形就是功率管栅极前的信号,已经能验证90%的问题。
需要准备的东西清单:
- STM32F407最小系统板一块,或者任意带高级定时器互补输出的板子
- 示波器一台,建议双通道起步,四通道更省事
- 探头两根以上,带弹簧地线夹的优先
- 杜邦线若干、接线端子若干
- 5V或3.3V电源给控制板供电,绝对不要在这个阶段接母线高压
这个实验我建议从头到尾都用USB线给的5V供电,电流很小,即使波形接错了也能量有限。真正验证完波形逻辑后,再逐步加母线电压。
3.2 探头接法和公共地问题
示波器探头的地线夹都是连接到示波器外壳大地的,两个通道共地。做这种数字信号测试,最忌讳的是A探头夹在板子某处,B探头夹在另一处,造成地环路。正确的做法:把示波器两个探头的接地夹扣在同一块板的同一个公共GND测试点上,甚至两个夹子叠在一起扣同一个引脚都可以。
探头倍率也要注意。很多探头默认在1X挡,但Hantek这类便携示波器在1X挡位显示的带宽会大幅下降,波形的边沿会变钝,影响死区测量的准确度。把探头拨到10X挡,示波器输入阻抗变成10MΩ,对被测信号的影响更小,抓到的边沿也更真实。
3.3 上电后的第一次检查
一切接好、代码烧录完毕后的上电瞬间,什么都先不测,听声音、闻味道。没有异响、没有异味、板子温度正常,再进行下一步。然后把示波器探头点到CH1输出引脚,看有没有稳定的20kHz方波。此时如果波形完全没有,先查主输出使能位MOE是不是被某个错误条件关掉了,刹车引脚电平是否处于激活态,最后再怀疑接线。
还有个小细节:程序在初始化过程中,六路引脚的状态会经历一段“未定义”时间。如果你的板子上驱动芯片对输入状态极其敏感,建议在GPIO配置后立刻让引脚输出一个确定的电平(比如整体拉低),完成PWM配置后再启动输出。这一段顺序问题,程序里多写几行注释,未来自己回头看不犯迷糊。
4. 验证实验的操作流程:四关全过才算合格
4.1 第一关:静态输出状态与有效电平
很多人一开始验证互补PWM就把示波器怼上去看动态波形,其实第一关应该先确认静态状态。所谓的静态状态,是指PWM还没开始运行时,或者令占空比固定为0%时,六路引脚是低电平还是高电平。
这一步很关键的原因是:有些半桥驱动芯片的输入逻辑是“低电平有效”,或者有使能脚存在反向逻辑。如果你的有效电平概念和驱动芯片的要求相反,动态波形再完美也是白搭。
把代码里的占空比临时改成0%,用万用表直接量CH1和CH1N引脚的电平,记录一组数据。然后改成100%,再量一次。记录完恢复50%做动态测试。我习惯把这一步做成一张记录表,检查所有六路引脚在0%和100%两种占空比下的状态,确认它们总是互补的。
4.2 第二关:动态互补性检查
把占空比恢复到50%,示波器CH1接到PE9,CH2接到PE8,分别对应CH1和CH1N。时基调到每格10us,看波形整体形态。正常情况下两条波形应该是上下对称的:一个高电平时另一个低电平,频率一致,几乎没有重叠区间。
互补性的验证有个非常实用的技巧:示波器上有“反相显示”功能,把CH2设置为反相后再看,正常情况下CH2的波形应该和CH1完全重合。如果两条线在屏幕上叠在一起严丝合缝,说明这段时间它们确实是反相关系。如果不是,就说明存在明显的死区或者相位偏差。这个技巧用在低端示波器上很好使,省得你肉眼去找两个通道的间距。
4.3 第三关:死区测量的完整过程
死区测量是全部实验里最考验读图能力的环节。方法是把两个通道波形放在同一屏幕上,水平时基放到每格500ns或者更小,把上升沿切换点放大。我用文字描述一下看到的画面应该是怎样的:
下图描述为一个示意:CH1从高变低的过程中,CH1N并不是立刻从低变高,而是经过大约500ns的“双低”间隔后才拉高。这个双低区间,就是死区。
实操测量步骤:
- 用示波器上的“光标(Cursor)”功能,把X1光标放到CH1下降沿的50%电平点;
- 把X2光标放到CH1N上升沿的50%电平点;
- 读时间差值,就是死区时间;
- 同样的方法测量CH1上升沿到CH1N下降沿之间的间隔。
需要注意:正反两个方向的死区时间理论上应该一致,但实际电路中上下管的驱动电路不对称会导致轻微偏差。如果偏差超过30%,排查驱动芯片的传播延迟是否匹配、栅极电阻是否焊错。我曾经遇到过把上管和下管的栅极电阻装反了的情况,死区一边300ns一边800ns,很离谱。
4.4 第四关:相序检查(做电机驱动才需要)
如果做完前两对通道,第三对也按照同样流程验证,六路波形本身已经合格。但矢量控制还需要确认三相桥臂之间的相位关系。简单来说,当你想输出一个旋转磁场时,U、V、W三路信号的基波相位应该互差120°电角度。
验证方法:示波器开三个通道,同时测量PE9、PE11、PE13三路输出。在固定相序模式下(我习惯用一个扫描式的占空比序列做验证),观察相邻两路上升沿之间的时间差。对于一个PWM周期T,120°电角度对应的延迟时间是T的三分之一。20kHz的PWM周期为50us,那么理论上相邻通道之间应该相差约16.7us。
这里有个重要的工程提醒:示波器上抓到的相位差是“相序的宏观表现”,真正的SVPWM实现还要考虑扇区切换和死区带来的相位误差。验证实验里确认宏观相序正确就够,不要拿示波器去较真管级信号的每度相位。
5. 我把回路搭完整之后踩过的坑
5.1 极性配置差一位,高电平变低电平
第一次在F407上配六路互补输出时,我漏看了OCNPolarity这一项。默认值是什么?查手册才发现,如果OCNPolarity没有显式配置为高有效,TIM1的互补通道输出的是倒置逻辑:原本高电平时它输出低电平,看起来像是在输出一个负占空比的信号。驱动芯片看到这个错误电平,上管被关断的同时下管被打开或半打开,板子一接功率级就出事。
所以每次配完定时器,第一件事就是回头确认OCPolarity和OCNPolarity两端都设成一致的极性。这个错误在纯逻辑测试时很难抓到,因为示波器只显示波形,不显示高低电平对应的管子状态。唯有把波形和驱动逻辑翻译成桥臂状态后再看,才能发现破绽。
5.2 死区设小了:开关损耗与器件损伤的分界点
有次做IGBT驱动实验,我把死区从1us调到300ns,想看看波形的“理论畸变更小”能不能带更好性能。结果上电不到十分钟,IGBT模块的温度直接飙升,关机摸一下烫到不能碰。原因并不是发生了完全直通短路,而是关断拖尾电流在下管开通瞬间还没完全消失,产生了巨大的瞬时功耗,累积下来温升极快。
这个教训告诉我一个经验法则:死区时间的下限由器件关断延迟加上驱动芯片传输延迟决定,不要一拍脑袋按教科书上的最小值来。IGBT因为拖尾电流大,死区一般建议不低于1us到2us;高压MOSFET取决于总栅极电荷,常见建议是500ns起步;碳化硅MOSFET因为开关速度极快,可以把死区压到200ns甚至更低。选死区要做温度实验,别省。
5.3 “看起来正常”的无负载波形会骗你
实验室里不带功率管、不做母线电压测试,直接测MCU引脚波形,看到的互补PWM几乎都是完美对称的。把这些信号接上驱动芯片之后,因为驱动芯片本身有传播延迟、输出电阻、图腾柱电流限制,波形边沿会变差。所以必须在驱动芯片输出端再测一次波形,确认死区没有被驱动芯片内部的处理吃掉或者反向拉长。
我的习惯是至少测三个点:MCU引脚波形、驱动芯片输出端波形、功率管栅极波形。三层波形各测一次,形成完整的链路档案。尤其是栅极波形,如果出现振铃、台阶、尖峰,优先查栅极电阻阻值和布线寄生电感,这不是PWM配置能解决的,却是PWM验证必须覆盖的边界。
6. 六路互补验证完之后,下一步怎么升级
6.1 从固定占空比到中心对齐PWM
验证实验用的通常是边沿对齐模式(计数器从0向上计数到ARR然后归零)。做电机控制时,更多人改用中心对齐模式:计数器先向上再向下,PWM波形以周期中点为中心对称。这种模式输出的波形谐波能量集中在两倍开关频率处,对电流采样时序也更友好。
中心对齐只需要把CounterMode从TIM_COUNTERMODE_UP改成TIM_COUNTERMODE_CENTERALIGNED1,然后占空比更新时机的计算方式随之变化:占空比不是先高后低再高,而是一个周期内的两个比较点决定高电平区间。改用这种模式后,需要重新做一遍全部验证流程,波形形状和上边沿对齐模式差异很大,不要想当然。
6.2 PWM同步ADC采样
闭环控制的下一站是电流采样。全桥驱动的电流采样有个规律:PWM中心点的瞬间,相当于“空矢量”作用区间,此时母线电流等于相电流。利用TRGO触发ADC采样,把模数转换时刻和PWM中心对齐,能获得最干净的低噪声采样窗口。
这个配置在HAL里就是TIM_SelectOutputTrigger选成TIM_TRGO_UPDATE或TIM_TRGO_OC4REF,然后将ADC的外部触发源设置为对应的定时器触发。配置完之后,把示波器探头接到DAC调试输出,能看到电流波形在PWM中点处不存在开关噪声毛刺。
6.3 死区补偿是最后一公里
死区会让实际输出电压比理想电压略微偏低,母线电压越高、死区时间越长,偏差越明显。低速带载时,这种偏差会造成电流波形畸变,听起来就是电机有“兹兹”的杂音。解决方向是死区补偿:根据相电流方向,在PWM比较值上做微调,把死区丢掉的那一小段电压补回来。
死区补偿的调试依赖前面全部验证工作的结果。先确保死区时间测量精确,再根据电流极性逐相补偿,每步都用电流探头接上示波器看波形畸变的改善。顺序错不得:死区没测准,补偿参数完全是空中楼阁。
我在实际项目里,六路互补PWM的验证始终占据开发周期前端的核心位置。波形验证花掉的时间,远比返工烧板后花的维修时间少得多。把频率、占空比、死区、相序这四件事做成标准检查单,每次启动新硬件都从头过一遍,习惯稳定之后,这套流程几乎不花什么时间,却帮我避开了日后大量低级的功率级故障。