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。
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; // 零矢量时间
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 整体流程:一张图说清楚
说了这么多,咱们把整个流程串起来。下面这张图是我做项目时一直贴在工位上的:
你看,整个流程非常清晰。从输入到输出,只需要几步简单的数学运算和查表。我在实际代码里,这段逻辑通常放在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的扇区判断和时间计算这块就算彻底拿下了。