3. 扇区判断与作用时间:如何判断参考电压矢量所在扇区,基本矢量作用时间的计算

好,咱们进入SVPWM最核心的实操环节。

前面我们讲了,SVPWM的本质就是用八个基本电压矢量去拼出一个任意方向的参考矢量。那问题来了——你拿到一个\(V_{\alpha}\)和\(V_{\beta}\),怎么知道它落在哪个扇区?又该让哪两个基本矢量各干多久的活?

这部分我当年刚接触时也绕了一阵,后来发现其实就两件事:先判区,再算时。搞清楚了,后面代码写起来特别顺。

3.1 扇区判断:别用角度查表,用逻辑判断

很多人第一反应是:先算角度\(\theta = \arctan(V_{\beta}/V_{\alpha})\),然后看角度落在0~60°还是60~120°……

我建议你千万别这么干。为什么?反正切计算太慢了,在MCU上跑一个atan2可能要几十微秒,而且还有除零风险。我在项目里见过有人这么写,结果PWM频率一上去,CPU直接跑满。

其实有更聪明的办法——直接用\(V_{\alpha}\)和\(V_{\beta}\)的符号和大小关系,就能把扇区定下来。

具体做法是这样的:定义三个中间变量:

B1 = V_beta
B2 = (sqrt(3)/2) * V_alpha - (1/2) * V_beta
B3 = -(sqrt(3)/2) * V_alpha - (1/2) * V_beta

然后看这三个值的正负。每个Bx如果大于0,对应位就置1,否则置0。最后合成一个3位的数:

N = 4*sign(B3) + 2*sign(B2) + 1*sign(B1)

这个N值从1到6,直接映射到扇区I~VI。映射关系我习惯用查表:

N值 1 2 3 4 5 6
扇区 II VI I IV III V

你看,整个过程只有加减法和比较,没有三角函数,没有查角度表。我在实际项目中用STM32F103跑72MHz,这段判断只需要不到1μs。

一个小技巧:如果你用的MCU没有浮点单元,可以把sqrt(3)/2 ≈ 0.8660254 提前算好存成Q15或Q31格式的定点数。我习惯用Q15,精度够用,计算还快。

3.2 基本矢量作用时间:伏秒平衡的落地

扇区判完了,接下来就是算时间。说白了,就是解一个伏秒平衡方程:

\(V_{ref} \cdot T_s = V_x \cdot T_x + V_y \cdot T_y + V_0 \cdot T_0\)

其中\(V_x\)和\(V_y\)是当前扇区相邻的两个非零矢量,\(V_0\)是零矢量(可以是000或111)。\(T_x\)和\(T_y\)就是我们要算的。

我直接给你一个通用公式,不用每个扇区都重新推导。先算三个公共变量:

X = (sqrt(3) * V_beta * T_s) / V_dc
Y = ( (3/2)*V_alpha + (sqrt(3)/2)*V_beta ) * T_s / V_dc
Z = ( -(3/2)*V_alpha + (sqrt(3)/2)*V_beta ) * T_s / V_dc

然后根据扇区,查表得到\(T_1\)和\(T_2\):

扇区 T1(主矢量) T2(辅矢量)
I Z Y
II Y -X
III -Z X
IV -X Z
V X -Y
VI -Y -Z

嗯,这里要注意:算出来的\(T_1\)和\(T_2\)可能超过\(T_s\),或者加起来超过\(T_s\)。这就是过调制的情况。我一般这样处理:

if (T1 + T2 > T_s) {
    // 过调制,等比例缩小
    T1 = T1 * T_s / (T1 + T2);
    T2 = T2 * T_s / (T1 + T2);
}
T0 = T_s - T1 - T2;  // 零矢量时间
我曾经踩过的坑:过调制时直接截断T1和T2,没有等比例缩放。结果电机在高速区转矩脉动特别大,还伴有明显的啸叫声。后来才发现是过调制处理不对。记住:一定要保持T1:T2的比例不变,只缩小它们的幅值。

