3. SVPWM原理与实现:空间矢量调制、扇区判断、作用时间计算、七段式与五段式SVPWM、代码实现
SVPWM,全称 Space Vector Pulse Width Modulation,空间矢量脉宽调制。说白了,它就是把逆变器的六个开关管,通过特定的组合,合成一个我们想要的电压矢量。
我刚开始接触FOC时,总觉得SVPWM很神秘。后来自己手写了一遍代码,才发现它其实就是一套固定的几何计算。你只要掌握了扇区判断和时间计算,剩下的就是套公式。
3.1 空间矢量调制原理
三相逆变器有6个开关管,上下桥臂互补导通。一共8种开关状态:6个非零矢量(V1~V6)和2个零矢量(V0、V7)。
这6个非零矢量,在空间上相隔60°,形成一个正六边形。我们的目标电压矢量,就在这个六边形内部旋转。
你看上图,Vref 落在扇区 I,它就可以由 V1 和 V2 合成。这就是 SVPWM 的几何本质——伏秒平衡。
3.2 扇区判断
怎么判断 Vref 在哪个扇区?我见过有人用查表法,也有人用坐标比较法。我个人习惯用 三值判断法,代码量小,速度快。
先定义三个中间变量:
B1 = Ubeta
B2 = ( sqrt(3)*Ualpha - Ubeta ) / 2
B3 = ( -sqrt(3)*Ualpha - Ubeta ) / 2
然后根据 B1、B2、B3 的符号,就能确定扇区:
| B1 | B2 | B3 | 扇区 |
|---|---|---|---|
| + | + | - | I |
| + | - | - | II |
| + | - | + | III |
| - | - | + | IV |
| - | + | + | V |
| - | + | - | VI |
sector = sign(B1) + 2*sign(B2) + 4*sign(B3),然后查表映射到 1~6。这样省去了 if-else 嵌套,流水线执行更快。
3.3 作用时间计算
确定了扇区,下一步就是算两个相邻矢量的作用时间 T1 和 T2。以扇区 I 为例:
T1 = ( sqrt(3)*Ualpha - Ubeta ) / 2 * Ts / Vdc
T2 = Ubeta * Ts / Vdc
其中 Ts 是 PWM 周期,Vdc 是母线电压。
其他扇区的时间计算,本质上就是坐标旋转。我建议你直接查表,或者用统一的公式:
X = Ubeta * Ts / Vdc
Y = ( sqrt(3)*Ualpha + Ubeta ) / 2 * Ts / Vdc
Z = ( -sqrt(3)*Ualpha + Ubeta ) / 2 * Ts / Vdc
然后根据扇区查表:
| 扇区 | T1 | T2 |
|---|---|---|
| I | -Z | X |
| II | Z | Y |
| III | X | -Y |
| IV | -X | Z |
| V | -Y | -Z |
| VI | Y | -X |
T1 = T1 * Ts / (T1+T2),T2 = T2 * Ts / (T1+T2)。我曾经在项目里忘了做这个处理,结果电机在高速时电流波形畸变严重,找了半天才发现是过调制没处理。
3.4 七段式与五段式SVPWM
时间算完了,怎么安排开关顺序?这里有两种主流方式。
七段式 SVPWM
每个 PWM 周期内,开关状态按 000 → 100 → 110 → 111 → 110 → 100 → 000 的顺序切换。每次只切换一个开关管,谐波小,但开关次数多,损耗大。
以扇区 I 为例,三相占空比:
Ta = (Ts - T1 - T2) / 4
Tb = Ta + T1/2
Tc = Tb + T2/2
五段式 SVPWM
去掉中间的零矢量 111,只保留 000。开关顺序变成 000 → 100 → 110 → 000。开关次数减少 1/3,损耗降低,但谐波会大一些。
五段式的占空比计算也简单:
Ta = (Ts - T1 - T2) / 2
Tb = Ta + T1
Tc = Tb + T2
| 对比项 | 七段式 | 五段式 |
|---|---|---|
| 开关次数 | 多(7次) | 少(5次) |
| 谐波含量 | 低 | 高 |
| 开关损耗 | 大 | 小 |
| 适用场景 | 低中速、对噪声敏感 | 高速、对效率要求高 |
3.5 代码实现
下面给出一段完整的 SVPWM 实现代码。我习惯用浮点运算,方便调试。量产时你可以转成定点数。
// 输入:Ualpha, Ubeta(标幺值,范围 -1~1)
// 输出:Ta, Tb, Tc(占空比,范围 0~1)
void svpwm(float Ualpha, float Ubeta, float *Ta, float *Tb, float *Tc)
{
float Ts = 1.0f; // 归一化周期
float Vdc = 1.0f; // 归一化母线电压
// 1. 扇区判断
float B1 = Ubeta;
float B2 = (1.732f * Ualpha - Ubeta) * 0.5f;
float B3 = (-1.732f * Ualpha - Ubeta) * 0.5f;
int sector = 0;
if (B1 > 0) sector += 1;
if (B2 > 0) sector += 2;
if (B3 > 0) sector += 4;
// 扇区映射表
const int sector_map[8] = {0, 1, 5, 0, 3, 2, 4, 0};
sector = sector_map[sector];
// 2. 计算 X, Y, Z
float X = Ubeta * Ts / Vdc;
float Y = (1.732f * Ualpha + Ubeta) * 0.5f * Ts / Vdc;
float Z = (-1.732f * Ualpha + Ubeta) * 0.5f * Ts / Vdc;
// 3. 查表得到 T1, T2
float T1 = 0, T2 = 0;
switch (sector) {
case 1: T1 = -Z; T2 = X; break;
case 2: T1 = Z; T2 = Y; break;
case 3: T1 = X; T2 = -Y; break;
case 4: T1 = -X; T2 = Z; break;
case 5: T1 = -Y; T2 = -Z; break;
case 6: T1 = Y; T2 = -X; break;
}
// 4. 过调制处理
if (T1 + T2 > Ts) {
float scale = Ts / (T1 + T2);
T1 *= scale;
T2 *= scale;
}
// 5. 七段式占空比计算
float Ta_on = (Ts - T1 - T2) * 0.25f;
float Tb_on = Ta_on + T1 * 0.5f;
float Tc_on = Tb_on + T2 * 0.5f;
// 6. 根据扇区分配三相占空比
switch (sector) {
case 1: *Ta = Ta_on; *Tb = Tb_on; *Tc = Tc_on; break;
case 2: *Ta = Tb_on; *Tb = Ta_on; *Tc = Tc_on; break;
case 3: *Ta = Tc_on; *Tb = Ta_on; *Tc = Tb_on; break;
case 4: *Ta = Tc_on; *Tb = Tb_on; *Tc = Ta_on; break;
case 5: *Ta = Tb_on; *Tb = Tc_on; *Tc = Ta_on; break;
case 6: *Ta = Ta_on; *Tb = Tc_on; *Tc = Tb_on; break;
}
}
嗯,SVPWM 的核心就这些。你把它跑通了,FOC 的底层驱动就算拿下了。剩下的就是调 PI 参数,让电机转得又稳又顺。