4. 扇区判断与矢量合成:基于参考电压矢量在αβ坐标系的分量,判断所在扇区并计算相邻矢量作用时间
好,我们继续往下走。前面我们把 Clark 变换讲清楚了,也知道了怎么把三相电压映射到 αβ 坐标系。那接下来要解决一个很实际的问题:这个旋转的参考电压矢量,到底落在哪个扇区?
你想想看,SVPWM 的核心就是「用相邻的两个基本矢量去合成目标矢量」。如果你连它在哪个扇区都不知道,那该用哪两个矢量去合成?所以扇区判断是第一步,也是后面所有计算的基础。
核心思路: 通过 Uα 和 Uβ 的符号与比例关系,确定参考矢量所在的 60° 扇区。一共 6 个扇区,每个扇区对应一组相邻的基本矢量。
4.1 扇区判断的数学原理
我个人习惯用「三值逻辑」来做扇区判断。说白了,就是定义三个中间变量 A、B、C,每个变量只有 0 或 1 两种取值。然后通过一个简单的公式算出扇区号。
具体怎么做?我们先定义三个参考量:
- Uref1 = Uβ
- Uref2 = (√3/2)·Uα - (1/2)·Uβ
- Uref3 = -(√3/2)·Uα - (1/2)·Uβ
然后根据这三个量的正负,给 A、B、C 赋值:
| 条件 | A / B / C |
|---|---|
| Uref1 > 0 | A = 1,否则 A = 0 |
| Uref2 > 0 | B = 1,否则 B = 0 |
| Uref3 > 0 | C = 1,否则 C = 0 |
最后,扇区号 N 的计算公式是:
N = A + 2·B + 4·C
嗯,这里要注意:算出来的 N 是 1~6 的整数,正好对应六个扇区。我在项目中第一次看到这个公式时觉得挺巧妙的——只用三次比较和一次加法,就搞定了扇区判断,效率很高。
我的经验: 实际写代码时,Uref2 和 Uref3 里的 √3/2 可以预先算好存成常量,避免每次计算都做开方。我一般用 0.8660254 这个近似值,精度完全够用。
4.2 扇区与基本矢量的对应关系
知道了扇区号 N,接下来就要确定该用哪两个基本矢量。每个扇区对应的相邻矢量是固定的,我整理了一张表:
| 扇区号 N | 相邻基本矢量 | 对应开关状态 |
|---|---|---|
| 1 | U0 (000) 和 U60 (100) | 000 → 100 |
| 2 | U60 (100) 和 U120 (110) | 100 → 110 |
| 3 | U120 (110) 和 U180 (010) | 110 → 010 |
| 4 | U180 (010) 和 U240 (011) | 010 → 011 |
| 5 | U240 (011) 和 U300 (001) | 011 → 001 |
| 6 | U300 (001) 和 U0 (000) | 001 → 000 |
这张表你最好记熟,写代码时直接查表就行,不用每次都重新推导。
4.3 矢量作用时间的计算
扇区判断完了,接下来就是算时间。每个扇区里,两个相邻矢量的作用时间 T1 和 T2 可以通过 Uα 和 Uβ 算出来。
我直接给通用公式,你拿去用就行:
// 定义三个通用变量 X、Y、Z
X = (√3 · Uβ · Ts) / Udc
Y = ( (3/2)·Uα + (√3/2)·Uβ ) · Ts / Udc
Z = ( -(3/2)·Uα + (√3/2)·Uβ ) · Ts / Udc
// 根据扇区号 N 查表得到 T1 和 T2
switch(N) {
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;
}
这里 Ts 是 PWM 周期,Udc 是直流母线电压。算出来的 T1 和 T2 就是两个基本矢量的导通时间。
我曾经踩过的坑: 算出来的 T1 + T2 可能会超过 Ts。这时候必须做饱和处理——把 T1 和 T2 按比例缩小,保证 T1 + T2 ≤ Ts。剩下的时间分配给零矢量(U0 或 U7)。如果不做这个处理,电机在过调制区会出问题,电流波形会畸变。
4.4 扇区判断与时间计算的流程图
为了让你看得更清楚,我画了一张流程图,把整个判断和计算过程串起来:
4.5 实际代码实现示例
纸上谈兵没意思,我给你看一段我实际用过的 C 代码。这段代码在 STM32 上跑过,效果不错:
// 扇区判断与时间计算函数
void svpwm_calc(float Ualpha, float Ubeta, float Ts, float Udc,
float *T1, float *T2, int *sector) {
float Uref1, Uref2, Uref3;
float X, Y, Z;
int A, B, C, N;
// 1. 计算三个参考量
Uref1 = Ubeta;
Uref2 = 0.8660254f * Ualpha - 0.5f * Ubeta;
Uref3 = -0.8660254f * Ualpha - 0.5f * Ubeta;
// 2. 赋值 A, B, C
A = (Uref1 > 0) ? 1 : 0;
B = (Uref2 > 0) ? 1 : 0;
C = (Uref3 > 0) ? 1 : 0;
// 3. 计算扇区号
N = A + 2 * B + 4 * C;
*sector = N;
// 4. 计算 X, Y, Z
X = (1.7320508f * Ubeta * Ts) / Udc;
Y = ((1.5f * Ualpha + 0.8660254f * Ubeta) * Ts) / Udc;
Z = ((-1.5f * Ualpha + 0.8660254f * Ubeta) * Ts) / Udc;
// 5. 查表得到 T1, T2
switch(N) {
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;
}
// 6. 饱和处理
float sum = *T1 + *T2;
if (sum > Ts) {
*T1 = (*T1 / sum) * Ts;
*T2 = (*T2 / sum) * Ts;
}
}
我的一点建议: 实际工程中,Udc 可能会有波动。如果你发现电机在母线电压变化时性能不稳定,可以考虑把 Udc 也作为实时输入,而不是固定常量。我在一个变频器项目里就是这么做的,效果立竿见影。
4.6 常见问题与避坑
最后,我总结几个实际中容易出问题的地方:
- 浮点精度问题: 嵌入式芯片的浮点运算能力有限,如果算出来的 T1 或 T2 是负数,说明扇区判断有误,或者参考电压已经超出了线性调制区。这时候要检查 Uα 和 Uβ 的幅值是否合理。
- 零矢量分配: T1 和 T2 确定后,剩下的时间 T0 = Ts - T1 - T2 要分给两个零矢量 U0(000) 和 U7(111)。我习惯各分一半,这样 PWM 波形对称,谐波小一些。
- 死区时间补偿: 算出来的 T1 和 T2 是理想值,实际还要考虑死区时间。如果死区时间占比较大(比如 PWM 频率很高时),建议做死区补偿,否则电流波形会失真。