
做电机控制或者并网逆变的工程师对SVPWM这五个字母基本都不陌生。我最早啃它是在调一台永磁同步电机FOC项目的时候导师直接扔了一篇应用笔记让我自己推说实话扇区判断、矢量作用时间这些表格在不同资料里符号还不统一一度让我怀疑自己推错了。后来花了一周时间把伏秒平衡从头到尾推了一遍再用Matlab把仿真跑通才算是真正把SVPWM装进脑子里。这篇就把我当时的推导笔记和仿真工程整理出来重点讲清楚三个问题扇区怎么判断、各个扇区的T1/T2到底怎么算、X/Y/Z这些公共变量是从哪来的。配套给出一套可以直接复现的Matlab代码适合刚接触电机控制的研究生、正在做逆变器项目的工程师或者想从公式层面彻底搞懂SVPWM的人。1. SVPWM到底在算什么从8个开关状态说起1.1 逆变器的开关状态与6个基本电压矢量三相两电平逆变器一共6个功率管组成3个桥臂每个桥臂要么上管导通输出接到母线正记作1要么下管导通输出接到母线负记作0。三个桥臂组合起来就是8种开关状态000到111。其中000和111是零矢量因为三相输出全部接到同一个电位负载上的线电压为零。剩下6个状态各对应一个非零电压矢量也就是常说的V1~V6它们把αβ平面均分成6个扇区每个扇区60°。你可能会问这6个矢量在αβ坐标系里到底多长这里有个容易被忽略的细节如果用幅值不变的Clarke变换带2/3系数来做三相到两相的变换那么从相电压推导出来的非零矢量幅度是2/3倍的母线电压Udc。例如状态100A相接到母线正B、C相接到母线负负载为Y接且中性点悬空时A相相对中性点的电压是2/3倍的UdcB、C相是负的1/3倍Udc经Clarke变换后得到的就是一个长度等于2/3 Udc、方向在0°的矢量。这6个基本矢量每两个之间夹角60°它们的端点画出来就是一个正六边形。SVPWM的目标就是在一个PWM周期Ts内用相邻的两个非零矢量加零矢量去拼出任意一个幅值和方向都符合要求的参考电压矢量Uref。拼的原理很简单伏秒平衡也就是一个周期内各矢量作用时间与矢量的乘积之和等于参考矢量乘以周期。1.2 为什么大家更愿意用SVPWM既然SPWM也能输出正弦电压为什么还要费劲做坐标变换、扇区判断这些操作最直接的原因是电压利用率。传统SPWM用正弦波和三角载波比较线性区内相电压基波幅值最大只能到Udc/2。而SVPWM的本质是让参考矢量轨迹走到六边形内切圆上内切圆的半径是(2/3)Udc乘以cos30°算出来是Udc/√3约等于0.577倍的Udc。相比Udc/2提升了大约15.47%。对电机驱动来说这意味着在同样的母线电压下电机能跑更高的转速、输出更大的转矩对并网逆变器来说意味着直流侧电压可以降低损耗和成本都能下来。另外SVPWM生成的PWM波形谐波更集中低次谐波含量少电流波形更干净数字化实现也特别适合单片机所以现在几乎成了矢量控制、直接转矩控制的标配调制策略。2. 扇区判断三个符号位搞定2.1 U1、U2、U3这三个判断量从哪来把参考电压矢量放到αβ平面后第一步是判断它落在哪个扇区。最笨的办法是算角度θ atan2(Uβ, Uα)然后看θ在30°~90°还是90°~150°……但单片机里反正切运算要占不少时钟周期所以工程上几乎不用。经典做法是构造三个判断量U1 Uβ U2 (√3/2)Uα - (1/2)Uβ U3 -(√3/2)Uα - (1/2)Uβ这三个量其实对应的是三条过原点的直线。U2 0就是60°那条边界线U3 0是120°那条边界线U1 0是α轴本身。对于空间中任意一个角度θ的矢量只要看这三个量分别大于0还是小于0就能唯一确定它所在的扇区。为什么是60°和120°这两条线因为6个扇区的边界正好在0°、60°、120°、180°、240°、300°这6个角度上而α轴对应0°和180°60°/240°共线120°/300°共线所以三条直线配合符号判断就够用了。2.2 扇区编号映射表把每个判断量大于0记作1否则记0然后算N A 2B 4C其中A对应U1、B对应U2、C对应U3。这样算出来的N值在1到6之间但N并不直接等于扇区编号需要一个映射表。我当年就是在这个映射表上栽过跟头因为不同资料里扇区编号的起点不同有的从0°~60°算扇区I有的从30°开始算表对不上导致波形全是乱的。这里给出我推导时使用的映射关系和前面V1在0°方向的定义是一致的N扇区3I1II5III4IV6V2VI把这个表写成代码就几行U1 Ubeta; U2 sqrt(3)/2 * Ualpha - 0.5 * Ubeta; U3 -sqrt(3)/2 * Ualpha - 0.5 * Ubeta; N double(U1 0) 2*double(U2 0) 4*double(U3 0);跑一个工频周期的开环仿真你会看到N按3→1→5→4→6→2的顺序循环一个周期循环6次这正是参考矢量依次扫过扇区I到VI的体现。如果仿真里N的跳变顺序不对先检查Uα、Uβ的符号定义再检查Clarke变换系数。3. 六个扇区作用时间详细计算X、Y、Z的完整推导3.1 从伏秒平衡推导公共变量扇区判断做完接下来就是算每个扇区里两个非零矢量的作用时间。这里最核心的技巧是引入三个公共变量X、Y、Z只算一次然后通过查表拿到每个扇区的T1、T2。很多资料直接甩出公式说由伏秒平衡可得但真正自己推一遍才能理解系数是怎么来的。以扇区I为例参考矢量落在V10°和V260°之间。V1和V2在αβ坐标系下的分量分别是V1 (2/3)Udc, 0 V2 (1/3)Udc, (√3/3)Udc设T1是V1的作用时间T2是V2的作用时间零矢量时间T0 Ts - T1 - T2。根据伏秒平衡Uα·Ts (2/3)Udc·T1 (1/3)Udc·T2 Uβ·Ts (√3/3)Udc·T2先由第二式直接解出T2T2 √3·Uβ·Ts / Udc这就是公共变量X。再看第一式把T2代进去T1 (3Uα·Ts)/(2Udc) - T2/2 (3Uα·Ts)/(2Udc) - (√3Uβ·Ts)/(2Udc) (3/2·Uα - √3/2·Uβ)·Ts / Udc这个就是公共变量Z。同理把参考矢量放在V2和V3之间推一遍会得到另一个公共变量Y (3/2·Uα √3/2·Uβ)·Ts / Udc。三个公共变量的最终形式X √3·Uβ·Ts / Udc Y (3/2·Uα √3/2·Uβ)·Ts / Udc Z (3/2·Uα - √3/2·Uβ)·Ts / Udc注意这里的Uα、Uβ是用带2/3系数的幅值不变Clarke变换得到的。如果你代码里用的是不带2/3系数的简化版Clarke那这些公式整体都要跟着缩放否则算出来的时间会差1.5倍波形会直接畸变这也是非常常见的坑。3.2 各扇区T1、T2分配表每个扇区用同样的伏秒平衡推一遍最终得到的分配表我整理成了下面这样。这里的T1指七段式发波序列中第一个出现的非零矢量作用时间T2指第二个出现的非零矢量作用时间顺序与发波序列严格对应这个顺序直接决定了后面比较值的分配不能搞反。扇区发波序列中矢量顺序T1T2I100→110ZXII010→110-ZYIII010→011X-YIV001→011-X-ZV001→101-YZVI100→101Y-X到这个位置你可能会疑惑为什么扇区II的T1是-Z而不是Z因为扇区II的参考矢量在60°~120°之间发波序列从000出发为了减少开关次数先开通B相得到010再开通A相得到110所以第一个出现的矢量是010对应时间要取负的Z才能保证在这个扇区内T1始终为正。每个扇区边界上相邻扇区的T1、T2是连续过渡的这也是验证表对不对的一个好方法。3.3 七段式发波与三相比较值有了T1、T2和T0下一步是生成七段式PWM。七段式是一个周期内按零矢量1 - 矢量1 - 矢量2 - 零矢量2 - 矢量2 - 矢量1 - 零矢量1的顺序排列开关次数固定谐波性能好是工程上最常用的方式。中间对齐的比较值这样算Tcm1 T0 / 4 Tcm2 Tcm1 T1 / 2 Tcm3 Tcm2 T2 / 2这三个值分别对应发波序列中三个开关动作的触发时刻也就是三角载波上升到某个值时对应相开通。但要最终得到三相各自的比较值还需要查下面这张表扇区A相比较值B相比较值C相比较值ITcm1Tcm2Tcm3IITcm2Tcm1Tcm3IIITcm3Tcm1Tcm2IVTcm3Tcm2Tcm1VTcm2Tcm3Tcm1VITcm1Tcm3Tcm2有了这个表再和中心对称的三角载波做比较就能输出一对互补带死区的PWM驱动信号。这里我把死区的问题放在后面第5节讲因为在仿真里不加死区波形也能跑但到了硬件上不加死区就是炸管子。4. Matlab仿真从公式到可运行的代码4.1 纯M脚本仿真一个函数搞定SVPWM为了把前面的公式一次验证到位我建议先别急着搭Simulink先用纯M脚本把算法核心跑通。算法核心就一个函数输入αβ轴电压和PWM周期、母线电压输出三相比较值。function [Ta, Tb, Tc] fcn_svpwm(Ualpha, Ubeta, Ts, Udc) % 输入Ualpha/Ubeta幅值不变ClarkePWM周期Ts母线电压Udc % 输出A/B/C三相的比较值时间单位与三角载波比较生成PWM % 1. 扇区判断 U1 Ubeta; U2 sqrt(3)/2 * Ualpha - 0.5 * Ubeta; U3 -sqrt(3)/2 * Ualpha - 0.5 * Ubeta; N double(U1 0) 2*double(U2 0) 4*double(U3 0); % 2. 公共变量X、Y、Z X sqrt(3) * Ubeta * Ts / Udc; Y (1.5 * Ualpha sqrt(3)/2 * Ubeta) * Ts / Udc; Z (1.5 * Ualpha - sqrt(3)/2 * Ubeta) * Ts / Udc; % 3. 各扇区T1/T2 switch N case 3, T1 Z; T2 X; case 1, T1 -Z; T2 Y; case 5, T1 X; T2 -Y; case 4, T1 -X; T2 -Z; case 6, T1 -Y; T2 Z; case 2, T1 Y; T2 -X; otherwise, T1 0; T2 0; end % 4. 过调制限幅 if T1 T2 Ts k Ts / (T1 T2); T1 k * T1; T2 k * T2; end T0 Ts - T1 - T2; % 5. 七段式比较值 Tcm1 T0 / 4; Tcm2 Tcm1 T1 / 2; Tcm3 Tcm2 T2 / 2; % 6. 三相分配 switch N case 3, Ta Tcm1; Tb Tcm2; Tc Tcm3; case 1, Ta Tcm2; Tb Tcm1; Tc Tcm3; case 5, Ta Tcm3; Tb Tcm1; Tc Tcm2; case 4, Ta Tcm3; Tb Tcm2; Tc Tcm1; case 6, Ta Tcm2; Tb Tcm3; Tc Tcm1; case 2, Ta Tcm1; Tb Tcm3; Tc Tcm2; end end有了这个函数驱动脚本就简单了。先生成三相对称参考电压做Clarke变换得到Uα、Uβ每个PWM周期调用一次函数算出比较值再和三角载波比较生成PWM。%% SVPWM开环仿真主脚本 clear; clc; close all; Udc 311; % 母线电压 (V) fsw 10000; % 开关频率 (Hz) Ts 1/fsw; Vref 150; % 参考相电压幅值 (V) Fref 50; % 基波频率 (Hz) Tend 0.06; % 仿真时长 (s) nPWM round(Tend / Ts); Ta_arr zeros(1, nPWM); Tb_arr zeros(1, nPWM); Tc_arr zeros(1, nPWM); Ua_vect zeros(1, nPWM); for k 1:nPWM theta 2*pi*Fref*(k-1)*Ts; Ua Vref*cos(theta); Ub Vref*cos(theta - 2*pi/3); Uc Vref*cos(theta 2*pi/3); % 幅值不变Clarke Ualpha (2/3)*(Ua - 0.5*Ub - 0.5*Uc); Ubeta (2/3)*(sqrt(3)/2*Ub - sqrt(3)/2*Uc); [Ta_arr(k), Tb_arr(k), Tc_arr(k)] fcn_svpwm(Ualpha, Ubeta, Ts, Udc); Ua_vect(k) Ua; end % 用三角载波重建PWM波形 dt Ts/100; t 0:dt:Tend; x mod(t, Ts); carrier x; carrier(x Ts/2) Ts - x(x Ts/2); pts_per_pwm round(Ts/dt); Ta_wave repelem(Ta_arr, pts_per_pwm); Tb_wave repelem(Tb_arr, pts_per_pwm); Tc_wave repelem(Tc_arr, pts_per_pwm); pwmA double(carrier Ta_wave); pwmB double(carrier Tb_wave); pwmC double(carrier Tc_wave); % 负载相电压Y接中性点悬空 Va pwmA * Udc; Vb pwmB * Udc; Vc pwmC * Udc; Vn (Va Vb Vc) / 3; Van Va - Vn; % 画图 figure; subplot(3,1,1); plot(t, pwmA, LineWidth, 0.5); ylim([-0.2 1.2]); title(A相PWM); subplot(3,1,2); plot(t, Van, LineWidth, 0.5); title(A相负载电压(相对中性点)); subplot(3,1,3); plot(t, movmean(Van, pts_per_pwm), LineWidth, 1.5); hold on; plot((0:nPWM-1)*Ts, Ua_vect, r--, LineWidth, 1.5); title(A相电压滤波后与参考电压对比); legend(滤波后电压, 参考电压);跑完这个脚本你会看到滤波后的相电压和参考电压基本重合说明伏秒平衡成立扇区判断和时间计算是正确的。这一步验证通过再往Simulink模型或者单片机里移植心里就有底了。4.2 Simulink模型搭建直接复用上面的函数纯脚本验证通过后Simulink模型搭建就顺理成章。核心思路是把上面脚本里的每个环节拆成模块我建议按这个顺序搭第一块是参考电压产生用三个Sine Wave模块幅值设Vref频率设50Hz相位依次相差120°。频率50Hz幅值150V母线311V调制比在0.84左右处于线性调制区不会触发过调制。第二块是Clarke变换用Gain和Sum模块搭或者直接在一个MATLAB Function里写。这里我习惯直接写一个函数封装减少连线出错的概率。第三块是SVPWM核心直接复用上面的fcn_svpwm函数放到MATLAB Function模块里输入Uα、Uβ和常数Ts、Udc输出Ta、Tb、Tc。第四块是载波比较。用Repeating Sequence模块产生三角波Sequence参数设置为[0, Ts/2, 0]三个点对应时间[0, Ts/2, Ts]这样就是一个中心对称的三角波。然后把Ta、Tb、Tc分别和载波用Relational Operator比较大于输出1就是PWM信号。第五块是逆变器和负载。最省事的方式是用三个受控电压源替代桥臂每个受控源输出电压等于PWM信号乘以Udc再接三相RL负载。也可以用Simulink自带的Universal Bridge模块加Mosfet做更接近实际的仿真但步长要求高跑起来慢验证算法阶段没必要。第六块是观测。用Scope看三相PWM、相电压、电流用FFT模块看谐波分布。这里有个细节Sin_Cos的信号在MATLAB Function里要注意数据类型Simulink默认double就行但如果你把载波比较和PWM生成都放进同一个函数里输出就是double类型的0/1信号后面接受控电压源时可以直接乘以母线电压。4.3 仿真结果怎么读结果主要看三样东西。第一个是相电压波形。它是一串等幅不等宽的脉冲脉冲宽度按正弦规律变化这就是SVPWM的等效正弦。把脉冲用低通滤波或移动平均处理后正弦波就出来了频率和参考一致幅值等于参考电压幅值。第二个是相电流波形。经过RL负载后电流是连续的正弦波谐波含量小。如果电流有明显的6k±1次谐波5、7、11、13次要回头检查你的扇区表和比较值分配表大概率是某个扇区的T1/T2顺序或者分配错了。第三个是FFT谐波分布。谐波主要分布在开关频率及其整数倍附近也就是10kHz、20kHz、30kHz这些位置低频段主要是基波。如果低频段出现明显谐波说明调制本身出了问题而不是滤波的问题。5. 常见问题与调试实录5.1 仿真发散优先怀疑求解器配置仿真发散这个词在电机仿真里太常见了。SVPWM本身是开环调制算法不会发散发散几乎都出在模型参数和求解器配置上。我遇到过的情况大致有三类。第一类是仿真步长太大。PWM开关频率10kHz时一个周期只有100μs而电机或负载的时间常数可能只有几百微秒甚至更小步长大于1e-5秒基本必发散。把步长改到1e-6秒左右或者用ode15s这类变步长刚性求解器问题一般就解决了。第二类是代数环。如果用Relational Operator直接比较载波和比较值而这个比较值又间接依赖载波就会形成代数环。解法是给比较值信号加一个Unit Delay延迟一个仿真步长或者用Memory模块打断代数环。实际硬件里PWM比较值和载波本来就是同步关系仿真里延迟一个步长影响可以忽略。第三类是参数极端值。比如参考电压幅值超过母线电压对应极限进入过调制还很深或者电感设置成1e-3量级还硬要跑大电流电流瞬间冲上去数值就崩了。先把参数拉回线性区再逐步尝试边界工况。5.2 波形不对按N值到T1/T2到比较值逐级排查如果你发现电流波形乱七八糟或者滤波后电压和参考对不上最有效的排查方式是逐级打印中间变量。先打印N值看它是不是按3-1-5-4-6-2的顺序循环。如果不是问题出在扇区判断重点检查U1/U2/U3的三个判断量和N的编码方式。再打印每个扇区的T1、T2看是不是始终大于0且T1T2不超过Ts。如果有负值问题出在X/Y/Z公式或者T1/T2查表尤其是扇区II、IV、VI那几个带负号的表项。最后打印Ta、Tb、Tc看三个比较值是不是始终处于0到Ts/2之间并且保持最小值对应第一个动作相的逻辑。如果比较值超过Ts/2说明T1T2算大了检查过调制限幅有没有生效。5.3 过调制与限幅线性区边界必须处理当调制比较高参考矢量超出六边形内切圆时T1T2会大于Ts这时候如果不处理算出来的T0是负的比较值会超出载波范围实际输出波形会严重畸变。标准做法是等比例缩放if T1 T2 Ts k Ts / (T1 T2); T1 k * T1; T2 k * T2; end这样T1T2就等于TsT0变成0相当于输出从七段式变成了五段式多出来的部分进入过调制区电压利用率进一步上升但电流谐波也会增大。工程上要分清楚应用场景如果是电机驱动短暂过调制可以提高电压输出如果是并网逆变器并网电流质量要求高一般会限制调制比不让它进入过调制区。5.4 硬件移植从Matlab到STM32F103C8这类MCU要注意什么仿真验证通过后移植到MCU最容易踩的坑有两个。第一个是浮点运算速度。STM32F103C8这类Cortex-M3没有硬件浮点单元如果直接按上面的公式用float算在10kHz甚至20kHz开关频率下CPU占用率会很高。工程上通常做标幺化把Uα、Uβ除以Udc然后把时间统一乘上PWM定时器的计数周期这样公式里的除法和乘法大部分变成常系数乘法再用定点或查表处理√3相关的因子。第二个坑是三角载波的实现方式。很多MCU的PWM定时器是增/减计数模式计数范围是0到ARR中心对齐的三角波正好对应七段式发波。这时候Ta、Tb、Tc要映射到定时器比较寄存器的值还要注意高有效还是低有效以及死区时间的插入。死区时间不够或者极性配对反了逆变器上下桥臂直通瞬间就烧管子。调试时先加500ns死区确认波形正常再往下调。我在实际项目中还发现从Matlab仿真到硬件最容易出问题的不是算法本身而是参考电压的相位定义。Simulink里sin和cos的零点不同导致Uα、Uβ的初始相位差90°如果电机编码器角度对齐没做好电流环会直接发散。所以做FOC的话第一步永远是确认反电势和SVPWM参考矢量的相位关系而不是上来就调PI参数。5.5 常见问题速查表现象可能原因排查方法仿真发散步长过大、代数环、参数超限步长改1e-6加Unit Delay检查电压电流边界N跳变顺序不对Clarke变换系数错、U1/U2/U3符号错打印N对比3-1-5-4-6-2循环滤波后电压幅值不对Clarke变换系数和X/Y/Z公式不匹配统一用带2/3系数的幅值不变变换某扇区电流畸变该扇区T1/T2表项或比较值分配错打印该扇区T1、T2、Ta/Tb/Tc调制比高时波形塌陷过调制限幅没生效检查T1T2Ts的缩放逻辑硬件上电直通死区时间未加或极性配反先加500ns死区验证波形SVPWM这东西公式表在网上随便一搜一大把但真正用到项目里细节全在符号约定和查表顺序上。我个人的建议是别急着背表花半天时间把扇区I的伏秒平衡亲手推一遍把X、Y、Z的来历搞清楚剩下五个扇区自然就通了。Matlab仿真这一步也别跳过哪怕只是跑通上面的M脚本对理解整个调制过程都比光看公式强得多。后续如果想往深了做可以再研究五段式断续调制、死区补偿、以及SVPWM和载波PWM的等效关系这些都是在SVPWM骨架上长出来的枝叶。