3、SVPWM原理与实现:空间矢量调制原理、扇区判断、作用时间计算、代码实现
说到FOC,SVPWM是绕不开的核心环节。说白了,它就是告诉逆变器该怎么开关,才能让电机转得又稳又顺。我刚开始接触这个的时候,觉得这东西玄乎得很,后来亲手调过几版代码,才真正理解了它的妙处。
3.1 空间矢量调制的基本原理
传统的SPWM(正弦脉宽调制)大家应该都熟悉,它让三相电压各自按正弦变化。但SVPWM不一样——它从整体角度出发,把三相看作一个合成矢量在空间旋转。
你想想看,电机定子绕组在空间上相差120度,三相电流产生的合成磁动势,其实就是一个旋转的矢量。SVPWM的目标,就是用八个基本的开关状态,去逼近这个旋转的参考矢量。
核心思想:用有限个离散的电压矢量,通过时间平均的方式,合成任意方向和幅值的期望电压矢量。
这八个基本矢量包括六个非零矢量和两个零矢量(V0和V7)。六个非零矢量把平面分成六个扇区,每个扇区60度。我习惯把这六个矢量记作V1~V6,它们分别对应(100)、(110)、(010)、(011)、(001)、(101)这六种开关组合。
我的小技巧:记不住扇区顺序?你只要记住V1对应A相上桥臂导通,然后按逆时针方向,每次只切换一相开关状态,就能推出剩下的五个矢量。
3.2 扇区判断
拿到参考电压矢量Vref之后,第一件事就是判断它在哪个扇区。我在项目中遇到过一个问题:扇区判断搞错了,电机直接嗡嗡响就是不转。后来排查了半天,发现是判断逻辑写反了。
扇区判断通常用反Clark变换后的Uα和Uβ来计算。具体做法是这样的:
- 定义三个中间变量:
B1 = Uβ
B2 = (√3/2) * Uα - (1/2) * Uβ
B3 = -(√3/2) * Uα - (1/2) * Uβ - 根据B1、B2、B3的符号,计算扇区号:
Sector = 4*sign(B3) + 2*sign(B2) + sign(B1)
嗯,这里要注意:sign函数返回1表示正数,返回0表示负数。算出来的Sector值在1~6之间,直接对应六个扇区。
| 扇区 | B1符号 | B2符号 | B3符号 |
|---|---|---|---|
| I | + | + | - |
| II | + | - | + |
| III | + | - | - |
| IV | - | + | + |
| V | - | + | - |
| VI | - | - | + |
3.3 作用时间计算
扇区确定后,就要算两个相邻非零矢量和零矢量的作用时间了。这个计算基于伏秒平衡原理——说白了,就是让合成效果跟期望的Vref一样。
我一般先算三个通用变量X、Y、Z:
X = (√3 * Ts / Vdc) * Uβ
Y = (√3 * Ts / Vdc) * ( (√3/2)*Uα + (1/2)*Uβ )
Z = (√3 * Ts / Vdc) * ( -(√3/2)*Uα + (1/2)*Uβ )
然后根据扇区查表,得到T1和T2:
| 扇区 | T1 | T2 |
|---|---|---|
| I | Z | Y |
| II | Y | -X |
| III | -Z | X |
| IV | -X | Z |
| V | X | -Y |
| VI | -Y | -Z |
注意:如果T1+T2 > Ts,说明参考矢量超出了六边形内切圆,需要做过调制处理。我一般直接按比例缩放:T1' = T1 * Ts / (T1+T2),T2' = T2 * Ts / (T1+T2)。
零矢量时间T0 = Ts - T1 - T2,通常平均分配给V0和V7,这样能减少谐波。
3.4 代码实现
下面是我在实际项目中用过的SVPWM核心代码,C语言写的,跑在STM32上。我习惯把扇区判断和时间计算放在一个函数里,这样调用起来方便。
void svpwm_calc(SVPWM_Handle *h)
{
float Ualpha = h->Ualpha;
float Ubeta = h->Ubeta;
float Ts = h->Ts;
float Vdc = h->Vdc;
// 1. 扇区判断
float B1 = Ubeta;
float B2 = 0.866025f * Ualpha - 0.5f * Ubeta; // √3/2 ≈ 0.866
float B3 = -0.866025f * Ualpha - 0.5f * Ubeta;
uint8_t sector = 0;
if (B1 > 0) sector |= 1;
if (B2 > 0) sector |= 2;
if (B3 > 0) sector |= 4;
// sector值映射到1~6
static const uint8_t sector_map[] = {0,1,5,0,3,2,4,0};
sector = sector_map[sector];
// 2. 计算X,Y,Z
float K = 1.7320508f * Ts / Vdc; // √3
float X = K * Ubeta;
float Y = K * (0.866025f * Ualpha + 0.5f * Ubeta);
float Z = K * (-0.866025f * Ualpha + 0.5f * Ubeta);
// 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;
default: T1 = 0; T2 = 0; break;
}
// 4. 过调制处理
float sum = T1 + T2;
if (sum > Ts) {
float scale = Ts / sum;
T1 *= scale;
T2 *= scale;
}
// 5. 计算占空比(七段式SVPWM)
float T0 = Ts - T1 - T2;
float Ta = T0 * 0.25f;
float Tb = Ta + T1 * 0.5f;
float Tc = Tb + T2 * 0.5f;
// 根据扇区分配三相占空比
switch(sector)
{
case 1: h->DutyA = Tb; h->DutyB = Ta; h->DutyC = Tc; break;
case 2: h->DutyA = Ta; h->DutyB = Tc; h->DutyC = Tb; break;
case 3: h->DutyA = Ta; h->DutyB = Tb; h->DutyC = Tc; break;
case 4: h->DutyA = Tc; h->DutyB = Tb; h->DutyC = Ta; break;
case 5: h->DutyA = Tc; h->DutyB = Ta; h->DutyC = Tb; break;
case 6: h->DutyA = Tb; h->DutyB = Tc; h->DutyC = Ta; break;
}
}
避坑指南:我曾经在扇区映射表上栽过跟头。不同文献里扇区编号方式不一样,有的从0开始,有的从1开始。建议你拿到代码后,先用仿真验证一下扇区判断是否正确。
3.5 知识体系总览
下面这张图是我自己整理的SVPWM知识结构,从原理到实现一目了然。你可以把它当作一个快速索引,哪里不懂就回头看对应的章节。
这张图把SVPWM的整个流程串起来了。从基本原理出发,经过扇区判断、时间计算,最终输出三相占空比。我个人觉得,理解了这个流程,SVPWM就算入门了。
实际调试的时候,我建议你先用开环方式验证SVPWM输出是否正确。给一个固定的Uα和Uβ,看看三相占空比是不是按预期变化。这一步走通了,后面闭环控制才有基础。
公众号:蓝海资料掘金营,微信deep3321