3、SVPWM原理与实现:空间矢量调制原理、扇区判断、作用时间计算、七段式与五段式SVPWM的C代码实现。
好,咱们进入SVPWM这一节。
说实话,在伺服驱动里,SVPWM是绕不开的核心。你想想看,FOC算出来两个电压分量Uα和Uβ,怎么变成三相桥臂的开关信号?这就是SVPWM要干的事。
我刚开始做驱动时,总觉得SVPWM很玄乎。后来亲手调过几次波形,才明白它本质上就是个「电压合成」的数学游戏。说白了,就是用六个固定的开关矢量,去拼出任意方向、任意大小的电压矢量。
3.1 空间矢量调制原理
先看这张图(脑子里想象一下):三相逆变器有六个开关管,上下桥臂互补导通。一共就8种开关状态——6个非零矢量和2个零矢量。
这6个非零矢量,把空间分成了6个扇区,每个扇区60度。我们要合成的目标电压矢量Uref,就在这个六边形里转圈圈。
为什么叫「空间矢量」?因为三相电压在空间上相差120度,合成后就是一个旋转的矢量。你给它加一个正弦波,它就在空间画一个圆。嗯,理想情况是圆,实际上因为开关频率和死区影响,会有点畸变。
关键点: SVPWM的实质,就是在一个开关周期内,用相邻的两个非零矢量加上零矢量,去等效目标矢量。等效的原则是「伏秒平衡」——矢量乘以时间,加起来等于目标矢量乘以周期。
3.2 扇区判断
拿到Uα和Uβ后,第一件事就是判断它在哪个扇区。
我见过有人用查表法,也有人用坐标变换。我个人习惯用下面这个公式,简单粗暴:
// 扇区判断 - 我的常用写法
// 输入: Ualpha, Ubeta
// 输出: 扇区号 1~6
float B1 = Ubeta;
float B2 = 0.866f * Ualpha - 0.5f * Ubeta; // sqrt(3)/2 ≈ 0.866
float B3 = -0.866f * Ualpha - 0.5f * Ubeta;
int sector = 0;
if (B1 > 0) sector = 1;
if (B2 > 0) sector = sector | 2;
if (B3 > 0) sector = sector | 4;
// 查表映射到1~6
const int sectorMap[8] = {0, 1, 5, 0, 3, 2, 4, 0};
sector = sectorMap[sector];
为什么这么写?其实B1、B2、B3就是三个参考轴上的投影。你想想看,三个比较器的结果组合起来,正好对应6个扇区。我曾经在这个判断上吃过亏——浮点数精度问题导致扇区边界误判,后来加了0.0001的滞回才解决。
避坑指南: 扇区边界处,Uα和Uβ都很小,浮点运算容易抖动。我建议在扇区判断前加一个死区判断:如果Uref幅值小于某个阈值(比如0.001),直接输出零矢量,别去算扇区了。
3.3 作用时间计算
扇区定了,接下来算两个相邻矢量的作用时间T1和T2。公式推导我就不展开了,直接给结论:
| 扇区 | T1 | T2 |
|---|---|---|
| 1 | K * (Ubeta) | K * (0.866*Ualpha - 0.5*Ubeta) |
| 2 | K * (0.866*Ualpha + 0.5*Ubeta) | K * (-0.866*Ualpha + 0.5*Ubeta) |
| 3 | K * (Ualpha) | K * (-Ubeta) |
| 4 | K * (-Ubeta) | K * (-0.866*Ualpha - 0.5*Ubeta) |
| 5 | K * (-0.866*Ualpha + 0.5*Ubeta) | K * (0.866*Ualpha + 0.5*Ubeta) |
| 6 | K * (-0.866*Ualpha - 0.5*Ubeta) | K * (Ubeta) |
其中K = sqrt(3) * Tpwm / Vdc,Tpwm是开关周期,Vdc是母线电压。
算完T1和T2后,记得做饱和处理:如果T1+T2 > Tpwm,就要等比例缩小。这个叫「过调制」,我后面会专门讲。
注意: 实际工程中,T1和T2要留出死区时间和最小脉宽。我曾经遇到过电机低速抖动,查了半天发现是T1算出来太小,IGBT根本来不及开通。后来加了最小脉宽限制,问题就解决了。
3.4 七段式SVPWM
七段式,就是在一个周期内,开关状态变化7次。它的特点是谐波小,但开关损耗大。
实现思路:先算出三个比较值Ta、Tb、Tc,然后跟三角载波比较,生成PWM波。
// 七段式SVPWM - C代码实现
// 输入: T1, T2, sector, Tpwm
// 输出: 三相占空比 Ta, Tb, Tc
float T0 = Tpwm - T1 - T2;
float Thalf = T0 * 0.5f;
float Ta, Tb, Tc;
switch(sector) {
case 1:
Ta = T1 + T2 + Thalf;
Tb = T2 + Thalf;
Tc = Thalf;
break;
case 2:
Ta = T1 + Thalf;
Tb = T1 + T2 + Thalf;
Tc = Thalf;
break;
case 3:
Ta = Thalf;
Tb = T1 + T2 + Thalf;
Tc = T2 + Thalf;
break;
case 4:
Ta = Thalf;
Tb = T1 + Thalf;
Tc = T1 + T2 + Thalf;
break;
case 5:
Ta = T2 + Thalf;
Tb = Thalf;
Tc = T1 + T2 + Thalf;
break;
case 6:
Ta = T1 + T2 + Thalf;
Tb = Thalf;
Tc = T1 + Thalf;
break;
}
// 最后写入硬件比较寄存器
// EPWM1_CMPA = Ta;
// EPWM2_CMPA = Tb;
// EPWM3_CMPA = Tc;
这段代码我用了好多年,从DSP移植到MCU,基本没改过。唯一要注意的是,不同芯片的PWM计数器模式可能不同——有的用向上计数,有的用增减计数。七段式必须用增减计数,否则波形不对。
3.5 五段式SVPWM
五段式,也叫不连续调制。它在一个周期内只变化5次,比七段式少了两次开关动作。好处是开关损耗降低约1/3,坏处是谐波大一些。
实现上,五段式就是把零矢量全部放在开头或结尾,中间只插入一个零矢量。说白了,就是让某一相桥臂在一个周期内不动作。
// 五段式SVPWM - 以扇区1为例
// 这里采用「零矢量全部放在中间」的方式
float T0 = Tpwm - T1 - T2;
// 五段式:只用一个零矢量,放在中间
float Ta, Tb, Tc;
switch(sector) {
case 1:
Ta = T1 + T2; // A相一直高
Tb = T2;
Tc = 0;
break;
// ... 其他扇区类似
}
// 注意:五段式需要根据扇区选择哪一相「钳位」
我个人的经验是:在高速运行时(比如3000rpm以上),用五段式效果不错,发热明显降低。但低速时还是切回七段式,否则电流纹波太大,听着电机嗡嗡响。
实用技巧: 很多商用驱动器会动态切换——低速用七段式保证电流质量,高速用五段式降低损耗。切换点一般在额定转速的30%~50%之间,具体要看你的散热条件。
3.6 两种方式的对比
| 对比项 | 七段式 | 五段式 |
|---|---|---|
| 开关次数/周期 | 7次 | 5次 |
| 开关损耗 | 高 | 低(约降30%) |
| 谐波含量 | 低 | 高 |
| 电流纹波 | 小 | 大 |
| 适用场景 | 低速、高精度 | 高速、散热受限 |
嗯,最后说一句。SVPWM的代码实现其实不难,难的是调参和适配硬件。我建议你先把七段式跑通,用示波器看相电压波形——标准的马鞍波就对了。然后再去折腾五段式。
下一节我会讲死区补偿和最小脉宽处理,这两个坑我踩过不少,到时候细说。