第 5 章 SVPWM 空间矢量调制:FOC 的最后一块拼图
前言
前面四章,我们已经完成了:
- 六步换相实战
- Clark 变换:三相 → 两相静止 αβ
- Park 变换:交流 → 直流 dq
- 电流 PI 闭环:得到 Vd,Vq
- 反 Park 变换:得到控制电压 Vα,Vβ
现在我们手里有了α-β 坐标系下的电压矢量,但单片机最终要输出的是U、V、W 三相 PWM 占空比。
把电压矢量翻译成三相 PWM 的过程,就是SVPWM(Space Vector Pulse Width Modulation)。
它是 FOC 控制的最后、最工程化的一步,也是决定电机:
- 力矩平滑度
- 低速噪音
- 电流谐波
- 电压利用率
的关键环节。
1. 什么是电压矢量?(极简理解)
三相逆变器一共 6 个开关管,只有 8 种有效开关组合:
- 6 个非零电压矢量(形成磁场旋转)
- 2 个零矢量(电压为 0,电机不产生力矩)
SVPWM 的核心思想一句话:
用 8 个基本矢量,按不同时间长短组合,合成出我们想要的任意电压矢量 Vref(α,β)
就像用红、绿、蓝三原色混合出任意颜色一样。
2. 六个扇区与基本电压矢量
把 360° 电角度平分为6 个扇区,每个 60°:
- 扇区 1:0° ~ 60°
- 扇区 2:60° ~ 120°
- 扇区 3:120° ~ 180°
- 扇区 4:180° ~ 240°
- 扇区 5:240° ~ 300°
- 扇区 6:300° ~ 360°
每个扇区使用相邻两个有效矢量 + 零矢量来合成目标电压。
电压利用率对比:
- 普通 SPWM:≈ 86.6%
- SVPWM:≈ 105%(同等母线电压下,电机能输出更高力矩)
这就是 FOC 电机高速更有劲的原因。
3. SVPWM 标准工程实现流程(固定 7 步)
工程上 SVPWM 永远按这 7 步走,一步都不能乱:
- 输入:Vα,Vβ
- 计算三个中间变量:Uref1,Uref2,Uref3
- 判断当前矢量在哪个扇区(1~6)
- 计算两个有效矢量的作用时间 T1,T2
- 限幅处理,防止过调制
- 根据扇区计算三相比较值 CMRV、CMRV、CMRW
- 装入 TIM1 捕获比较寄存器,输出 PWM
下面直接给出每一步的工程公式与代码。
4. 扇区判断公式(最常用标准形式)
定义:
根据三者符号判断扇区:
- 若 A>0 → 记为 1
- 若 B>0 → 记为 2
- 若 C>0 → 记为 4
相加得到sector = 1~6
这是工业电机库最通用的扇区判断法,没有之一。
5. 作用时间 T1、T2 计算
其中:
- Udc:母线电压
- PWM_PERIOD:定时器重装载值(ARR)
然后必须做饱和限制:
防止过调制导致波形畸变。
6. 三相占空比计算(核心)
根据不同扇区,三相中点对称 PWM 比较值如下:
扇区 1:
扇区 2:
扇区 3:
扇区 4:
扇区 5:
扇区 6:
最终:
直接写入定时器寄存器即可输出 SVPWM。
7. 完整工程代码(浮点版)
#include "math.h" #define PWM_PERIOD 7200 // 10kHz, 72MHz #define U_DC 24.0f // 母线电压24V typedef struct { float Valpha; float Vbeta; uint16_t CCRU; uint16_t CCRV; uint16_t CCRW; }SVPWM_Struct; SVPWM_Struct svpwm; /** * @brief SVPWM 工程实现完整版 * @param Valpha,Vbeta: 反Park输出电压 */ void SVPWM_Calc(float Valpha, float Vbeta) { float Uref1, Uref2, Uref3; float T1, T2, T; float Ta, Tb, Tc; uint8_t sector = 0; // 1. 计算扇区判断变量 Uref1 = Vbeta; Uref2 = (sqrtf(3.0f)*0.5f)*Valpha - 0.5f*Vbeta; Uref3 = (-sqrtf(3.0f)*0.5f)*Valpha - 0.5f*Vbeta; // 2. 扇区判断 sector = 0; if(Uref1 > 0) sector += 1; if(Uref2 > 0) sector += 2; if(Uref3 > 0) sector += 4; // 3. 计算作用时间 T = (sqrtf(3.0f) * PWM_PERIOD) / U_DC; T1 = T * Uref3 * (sqrtf(3.0f)/3.0f); T2 = T * Uref2 * (sqrtf(3.0f)/3.0f); // 4. 过调制限制 if(T1 + T2 > PWM_PERIOD) { float k = PWM_PERIOD / (T1 + T2); T1 *= k; T2 *= k; } // 5. 根据扇区计算三相占空比 switch(sector) { case 1: Ta = (PWM_PERIOD - T1 - T2)/4.0f; Tb = Ta + T1/2.0f; Tc = Tb + T2/2.0f; break; case 2: Ta = (PWM_PERIOD - T1 - T2)/4.0f; Tc = Ta + T1/2.0f; Tb = Tc + T2/2.0f; break; case 3: Tb = (PWM_PERIOD - T1 - T2)/4.0f; Tc = Tb + T1/2.0f; Ta = Tc + T2/2.0f; break; case 4: Tb = (PWM_PERIOD - T1 - T2)/4.0f; Ta = Tb + T1/2.0f; Tc = Ta + T2/2.0f; break; case 5: Tc = (PWM_PERIOD - T1 - T2)/4.0f; Ta = Tc + T1/2.0f; Tb = Ta + T2/2.0f; break; case 6: Tc = (PWM_PERIOD - T1 - T2)/4.0f; Tb = Tc + T1/2.0f; Ta = Tb + T2/2.0f; break; default: Ta = Tb = Tc = PWM_PERIOD/2; break; } // 输出限幅 & 赋值 if(Ta < 0) Ta = 0; if(Ta > PWM_PERIOD) Ta = PWM_PERIOD; if(Tb < 0) Tb = 0; if(Tb > PWM_PERIOD) Tb = PWM_PERIOD; if(Tc < 0) Tc = 0; if(Tc > PWM_PERIOD) Tc = PWM_PERIOD; svpwm.CCRU = (uint16_t)Ta; svpwm.CCRV = (uint16_t)Tb; svpwm.CCRW = (uint16_t)Tc; // 直接更新PWM占空比 TIM1->CCR1 = svpwm.CCRU; TIM1->CCR2 = svpwm.CCRV; TIM1->CCR3 = svpwm.CCRW; }8. 如何在完整 FOC 中调用 SVPWM?
完整调用链:
// 1. 采样电流 Iu = Get_ADC_CurrentU() - OFFSET; Iv = Get_ADC_CurrentV() - OFFSET; Iw = -Iu - Iv; // 2. Clark Clark_Transform(Iu, Iv, Iw, &Ialpha, &Ibeta); // 3. Park Park_Transform(Ialpha, Ibeta, theta, &Id, &Iq); // 4. PI电流环 Vd = PI_Calc(&PID_Id, 0.0f, Id); Vq = PI_Calc(&PID_Iq, Iq_ref, Iq); // 5. 反Park Inv_Park(Vd, Vq, theta, &Valpha, &Vbeta); // 6. SVPWM 输出 SVPWM_Calc(Valpha, Vbeta);一个周期走完,电机就进入真正的 FOC 闭环运行。
9. SVPWM 工程常见坑(必看)
9.1 母线电压未实时采样
如果电池电压波动,SVPWM 会过调制或欠调制,电机抖动。解决:实时采样 Udc 带入计算。
9.2 PWM 中心对齐模式没开
SVPWM 必须用中心对齐 PWM,否则波形不对称、噪音大。
9.3 扇区判断符号错误
只要有一个符号取反,整个 6 个扇区全乱,电机直接啸叫。
9.4 死区设置不合理
死区太小炸管,太大力矩弱、低速抖。建议:1~3μs。
9.5 角度 θ 不同步
ADC 采样、运算、PWM 更新之间有延迟,角度必须做相位补偿,否则低速抖动明显。
10. 本章总结
SVPWM 是 FOC 的最终输出环节,核心就 3 点:
- 8 个基本矢量合成任意电压矢量
- 工程流程固定:扇区判断 → 时间计算 → 三相占空比
- 代码直接套用即可,重点是坐标系统一、角度同步、限幅保护
到本章为止,FOC 全套核心算法已经全部讲完:
- 坐标变换
- 电流闭环
- 空间矢量调制
你已经具备写出完整 FOC 电机驱动的全部理论与代码基础。