第四章 PWM原理与调试:频率、占空比与死区时间
各位工程师,咱们今天聊聊PWM。说实话,PWM这东西看着简单,但真正调起来,坑还真不少。我见过不少新手,上来就写代码,结果电机要么嗡嗡响不动,要么直接烧管子。嗯,咱们一步步来。
4.1 PWM频率与占空比的计算
先搞清楚两个核心参数:频率和占空比。
频率决定了PWM波形的快慢。对于电机控制,频率太低,电机会抖动、发出刺耳噪音;频率太高,开关损耗增大,驱动器发热严重。我个人习惯,直流有刷电机用10kHz-20kHz,步进电机用20kHz-40kHz,无刷电机(BLDC)用16kHz-20kHz。你想想看,20kHz以上人耳就听不到了,电机安静很多。
占空比就是高电平时间占整个周期的比例。占空比50%,意味着一半时间导通,一半时间关断。电机转速和占空比基本成正比——当然,实际还要考虑死区、管压降等因素。
举个例子:
假设PWM频率 f = 20kHz
周期 T = 1/f = 50μs
如果占空比 D = 60%
则高电平时间 Ton = T × D = 50μs × 0.6 = 30μs
低电平时间 Toff = T - Ton = 20μs
我在项目中遇到过一个问题:客户要求电机在1%占空比下启动,结果电机根本不转。为什么?因为占空比太低,有效电压不足以克服静摩擦力。后来我加了个软启动策略,从10%开始慢慢往上加,问题就解决了。
4.2 定时器配置
PWM的核心是定时器。以STM32为例,高级定时器TIM1和TIM8支持互补输出和死区插入,最适合电机控制。
配置步骤其实不复杂:
- 选择定时器时钟源:一般用内部时钟,比如72MHz
- 设置预分频器(PSC):降低时钟频率到合适范围
- 设置自动重装载值(ARR):决定PWM频率
- 设置比较值(CCR):决定占空比
- 配置输出模式:PWM1或PWM2,极性选择
- 使能互补输出和死区:如果驱动H桥或三相逆变器
代码示例(STM32 HAL库):
// 定时器初始化结构体
TIM_HandleTypeDef htim1;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
htim1.Init.Period = 50 - 1; // 1MHz / 50 = 20kHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim1);
// PWM通道配置
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 25; // 占空比 25/50 = 50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
// 启动PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 互补输出
这里有个细节:预分频器和自动重装载值怎么搭配?我建议先确定目标频率,再反推参数。比如要20kHz,定时器时钟1MHz,那么ARR = 1MHz / 20kHz - 1 = 49。说白了,ARR+1就是计数值,决定了频率。
4.3 死区时间设置
死区时间,这是电机驱动里最容易忽略但又最关键的一环。
为什么要死区?因为MOS管或IGBT开关需要时间。如果上管还没完全关断,下管就导通了,那就直通短路——电流瞬间飙升,管子直接冒烟。我刚开始做驱动时,就烧过好几个MOS管,后来才明白死区的重要性。
死区时间怎么算?
死区时间 ≥ 关断延迟时间 + 开通延迟时间 + 安全裕量
一般MOS管的关断延迟在50ns-200ns,开通延迟在20ns-100ns。安全裕量我习惯加50%。所以:
死区时间 ≈ (200ns + 100ns) × 1.5 = 450ns
实际工程中,我常用500ns-1μs。频率越高,死区时间要越短,否则占空比损失太大。比如20kHz下,1μs死区占整个周期的2%,意味着最大占空比只能到98%。
STM32配置死区:
// 死区时间寄存器设置
// 以TIM1为例,BDTR寄存器的DTG[7:0]位
// 死区时间 = DTG × Tdtg,其中Tdtg = 1/定时器时钟
// 假设定时器时钟1MHz,Tdtg = 1μs
// 要设置500ns死区,DTG = 0.5μs / 1μs = 0.5 → 取整为1
// 实际死区 = 1 × 1μs = 1μs
// HAL库配置
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_ENABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_ENABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 1; // 死区时间1μs
sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
4.4 PWM波形测量与验证
代码写完了,参数设好了,怎么知道对不对?用示波器看。
测量步骤:
- 探头连接:地线夹接GND,探头接PWM输出引脚。注意地线要短,否则会引入噪声。
- 设置时基:根据频率调整。20kHz周期50μs,时基设10μs/div比较合适。
- 触发设置:边沿触发,上升沿,触发电平设在1.65V(3.3V系统的一半)。
- 测量参数:频率、占空比、上升时间、下降时间、死区时间。
验证要点:
- 频率是否准确:示波器测出的频率和设定值偏差应在1%以内
- 占空比是否线性:从0%到100%逐点测试,看实际占空比和设定值是否一致
- 死区时间是否足够:用示波器的光标测量两路互补信号之间的间隔
- 波形是否干净:有没有过冲、振铃、毛刺
我遇到过最典型的问题:示波器一看,PWM波形上有高频振荡。后来发现是探头地线太长,形成了环路天线。换成短地线弹簧夹,波形立马干净了。你想想看,有时候问题不在电路,在测量方法。
最后说一句:PWM调试,说白了就是频率、占空比、死区三个参数的平衡。频率高了开关损耗大,低了噪音大;占空比大了力矩足但发热多;死区长了安全但效率低。没有绝对正确的参数,只有最适合你应用的参数。多试几次,多看看波形,慢慢就有感觉了。