第三章 SVPWM原理与实现:从理论到代码

SVPWM,全称空间矢量脉宽调制。说白了,它就是FOC控制里那个把「虚拟电压矢量」变成「真实MOS管开关动作」的桥梁。

我刚开始接触FOC时,觉得Clark变换和Park变换才是核心,SVPWM不过是底层驱动。直到有一次,我在调试一个高速电机时,发现电流波形里总有奇怪的毛刺,电机嗡嗡响,效率上不去。折腾了两天,最后发现是SVPWM的扇区判断写错了。嗯,从那以后我再也不敢小看它了。

这一章,我们就把它彻底讲透。

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

先问一个问题:三相逆变器只有6个MOS管,开关组合一共就8种状态(6个有效矢量 + 2个零矢量)。你怎么用这8种状态,合成出任意方向、任意大小的电压矢量?

答案就是:在一个开关周期内,用相邻的两个有效矢量,分别作用不同的时间,再插入零矢量填满剩余时间。这就是「伏秒平衡」原理。

举个例子。假设我想合成一个指向30°方向、幅值为U的电压矢量。那我就在一个周期T内,让U₄(100,指向0°)作用T₄时间,让U₆(110,指向60°)作用T₆时间,剩下的时间插零矢量。只要满足:

  • T₄ + T₆ + T₀ = T
  • U₄ × T₄ + U₆ × T₆ = U × T

这个合成的效果,在宏观上就等价于直接输出U矢量。

核心思想:用有限个离散的开关状态,通过时间平均,合成连续的电压空间矢量。

下面这张图,是我自己画的SVPWM整体流程。你看一遍,心里就有谱了。

SVPWM 核心流程 输入:Uα, Uβ 步骤1:扇区判断 步骤2:计算作用时间 T₁, T₂ 步骤3:计算占空比 Ta, Tb, Tc 输出:PWM 比较值

3.2 扇区判断

拿到Uα和Uβ之后,第一件事就是判断目标矢量落在哪个扇区。六个扇区,每个60°,怎么判断?

我见过有人用查表法,也有人用角度atan2。但工程上最常用的,是三值判断法。速度快,没有浮点开销。

定义三个中间变量:

B₁ = Uβ
B₂ = (√3/2) × Uα - (1/2) × Uβ
B₃ = -(√3/2) × Uα - (1/2) × Uβ

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

B₁ 符号 B₂ 符号 B₃ 符号 扇区
+--I
++-II
-+-III
-++IV
--+V
+-+VI

小技巧:实际代码里,我习惯把B₁、B₂、B₃的符号位打包成一个3位的二进制数,然后直接查一个长度为6的数组。这样连if-else都省了,一个周期就出结果。

3.3 作用时间计算

扇区判断完了,接下来算T₁和T₂。这两个时间,就是相邻两个有效矢量的作用时长。

不同扇区对应的T₁、T₂计算公式不一样。但别慌,它们有统一的模式。我直接给你一个通用公式:

X = (√3 × Uβ × T) / Udc
Y = ( (√3/2) × Uβ + (3/2) × Uα ) × T / Udc
Z = ( (√3/2) × Uβ - (3/2) × Uα ) × T / Udc

然后根据扇区查表:

扇区 T₁ T₂
I-ZX
IIZY
IIIX-Y
IV-XZ
V-Y-Z
VIY-X

算完之后,记得做一次限幅处理。如果T₁ + T₂ > T,就按比例缩放:

if (T₁ + T₂ > T) {
    scale = T / (T₁ + T₂);
    T₁ *= scale;
    T₂ *= scale;
}

注意:这个限幅处理很多人会忘。如果不做,过调制时PWM会饱和,电流波形直接畸变。我曾经在量产前测试时发现电机在高速区抖动,查了两天才发现是这里漏了限幅。切记!

3.4 七段式SVPWM

有了T₁和T₂,接下来要生成三相的PWM占空比。七段式,顾名思义,一个开关周期被分成7段。

它的特点是:每次切换只改变一相的状态。这样开关损耗小,谐波也小。

以扇区I为例(U₄→U₆→U₇→U₆→U₄),三相占空比计算如下:

T₀ = T - T₁ - T₂
Ta = T₀ / 4
Tb = Ta + T₁ / 2
Tc = Tb + T₂ / 2

然后根据扇区,把Ta、Tb、Tc映射到实际的A、B、C三相比较值上。映射关系如下:

扇区 比较值 A 比较值 B 比较值 C
ITaTbTc
IITbTaTc
IIITcTaTb
IVTcTbTa
VTbTcTa
VITaTcTb

你看,其实就是把Ta、Tb、Tc三个值,按扇区重新排列一下。代码实现就是一个switch-case或者查表。

3.5 五段式SVPWM

五段式和七段式的区别在哪?说白了,五段式只插入一个零矢量(要么U₇,要么U₀),而不是两个都用。

