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

好,咱们进入SVPWM这一节。

说实话,在伺服驱动里,SVPWM是绕不开的核心。你想想看,FOC算出来两个电压分量Uα和Uβ,怎么变成三相桥臂的开关信号?这就是SVPWM要干的事。

我刚开始做驱动时,总觉得SVPWM很玄乎。后来亲手调过几次波形,才明白它本质上就是个「电压合成」的数学游戏。说白了,就是用六个固定的开关矢量,去拼出任意方向、任意大小的电压矢量。

3.1 空间矢量调制原理

先看这张图(脑子里想象一下):三相逆变器有六个开关管,上下桥臂互补导通。一共就8种开关状态——6个非零矢量和2个零矢量。

这6个非零矢量,把空间分成了6个扇区,每个扇区60度。我们要合成的目标电压矢量Uref,就在这个六边形里转圈圈。

为什么叫「空间矢量」?因为三相电压在空间上相差120度,合成后就是一个旋转的矢量。你给它加一个正弦波,它就在空间画一个圆。嗯,理想情况是圆,实际上因为开关频率和死区影响,会有点畸变。

关键点: SVPWM的实质,就是在一个开关周期内,用相邻的两个非零矢量加上零矢量,去等效目标矢量。等效的原则是「伏秒平衡」——矢量乘以时间,加起来等于目标矢量乘以周期。

3.2 扇区判断

拿到Uα和Uβ后,第一件事就是判断它在哪个扇区。

我见过有人用查表法,也有人用坐标变换。我个人习惯用下面这个公式,简单粗暴:

// 扇区判断 - 我的常用写法
// 输入: Ualpha, Ubeta
// 输出: 扇区号 1~6

float B1 = Ubeta;
float B2 = 0.866f * Ualpha - 0.5f * Ubeta;   // sqrt(3)/2 ≈ 0.866
float B3 = -0.866f * Ualpha - 0.5f * Ubeta;

int sector = 0;
if (B1 > 0) sector = 1;
if (B2 > 0) sector = sector | 2;
if (B3 > 0) sector = sector | 4;

// 查表映射到1~6
const int sectorMap[8] = {0, 1, 5, 0, 3, 2, 4, 0};
sector = sectorMap[sector];

为什么这么写?其实B1、B2、B3就是三个参考轴上的投影。你想想看,三个比较器的结果组合起来,正好对应6个扇区。我曾经在这个判断上吃过亏——浮点数精度问题导致扇区边界误判,后来加了0.0001的滞回才解决。

避坑指南: 扇区边界处,Uα和Uβ都很小,浮点运算容易抖动。我建议在扇区判断前加一个死区判断:如果Uref幅值小于某个阈值(比如0.001),直接输出零矢量,别去算扇区了。

3.3 作用时间计算

扇区定了,接下来算两个相邻矢量的作用时间T1和T2。公式推导我就不展开了,直接给结论:

扇区T1T2
1K * (Ubeta)K * (0.866*Ualpha - 0.5*Ubeta)
2K * (0.866*Ualpha + 0.5*Ubeta)K * (-0.866*Ualpha + 0.5*Ubeta)
3K * (Ualpha)K * (-Ubeta)
4K * (-Ubeta)K * (-0.866*Ualpha - 0.5*Ubeta)
5K * (-0.866*Ualpha + 0.5*Ubeta)K * (0.866*Ualpha + 0.5*Ubeta)
6K * (-0.866*Ualpha - 0.5*Ubeta)K * (Ubeta)

其中K = sqrt(3) * Tpwm / Vdc,Tpwm是开关周期,Vdc是母线电压。

算完T1和T2后,记得做饱和处理:如果T1+T2 > Tpwm,就要等比例缩小。这个叫「过调制」,我后面会专门讲。

注意: 实际工程中,T1和T2要留出死区时间和最小脉宽。我曾经遇到过电机低速抖动,查了半天发现是T1算出来太小,IGBT根本来不及开通。后来加了最小脉宽限制,问题就解决了。

3.4 七段式SVPWM

七段式,就是在一个周期内,开关状态变化7次。它的特点是谐波小,但开关损耗大。

实现思路:先算出三个比较值Ta、Tb、Tc,然后跟三角载波比较,生成PWM波。

// 七段式SVPWM - C代码实现
// 输入: T1, T2, sector, Tpwm
// 输出: 三相占空比 Ta, Tb, Tc

float T0 = Tpwm - T1 - T2;
float Thalf = T0 * 0.5f;

float Ta, Tb, Tc;

switch(sector) {
    case 1:
        Ta = T1 + T2 + Thalf;
        Tb = T2 + Thalf;
        Tc = Thalf;
        break;
    case 2:
        Ta = T1 + Thalf;
        Tb = T1 + T2 + Thalf;
        Tc = Thalf;
        break;
    case 3:
        Ta = Thalf;
        Tb = T1 + T2 + Thalf;
        Tc = T2 + Thalf;
        break;
    case 4:
        Ta = Thalf;
        Tb = T1 + Thalf;
        Tc = T1 + T2 + Thalf;
        break;
    case 5:
        Ta = T2 + Thalf;
        Tb = Thalf;
        Tc = T1 + T2 + Thalf;
        break;
    case 6:
        Ta = T1 + T2 + Thalf;
        Tb = Thalf;
        Tc = T1 + Thalf;
        break;
}

// 最后写入硬件比较寄存器
// EPWM1_CMPA = Ta;
// EPWM2_CMPA = Tb;
// EPWM3_CMPA = Tc;

这段代码我用了好多年,从DSP移植到MCU,基本没改过。唯一要注意的是,不同芯片的PWM计数器模式可能不同——有的用向上计数,有的用增减计数。七段式必须用增减计数,否则波形不对。

3.5 五段式SVPWM

五段式,也叫不连续调制。它在一个周期内只变化5次,比七段式少了两次开关动作。好处是开关损耗降低约1/3,坏处是谐波大一些。

实现上,五段式就是把零矢量全部放在开头或结尾,中间只插入一个零矢量。说白了,就是让某一相桥臂在一个周期内不动作。

// 五段式SVPWM - 以扇区1为例
// 这里采用「零矢量全部放在中间」的方式

float T0 = Tpwm - T1 - T2;

// 五段式:只用一个零矢量,放在中间
float Ta, Tb, Tc;

switch(sector) {
    case 1:
        Ta = T1 + T2;      // A相一直高
        Tb = T2;
        Tc = 0;
        break;
    // ... 其他扇区类似
}

// 注意:五段式需要根据扇区选择哪一相「钳位」

我个人的经验是:在高速运行时(比如3000rpm以上),用五段式效果不错,发热明显降低。但低速时还是切回七段式,否则电流纹波太大,听着电机嗡嗡响。

实用技巧: 很多商用驱动器会动态切换——低速用七段式保证电流质量,高速用五段式降低损耗。切换点一般在额定转速的30%~50%之间,具体要看你的散热条件。

3.6 两种方式的对比

对比项七段式五段式
开关次数/周期7次5次
开关损耗低(约降30%)
谐波含量
电流纹波
适用场景低速、高精度高速、散热受限

嗯,最后说一句。SVPWM的代码实现其实不难,难的是调参和适配硬件。我建议你先把七段式跑通,用示波器看相电压波形——标准的马鞍波就对了。然后再去折腾五段式。

下一节我会讲死区补偿和最小脉宽处理,这两个坑我踩过不少,到时候细说。