PWM 与三相逆变桥原理:从硬件到寄存器
系列:电机控制系列 - 第 3 篇 目标平台:STM32F407ZGT6 阅读时间:30 分钟 前置知识:电路基础、电机基础
前言
你可能在想:"PWM 不就是调节占空比吗?有什么难的?"
但在电机控制中,PWM 没那么简单:
- ❌ 死区时间不够 → MOSFET 烧毁
- ❌ PWM 频率太低 → 电机噪声大、转矩脉动
- ❌ 死区补偿不足 → 电流畸变、低速性能差
本篇目标:彻底理解 PWM 与三相逆变桥,掌握 STM32 寄存器级配置。
一、PWM 基础原理
1.1 什么是 PWM?
PWM(Pulse Width Modulation):脉冲宽度调制
占空比 D = 高电平时间 / 周期 × 100%
┌───┐ ┌───┐ ┌───┐
│ │ │ │ │ │
─────┘ └───┘ └───┘ └───
└─Ton─┘
└───── T ─────┘
平均电压 U_avg = D × U_dc例子:
- 母线电压 24V
- 占空比 50%
- 平均电压 = 0.5 × 24V = 12V
1.2 PWM 参数
频率(Frequency)
f = 1 / T
电机控制常用频率:
- 低速应用:10-20 kHz
- 高速应用:30-50 kHz
- 超高频率(SiC/GaN):100-500 kHz频率选择权衡:
- ✅ 频率高 → 纹波小、噪声低
- ❌ 频率高 → 开关损耗大、发热
分辨率(Resolution)
分辨率 = 母线电压 / PWM 级数
STM32F407:
- 定时器时钟 84 MHz
- PWM 频率 20 kHz
- 分辨率 = 84MHz / 20kHz = 4200 级
精度 = 24V / 4200 ≈ 5.7 mV1.3 单极性 vs 双极性 PWM
单极性 PWM
相电压在 0 ~ U_dc 之间切换
优点:
✅ 开关损耗小(只开关一半)
✅ 纹波小
缺点:
❌ 需要复杂的调制策略双极性 PWM
相电压在 +U_dc/2 ~ -U_dc/2 之间切换
优点:
✅ 控制简单
✅ 动态响应快
缺点:
❌ 开关损耗大(翻倍)
❌ 纹波大电机控制常用:单极性 SVPWM
二、三相全桥逆变器
2.1 拓扑结构
U_dc (+)
│
┌────┴────┬────┴────┬────┴────┐
│ │ │ │
Q1 Q3 Q5 │
│ │ │ │
├────A────┼────B────┼────C────┤
│ │ │ │
Q4 Q6 Q2 │
│ │ │ │
└────┬────┴────┬────┴────┬────┘
│ │ │
U_dc (-)
6 个 MOSFET(或 IGBT):
- 上桥臂:Q1, Q3, Q5
- 下桥臂:Q4, Q6, Q2
- 同一相上下桥臂互补(不能同时导通)2.2 八种开关状态
开关状态(Q1Q3Q5):
┌─────┬───────┬──────────┬────────┐
│状态 │ Q1 Q3 Q5 │ 输出电压矢量 │ 备注 │
├─────┼───────┼──────────┼────────┤
│ V0 │ 0 0 0 │ (0, 0) │ 零矢量 │
│ V1 │ 1 0 0 │ (2/3·Udc, 0) │ 有效 │
│ V2 │ 1 1 0 │ (1/3·Udc, √3/3·Udc) │ 有效 │
│ V3 │ 0 1 0 │ (-1/3·Udc, √3/3·Udc)│ 有效 │
│ V4 │ 0 1 1 │ (-2/3·Udc, 0) │ 有效 │
│ V5 │ 0 0 1 │ (-1/3·Udc, -√3/3·Udc)│ 有效│
│ V6 │ 1 0 1 │ (1/3·Udc, -√3/3·Udc) │ 有效│
│ V7 │ 1 1 1 │ (0, 0) │ 零矢量 │
└─────┴───────┴──────────┴────────┘
注:有效矢量 = 6 个,零矢量 = 2 个2.3 六边形电压轨迹
V3
/\
/ \
/ \
V4 /______\ V2
/ / \
/ / \
/______/_____\
V5 V0/V7 V1
V6
六个有效矢量构成正六边形
SVPWM 目标:用相邻矢量合成任意方向电压三、死区效应
3.1 为什么需要死区?
问题:上下桥臂不能同时导通(直通)
如果 Q1 和 Q4 同时导通:
U_dc (+)
│
Q1 ─┬─ Q4
│ │ │
└──┴──┘
│
U_dc (-)
→ 直通短路!
→ 电流爆炸!
→ MOSFET 烧毁!解决方法:插入死区时间
理想 PWM:
Q1: ────┐ ┌────────
└─────┘
Q4: ──────────┐ ┌──
└─────┘
实际 PWM(带死区):
Q1: ────┐ ┌──────
└───────┘
└死区┘
Q4: ──────────┐ ┌
└───────┘
└死区┘
死区时间:上下桥臂都关断3.2 死区时间设置
死区时间 T_dt 取决于:
1. MOSFET 开关时间(ton, toff)
2. 驱动电路延迟
3. 安全裕量
典型值:
- 小功率 MOSFET:100-500 ns
- 大功率 IGBT:1-3 μsSTM32F407 死区计算:
定时器时钟:84 MHz
DTG[7:0] 寄存器:
DTG[7:5] = 0xx: T_dt = DTG[6:0] × T_dts
DTG[7:5] = 10x: T_dt = (64 + DTG[5:0]) × 2 × T_dts
DTG[7:5] = 110: T_dt = (32 + DTG[4:0]) × 8 × T_dts
DTG[7:5] = 111: T_dt = (32 + DTG[4:0]) × 16 × T_dts
其中 T_dts = 1 / 84MHz ≈ 11.9 ns例子:
// 死区时间 500ns
// 500ns / 11.9ns ≈ 42
// DTG = 42 = 0x2A
TIM1->BDTR = (0x2A << TIM_BDTR_DTG_Pos);3.3 死区对电压的影响
死区导致电压畸变:
理想输出电压:U_ref
实际输出电压:U_ref - ΔU
ΔU = ±(T_dt / T_pwm) × U_dc
当电流 > 0 时:ΔU < 0(电压减小)
当电流 < 0 时:ΔU > 0(电压增加)影响:
- ❌ 低速时电流畸变(死区占比大)
- ❌ 转矩脉动
- ❌ 噪声增大
解决:死区补偿(第 18 篇详细讲解)
四、STM32 PWM 配置(寄存器级)
4.1 TIM1 高级定时器
为什么用 TIM1?
- ✅ 支持互补输出(CH1/CH1N, CH2/CH2N, CH3/CH3N)
- ✅ 支持死区插入
- ✅ 支持刹车功能
- ✅ 适合电机控制
引脚分配:
STM32F407ZGT6:
TIM1_CH1 → PA8 (A 相上桥臂)
TIM1_CH1N → PA7 (A 相下桥臂)
TIM1_CH2 → PA9 (B 相上桥臂)
TIM1_CH2N → PB0 (B 相下桥臂)
TIM1_CH3 → PA10 (C 相上桥臂)
TIM1_CH3N → PB1 (C 相下桥臂)
TIM1_BKIN → PA6 (刹车输入)4.2 完整配置代码
/**
* @file pwm.c
* @brief TIM1 三相 PWM 配置(寄存器级)
* @author 牛马工程师
*/
#include "stm32f4xx.h"
#define PWM_FREQUENCY 20000 // 20kHz
#define TIMER_CLOCK 84000000 // 84MHz
#define DEAD_TIME_NS 500 // 500ns
/**
* @brief GPIO 配置
*/
void PWM_GPIO_Init(void) {
// 使能 GPIO 时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;
// 配置 PA8, PA9, PA10 (TIM1 CH1/CH2/CH3)
GPIOA->MODER |= (2 << GPIO_MODER_MODE8_Pos) | // 复用功能
(2 << GPIO_MODER_MODE9_Pos) |
(2 << GPIO_MODER_MODE10_Pos);
GPIOA->AFR[1] |= (1 << GPIO_AFRH_AFSEL8_Pos) | // AF1
(1 << GPIO_AFRH_AFSEL9_Pos) |
(1 << GPIO_AFRH_AFSEL10_Pos);
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT8 | GPIO_OTYPER_OT9 | GPIO_OTYPER_OT10); // 推挽
GPIOA->OSPEEDR |= (3 << GPIO_OSPEEDR_OSPEED8_Pos) | // 高速
(3 << GPIO_OSPEEDR_OSPEED9_Pos) |
(3 << GPIO_OSPEEDR_OSPEED10_Pos);
// 配置 PA7, PB0, PB1 (TIM1 CH1N/CH2N/CH3N)
GPIOA->MODER |= (2 << GPIO_MODER_MODE7_Pos);
GPIOA->AFR[0] |= (1 << GPIO_AFRL_AFSEL7_Pos);
GPIOA->OTYPER &= ~GPIO_OTYPER_OT7;
GPIOA->OSPEEDR |= (3 << GPIO_OSPEEDR_OSPEED7_Pos);
GPIOB->MODER |= (2 << GPIO_MODER_MODE0_Pos) | (2 << GPIO_MODER_MODE1_Pos);
GPIOB->AFR[0] |= (1 << GPIO_AFRL_AFSEL0_Pos) | (1 << GPIO_AFRL_AFSEL1_Pos);
GPIOB->OTYPER &= ~(GPIO_OTYPER_OT0 | GPIO_OTYPER_OT1);
GPIOB->OSPEEDR |= (3 << GPIO_OSPEEDR_OSPEED0_Pos) | (3 << GPIO_OSPEEDR_OSPEED1_Pos);
}
/**
* @brief TIM1 PWM 配置
*/
void TIM1_PWM_Init(void) {
// 1. 使能 TIM1 时钟
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
// 2. 时基配置
uint32_t psc = TIMER_CLOCK / 1000000 - 1; // 1MHz 计数频率
uint32_t arr = 1000000 / PWM_FREQUENCY - 1; // 20kHz → ARR = 49
TIM1->PSC = psc; // 预分频
TIM1->ARR = arr; // 自动重装载值
TIM1->CR1 |= TIM_CR1_ARPE; // ARR 预装载使能
// 3. PWM 模式配置(模式 1:向上计数时 CNT < CCR 输出有效)
TIM1->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos) | TIM_CCMR1_OC1PE; // CH1 PWM 模式 1
TIM1->CCMR1 |= (6 << TIM_CCMR1_OC2M_Pos) | TIM_CCMR1_OC2PE; // CH2
TIM1->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos) | TIM_CCMR2_OC3PE; // CH3
// 4. 使能输出和互补输出
TIM1->CCER |= TIM_CCER_CC1E | TIM_CCER_CC1NE | // CH1 + CH1N
TIM_CCER_CC2E | TIM_CCER_CC2NE | // CH2 + CH2N
TIM_CCER_CC3E | TIM_CCER_CC3NE; // CH3 + CH3N
// 5. 死区配置(500ns)
// T_dts = 1/84MHz = 11.9ns
// DTG = 500ns / 11.9ns = 42
uint8_t dtg = (uint8_t)(DEAD_TIME_NS * 84 / 1000);
TIM1->BDTR |= (dtg << TIM_BDTR_DTG_Pos);
// 6. 刹车配置(可选)
TIM1->BDTR |= TIM_BDTR_BKE | // 刹车使能
TIM_BDTR_AOE | // 自动输出使能
(1 << TIM_BDTR_BKP_Pos); // 刹车极性(低电平有效)
// 7. 主输出使能(MOE)
TIM1->BDTR |= TIM_BDTR_MOE;
// 8. 使能计数器
TIM1->CR1 |= TIM_CR1_CEN;
// 9. 初始化占空比(50%)
TIM1->CCR1 = arr / 2;
TIM1->CCR2 = arr / 2;
TIM1->CCR3 = arr / 2;
}
/**
* @brief 更新 PWM 占空比
* @param channel 通道(1/2/3)
* @param duty 占空比(0.0 - 1.0)
*/
void PWM_Set_Duty(uint8_t channel, float duty) {
uint16_t ccr = (uint16_t)(duty * TIM1->ARR);
switch (channel) {
case 1:
TIM1->CCR1 = ccr;
break;
case 2:
TIM1->CCR2 = ccr;
break;
case 3:
TIM1->CCR3 = ccr;
break;
}
}
/**
* @brief 刹车中断(可选)
*/
void TIM1_BRK_TIM9_IRQHandler(void) {
if (TIM1->SR & TIM_SR_BIF) {
// 刹车触发
TIM1->SR &= ~TIM_SR_BIF; // 清除标志
// 关闭所有输出
TIM1->BDTR &= ~TIM_BDTR_MOE;
// 用户处理(报警、停机等)
// ...
}
}4.3 关键寄存器说明
| 寄存器 | 功能 | 关键位 |
|---|---|---|
| CR1 | 控制寄存器 1 | CEN(计数使能)、ARPE(ARR 预装载) |
| PSC | 预分频 | 计数频率 = f\_clk / (PSC + 1) |
| ARR | 自动重装载 | PWM 周期 = ARR × (1/f\_cnt) |
| CCMR1/2 | 捕获/比较模式 | OCxM(PWM 模式)、OCxPE(预装载) |
| CCER | 捕获/比较使能 | CCxE(输出使能)、CCxNE(互补输出使能) |
| CCR1/2/3 | 捕获/比较值 | 占空比 = CCR / ARR |
| BDTR | 刹车和死区 | DTG(死区)、MOE(主输出使能)、BKE(刹车使能) |
| SR | 状态寄存器 | BIF(刹车中断标志) |
五、PWM 测试与调试
5.1 示波器测试
测试点:
- 上桥臂栅极(Q1 Gate)
- 下桥臂栅极(Q4 Gate)
- 相电压(A 相输出)
预期波形:
上桥臂:
┌───┐ ┌───┐
│ │ │ │
┘ └───┘ └───
下桥臂:
─────┐ ┌────
└─────┘
└死区┘
死区时间测量:
光标 1:上桥臂下降沿
光标 2:下桥臂上升沿
Δt ≈ 500ns5.2 常见问题
问题 1:PWM 无输出
可能原因:
- ❌ MOE 位未置 1
- ❌ GPIO 复用配置错误
- ❌ 时钟未使能
解决方法:
// 检查 MOE 位
if (!(TIM1->BDTR & TIM_BDTR_MOE)) {
TIM1->BDTR |= TIM_BDTR_MOE;
}
// 检查 GPIO
if ((GPIOA->AFR[1] & GPIO_AFRH_AFSEL8) == 0) {
// 重新配置
}问题 2:死区时间不对
可能原因:
- ❌ DTG 计算错误
- ❌ 定时器时钟频率不对
解决方法:
// 重新计算
// 假设定时器时钟 84MHz,死区 1us
// DTG = 1us / 11.9ns = 84
// DTG[7:0] = 84 = 0x54
// 但 DTG[7:5] = 0xx 时,最大 127 × 11.9ns = 1.51us
// 所以直接用 84
TIM1->BDTR = (TIM1->BDTR & ~TIM_BDTR_DTG) | (84 << TIM_BDTR_DTG_Pos);问题 3:电机抖动
可能原因:
- ❌ 死区时间过大
- ❌ PWM 频率太低
- ❌ 电源纹波大
解决方法:
// 减小死区
TIM1->BDTR = (TIM1->BDTR & ~TIM_BDTR_DTG) | (20 << TIM_BDTR_DTG_Pos); // 238ns
// 提高 PWM 频率
TIM1->ARR = 1000000 / 30000 - 1; // 30kHz六、总结
6.1 核心要点
PWM 基础
- 占空比控制平均电压
- 频率选择:权衡纹波 vs 损耗
- 分辨率:影响控制精度
三相逆变桥
- 6 个 MOSFET,8 种开关状态
- 上下桥臂互补,需死区
- 六边形电压轨迹
死区效应
- 防止直通,保护 MOSFET
- 导致电压畸变(低速影响大)
- 需要补偿
STM32 配置
- TIM1 高级定时器
- 寄存器:PSC、ARR、CCRx、BDTR
- 死区:DTG 寄存器
6.2 下一步
下一篇:电机数学模型:从物理到方程
我们将学习:
- ✅ abc 坐标系下的电压/磁链/转矩方程
- ✅ 离散化与数值实现
- ✅ 参数测量方法
附录:PWM 参数计算工具
Excel 计算器:
输入:
- 母线电压(V)
- PWM 频率(Hz)
- 死区时间(ns)
输出:
- PSC 值
- ARR 值
- DTG 值
- 分辨率(mV)在线工具:
- STM32 PWM Calculator
- https://www.st.com/stm32-pwm-calculator
参考资料
- STM32F407 Reference Manual (RM0090)
- STM32F4 HAL Driver Description
- 《电力电子技术》- 王兆安
- TI Application Note: "Understanding and Applying Dead Time"
版权声明:本文采用 CC BY-NC-SA 4.0 协议,欢迎转载,但请注明出处。
更新日志:
- 2026-03-13:发布第 1 版