这样做的好处是:每个周期少两次开关动作,开关损耗降低约1/3。

代价呢?谐波含量会大一些,电流纹波也大一点。

我个人的经验是:大功率、对效率要求高的场合,用五段式。小功率、对电流波形质量要求高的场合,用七段式。

五段式的占空比计算也简单。以扇区I、只插U₇为例:

T₀ = T - T₁ - T₂
Ta = T₀ / 2
Tb = Ta + T₁
Tc = Tb + T₂

映射到三相的方式,和七段式完全一样。

3.6 七段式 vs 五段式:对比

我整理了一个表格,方便你对比选择:

对比项 七段式SVPWM 五段式SVPWM
开关次数/周期6次4次
开关损耗较高较低(约降1/3)
谐波含量较低较高
电流纹波较小较大
适用场景小功率、高精度大功率、高效率

3.7 代码实现

说了这么多,直接上代码。这是我在STM32上跑过的实现,C语言,定点数运算,没有浮点。

// 输入:Ualpha, Ubeta(Q15格式,范围-32768~32767)
// 输出:比较值到TIM寄存器
void SVPWM_Calc(int16_t Ualpha, int16_t Ubeta) {
    int32_t B1, B2, B3;
    int32_t X, Y, Z;
    int32_t T1, T2, T0;
    int32_t Ta, Tb, Tc;
    uint8_t sector;
    int32_t pwmA, pwmB, pwmC;

    // 1. 扇区判断
    B1 = Ubeta;
    B2 = (int32_t)((int32_t)Ualpha * 0.866f) - (Ubeta >> 1);
    B3 = -(int32_t)((int32_t)Ualpha * 0.866f) - (Ubeta >> 1);

    sector = 0;
    if (B1 > 0) sector |= 0x01;
    if (B2 > 0) sector |= 0x02;
    if (B3 > 0) sector |= 0x04;

    // 2. 计算X, Y, Z
    X = (int32_t)((int32_t)Ubeta * 1.732f);
    Y = (int32_t)((int32_t)Ualpha * 1.5f + (int32_t)Ubeta * 0.866f);
    Z = (int32_t)((int32_t)Ualpha * 1.5f - (int32_t)Ubeta * 0.866f);

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

    // 4. 限幅
    if ((T1 + T2) > PWM_PERIOD) {
        int32_t scale = PWM_PERIOD / (T1 + T2);
        T1 = (T1 * scale) >> 15;
        T2 = (T2 * scale) >> 15;
    }

    // 5. 计算占空比(七段式)
    T0 = PWM_PERIOD - T1 - T2;
    Ta = T0 >> 2;
    Tb = Ta + (T1 >> 1);
    Tc = Tb + (T2 >> 1);

    // 6. 扇区映射
    switch (sector) {
        case 1: pwmA = Tb; pwmB = Ta; pwmC = Tc; break;  // II
        case 2: pwmA = Ta; pwmB = Tc; pwmC = Tb; break;  // VI
        case 3: pwmA = Ta; pwmB = Tb; pwmC = Tc; break;  // I
        case 4: pwmA = Tc; pwmB = Tb; pwmC = Ta; break;  // IV
        case 5: pwmA = Tc; pwmB = Ta; pwmC = Tb; break;  // III
        case 6: pwmA = Tb; pwmB = Tc; pwmC = Ta; break;  // V
        default: pwmA = 0; pwmB = 0; pwmC = 0; break;
    }

    // 7. 写入TIM寄存器
    TIM->CCR1 = pwmA;
    TIM->CCR2 = pwmB;
    TIM->CCR3 = pwmC;
}

关于代码的几点说明:

  • 代码里用了浮点乘法(0.866f、1.732f),实际工程中建议提前算成定点数,或者用查表法替代。
  • PWM_PERIOD是定时器的自动重装值,对应开关周期T。
  • 如果想改成五段式,只需要把第5步的占空比计算改成五段式公式即可,其他不变。

3.8 避坑指南

最后,分享几个我踩过的坑:

  • 死区补偿:SVPWM本身不处理死区。如果你发现轻载时电流波形有「台阶」,大概率是死区效应。需要额外做死区补偿。
  • 过调制区:当目标电压超过六边形边界时,必须做限幅。否则输出电压会失真,电机电流会失控。
  • 定时器对齐:SVPWM要求使用中心对齐PWM模式(中央对齐计数)。边沿对齐模式会导致扇区切换时刻不对称,产生谐波。
  • 浮点转定点:MCU没有FPU时,尽量把所有系数转成Q15或Q31格式。我见过有人直接在中断里跑浮点,结果PWM频率只能跑到2kHz,电机响得跟警报一样。

嗯,SVPWM这部分就讲到这里。代码可以直接拿去用,但建议你对着数据手册把定时器配置再确认一遍。毕竟,细节决定成败。