4、SVPWM原理:空间矢量调制、扇区判断、作用时间计算

各位同学,咱们今天聊聊SVPWM。说实话,我刚入行那会儿,看到这个缩写就头大。什么空间矢量、扇区判断,感觉像在学天书。但后来在项目里被它折磨过几回,才真正体会到——SVPWM其实就是FOC控制里那个「把理论变成电压」的关键一步。

你想想看,FOC算出来的是VdVq,但电机要的是三相电压VaVbVc。怎么变?SVPWM就是干这个的。它不光能变,还能让母线电压利用率比SPWM高15%左右。嗯,这个数字我记得很清楚,因为当年选型时算过一笔账。

4.1 空间矢量调制的基本思想

先说说什么是「空间矢量」。说白了,就是把三相电压看成一个旋转的矢量。这个矢量在复平面上转,每转一圈,电机就跟着转一圈。理想情况下,我们希望这个矢量画出一个圆——这样磁场最均匀,转矩脉动最小。

但问题是,逆变器只有8种开关状态(6个有效矢量和2个零矢量)。你没法直接输出一个连续旋转的矢量。怎么办?用平均值等效。在一个PWM周期里,用相邻的两个有效矢量和零矢量拼凑,让它们的合成效果等于目标矢量。

核心思想:「伏秒平衡」——在一个开关周期内,用有限个基本电压矢量的组合,去逼近任意方向、任意大小的目标电压矢量。

我在项目中遇到过一个问题:有人觉得SVPWM和SPWM是两码事。其实它们本质上是相通的。SVPWM的调制波里隐含了三次谐波,这恰好是它母线利用率高的原因。你如果拿示波器看SVPWM的相电压波形,会发现它不是纯正弦——但线电压是正弦的。这个细节,很多教材没讲透。

4.2 扇区判断

好,现在我们知道要用相邻矢量去合成目标矢量。但问题来了:目标矢量落在哪个扇区?你得先知道它在哪,才能选哪两个矢量去合成。

扇区判断的方法有很多。我个人习惯用的符号和大小关系来判断。具体来说,定义三个中间变量:

B1 = Vβ
B2 = (√3/2)*Vα - (1/2)*Vβ
B3 = -(√3/2)*Vα - (1/2)*Vβ

然后根据这三个值的符号,查表得到扇区号:

B1B2B3扇区
+--I
++-II
-+-III
-++IV
--+V
+-+VI

还有一种更直接的方法:用的反正切算角度,然后除以60°取整。但我不推荐这么做——反正切计算在MCU上太慢了,尤其是低端芯片。你想想看,每个PWM周期都要算一次,CPU时间全耗在这上面了。

我的经验:扇区判断用查表法最快。先把量化成几个离散值,然后直接查表。我在STM32F103上做过测试,查表法比计算法快了将近5倍。

4.3 作用时间计算

扇区判断完了,接下来就是算两个相邻矢量的作用时间。假设目标矢量在扇区I,相邻矢量是U4(100)U6(110)。根据伏秒平衡:

T4 = (√3 * Ts / Vdc) * ( (√3/2)*Vα - (1/2)*Vβ )
T6 = (√3 * Ts / Vdc) * Vβ
T0 = Ts - T4 - T6

其中Ts是PWM周期,Vdc是母线电压。注意,这里有个坑:如果T4 + T6 > Ts,说明目标矢量超出了六边形的内切圆——也就是过调制了。这时候需要做等比例缩小:

if (T4 + T6 > Ts) {
    T4 = T4 * Ts / (T4 + T6);
    T6 = T6 * Ts / (T4 + T6);
    T0 = 0;
}

我曾经在一个项目中忽略了过调制处理,结果电机在高转速时电流波形畸变得厉害,还伴随着刺耳的啸叫声。排查了两天才发现是这里的问题。嗯,从那以后,我每次写SVPWM代码都会先检查过调制保护。

其他扇区的计算公式类似,只是系数和符号不同。你可以把五个扇区的公式都推导一遍,然后统一成一个通用公式。我习惯用查表法来存这些系数,代码看起来更整洁。

4.4 七段式与五段式SVPWM

作用时间算完了,接下来要考虑怎么安排开关序列。常见的有两种方式:七段式和五段式。

七段式:每个PWM周期内,开关切换7次。优点是谐波小,电流纹波小。缺点是开关损耗大。适合对电流质量要求高的场合,比如伺服驱动。

五段式:每个PWM周期内,开关切换5次。开关损耗比七段式低约30%。但谐波会大一些。适合对效率要求高的场合,比如电动车。

我在做一款低速大扭矩的永磁同步电机驱动器时,用的就是七段式。因为低速时开关频率可以设得比较低(比如5kHz),开关损耗不是主要矛盾,电流质量才是。但后来做高速电机时,开关频率上到20kHz,不得不换成五段式——不然散热器扛不住。

注意:五段式SVPWM在切换时,有一相会保持不动(不切换)。这会导致该相电流纹波比其他两相大。如果电机对三相平衡要求很高,慎用五段式。

4.5 代码实现要点

最后,我给大家一个SVPWM的代码框架。实际项目中,你只需要把扇区判断和作用时间计算填进去就行:

void svpwm_calc(float V_alpha, float V_beta, float Vdc, float Ts) {
    // 1. 扇区判断
    int sector = calc_sector(V_alpha, V_beta);
    
    // 2. 计算中间变量X, Y, Z
    float X = (sqrt3 * Ts / Vdc) * V_beta;
    float Y = (sqrt3 * Ts / Vdc) * (0.5*V_beta + 0.866*V_alpha);
    float Z = (sqrt3 * Ts / Vdc) * (0.5*V_beta - 0.866*V_alpha);
    
    // 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;
    }
    
    // 4. 过调制处理
    if (T1 + T2 > Ts) {
        T1 = T1 * Ts / (T1 + T2);
        T2 = T2 * Ts / (T1 + T2);
    }
    
    // 5. 计算三相占空比(七段式)
    float T0 = Ts - T1 - T2;
    float Ta = T0 / 4;
    float Tb = Ta + T1 / 2;
    float Tc = Tb + T2 / 2;
    
    // 6. 根据扇区分配占空比到三相
    // ...(这里省略,不同扇区分配不同)
}

这个框架我用了好多年,从STM32到TI的C2000,基本没怎么改过。你只要注意数据类型——用float还是q15,取决于你的MCU有没有FPU。没有FPU的话,建议用定点数,不然计算时间会拖垮整个控制周期。

好了,SVPWM的核心内容就这些。下一章咱们聊聊电流环的PI参数怎么整定——那个才是真正考验功力的地方。