FOC电机控制做到一定程度,大家都会碰到一个绕不开的问题:母线电压利用率。明明电池电压或者母线电压摆在那里,电机高速的时候就是感觉“使不上劲”,转速上不去,力矩又掉得厉害。有人第一反应是换更高电压的平台,有人怀疑是不是电流环没调好,但实际上,很多情况下问题出在SVPWM的调制策略上——你还在用线性区的调制方式,白白浪费了一截母线电压。这篇文章就把SVPWM过调制这件事讲透,从原理到扇区判定、作用时间推导,再到工程上怎么用代码实现,最后聊聊我实际调试中踩过的坑,希望能给正在搞FOC、搞PMSM、搞BLDC无感控制的朋友一些参考。
1. 为什么要过调制:SVPWM的局限与突破
1.1 FOC控制系统里,SVPWM到底在扮演什么角色
FOC矢量控制的最终目的,是通过控制定子电流的d-q轴分量,实现对电机转矩和磁链的解耦控制。但电流环输出的其实是两个电压给定值:U_d_ref和U_q_ref,这两个是旋转坐标系下的量。要对电机做功,必须把它们变换到静止坐标系下,变成U_alpha和U_beta,然后再通过PWM波的形式,施加到三相逆变器的六个开关管上。SVPWM就是干这个活的——它把抽象的电压矢量变成实际的开关动作序列。
我经常跟刚入门的朋友打比方:FOC的电流环就像是你大脑决定“我要往哪个方向走多少力”,而SVPWM就是你的双腿,负责真正执行这个指令。如果腿的爆发力不够,大脑想得再好,跑起来还是慢半拍。SVPWM的“爆发力”上限,就是它能在不严重畸变的前提下,输出多大的基波电压。
1.2 正弦矢量调制的瓶颈:母线电压利用率
传统正弦PWM(SPWM)调制方式下,相电压的峰值最大只能到母线电压的一半,也就是U_dc/2。换算成线电压,有效值大约是0.612倍母线电压。而SVPWM的线性调制区,相电压峰值可以做到U_dc/√3,母线电压利用率比SPWM提升了约15.5%。这就是为什么FOC系统普遍用SVPWM而不是SPWM。
但即便是SVPWM,它的线性调制区也有天花板。这个天花板对应的是调制比M = 1(有的资料里也写作调制比m = √3·U_ref / U_dc),此时的电压矢量轨迹是一个正六边形的内切圆。也就是说,电压矢量只能在六边形内切圆内保持“圆整”的输出,一旦U_alpha和U_beta合成出来的参考电压矢量半径超过这个圆,逆变器就无法严格输出一个旋转的圆形磁链轨迹了。
那超过之后怎么办?两个选择:一是硬限幅,把超出部分直接截断,虽然系统不会崩,但电压输出会失真,相当于“削顶”;二是用过调制技术,在保持基本控制的前提下,有策略地“放大”电压输出,通过牺牲一部分波形质量来换取更高的基波电压。后者就是本文要讲的SVPWM过调制。
这里要特别强调一个概念:过调制不是“超频”,也不是把PWM频率往死里提。它是调整调制策略本身,在电压矢量超出线性区之后,仍然让逆变器输出尽可能接近我们希望施加的基波电压。换句话说,过调制是“策略性的失真”,而非“无脑的加大占空比”。
2. SVPWM基础原理与扇区判断
2.1 空间矢量合成的本质:从三相到两相再到开关序列
先简单回顾一下SVPWM的原理,因为过调制完全建立在它之上,基础不牢的话后面很容易乱。
三相逆变器有六个开关管,上下桥臂互补导通,所以实际上只有8种开关状态:6个非零矢量(V1~V6)和2个零矢量(V0、V7)。这6个非零矢量在复平面上把360°分成6个60°的扇区,相邻两个非零矢量之间可以线性合成任意方向的电压矢量,这就是“空间矢量PWM”名字的由来。
要把一个给定的参考电压矢量V_ref(由U_alpha和U_beta合成)变成实际的开关序列,分三步走:
第一步,判断V_ref在哪个扇区。这一步通常基于U_beta、U_alpha、√3·U_alpha等量的正负关系来判断,不需要查表,几条if语句就能搞定。
第二步,计算相邻两个非零矢量的作用时间T1和T2,以及零矢量的作用时间T0。这里就涉及到很多教程里反复出现的“中间变量X、Y、Z”了,网上到处能搜到公式,但很少有人讲清楚为什么。
第三步,根据扇区把T1、T2、T0分配到不同的比较寄存器上,生成三相互补的PWM波形。
2.2 扇区判定后,两个非零电压矢量作用时间的中间变量推导
这个“公共量(中间变量)”的推导过程,是很多初学者卡壳的地方。网上直接丢一个公式表给你:X = √3·U_beta·Ts / U_dc,Y = (3/2·U_alpha + √3/2·U_beta)·Ts / U_dc,Z = (−3/2·U_alpha + √3/2·U_beta)·Ts / U_dc。然后告诉你不同扇区T1、T2由X、Y、Z组合而成。但为什么是这个式子的组合?我推导一遍。
先把参考电压矢量V_ref用alpha-beta坐标系表示,假设它落在第I扇区(0°~60°),那么它在V1方向(alpha轴)上的分量为U_alpha,在V2方向(即60°方向)上的分量可以由U_beta和U_alpha换算出来。此时根据电压-时间平衡原理:V_ref·Ts = V1·T1 + V2·T2。
注意V1的方向是alpha轴,幅值大小为2/3·U_dc(这是逆变器非零矢量的幅值,由等幅值Clark变换决定),V2方向是60°方向。将V_ref分解到这两个方向:
沿V1方向分量:U_alpha = T1/Ts · 2/3·U_dc + T2/Ts · 2/3·U_dc · cos60°
沿V2垂直方向分量:U_beta = T2/Ts · 2/3·U_dc · sin60°
把cos60°=0.5,sin60°=√3/2代入,整理后可以得到:
T1 = (3/2·U_alpha − √3/2·U_beta)·Ts / U_dc
T2 = √3·U_beta·Ts / U_dc
看到了吗?这两个式子其实和上面那个X、Y、Z公式是直接对应的。对于不同扇区,只是把参考矢量投影到该扇区“自己的两个相邻矢量方向”上,所以形式上就是X、Y、Z在不同扇区的不同组合。我建议你们别死记公式表,而是亲手把6个扇区各投影一遍,算完之后你会发现,所谓的X、Y、Z本质上就是三个预先算好的投影分量,T1和T2只是从这三个量里“取两个来用”。搞懂这一步,后面理解过调制算法中电压矢量的调整逻辑就容易得多。
顺便提一句,很多STM32的FOC代码里,SVPWM实现为了省时间,会直接把U_alpha和U_beta缩放到比较寄存器周期值域内,上面的Ts/U_dc就直接变成一个缩放系数,这就涉及到标幺化的问题,后面讲代码时再细说。
3. 过调制原理:两个区间,两套策略
3.1 调制比这个概念,先对齐一下
讲到过调制,绕不开调制比M。不同文献里调制比的定义略有区别,我先统一本文的口径:M = π·U_ref / (3·U_dc)(有些资料用M = √3·U_ref / U_dc,两者相差约0.785,看文献时注意别搞混)。我用的是更常见的,定义线性调制区上限M = 1对应的电压幅值等于SVPWM线性区电压的上限,也就是U_lin_max = U_dc/√3。
之所以要定义调制比,是因为过调制算法的核心输入就是它。当M ≤ 1时,在六边形内切圆内部,调制是线性的,这是常规SVPWM区域。当M > 1时,参考电压矢量幅值已经超出了内切圆半径,逆变器拥堵在六边形边界上,必须对参考电压做“修正”,才能继续生成PWM。过调制就是研究这种修正策略的。
工程上,M的值从1往上可以一直增加到约1.1027(对应六边形的顶点幅值2/3·U_dc,即相电压基波峰值可以做到0.6366·U_dc,比线性区的0.577·U_dc提升了约10%)。实际应用中,我们通常会把这个过程分成两个区间,分别用不同的策略来处理。
3.2 过调制区间一:从内切圆到六边形边界的平滑过渡
过调制区间一,对应的调制比范围大约是1 ≤ M ≤ 1.05(有些文献里给的界线是M ≤ 0.952,视不同归一化方式而定)。这个区间里,参考电压矢量的幅值超过了内切圆,但还是落在六边形内部。
区间一的修正策略很直观:将参考电压矢量轨迹中超出六边形边界的部分,沿着半径方向“拉回”到六边形边界上;而未超出的部分保持不动。这个修正后的轨迹,既有一部分跟踪原参考圆,又有一部分贴着六边形边界走,像一个“被六边形框住的圆”。
为什么区间一用这种“局部修正”而不是直接放大?因为M在1附近时,参考电压矢量超出边界的角度范围比较小,多数角度仍在六边形内部,此时局部修正引起的波形失真很小,基波电压提升的效率很高。具体实现时要计算参考电压矢量与六边形边界相交的临界角度,然后在修正轨迹和原始轨迹之间切换。
这个临界角度的计算不需要特别复杂的数学,结合当前扇区的边界直线方程,直接代入U_alpha、U_beta解出来就行。工程实现时也可以查表或做近似处理,优先保证实时性。
3.3 过调制区间二:大调制比下的幅值-相位折衷
当M超过区间一的上限,就在过调制区间二,范围大约是M ≥ 1.05,直到最大约1.1027。这个区间的特点是,参考电压矢量轨迹已经大幅超出六边形,如果还用区间一的“拉回”策略,修正后的轨迹会严重偏离圆形磁链轨迹,导致电流畸变和低频谐波明显增大。
区间二的思路是“幅值-相位折衷”。修正后的电压矢量轨迹不再试图跟随原始参考圆,而是让实际输出的电压矢量沿六边形边界走,同时调整保持角(holding angle,也叫保持角αh)——即在扇区起始处,把电压矢量“压”在扇区起始边界一个角度范围内,然后再切换到沿六边形边界滑动。
实质是:输出电压矢量先保持在一个方向上输出最大电压(沿扇区边界),这个保持过程持续一定角度,然后释放,让矢量沿六边形边界快速“追上”参考矢量的相位。通过控制这个保持角,可以调节输出基波电压的幅值,使得实际基波幅值等于我们期望的参考值。保持角越大,输出电压基波幅值越高,直到M最大时保持角为π/6,也就是整个扇区都在限制输出最大电压。
这么说可能有点抽象,我换个角度解释:区间二的核心是“用相位滞后换电压幅值”。当参考电压超过了逆变器能力极限时,你不可能完全满足它,只能折衷——要么幅值准确、相位偏差大,要么相位接近、幅值偏差大。区间二选择了更接近“幅值准确”的方案,因为对于电机控制来说,电压幅值直接决定转矩输出能力,相位偏差可以靠电流环的闭环调节来补偿。
4. 过调制算法的工程实现
4.1 实现方案选型:查表法 vs 实时计算
聊完原理,直接上工程实现。过调制算法在MCU上的实现,通常有两种思路:查表法和实时计算法。
查表法是提前算出每个调制比对应的修正参数(比如保持角、临界角度等),存到Flash里,程序运行时通过当前M值查表得到参数,然后查表修正角度偏移。这种方式计算量小,适合主频不高的单片机,比如STM32F103这类。但缺点是代码不够灵活,如果M的分辨率不够细,调制比变化时会有台阶感。
实时计算法是用数学公式直接算出修正轨迹所需的参数。比如在区间一,需要实时计算参考电压矢量与六边形边界的交点;在区间二,需要计算保持角αh与调制比M的对应关系。这种方式存储开销小、精度高,但计算量大一些,需要单片机有较强的浮点运算能力,或者使用定点数学。
我自己的经验是:如果是做产品,优先实时计算法,因为现在的MCU性能都不差,Cortex-M4以上的内核跑浮点运算是很轻松的;如果只是做验证、做原型,查表法省事,调起来也直观。
下面重点说实时计算法的实现细节。三维模型不太需要,但流程图、公式是必要的。
4.2 区间一的实时实现步骤
区间一的算法逻辑,本质上就是把参考电压矢量的轨迹分成两段处理。对每个PWM周期:
- 由u_alpha_ref和u_beta_ref,算出参考电压矢量的幅值V_ref和角度θ_ref。
- 判断V_ref是否超出六边形边界在当前角度下允许的最大幅值V_bound(θ)。六边形边界幅值函数是分段线性的,按扇区写判断函数即可。
- 如果未超出,直接输出原始V_ref,继续走常规SVPWM。
- 如果超出,将电压矢量幅值改为V_bound(θ),角度保持θ_ref不变,即“径向拉回”。
这一段的关键在于V_bound(θ)的计算。以第I扇区为例(0°~60°),六边形的第I扇区边界由V1矢量方向和V2矢量方向的连线构成,边界上任意一点的幅值可以表示为V_bound(θ) = (√3/4·U_dc) / sin(π/3 − θ),其中θ是该点与alpha轴的夹角。其他扇区对称类推。
代码里不需要真的去算三角函数的除法,可以把角度转换到各个扇区的本地坐标里,再用一次直线插值或者反正切来求边界幅值。如果PWM频率是10kHz~20kHz,每个周期都这样算也是没问题的。
4.3 区间二的保持角计算与实现
区间二的实现比区间一复杂。核心是实时计算保持角αh和调制比M的关系。
一个常用的近似公式是:αh = π/6 · (1 − √((1.1027 − M) / (1.1027 − 1.05))),但这个公式在不同文献里有不同变体。更准确的做法是通过迭代或查表得到一个多项式拟合,比如用二次多项式拟合αh(M)。
实时控制时,对每个PWM周期的参考电压矢量角度θ_ref,执行以下逻辑:
- 将θ_ref映射到当前扇区内的本地角度θ_local(0~π/3)。
- 若θ_local < αh,则将电压矢量强制定位到扇区起始边界方向,即保持电压矢量方向不变,幅值为最大边界值(通常是六边形顶点方向)。
- 若θ_local ≥ αh,则让电压矢量沿六边形边界滑动,即按修正后的轨迹输出,直到该扇区结束。
这种方式会让输出电压的相位发生一定的“滞后”或“提前”,具体的相位修正取决于保持角的定义方向。工程上,因为电流环是闭环的,相位的小幅偏差可以通过电流环的PI调节器自动纠正,所以不必过度担心相位误差的影响。
4.4 一个最小可跑的过调制代码骨架
我简化一个区间一+区间二的实现代码,重点展示逻辑,具体工程代码需要按你的平台适配。以C语言风格写,假设输入是alpha轴和beta轴的电压给定值(标幺化到U_dc/√3为1.0的标幺系统):
// 输入:u_alpha, u_beta 标幺值(线性区上限为1.0) // 输出:修正后的u_alpha_mod, u_beta_mod void svpwm_overmodulation(float u_alpha, float u_beta, float mod_index, float *u_alpha_mod, float *u_beta_mod) { float v_ref, theta_ref; float theta_local, sector; float v_bound; // 1. 向量坐标变换到极坐标 v_ref = sqrtf(u_alpha * u_alpha + u_beta * u_beta); theta_ref = atan2f(u_beta, u_alpha); // 2. 判断调制比,只对过调制区修正 if (v_ref <= 1.0f) { // 线性区,不做修正 *u_alpha_mod = u_alpha; *u_beta_mod = u_beta; return; } // 3. 判断所属扇区,并转换到本地角度(0~60°) sector = (uint8_t)(theta_ref / (PI / 3.0f)); theta_local = theta_ref - sector * (PI / 3.0f); // 4. 区间一:M <= 1.05 左右(按实际过调制区间边界调整) if (mod_index < 1.05f) { // 计算当前角度下六边形边界的幅值 v_bound = (SQRT3 / 4.0f) / sinf(PI / 3.0f - theta_local); if (v_ref > v_bound) { v_ref = v_bound; // 对超出的部分沿径向修正 } } else { // 5. 区间二:幅值-相位折衷 float alpha_h = compute_hold_angle(mod_index); // 保持角,由M查表或公式计算 if (theta_local < alpha_h) { // 保持模式:输出扇区边界的最大电压矢量 v_ref = 2.0f / SQRT3; // 六边形顶点幅值 theta_ref = sector * (PI / 3.0f); // 锁定在扇区起始边界方向 } else { // 跟随六边形边界 v_ref = (SQRT3 / 4.0f) / sinf(PI / 3.0f - theta_local); } } // 6. 转换回直角坐标 *u_alpha_mod = v_ref * cosf(theta_ref); *u_beta_mod = v_ref * sinf(theta_ref); }注意,这个骨架为清晰起见做了不少简化。比如保持角计算函数compute_hold_angle没有展开,实际用查表或多项式实现;还有θ_ref直接用了全局角度,实际代码里要注意角度跨扇区时的连续性问题;另外把六边形顶点幅值近似成2/√3,严格来说应该是√3/2·U_dc归一化后的值,这里按标幺系统简化了。真实项目里,你还需要把修正后的u_alpha_mod、u_beta_mod送到常规SVPWM函数里去做扇区判断和T1、T2计算。
我建议的实现顺序是:先把线性区SVPWM调得非常稳,确认母线电压利用率达到线性区上限;然后加区间一,观察电流波形和转速提升;确认没问题后再加区间二,并重点关注电流谐波。
5. 参数调整与系统联调:实测经验与性能变化
5.1 过调制对电流环的影响:一个常常被忽略的交互
加了过调制之后,很多人以为只要电压输出变大了,电机自然就能跑得更快。但实际联调时会发现,过调制会让电流环的参数需要重新调整。
原因是这样的:过调制本质上是非线性环节,尤其进入区间二后,输出电压和给定电压之间存在明显的非线性关系(幅值饱和、相位修正)。这会让电流环看到的被控对象发生变化——原来近似线性的电压-电流关系,在过调制区变成了分段非线性的特性。如果你还用线性区调好的PI参数,过调制区可能会出现电流环响应变慢甚至振荡。
实际调测中,我常用的办法是:在深度过调制区,适当降低电流环PI的P值,防止因为非线性引起的超调;同时略微加大I值,保证稳态精度。但这没有一劳永逸的参数,只能靠实验调。更好的方案是让电流环的PI参数随调制比做线性过渡,比如在M超过0.95时开始逐步调整,这个在工程上是可行的,但会增加调参工作量。
5.2 母线电压波动和过调制的关系
直流母线电压不是恒定不变的,尤其在电池供电的设备上,电池电压会随负载和SOC变化。母线电压波动直接影响调制比的计算——母线电压低了,同样的给定电压对应的M值就高了,更容易进入深度过调制。
我遇到过一个实际案例:电池供电的吸尘器电机,电池从满电4.2V单节跌到3.7V时,同样的电频率下,M值从1.02飙到1.08,噪声一下变大,电流波形出现明显畸变。排查了半天,最后发现是M值计算里用的U_dc是常数而不是实时采样值。把母线电压采样引进来参与实时计算后,问题就解决了。
所以,做过调制算法时,U_dc一定不要用固定值,必须实时采样滤波后参与运算。否则你调好的过调制表,在不同电池电压下会“漂移”。
5.3 高速弱磁区和过调制如何配合
FOC系统里,过调制和弱磁控制经常同时出现。电机在高速段,反电动势接近甚至超过母线电压,这时候需要弱磁来“削磁”,同时需要过调制来提高电压输出。两者叠加,才是真正的“高速大扭矩”方案。
我的经验是:优先进入弱磁,再进入过调制。也就是说,在M接近1时先加大id的负向电流(弱磁),让M值回落到安全范围;只有当弱磁已经加到极限、M仍然超过1时,才启动过调制。这样做的好处是,弱磁带来的谐波比过调制稍小,优先保证波形质量;过调制作为“最后手段”,用来榨取最后一点电压潜力。
当然也有逆向的做法:先过调制后弱磁。在一些对高速响应要求极高的场合(比如无人机电机快速突加负载),有过调制先行的方案。但那种方案对过调制算法的要求更高,因为深度过调制时电流畸变较大,可能会影响弱磁环的稳定性。
5.4 实际代码里的PWM死区补偿不能省
做过调制的朋友注意,过调制区的占空比可能接近0%或100%,尤其在六边形顶点附近,逆变器的死区影响会被放大。死区会导致电流波形上出现额外的低次谐波,这在过调制区尤其明显。
所以工程上做过调制时,最好把死区补偿一起做上。死区补偿的本质是,根据电流方向对占空比做微调,抵消死区时间内体二极管续流造成的电压偏差。如果电流方向检测不准,死区补偿反而会引入新的谐波,所以要先确认电流采样和重构电路的相移校准没问题。
我在调试过一个80kW级别的电机控制器时,发现过调制区电流波形总有毛刺,查了很久,最后定位到是死区补偿表在电流过零点附近存在一个“跳变”,跟过调制修正量的非线性叠加后形成了振荡。解决办法是让死区补偿量在电流过零点附近做平滑过渡,不要突变。
6. 常见问题与调试技巧
6.1 电流畸变过大怎么查
过调制必然带来电流畸变,但畸变过大一定是异常的。按照我的排查顺序来:
第一步,确认是否真的进入了过调制区。打印或通过调试器观察M值,如果M < 1但电流畸变已经很大,说明不是过调制的问题,而是电流采样、编码器角度或者PI参数有bug。
第二步,确认扇区切换是否正常。过调制对扇区边界的敏感度高于线性区,边界处理不好会在60°处出现明显的电流突起。可以在示波器上观察相电流在扇区跳变点有没有“台阶”,或在代码里加一个调试变量,统计每个扇区的作用时间连续性。
第三步,确认保持角计算是否正确。区间二的保持角算偏了,最典型的现象是:调制比越高,电流波形越不对称(比如正向半周和负向半周不一致),这个时候特别要注意角度连续性问题。
6.2 母线电压利用率到底提了多少
有些朋友做完过调制后,觉得效果不明显。这里要明白:过调制提升的是基波电压有效值,不是I/O口电压。从线性区到最大过调制,基波电压最多提升约10.3%。听起来不多,但对应到电机转速,就是多出将近10%的转速上限。在电机设计不变的前提下,这10%往往是“够用”和“不够用”的分界线。
另外注意,过调制提升的是基波幅值,但代价是高次谐波显著增加。所以评估过调制效果时,不要只盯着转速,还要看电机温升和噪声。如果电机因为谐波发热严重,转速上去了但可持续工作时间大幅缩短,那就需要重新权衡过调制深度。
6.3 一个实用的过调制深度调节技巧
最后分享一个很实用的小技巧。工程上可以做一个可变的过调制限幅:当电机温度较低时,允许进入较深的过调制区间;当温度超过一定阈值时,自动限制M的最大值。这样既不牺牲短时过载能力,又能保护电机不过热。
实现也简单,就是在过调制算法入口处加一个系数:
float max_mod_index = get_temp_limit(tr_temp); if (mod_index > max_mod_index) { mod_index = max_mod_index; }温度限幅的曲线可以做成线性的,比如从80℃开始限幅,到120℃时完全禁止过调制。这个方法我在好几个项目里用了,效果都很好,既兼顾了性能又照顾了热安全,你们可以试试。
过调制这个东西,原理上不复杂,但真正做到“既要高速又要稳定”,还有很多细节要抠。本文从SVPWM的中间变量推导说起,把过调制的两个区间、工程实现和常见问题都过了一遍,核心就是一句话:过调制是在逆变器物理极限下,用可控的波形失真换取更高的基波电压。理解了这个本质,再去看那些公式、代码和调试现象,思路就会清晰很多。