调试FOC的时候,我估计大家手头都会备几段SVPWM代码。很多工程库把扇区判断、作用时间计算这些逻辑封装得严严实实,直接给你一张查表把结果算完。平时调起来很省事,可一旦换芯片、换PWM频率、或者想把电压利用率再往上压一压,问题就来了:为什么这个扇区映射这么写?T1、T2为什么是查表里的那两项?有没有一种更直观、更好推、从三相坐标直接看的方法?
这篇文章要聊的就是标题里这个三投影法。它不是什么新算法,本质上还是SVPWM的伏秒平衡,只是换了一种组织方式:先把期望电压矢量投影到三相绕组方向,再把三个投影值排序,扇区判定更直接,作用时间的计算也更统一,基本可以砍掉大部分查表逻辑。对刚接触PMSM FOC的同学来说,这是理解SVPWM底层原理的一条捷径;对已经在写产品的工程师,它也能帮你把代码里的每个分支都说得清为什么。
1. 从电流环出来之后,SVPWM到底在算什么
1.1 电流环输出的Vα、Vβ是什么
先理一下FOC的控制链路。电流环PI控制器输出的是d轴和q轴电压指令,经过Park逆变换后得到的是两相静止坐标系下的期望电压分量,也就是我们常说的Vα、Vβ。这个电压矢量不是随便给的一个数,它代表逆变器在一个开关周期内应该输出的平均电压矢量,模值对应线电压的空间矢量大小,方向决定定子磁链的走向。
如果逆变器是线性放大器,直接给Vα、Vβ就行。但现实里逆变器只做一件事:把直流母线电压Vdc按开关状态切成不同的脉冲。于是问题变成:怎么用开关动作在一个PWM周期里合成出期望的Vα、Vβ?这就是SVPWM的原始任务。
1.2 传统查表法为什么不好教、也不好调
传统SVPWM的做法是先判断期望电压矢量落在哪个扇区,然后找该扇区两个相邻非零矢量的作用时间。扇区判断常见下面这段逻辑:
if (vBeta > 0) ... else if (vAlpha > 0) ...或者更常见的是算一个N值,再查表映射扇区:
N = (va > 0) + 2 * (vb > 0) + 4 * (vc > 0); sector = table[N];这些代码本身没错,但问题是它把“为什么”藏起来了。初学者很难从N值反推出扇区边界在哪里,更不要说把扇区判断和后面的T1、T2计算公式关联起来。最要命的是,不同教材对扇区编号、矢量命名、中间变量X/Y/Z的定义不完全一样,代码抄过来容易,想迁移、想改造成连续调制或钳位调制,就很吃力。
1.3 三投影法换了个视角
我理解的三投影法,是放弃“在αβ平面里画圆、判断角度”的习惯,把Vα、Vβ反向变换回三相静止坐标系,得到三个电压投影:
va = Vα vb = -0.5 * Vα + 0.866 * Vβ vc = -0.5 * Vα - 0.866 * Vβ这三个值代表同一个电压矢量在A、B、C三相对称绕组轴线上分别投影出的电压。因为它们来自三相对称电压,天然满足:
va + vb + vc = 0所以三个变量实际只有两个自由度。到这里并没有引入新物理量,真正的变化是后续处理:对va、vb、vc排个序,最大值减中间值、中间值减最小值,直接对应到相邻非零矢量的作用时间。这就是“三投影法”的精髓——把二维矢量的扇区判断和伏秒平衡,变成一维排序和差值运算。
2. 三投影的几何意义与隐藏的零序自由度
2.1 反Clarke变换的几何含义
如果Vα、Vβ是电压空间矢量在两个正交轴上的分量,那么va、vb、vc可以看成这个矢量分别在三个绕组轴线上做的投影。取等幅值Clarke变换的逆变换:
va = Vα vb = -1/2 * Vα + √3/2 * Vβ vc = -1/2 * Vα - √3/2 * Vβ系数√3/2来自绕组轴线的几何夹角120度。这个变换没有改变矢量幅值,所以用va、vb、vc去判断方向,跟用Vα、Vβ判断方向是一致的。
做FOC的同事经常有几个误区:第一,以为va、vb、vc就是最终加在电机三相端的相电压,其实它们只是中间变量;第二,以为va+vb+vc=0就一定是三个“桥臂电压”,其实电动机中性点电压可以浮动,三相对称电压的公共零序分量并不影响线电压。这两个点搞清楚,后面算PWM比较值时就不会被“电压对不上”的问题卡住。
2.2 “投影值”可以直接当成时间使用
三个投影值都有电压量纲,只要做一次归一化:
Ta_raw = va * Ts / Vdc Tb_raw = vb * Ts / Vdc Tc_raw = vc * Ts / VdcTa_raw、Tb_raw、Tc_raw就具有时间量纲,可以理解成“如果单纯跟踪这个投影电压所需要的导通时间”。这也就是标题里说的三相电压作用时间的雏形。
注意这里我用“raw”表示原始值。这个值可能是负的,也可能大于Ts,是不能直接给PWM比较器的。为什么?因为三相逆变器桥臂输出电压范围只能从0到Vdc,而va、vb、vc是相对电机中性点定义的,两者之间差一个共模电压。这个共模电压正好是我们要利用的自由度,也是后面生成中心对称PWM时需要注入的零序分量。
2.3 零序分量怎么“藏”进去的
高中物理里我们学过一个概念:三相交流电如果同时加一个相同的直流偏置,线电压完全不变。就像三个人站在一起,三个人同时踮脚,彼此之间的相对高度不变,但整体高度变了。三相逆变器输出电压也只有相对值有意义,电机端电压是浮动的,中性点电压会自动适应。
所以在生成PWM比较值时,我们要做的不是直接拿va、vb、vc去和三角载波比较,而是先人为加入一个公共偏置v_offset,让三个桥臂占空比都落到0到1之间。这个v_offset就是零序分量。三投影法把电压投影、零序注入、时间计算、扇区判断串成了一条逻辑线,比从角度出发的传统SVPWM更直观。
3. 六个扇区的判定:从if树到排序
3.1 排序法:最优雅的判扇区方式
有了va、vb、vc之后,扇区判断有一个非常漂亮的等价关系:只要把三个投影值从大到小排序,最大值对应的相就指向扇区所在的大方向,再按中间值偏转位置就能唯一确定扇区。
我把六个扇区的排序关系整理成表:
| 扇区 | 排序关系 | 对应空间范围 |
|---|---|---|
| 扇区I | va ≥ vb ≥ vc | 0° ~ 60° |
| 扇区II | vb ≥ va ≥ vc | 60° ~ 120° |
| 扇区III | vb ≥ vc ≥ va | 120° ~ 180° |
| 扇区IV | vc ≥ vb ≥ va | 180° ~ 240° |
| 扇区V | vc ≥ va ≥ vb | 240° ~ 300° |
| 扇区VI | va ≥ vc ≥ vb | 300° ~ 360° |
这张表的价值在于:代码里不再需要三个if叠起来判断角度,也不用给N值查扇区表。你只做一次三变量排序,拿到max、mid、min以及分别对应的相别,扇区自然就有了。
3.2 符号组合法能不能用
有人觉得排三数麻烦,想用正负号直接判断扇区。我实测下来,符号组合法在边界附近很容易出歧义。比如扇区I内部,vb在0°到30°之间是负的,在30°到60°之间是正的,你拿vb的符号去判断,同一个扇区内部就会出现两种符号状态,必然导致逻辑分支变复杂。
所以我的建议是:用排序法做主要手段,符号法只作为快速粗判。如果你看过一些开源库用“P>0、Q>0、R>0”三个条件加查表判断扇区,那其实是把排序结果编码成了三位bits,本质和排序法等价,只是可读性差一些。
3.3 边界上的相等情况
三投影法在扇区边界上会出现va、vb、vc其中两个相等,比如0°时vb=vc,60°时va=vb。排序时的相等关系会让扇区落在相邻两扇区之间,但这是正常现象:此时T1或T2刚好为零,对应一个零矢量作用,实际输出连续平滑。
工程实现时需要注意,浮点数在边界处可能有微小抖动,导致扇区判定在相邻扇区之间来回跳。一个周期内跳一次可能没什么影响,但如果PWM切换表也因此跳变,会造成电流纹波变大。解决方案很简单:给排序结果加一个几mV的滞回阈值,或者用定点数量化后比较,都能把边界抖动控制住。
4. 三相电压作用时间的核心推导
4.1 先用扇区I把伏秒平衡推导出来
扇区I的相邻非零矢量用开关状态表示为U1(100)和U2(110)。在一个开关周期Ts内,设定U1作用T1,U2作用T2,零矢量作用T0 = Ts - T1 - T2。
期望电压的线电压分量可以写成:
va - vb = (T1/Ts) * Vdc vb - vc = (T2/Ts) * Vdc这个式子的物理含义很直白:状态U1(100)时线电压vab等于Vdc,状态U2(110)时线电压vab等于0,所以平均后的vab只和T1有关;同理,状态U2(110)让线电压vbc保持Vdc,所以vbc的平均分量只和T2有关。
反解得到:
T1 = (va - vb) * Ts / Vdc T2 = (vb - vc) * Ts / Vdc可以看出,在扇区I里,作用时间就是三投影按排序相邻两项的差值。而扇区I的排序是va≥vb≥vc,所以上式正好对应:
T1 = (vmax - vmid) * Ts / Vdc T2 = (vmid - vmin) * Ts / Vdc4.2 从特例到通用公式
在扇区II里做同样推导,相邻矢量变成U2(110)和U3(010),最后也能得到类似结果:两个相邻非零矢量的作用时间仍然是“最大值减中间值”和“中间值减最小值”这两个差值,只是T1、T2分别对应的是U3和U2。
我据此总结了一个通用规律:不管落在哪个扇区,T1和T2都只需要从排序后的三个投影电压中做两次差值计算,然后把这两个时间按扇区映射到对应的两个非零矢量上。
这里给出常用的映射表,扇区编号按0~360度范围顺序编号:
| 扇区 | T1(vmax - vmid)对应矢量 | T2(vmid - vmin)对应矢量 |
|---|---|---|
| 扇区I | U1(100) | U2(110) |
| 扇区II | U3(010) | U2(110) |
| 扇区III | U3(010) | U4(011) |
| 扇区IV | U5(001) | U4(011) |
| 扇区V | U5(001) | U6(101) |
| 扇区VI | U1(100) | U6(101) |
这张表就是很多传统SVPWM代码里“扇区→作用时间对应关系”查表项的来源。在传统查表法里,你可能只看到X、Y、Z三个中间变量和扇区编号,不清楚为什么某些扇区取X、某些取Y。用三投影法一推,本质一目了然。
4.3 数值算例:40度方向、0.5倍母线电压
取Vdc=100V,Ts=100us,期望电压矢量幅值50V(即0.5Vdc,线性调制区内),方向40度。
Vα = 50 * cos40° ≈ 38.3V,Vβ = 50 * sin40° ≈ 32.1V。
反Clarke变换:
va = 38.3V vb = -0.5*38.3 + 0.866*32.1 ≈ 8.7V vc = -0.5*38.3 - 0.866*32.1 ≈ -47.0V排序结果:vmax=va,vmid=vb,vmin=vc,位于扇区I。
作用时间:
T1 = (38.3 - 8.7) * 100us / 100 = 29.6us T2 = (8.7 + 47.0) * 100us / 100 = 55.7us T0 = 100 - 29.6 - 55.7 = 14.7usT1、T2、T0全部为正且和Ts匹配,说明电压矢量在可线性调制区域。T2比T1大,符合40度方向更接近60度扇区边界(即U2方向)的空间直觉。这个例子的计算过程可以直接在Excel或Matlab里复现,方便对照自己的C代码。
4.4 极限条件与过调制临界
线性调制的临界是T1 + T2 = Ts,此时T0=0,没有零矢量插入,逆变器输出最大正弦线电压峰值等于Vdc。如果T1 + T2 > Ts,说明期望电压矢量已经超出六边形内切圆,需要过调制处理。
在三投影法里,这个限制条件可以写成:
vmax - vmin <= Vdc也就是说,三个投影电压中最大和最小之间的电压差不能超过母线电压。超过之后,电流环给出来的电压指令将无法被逆变器物理实现。常见处理方式有两种:把T1、T2等比例缩放到和Ts匹配(过调制模式一),或者用方波模式(过调制模式二)。我建议在电机控制里优先做等比例限幅,简单且对电流谐波影响相对可控:
if (t1 + t2 > Ts) { float scale = Ts / (t1 + t2); t1 *= scale; t2 *= scale; }5. 从作用时间到三相PWM比较值
5.1 七段式对称PWM的分配
有了T1、T2、T0之后,怎么变成真正的三相PWM占空比?工程上用得最多的是七段式对称PWM,零矢量一分为四,两个非零矢量各自分半,左右对称。
拿扇区I举例,一个开关周期内的状态序列为:
000 -> 100 -> 110 -> 111 -> 110 -> 100 -> 000每段持续时间为:
T0/4 -> T1/2 -> T2/2 -> T0/2 -> T2/2 -> T1/2 -> T0/4这样排列的好处是每个桥臂在一个周期内最多切换两次,开关损耗低,同时电流纹波相对较小。
5.2 比较值的通用生成方式
定义三个比较值:
CMP1 = T0 / 4 CMP2 = T0 / 4 + T1 / 2 CMP3 = T0 / 4 + T1 / 2 + T2 / 2在扇区I,CMP1给A相,CMP2给B相,CMP3给C相。
换个扇区就要重新分配,因为每个扇区的状态序列顺序不同。这里给出三投影法推荐的做法:用前面排序时记录的相序信息,把CMP1、CMP2、CMP3映射到三相通道上。比如扇区II排序是vb≥va≥vc,对应T1是U3时间、T2是U2时间,分配方式就和扇区I不同。
工程上最不容易出错的方式是维护一张“比较值→三相通道”的小表。表的内容虽然还是查表,但它是从排序结果推出的,和扇区编号逻辑完全对应,调试起来思路清晰。
5.3 一个可以直接改的代码骨架
下面这段C代码是我在项目里用的三投影法核心部分,抽取了最关键的逻辑:
// 输入:SVPWM期望电压矢量Valpha、Vbeta(伏) // 输出:三相PWM比较值cmpA、cmpB、cmpC(0~Ts) const float SQRT3 = 1.7320508f; const float invVdc = 1.0f / Vbus; // Vbus是直流母线电压 float va = Valpha; float vb = -0.5f * Valpha + SQRT3 * 0.5f * Vbeta; float vc = -0.5f * Valpha - SQRT3 * 0.5f * Vbeta; // 排序,记录对应相号 float *v[3] = {&va, &vb, &vc}; // 手感排序或排序网络都可以 // 得到vmax、vmid、vmin及idx_max、idx_mid、idx_min float t1 = (vmax - vmid) * Ts * invVdc; float t2 = (vmid - vmin) * Ts * invVdc; if (t1 + t2 > Ts) { /* 限幅 */ } float cmp1 = (Ts - t1 - t2) * 0.25f; float cmp2 = cmp1 + t1 * 0.5f; float cmp3 = cmp2 + t2 * 0.5f; // 根据idx_max/idx_mid/idx_min分配三相比较值 // 分配表由扇区唯一确定这段代码的核心价值在于:它把扇区判断从显式的if/else变成了排序,把作用时间计算从X/Y/Z查表变成两个差值。换一个分配表就能适配不同调制策略,比如零序注入改成中点钳位,或者换成五段式断续调制,只需要调整比较值的生成方式。
6. 工程实现中的几个坑与优化
6.1 归一化系数:等幅值还是等功率
Clarke变换有两种归一化,一种等幅值,一种等功率。三投影法公式里的系数按等幅值写法推最顺。如果你用的是等功率Clarke变换,反变换系数会差√(2/3)倍,导致va、vb、vc的幅值和母线电压比较关系全都对不上,作用时间算出来偏大。
最安全的做法是:全程统一用等幅值Clarke,并且让Vα、Vβ的幅值上限等于Vdc/√3。这个值就是线性调制区能输出的最大相电压峰值,所有电流环限幅、弱磁控制里的电压圆限制都应以此为参考。
6.2 死区时间会偷走多少作用时间
实际逆变器必须加死区,而死区会让输出平均电压和指令电压之间出现误差。理论分析时死区误差大概正比于死区时间和开关周期,具体到三投影法里,表现为每个周期T1、T2的实际有效时间比指令值短。
死区补偿的工程实现很灵活,常见策略是在电流方向已知的前提下,把三相投影电压做小幅修正。但要注意,三投影法的排序结果本身就包含电流方向无关的物理关系,死区补偿一定要加在投影之前或投影值上,而不是直接改最后比较值,否则容易出现方向反、补偿越补越差的情况。
6.3 过调制限幅不能破坏伏秒平衡
过调制限幅看起来简单,但容易犯一个错误:只把T1、T2等比缩小,却没注意映射表和中心对齐的对称性。比如某些嵌入式中断里做了限幅,但比较值计算用到的还是未限幅的T0,导致PWM周期不是Ts,电流环采样点跟着错位。
我的经验是先限幅、再算CMP1/CMP2/CMP3,保证三个比较值依然以Ts/2对称。如果实际中存在最小脉宽限制(比如死区+最小开通时间),还要在分配三相比较值之前做一个最小时间钳位,否则窄脉冲会导致电流采样异常、周期抖动加剧。
6.4 调试时先看T1/T2,再看输出波形
三投影法调起来有个很顺手的检查手段:直接把T1、T2打印出来或者用DAC输出。正常情况下,T1、T2应该连续变化,且在扇区边界处平滑切换,不允许出现跳变尖峰。如果发现T1或者T2在某个角度突然跳变,多半是扇区映射表里矢量和时间对应关系写错了。
另外一个实用经验:可以先用开环状态给定一个旋转的Vα、Vβ,让电压矢量匀速转圈,然后用示波器观察三相PWM比较值。正常情况下比较值都是中心对称的正弦调制波叠加零序分量,看起来不是标准正弦,但开关时刻始终保持在0~Ts之间。用手电筒照向相电压滤波后的波形,如果平滑正弦,说明三投影法和作用时间计算已经正确,再进行闭环电流环调试,问题定位会快很多。
最后再说个小建议:很多人习惯把SVPWM底层函数当成“黑盒”用,但当你真正自己推一遍三投影法之后,再去看传统代码里的扇区判断和X/Y/Z查表,会突然觉得那些表项都是顺理成章的。换一个角度理解SVPWM,比记十张表管用得多。