3. PWM触发与死区:定时器PWM输出配置、互补PWM与死区插入、PWM更新触发ADC采样

各位同学,咱们今天聊点实在的。PWM触发和死区配置,这玩意儿是电机控制的"心跳"。我见过不少新手,算法写得飞起,结果板子一上电,MOS管直接冒烟——多半就是死区没配好。嗯,咱们一步步来。

3.1 定时器PWM输出配置——从寄存器到波形

说白了,PWM输出就是让定时器的某个通道,在计数值匹配时翻转电平。我习惯用STM32的TIM1或TIM8来做电机控制,因为它们支持互补输出和刹车功能。

配置的核心就三步:

  1. 设定时器时钟和重装值——决定PWM频率
  2. 配通道模式——PWM1还是PWM2,极性高低
  3. 写比较寄存器——决定占空比

举个例子,我常用的配置代码片段:

// 以TIM1为例,输出50kHz PWM
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;

// 1. 时基配置
TIM_TimeBaseStructure.TIM_Prescaler = 0;          // 不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1; // 中央对齐
TIM_TimeBaseStructure.TIM_Period = 720 - 1;       // 72MHz / 720 = 100kHz? 不对,中央对齐要×2
// 实际频率 = 72MHz / (720 * 2) = 50kHz
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

// 2. 通道配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 360;              // 50%占空比
TIM_OC1Init(TIM1, &TIM_OCInitStructure);

// 3. 使能输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);

我的小习惯:中央对齐模式(Center Aligned)比边沿对齐更适合FOC。为什么?因为中央对齐模式下,PWM的更新时刻正好在计数值的"谷底"和"峰顶",这时候ADC采样噪声最小。我在项目里吃过亏,边沿对齐采样,结果电流波形毛刺多到没法看。

3.2 互补PWM与死区插入——别让MOS管"直通"

你想想看,一个桥臂上下两个MOS管,如果同时导通,电源直接短路——这就是"直通"(Shoot-through)。死区就是故意插入的一段"全关断"时间,让上下管安全切换。

互补PWM,就是同一个定时器通道,输出一对极性相反的波形。STM32的TIM1和TIM8自带死区插入硬件,咱们只需要配一个寄存器:

// 死区配置示例:插入1μs死区
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
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 = 72;          // 72个时钟周期 = 1μs @72MHz
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);

我曾经踩过的坑:死区不是越大越好!死区太大,波形失真,电流谐波增加,电机噪音大、发热严重。死区太小,又可能直通。我一般按经验公式来算:死区时间 ≥ MOS管关断延迟 + 开通延迟 + 2×驱动芯片延迟。比如IRFS3006这种大功率管,我通常设1.5~2μs。

互补PWM的波形关系,我画了张图帮你理解:

互补PWM与死区插入示意图 上管PWM 下管PWM 死区 死区放大细节 上管关断 死区(全关断) 1μs 下管开通

关键点:死区时间内,上下管都是关断状态。电流通过MOS管的体二极管续流。这段时间虽然会有损耗,但总比炸管子强。

3.3 PWM更新触发ADC采样——"同步"是FOC的灵魂

做FOC控制,电流采样时机非常关键。你想想看,如果PWM正在开关切换时去采电流,那噪声大得离谱。正确的做法是:在PWM的"安静时刻"触发ADC。

对于中央对齐模式,最干净的采样点有两个:

  • 计数值到达"谷底"(下溢)——此时所有PWM通道都处于中间状态
  • 计数值到达"峰顶"(上溢)——同上

我习惯用谷底采样,因为这时候电流刚好稳定。配置方法:

// 配置TIM1更新事件触发ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // 或TIM1_TRGO
// 更常用的方式:用TIM1的TRGO触发
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);  // 更新事件作为触发输出

// ADC配置
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_TRGO;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
ADC_Init(ADC1, &ADC_InitStructure);

我的经验:采样窗口(Sampling Time)别设太短。我一般设到7.5个ADC时钟周期以上。太短了,采样电容充不满,采出来的电流值偏小,算出来的角度和转矩全错。有一次我排查了三天,最后发现是采样时间设成了1.5个周期——嗯,从那以后我再也不敢省这个参数了。

3.4 完整的触发时序——一张图说清楚

我把PWM、死区、ADC采样三者的时序关系画在一起,方便你理解:

PWM触发与ADC采样完整时序 三角载波 上管PWM 下管PWM ADC触发 谷底采样 峰顶采样 时序要点: 1. 三角载波到达谷底(最小值)时,PWM状态稳定 2. 此时触发ADC,采样电流/电压信号 3. ADC转换完成后,触发DMA传输,更新PWM比较值 4. 下一个PWM周期开始,新的占空比生效

3.5 避坑指南——我踩过的雷

常见问题 现象 解决方案
死区过小 MOS管发热严重,甚至炸管 加大死区,用示波器测上下管栅极波形,确保无交叠
死区过大 电机噪音大,电流波形畸变 在保证安全的前提下,逐步减小死区
ADC采样时机不对 电流读数跳动,FOC控制不稳 改用PWM更新事件触发ADC,确保在"安静窗口"采样
PWM频率与ADC采样率不匹配 控制周期混乱 确保ADC采样率 = PWM频率 × 采样点数(通常1~2点)

我曾经犯过的错:有一次做高压电机驱动(380V),我偷懒把死区设成了0.5μs,觉得"应该没问题"。结果上电测试,刚转起来,砰的一声——MOS管炸了三个,PCB铜箔都烧断了。后来查原因,是驱动芯片的传播延迟比我想象的大,实际死区几乎为0。嗯,从那以后我养成了习惯:死区设好后,必须用示波器实测上下管栅极波形,确认没有交叠。

3.6 小结

这一章的内容,说白了就是三件事:

  • PWM怎么配——频率、模式、极性
  • 死区怎么插——互补输出、死区时间计算
  • ADC怎么同步——PWM更新触发,找准采样窗口