FOC电机控制核心:SVPWM扇区判断与X、Y、Z推导实战
发布时间:2026/9/13 17:54:41来源:尧图网络
做FOC电机控制的人最后都得过SVPWM这一关。不管你是用ST的MCSDK、TI的InstaSPIN还是拿着论文从零写驱动电流环输出的Vd、Vq经过反Park变换变成Vα、Vβ之后最终都要靠SVPWM把这些“数学世界的电压”翻译成6个MOS管的开关动作。这篇文章是我自己从零实现标准SVPWM的完整记录重点讲清楚扇区判断后的中间变量X、Y、Z是怎么推导出来的同时把扇区判断、作用时间计算、七段式PWM生成、STM32定时器配置、调试验证这些环节一起串起来。适合正在读FOC源码、准备自己实现矢量控制或者代码能跑但波形不对、想老老实实把原理搞明白的工程师。1. FOC链路里SVPWM到底站在什么位置1.1 从电流环到桥臂开关的“翻译官”很多刚开始看FOC代码的朋友会有个误区觉得SVPWM只是某个函数、某段查表把它当成“工具人”。但实际上SVPWM是整个FOC控制链路的最后一级执行器直接决定了MOS管怎么开关、母线电压怎么变成三相交流电压。回顾一下典型的FOC控制框图电流采样得到Ia、Ib/Ic经过Clark变换得到Iα、Iβ再经过Park变换得到Id、IqId、Iq分别和参考值做PI调节输出Vd、VqVd、Vq再经过反Park变换得到Vα、Vβ。到这里一切还都发生在“数学坐标系”里。Vα、Vβ是一个以母线电压为参考的二维电压矢量频率和相位对应电机的电角度。然后SVPWM上场把Vα、Vβ这个二维矢量转换成三相桥臂在一个PWM周期内的占空比。不管位置信息是来自编码器、磁编码器还是无感算法里的滑模观测器、高频注入SVPWM都不关心。它只负责一件事给我一个电压矢量我还你一组开关时间。这也是为什么SVPWM可以独立出来学习和调试它本身并不依赖你用了什么位置估计算法。1.2 为什么标准SVPWM比SPWM更“顶用”既然SVPWM最终输出的是三相占空比那直接用三个正弦波和三角载波比较的SPWM不也行吗行但利用率差一截。SPWM的线性调制区里相电压峰值最多到Udc/2而SVPWM把三相看作一个整体在αβ平面上直接合成空间电压矢量线性区最大能到Udc/√3比SPWM高了大约15%。对电池供电的无人机、电动车、机器人关节来说这15%直接意味着更高的转速和力矩谁都不想白白浪费。这背后的本质是SVPWM输出的三相调制波并不是纯正弦而是注入了一个零序分量。这个零序分量在三相之间相互抵消不会出现在线电压里却能让相电压更好地利用母线电压。你可以简单理解为SVPWM是在SPWM的基础上自动叠加了类似三次谐波的成分所以母线利用率更高谐波表现也更好。标准SVPWM一般指七段式SVPWM也就是在一个PWM周期内开关序列按“零矢量-非零矢量1-非零矢量2-零矢量-非零矢量2-非零矢量1-零矢量”对称排列。这种对称结构开关次数固定、输出谐波小是工业驱动里最常用的做法。2. 三相逆变器的8个开关状态与空间矢量2.1 六个非零矢量和一个六边形三相全桥逆变器每相桥臂上下两个MOS管互补导通所以每相输出要么接母线正记为1要么接母线负记为0总共2的3次方等于8种状态000、100、110、010、011、001、101、111。其中000和111是两个零矢量三相输出都接同一电位负载上没有电压差。剩下6个非零矢量对应三相输出接正负母线的不同组合。把每个非零矢量对应的三相相电压相对于母线中点转换到αβ坐标系会得到一个幅值为2/3·Udc、角度依次相差60度的矢量围成一个正六边形。这6个矢量就是SVPWM的“基本词汇”。举个例子100状态表示A相上桥臂导通B、C相下桥臂导通输出相电压在α轴方向产生一个正矢量。110状态则是在60度方向。其他状态以此类推。零矢量在坐标原点不会让电机产生旋转磁场但它在七段式里负责“填时间”。2.2 伏秒平衡用一个周期合成任意电压矢量SVPWM的思想并不复杂在一个PWM周期Ts里如果期望输出的电压矢量正好落在六边形内就可以用它所在扇区相邻的两个非零矢量以及零矢量按照作用时间加权平均从效果上等效出这个期望矢量。这就是伏秒平衡或者叫冲量等效原理。具体到七段式假设电压矢量落在第一扇区0到60度之间那么一个周期的开关序列是000、100、110、111、110、100、000。其中100和110是第一扇区相邻的两个非零矢量000和111是零矢量。前半段和后半段完全对称这样能保证一个周期结束和下一个周期开始时桥臂状态不变不会出现额外的开关切换。这里有一个很重要的边界SVPWM能线性合成的电压矢量只能落在六边形的内切圆内这个内切圆的半径就是Udc/√3。一旦期望矢量超过这个半径就进入过调制区波形会失真这个后面会专门讲处理办法。3. 扇区判断为什么要用符号判断而不是atan23.1 用三个代数式确定扇区编号知道了SVPWM的基本原理接下来第一步就是判断期望电压矢量落在哪个扇区。很多初学者第一反应是用atan2算出角度再除以60度取整。但这有两个问题一是有个反正切计算在DSP或者普通MCU上不是最划算的二是边界处理容易出幺蛾子。更工程化的做法是直接算三个量Vβ√3·Vα - Vβ-√3·Vα - Vβ分别判断这三个量的正负号。比如Vβ大于0说明矢量在上半平面√3·Vα - Vβ大于0说明矢量在以60度为界的右侧-√3·Vα - Vβ大于0说明矢量在以120度为界的左侧。三个正负号组合起来就能唯一确定扇区。具体实现上用三个标志位组合成一个N值N A 2B 4C其中A、B、C分别是上面三个量的符号位。我用到N和扇区的映射关系是N扇区角度范围310° ~ 60°1260° ~ 120°53120° ~ 180°44180° ~ 240°65240° ~ 300°26300° ~ 360°这里要特别提醒一句N值和扇区的映射关系不是唯一的不同资料、不同代码里的定义可能不一样。一定要保证你自己的扇区判断逻辑和后面的查表逻辑是配套的否则会出现“判断的扇区和计算的时间对不上”这种非常隐蔽的bug。3.2 引入中间变量X、Y、Z扇区判断完成之后接下来需要计算当前扇区内两个非零矢量的作用时间。如果每个扇区都单独列方程解一遍代码会又长又容易错。更好的做法是先定义三个公共中间变量X √3·Vβ·Ts / UdcY (3/2·Vα √3/2·Vβ)·Ts / UdcZ (√3/2·Vβ - 3/2·Vα)·Ts / Udc这三个量把Vα、Vβ、Ts、Udc全部吸收进去了。到具体某个扇区时T1、T2就是X、Y、Z的正负组合。这个做法不仅代码简洁还有利于嵌入到定点MCU里因为中间变量的计算只需要加法和乘法没有反正切、开方这类运算。注意我这里X、Y、Z的定义和网上某些版本的代码可能有差异定义不同后面的扇区表也会不同。关键是整套逻辑自洽混用就会翻车。下面进入最核心的推导环节。4. 重点两个非零矢量作用时间的中间变量推导4.1 从第一扇区的伏秒平衡方程解出T1、T2以第一扇区为例期望电压矢量Vref落在0到60度之间相邻的两个非零矢量是100方向0度和110方向60度。设T1是100的作用时间T2是110的作用时间T0是两个零矢量的总时间那么T1 T2 T0 Ts然后写α轴和β轴的伏秒平衡方程。100矢量在α轴的贡献是(2/3)·Udc在β轴的贡献是0110矢量在α轴的贡献是(2/3)·Udc·cos60° (1/3)·Udc在β轴的贡献是(2/3)·Udc·sin60° (√3/3)·Udc。所以Vα·Ts (2/3)·Udc·T1 (1/3)·Udc·T2 Vβ·Ts (√3/3)·Udc·T2先看β轴方程直接解出T2T2 √3·Vβ·Ts / Udc这个式子刚好就是X。再看α轴方程把T2代入整理一下得到T1 (3/2·Vα - √3/2·Vβ)·Ts / Udc而根据Z的定义Z (√3/2·Vβ - 3/2·Vα)·Ts / Udc所以T1 -Z。你看第一扇区很快就推出来了T1 -ZT2 X。这里的关键是舍得动手把伏秒平衡方程列出来不要拿网上的表直接抄。自己推一遍之后后面所有扇区都只是同一个套路换了个角度而已。4.2 其他五个扇区的完整查表其余扇区完全同理比如第二扇区60到120度用110和010两个矢量第三扇区用010和011第四扇区用011和001第五扇区用001和101第六扇区用101和100。每个扇区都做一遍伏秒平衡方程求解整理后得到的完整表格如下扇区非零矢量起始边/结束边T1起始边矢量T2结束边矢量1100 / 110-ZX2110 / 010YZ3010 / 011X-Y4011 / 001-Y-X5001 / 101-Y-Z6101 / 100-X-Z这里说的“起始边”和“结束边”是指扇区角度范围的低边界矢量和最终边界矢量。需要注意这个顺序不一定等于实际PWM开关序列中非零矢量的出现顺序后面代码里我会再解释。4.3 过调制处理比很多人想的更重要查表算完T1、T2之后有一个动作极其容易被忽略判断T1 T2是否超过了Ts。如果超过了说明期望电压矢量的幅值已经超出了六边形内切圆进入了非线性区。这时候不能简单地把T1、T2各自钳位到某个上限那样会改变合成矢量的方向让电机的旋转磁场产生畸变轻则电流谐波变大重则闭环直接不稳。正确做法是等比缩放保持方向不变只把幅值压回到线性区的边界if (T1 T2 Ts) { float scale Ts / (T1 T2); T1 * scale; T2 * scale; }缩放完成之后零矢量时间T0 Ts - T1 - T2很自然地变成0。这时候一个PWM周期内没有纯零矢量时间实际上就是过调制运行状态波形会有一点削顶但方向始终正确。4.4 用Python快速验证你的SVPWM表是否自洽我强烈建议在写代码之前先写一段Python把所有扇区扫一遍验证自己的X/Y/Z定义和扇区表是否自洽。别问我为什么这么强调我见过太多人从网上随便复制一个表结果N映射对不上芯片上电后电机不转还嗡嗡叫。下面是一段可以在电脑上直接运行的验证脚本随机取几个角度计算作用时间后反推合成电压看看是否能还原原始的Vα、Vβimport math Udc 1.0 Ts 1.0 n2sector {3: 1, 1: 2, 5: 3, 4: 4, 6: 5, 2: 6} sector_vec { 1: (0, 60, 100, 110), 2: (60, 120, 110, 010), 3: (120, 180, 010, 011), 4: (180, 240, 011, 001), 5: (240, 300, 001, 101), 6: (300, 360, 101, 100), } def calc(v_alpha, v_beta): X math.sqrt(3) * v_beta * Ts / Udc Y (1.5 * v_alpha 0.866 * v_beta) * Ts / Udc Z (0.866 * v_beta - 1.5 * v_alpha) * Ts / Udc A 1 if v_beta 0 else 0 B 1 if math.sqrt(3) * v_alpha - v_beta 0 else 0 C 1 if -math.sqrt(3) * v_alpha - v_beta 0 else 0 N A 2 * B 4 * C sector n2sector[N] if sector 1: T1, T2 -Z, X elif sector 2: T1, T2 Y, Z elif sector 3: T1, T2 X, -Y elif sector 4: T1, T2 -Y, -X elif sector 5: T1, T2 -Y, -Z else: T1, T2 -X, -Z if T1 T2 Ts: scale Ts / (T1 T2) T1 * scale T2 * scale start_angle, end_angle, _, _ sector_vec[sector] v_sum_alpha 0.0 v_sum_beta 0.0 v_sum_alpha (2.0 / 3.0) * T1 * math.cos(math.radians(start_angle)) v_sum_beta (2.0 / 3.0) * T1 * math.sin(math.radians(start_angle)) v_sum_alpha (2.0 / 3.0) * T2 * math.cos(math.radians(end_angle)) v_sum_beta (2.0 / 3.0) * T2 * math.sin(math.radians(end_angle)) return sector, v_sum_alpha, v_sum_beta for deg in [15, 45, 90, 150, 210, 270, 330]: vmag 0.4 v_alpha vmag * math.cos(math.radians(deg)) v_beta vmag * math.sin(math.radians(deg)) sector, v_sum_alpha, v_sum_beta calc(v_alpha, v_beta) err math.hypot(v_sum_alpha - v_alpha, v_sum_beta - v_beta) print(fangle{deg} sector{sector} err{err:.2e})如果输出误差都在1e-12量级说明你的表是自洽的。这时候再把逻辑搬到MCU上心里才有底。5. 七段式SVPWM的切换点计算与PWM生成5.1 从T1、T2到三个切换时刻作用时间算出来接下来要把时间转成中心对齐PWM的比较值。在七段式SVPWM里一个PWM周期被分成七段很多文章直接给三个切换时刻的公式te0 T0 / 4te1 te0 T_first / 2te2 te1 T_second / 2这里的T_first和T_second指的是在七段式开关序列中第一个出现的非零矢量和第二个出现的非零矢量。这一步是很多实现出错的地方你把第一扇区方程里解出来的T1、T2当成开关序列里的先后顺序了。实际上为了保证七段式序列“相邻开关状态只切换一个桥臂”每个扇区两个非零矢量在序列里的先后顺序是固定的但不一定等于扇区起始边、结束边的顺序。以第二扇区为例如果直接按“起始边110先、结束边010后”来排从000切换到110需要同时动作A相和B相这就违背了每次只切换一个桥臂的原则。标准做法是先010再110所以T_first和T_second的顺序在部分扇区会交换。5.2 各扇区开关序列与比较值分配结合七段式对称序列我给出一份完整映射表注意这里的T1、T2仍然是我推导表里的“起始边/结束边”时间扇区前半段序列翻转顺序T_firstT_second比较器分配1000 → 100 → 110 → 111A → B → CT1T2CCR_Ate0, CCR_Bte1, CCR_Cte22000 → 010 → 110 → 111B → A → CT2T1CCR_Ate1, CCR_Bte0, CCR_Cte23000 → 010 → 011 → 111B → C → AT1T2CCR_Ate2, CCR_Bte0, CCR_Cte14000 → 001 → 011 → 111C → B → AT2T1CCR_Ate2, CCR_Bte1, CCR_Cte05000 → 001 → 101 → 111C → A → BT1T2CCR_Ate1, CCR_Bte2, CCR_Cte06000 → 100 → 101 → 111A → C → BT2T1CCR_Ate0, CCR_Bte2, CCR_Cte1代码里可以这样组织先算te0再根据扇区确定T_first、T_second、te1、te2最后按表给三个通道比较器赋值。我在STM32上通常用一个switch实现很直观。5.3 STM32定时器配置要点STM32用SVPWM一般选带互补输出的高级定时器比如TIM1或TIM8。关键配置是中心对齐模式因为SVPWM要求三角载波和中心对齐PWM天然匹配。PWM频率和定时器周期ARR的关系是f_pwm Tclk / (2 × (ARR 1))比如Tclk72MHz想要20kHz的PWM频率ARR大概就是1799。注意中心对齐模式下计数方向为先增后减一个完整周期会经历两次计数溢出所以实际PWM周期是2 × (ARR1)个时钟周期。死区时间也要在这里配置。SVPWM输出的是三相上下桥臂互补PWM如果死区设置太小母线可能会直接短路设置太大电流波形在过零点会出现明显畸变。我一般从1us左右开始调看相电流波形再微调。6. 调试验证从开环到FOC闭环6.1 先开环别急着闭环我见过很多朋友一上来就把电流环、速度环全跑起来波形一团乱麻根本不知道问题出在SVPWM还是PI参数上。正确顺序是先单独验证SVPWM。第一步给一个固定的Vα、Vβ比如Vα0.1Udc/√3Vβ0相当于给电机施加一个固定方向的电压矢量。正常情况下电机转子会转到这个矢量对应的角度并锁住。你用手去拨转子它会自己回到平衡位置。如果这步都不对直接查相序、PWM极性和死区。第二步给一个旋转的电压矢量也就是VαVmag·cos(θ)VβVmag·sin(θ)θ从0开始不断累加。Vmag保持在线性区内比如0.3Udc/√3。这时候电机应该会跟着磁场方向稳定转起来相电流波形接近正弦。用示波器看线电压Uab能看到明显的六步台阶每个台阶对应一个扇区。6.2 电流采样窗口和FOC联调SVPWM在FOC闭环里的一个特殊要求是电流采样时刻。常见的做法是用两相采样电阻重构三相电流ADC触发点放在PWM周期的中心位置。因为在中心点七段式SVPWM一定处于零矢量状态要么000要么111此时下桥臂采样电阻上的电流就是真实的相电流不会受到开关切换瞬间的干扰。实际写代码时用定时器的更新事件或者中心对齐溢出中断来触发ADC采样并且要把采样时刻避开死区。如果采样点离开关切换太近ADC会打到振铃波形满屏毛刺。联调时如果电流波形有规律地每60度出现一次畸变基本可以断定是扇区切换相关的映射问题优先检查N和扇区的映射表、比较器分配表是不是配套的。7. 常见问题与排查技巧实录7.1 波形异常的快速定位表调试SVPWM的过程中会遇到不少经典问题我整理了一个速查表症状可能原因排查方法电机啸叫、母线电流很大死区时间不够或互补PWM极性配反示波器测上下管驱动波形确认死区能转但力矩小、带载掉速母线电压Udc参数和实际不符或过调制被简单截断确认软件里的Udc是实际母线电压相电流波形60度一次畸变扇区映射或比较器分配表不配套用第4.4节Python脚本验证表扇区切换瞬间有尖峰电流比较值更新不同步开启预装载在更新事件统一更新CCR电流采样噪声极大ADC采样点离开关切换太近把采样点移到PWM中心并避开死区电机不转但电压波形看起来正常相序反了或SVPWM后三相不是接入电机UVW交换任意两相或者交换PWM输出通道7.2 复制SVPWM代码最容易翻车的三个细节最后说几个我在实际项目中栽过跟头的地方第一中间变量的定义和扇区表必须配套。网上版本很多X、Y、Z定义差一个符号扇区表就会全部错位。不要东拼西凑要么全抄一套要么自己推导一套然后务必用脚本验证。第二T1T2大于Ts时一定要等比缩放。很多人图省事直接写成if (T1 Ts) T1 Ts这会让合成电压矢量方向偏移。低速没感觉一旦高速出过调制谐波和电流噪声会让人怀疑人生。第三比较寄存器的更新要使用影子寄存器也就是预装载。SVPWM一个PWM周期内开关状态在切换如果中途被错误地更新了比较值会立刻产生一次多余的开关动作轻则电流尖峰重则开关器件应力超标。配置定时器时把CCR的预装载使能打开在定时器溢出中断里统一更新。我在实际调试中还有一个体会SVPWM这个环节看着原理很简单就一个查表加几行公式但真正把它调稳最花时间的往往不是算法本身而是“表跟表之间没对上”。所以别怕动手推公式花半小时把伏秒平衡方程自己列一遍比在示波器前面挠头三个小时划算得多。
网站建设高端定制企业官网