3. SVPWM原理与实现:空间矢量调制、扇区判断、作用时间计算、七段式与五段式SVPWM、代码实现

SVPWM,全称 Space Vector Pulse Width Modulation,空间矢量脉宽调制。说白了,它就是把逆变器的六个开关管,通过特定的组合,合成一个我们想要的电压矢量。

我刚开始接触FOC时,总觉得SVPWM很神秘。后来自己手写了一遍代码,才发现它其实就是一套固定的几何计算。你只要掌握了扇区判断和时间计算,剩下的就是套公式。

核心思想: 用相邻的两个非零电压矢量,加上零矢量,来合成任意方向、任意大小的电压矢量。

3.1 空间矢量调制原理

三相逆变器有6个开关管,上下桥臂互补导通。一共8种开关状态:6个非零矢量(V1~V6)和2个零矢量(V0、V7)。

这6个非零矢量,在空间上相隔60°,形成一个正六边形。我们的目标电压矢量,就在这个六边形内部旋转。

SVPWM 空间矢量与扇区分布 扇区 I 扇区 II 扇区 III 扇区 IV 扇区 V 扇区 VI V1 (100) V2 (110) V3 (010) V4 (011) V5 (001) V6 (101) Vref Vref 由相邻两个非零矢量合成 例如扇区 I:Vref = T1·V1 + T2·V2

你看上图,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

然后根据扇区查表:

扇区T1T2
I-ZX
IIZY
IIIX-Y
IV-XZ
V-Y-Z
VIY-X
注意过调制: 如果 T1 + T2 > Ts,说明 Vref 超出了六边形内切圆。这时候必须等比例缩小: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%,而且噪声变化人耳几乎听不出来。

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;
    }
}
踩坑提醒: 我曾经在 STM32 上直接跑这段代码,发现电机低速时抖动。后来查出来是浮点运算精度问题。建议在低速时用 double,或者把 Ualpha/Ubeta 放大到 Q15 格式再算。

嗯,SVPWM 的核心就这些。你把它跑通了,FOC 的底层驱动就算拿下了。剩下的就是调 PI 参数,让电机转得又稳又顺。