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

说到FOC,SVPWM是绕不开的核心环节。说白了,它就是告诉逆变器该怎么开关,才能让电机转得又稳又顺。我刚开始接触这个的时候,觉得这东西玄乎得很,后来亲手调过几版代码,才真正理解了它的妙处。

3.1 空间矢量调制的基本原理

传统的SPWM(正弦脉宽调制)大家应该都熟悉,它让三相电压各自按正弦变化。但SVPWM不一样——它从整体角度出发,把三相看作一个合成矢量在空间旋转。

你想想看,电机定子绕组在空间上相差120度,三相电流产生的合成磁动势,其实就是一个旋转的矢量。SVPWM的目标,就是用八个基本的开关状态,去逼近这个旋转的参考矢量。

核心思想:用有限个离散的电压矢量,通过时间平均的方式,合成任意方向和幅值的期望电压矢量。

这八个基本矢量包括六个非零矢量和两个零矢量(V0和V7)。六个非零矢量把平面分成六个扇区,每个扇区60度。我习惯把这六个矢量记作V1~V6,它们分别对应(100)、(110)、(010)、(011)、(001)、(101)这六种开关组合。

我的小技巧:记不住扇区顺序?你只要记住V1对应A相上桥臂导通,然后按逆时针方向,每次只切换一相开关状态,就能推出剩下的五个矢量。

3.2 扇区判断

拿到参考电压矢量Vref之后,第一件事就是判断它在哪个扇区。我在项目中遇到过一个问题:扇区判断搞错了,电机直接嗡嗡响就是不转。后来排查了半天,发现是判断逻辑写反了。

扇区判断通常用反Clark变换后的Uα和Uβ来计算。具体做法是这样的:

  1. 定义三个中间变量:
    B1 = Uβ
    B2 = (√3/2) * Uα - (1/2) * Uβ
    B3 = -(√3/2) * Uα - (1/2) * Uβ
  2. 根据B1、B2、B3的符号,计算扇区号:
    Sector = 4*sign(B3) + 2*sign(B2) + sign(B1)

嗯,这里要注意:sign函数返回1表示正数,返回0表示负数。算出来的Sector值在1~6之间,直接对应六个扇区。

扇区 B1符号 B2符号 B3符号
I + + -
II + - +
III + - -
IV - + +
V - + -
VI - - +

3.3 作用时间计算

扇区确定后,就要算两个相邻非零矢量和零矢量的作用时间了。这个计算基于伏秒平衡原理——说白了,就是让合成效果跟期望的Vref一样。

我一般先算三个通用变量X、Y、Z:

X = (√3 * Ts / Vdc) * Uβ
Y = (√3 * Ts / Vdc) * ( (√3/2)*Uα + (1/2)*Uβ )
Z = (√3 * Ts / Vdc) * ( -(√3/2)*Uα + (1/2)*Uβ )

然后根据扇区查表,得到T1和T2:

扇区 T1 T2
I Z Y
II Y -X
III -Z X
IV -X Z
V X -Y
VI -Y -Z

注意:如果T1+T2 > Ts,说明参考矢量超出了六边形内切圆,需要做过调制处理。我一般直接按比例缩放:T1' = T1 * Ts / (T1+T2),T2' = T2 * Ts / (T1+T2)。

零矢量时间T0 = Ts - T1 - T2,通常平均分配给V0和V7,这样能减少谐波。

3.4 代码实现

下面是我在实际项目中用过的SVPWM核心代码,C语言写的,跑在STM32上。我习惯把扇区判断和时间计算放在一个函数里,这样调用起来方便。

void svpwm_calc(SVPWM_Handle *h)
{
    float Ualpha = h->Ualpha;
    float Ubeta  = h->Ubeta;
    float Ts     = h->Ts;
    float Vdc    = h->Vdc;

    // 1. 扇区判断
    float B1 = Ubeta;
    float B2 = 0.866025f * Ualpha - 0.5f * Ubeta;  // √3/2 ≈ 0.866
    float B3 = -0.866025f * Ualpha - 0.5f * Ubeta;

    uint8_t sector = 0;
    if (B1 > 0) sector |= 1;
    if (B2 > 0) sector |= 2;
    if (B3 > 0) sector |= 4;
    // sector值映射到1~6
    static const uint8_t sector_map[] = {0,1,5,0,3,2,4,0};
    sector = sector_map[sector];

    // 2. 计算X,Y,Z
    float K = 1.7320508f * Ts / Vdc;  // √3
    float X = K * Ubeta;
    float Y = K * (0.866025f * Ualpha + 0.5f * Ubeta);
    float Z = K * (-0.866025f * Ualpha + 0.5f * Ubeta);

    // 3. 查表得到T1,T2
    float T1, T2;
    switch(sector)
    {
        case 1: T1 = Z;  T2 = Y;  break;
        case 2: T1 = Y;  T2 = -X; break;
        case 3: T1 = -Z; T2 = X;  break;
        case 4: T1 = -X; T2 = Z;  break;
        case 5: T1 = X;  T2 = -Y; break;
        case 6: T1 = -Y; T2 = -Z; break;
        default: T1 = 0; T2 = 0; break;
    }

    // 4. 过调制处理
    float sum = T1 + T2;
    if (sum > Ts) {
        float scale = Ts / sum;
        T1 *= scale;
        T2 *= scale;
    }

    // 5. 计算占空比(七段式SVPWM)
    float T0 = Ts - T1 - T2;
    float Ta = T0 * 0.25f;
    float Tb = Ta + T1 * 0.5f;
    float Tc = Tb + T2 * 0.5f;

    // 根据扇区分配三相占空比
    switch(sector)
    {
        case 1: h->DutyA = Tb; h->DutyB = Ta; h->DutyC = Tc; break;
        case 2: h->DutyA = Ta; h->DutyB = Tc; h->DutyC = Tb; break;
        case 3: h->DutyA = Ta; h->DutyB = Tb; h->DutyC = Tc; break;
        case 4: h->DutyA = Tc; h->DutyB = Tb; h->DutyC = Ta; break;
        case 5: h->DutyA = Tc; h->DutyB = Ta; h->DutyC = Tb; break;
        case 6: h->DutyA = Tb; h->DutyB = Tc; h->DutyC = Ta; break;
    }
}

避坑指南:我曾经在扇区映射表上栽过跟头。不同文献里扇区编号方式不一样,有的从0开始,有的从1开始。建议你拿到代码后,先用仿真验证一下扇区判断是否正确。

3.5 知识体系总览

下面这张图是我自己整理的SVPWM知识结构,从原理到实现一目了然。你可以把它当作一个快速索引,哪里不懂就回头看对应的章节。

SVPWM知识体系 SVPWM 空间矢量调制 基本原理 工程实现 八个基本矢量 伏秒平衡 扇区判断 时间计算 B1/B2/B3计算 扇区映射表 X/Y/Z计算 过调制处理 三相占空比输出

这张图把SVPWM的整个流程串起来了。从基本原理出发,经过扇区判断、时间计算,最终输出三相占空比。我个人觉得,理解了这个流程,SVPWM就算入门了。

实际调试的时候,我建议你先用开环方式验证SVPWM输出是否正确。给一个固定的Uα和Uβ,看看三相占空比是不是按预期变化。这一步走通了,后面闭环控制才有基础。


公众号:蓝海资料掘金营,微信deep3321