4、PWM输出原理:PWM模式、占空比与频率计算、互补输出与死区插入

PWM,说白了就是让单片机输出一串宽度可调的脉冲。你想想看,控制电机转速、调节LED亮度、驱动舵机,哪个离得开它?我最早接触PWM时,以为就是调调占空比那么简单。后来做电机驱动项目,才发现死区、互补输出这些坑,踩一个就够你折腾好几天。

这一节,咱们把PWM的底裤扒干净。从模式选择到频率计算,再到高级定时器才有的互补输出和死区插入,一步到位。

4.1 PWM模式:边沿对齐 vs 中央对齐

STM32的定时器支持两种PWM模式,我习惯叫它们「边沿对齐」和「中央对齐」。名字听着玄乎,其实看波形就懂了。

特性 PWM模式1(边沿对齐) PWM模式2(中央对齐)
计数方式 向上计数(0 → ARR) 向上/向下计数(0 → ARR → 0)
输出波形 电平在CNT=CCR时翻转一次 电平在CNT=CCR时翻转两次
频率 f = 定时器时钟 / (ARR+1) f = 定时器时钟 / (2 × ARR)
对称性 不对称,上升沿对齐 对称,波形中心对齐
适用场景 普通电机、LED调光 H桥、三相逆变、高精度控制
我的习惯:普通应用直接用边沿对齐,简单高效。但做电机FOC控制时,我必用中央对齐模式——谐波更少,电流采样时机也更好安排。

4.2 占空比与频率计算

这两个参数是PWM的灵魂。频率决定了PWM的「快慢」,占空比决定了「强弱」。咱们直接上公式。

频率计算

// 定时器时钟 = 72 MHz(以STM32F1为例)
// 预分频器 PSC = 71  → 计数时钟 = 72M / (71+1) = 1 MHz
// 自动重装载 ARR = 999 → PWM频率 = 1M / (999+1) = 1 kHz

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 71;      // PSC
TIM_TimeBaseStructure.TIM_Period = 999;        // ARR
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

占空比设置

// 占空比 = CCR / (ARR+1) × 100%
// 想要50%占空比 → CCR = 500
TIM_SetCompare1(TIM2, 500);   // 通道1输出50%占空比

// 想要25%占空比 → CCR = 250
TIM_SetCompare1(TIM2, 250);

核心公式:

  • PWM频率 = 定时器时钟 / ((PSC+1) × (ARR+1))
  • 占空比 = CCR / (ARR+1) × 100%
  • 分辨率 = 1 / (ARR+1) × 100%

嗯,这里要注意。ARR越大,分辨率越高,但频率就越低。我在项目中遇到过需要20kHz PWM频率、同时要0.1%分辨率的情况。算下来ARR得999,PSC得3.5——没法整除。最后只能妥协到0.12%分辨率,ARR设799。

避坑指南:我曾经因为PSC和ARR算错,导致电机发出刺耳啸叫。后来养成习惯:先定频率,再算PSC,最后用ARR调分辨率。算完一定用示波器确认。

4.3 互补输出与死区插入

这部分是高级定时器的看家本领。做H桥驱动、半桥逆变、电机控制,必须用互补输出。为什么?因为一个通道要同时控制上下两个MOS管。

互补输出是什么?

简单说,就是定时器的一个通道,自动生成两路相反的PWM信号。一路叫OCx,另一路叫OCxN。你设一个CCR值,两路波形自动互补。

// 使能互补输出(以TIM1为例)
TIM_OCInitTypeDef TIM_OCInitStructure;

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;                            // CCR值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       // 主输出高有效
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;     // 互补输出高有效
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;    // 空闲时低电平
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;

TIM_OC1Init(TIM1, &TIM_OCInitStructure);

死区插入——为什么需要它?

你想想看,上下两个MOS管如果同时导通,电源直接短路,瞬间冒烟。但实际电路中,MOS管关断比开通慢。如果互补信号完美对齐,就会出现「上管还没关死,下管已经开了」的尴尬局面。

死区就是故意插入一段「上下都关断」的时间。STM32高级定时器通过TIM_BDTR寄存器来配置。

// 死区时间配置(以TIM1为例)
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;

// 死区时间 = DTG[7:0] × t_dts
// 假设 t_dts = 125ns(时钟8MHz)
// 想要1μs死区 → DTG = 1μs / 125ns = 8
TIM_BDTRInitStructure.TIM_OSSR = TIM_OSSR_Enable;      // 运行模式关闭状态
TIM_BDTRInitStructure.TIM_OSSI = TIM_OSSI_Enable;      // 空闲模式关闭状态
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1; // 锁定级别
TIM_BDTRInitStructure.TIM_DeadTime = 8;                // 死区时间 = 8 × 125ns = 1μs
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable;   // 刹车功能
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;

TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
经验值:死区时间不是越大越好。我做过一个电机驱动项目,死区设了3μs,结果电机效率掉了5%。后来优化到500ns,效率回来了,MOS管也不发热。一般建议:死区 = MOS管关断延迟 × 1.5 ~ 2倍。

4.4 知识结构总览

下面这张图,把PWM输出的核心脉络串起来了。从模式选择到参数计算,再到高级功能,一目了然。

PWM输出知识结构图 PWM输出 PWM模式选择 边沿对齐 中央对齐 参数计算 频率 = f_clk/(PSC+1)/(ARR+1) 占空比 = CCR/(ARR+1) 高级功能 互补输出 死区插入 核心:ARR决定频率和分辨率,CCR决定占空比,BDTR决定死区 高级定时器(TIM1/TIM8)才支持互补输出和死区插入 电机控制 LED调光 开关电源

4.5 实战要点总结

  • 模式选型:普通应用用边沿对齐,高精度控制用中央对齐
  • 频率与分辨率权衡:ARR越大分辨率越高,但频率会降低。根据实际需求折中
  • 死区设置:参考MOS管数据手册的关断延迟,留1.5~2倍余量
  • 互补输出:只有高级定时器(TIM1、TIM8)支持,通用定时器不行
  • 刹车功能:配合死区使用,紧急情况下强制关闭所有输出
血的教训:我曾经在调试电机驱动板时,忘记使能TIM_CtrlPWMOutputs(TIM1, ENABLE)这个函数。结果折腾了两小时,PWM死活没输出。后来查手册才发现,高级定时器的互补输出通道需要额外使能主输出开关。嗯,这个坑我替你们踩过了。