3.3 零矢量分配:影响谐波和噪声的关键

T0算出来了,怎么分给000和111?这直接决定了PWM的波形对称性和谐波含量。

我常用的有两种方式:

  • 七段式SVPWM:每个周期内,零矢量000和111各出现一次,且对称分布在周期首尾和中间。谐波性能好,但开关次数多。
  • 五段式SVPWM:只用一个零矢量(比如只用000),开关次数减少1/3,但谐波会大一些。

我个人习惯在低转速时用七段式,追求电流波形平滑;高转速时切到五段式,降低开关损耗。你想想看,电机高速时反电动势已经很大了,电流纹波的影响相对变小,这时候省点开关损耗更划算。

七段式的具体分配规则(以扇区I为例):

Ta = T0 / 4
Tb = Ta + T1 / 2
Tc = Tb + T2 / 2

// 然后根据扇区,把Ta/Tb/Tc赋给三相比较值
// 扇区I:A相 = Tb, B相 = Ta, C相 = Tc

这个分配表每个扇区都不一样,我建议你直接做成一个6×3的查找表,运行时查表赋值,省得每次判断。

3.4 整体流程:一张图说清楚

说了这么多,咱们把整个流程串起来。下面这张图是我做项目时一直贴在工位上的:

SVPWM扇区判断与作用时间计算流程 SVPWM 扇区判断与作用时间计算流程 输入 Vα, Vβ, Ts, Vdc 步骤1:扇区判断 计算 B1/B2/B3 → 合成 N 值 → 查表得扇区 步骤2:计算 X/Y/Z 公共变量 X = √3·Vβ·Ts/Vdc, Y = (1.5Vα+0.866Vβ)·Ts/Vdc, Z = (-1.5Vα+0.866Vβ)·Ts/Vdc 步骤3:查表得 T1/T2 根据扇区从 6×2 查找表获取 步骤4:过调制处理 T1+T2 > Ts 时等比例缩小 输出 T1, T2, T0 用于生成 PWM 比较值

你看,整个流程非常清晰。从输入到输出,只需要几步简单的数学运算和查表。我在实际代码里,这段逻辑通常放在PWM下溢中断里执行,每次中断算一次,算完直接更新比较寄存器。

3.5 代码实现要点

最后给你一个代码骨架,这是我实际项目里提炼出来的:

// 输入:V_alpha, V_beta, V_dc, T_s
// 输出:T1, T2, sector

// 1. 扇区判断
float B1 = V_beta;
float B2 = 0.8660254f * V_alpha - 0.5f * V_beta;
float B3 = -0.8660254f * V_alpha - 0.5f * V_beta;

uint8_t N = 0;
if (B1 > 0) N |= 1;
if (B2 > 0) N |= 2;
if (B3 > 0) N |= 4;

// N值到扇区的映射表
const uint8_t sector_map[6] = {2, 6, 1, 4, 3, 5};
uint8_t sector = sector_map[N - 1];

// 2. 计算X/Y/Z
float X = 1.7320508f * V_beta * T_s / V_dc;
float Y = (1.5f * V_alpha + 0.8660254f * V_beta) * T_s / V_dc;
float Z = (-1.5f * V_alpha + 0.8660254f * V_beta) * T_s / V_dc;

// 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. 过调制处理
float sum = T1 + T2;
if (sum > T_s) {
    T1 = T1 * T_s / sum;
    T2 = T2 * T_s / sum;
}
float T0 = T_s - T1 - T2;
核心要点:
  • 扇区判断用逻辑比较,不用反正切——快一个数量级
  • X/Y/Z是公共变量,一次算完,所有扇区复用
  • T1/T2查表获取,避免每个扇区写一套公式
  • 过调制必须等比例缩放,不能直接截断

这段代码我用了好几年,从STM32到TMS320F28335,从空调压缩机到伺服驱动器,基本没改过核心逻辑。你把它吃透了,SVPWM的扇区判断和时间计算这块就算彻底拿下了。