ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SimpleFOC七段式SVPWM实操:从扇区计算到STM32调试验证

SimpleFOC七段式SVPWM实操:从扇区计算到STM32调试验证 代码调完了、波形也出来了才发现这套东西最折腾人的不是什么时候该给PWM而是搞清楚为什么这段波形长这样。如果你拿SimpleFOC跑无刷电机已经有一阵子大概率经历过这种事电机明明能转但总觉得声音不对带载掉速严重电流环参数调来调去都没脾气。最后查来查去问题出在最底层的PWM调制方式上——默认的正弦PWM喂给逆变器电压利用率就摆在那里怎么优化上层控制都弥补不了。这篇文章就是把我用STM32F405和SimpleFOC落地七段式SVPWM的完整过程掰开揉碎讲一遍从扇区判断、作用时间计算到CubeMX配置、示波器验证再到源码级微调和避坑记录全部附代码思路。如果你打算做FOC电机控制相关的项目、毕业设计或者只是想弄清楚SimpleFOC内部那个SpaceVectorPWM到底做了什么这篇应该能帮你少走不少弯路。1. 为什么要在SimpleFOC里折腾SVPWM先算清楚这笔账1.1 电压利用率SVPWM比正弦PWM高在哪里先说结论在同样的母线电压下SVPWM能输出的最大相电压峰值是正弦PWM的1.155倍也就是电压利用率提高了约15.47%。这个数字不是锦上添花对电池供电的电动工具、无人机、机器人关节来说意味着同样的电压平台能让电机跑更快、力矩更大。从原理上看三相逆变器的输出能力受限于母线电压Vdc。理想情况下逆变器能输出的最大线电压幅值就是Vdc。正弦PWM直接对三相正弦波做调制每相最大只能输出峰值为Vdc/2的相电压换算成线电压峰值是0.866×Vdc也就是有约13.4%的母线电压其实没用到。而SVPWM通过在正弦波上注入零序分量把相电压峰值抬到Vdc/√3线电压峰值刚好等于Vdc母线电压才算是被榨干了。这个零序分量就是示波器上看到马鞍波的直接原因。很多第一次做SVPWM的同学看到马鞍波会以为自己程序写错了实际上那恰恰说明SVPWM工作正常。马鞍波的鞍部就是零序分量叠加的结果这部分分量在电机三相之间互相抵消不会产生转矩但能让相电压有效范围向上撑开。1.2 七段式的好处对称波形、低谐波、采样友好SVPWM有三种常见实现形式五段式也叫DPWM、七段式以及不连续调制的一些变种。七段式每个PWM周期内零矢量被拆成V0(000)和V7(111)两段分别放在周期起始、中间和结尾形成零—有效—有效—零—有效—有效—零的对称结构。选择七段式有三个实际原因第一波形对称。一个周期内前半段和后半段完全对称谐波主要集中在开关频率及其整数倍附近低频谐波含量低电机运行噪音更干净。第二采样友好。FOC电流采样依赖ADC在PWM周期的特定时刻触发七段式对称结构让每相PWM的占空比中心点天然对齐ADC在中心点采样时三相电流都处于稳定续流区间采样抖动最小。这个对SimpleFOC尤其重要因为它的电流环就是在每个PWM周期同步触发的。第三开关损耗和纹波折中。五段式虽然开关次数少但零矢量只放在一边波形不对称低速时电流纹波偏大七段式开关次数略多一点换来的是更平滑的电流波形和更小的转矩脉动。在大多数电机控制场景下这个取舍是值得的。2. 从八个开关状态到七段时间分配SVPWM的核心拆解2.1 三相逆变器只有八种开关组合三相全桥逆变器由三个半桥组成每个半桥的输出要么接上管定义为1要么接下管定义为0。三个半桥拼起来一共有2³8种状态其中6个是非零电压矢量V1~V62个是零矢量V0000V7111。6个非零矢量在αβ平面上的分布很有规律每个矢量长度相等、夹角60°正好把一个360°平面划分成六个60°的扇区。V1指向0°V2指向60°V3指向120°依此类推。它们的幅值都是2/3×Vdc如果用等幅值坐标变换的话但实际产生的线电压是Vdc。SVPWM的思想非常直接任意时刻我们想要的期望电压矢量由FOC的逆Park变换从dq旋转坐标系得到不可能恰好等于这6个离散矢量。但是在一个PWM周期Ts内可以用两个相邻的有效矢量和两个零矢量做时间平均让平均值恰好等于期望矢量。这就像一个高中生用两个量程有限的砝码配合游码去称重虽然单个砝码精度不够但通过在一个周期内按比例分配时间总能配出任意想要的平均重量。2.2 扇区判断把电压矢量放到正确的60°扇区里既然手段是用该扇区两边的有效矢量去合成第一步当然是把期望电压矢量Vα、Vβ丢进正确的扇区。工程上不用真的去算arctan角度直接用三条直线的正负组合就能判断。以等幅值Clarke变换为基准我的实现里用下面三个中间量判断B0 Vbeta B1 0.866*Valpha - 0.5*Vbeta B2 -0.866*Valpha - 0.5*Vbeta然后给B0、B1、B2分别赋权1、2、4加出N值。验证几何关系后可以得到一个稳定的映射表N值实际扇区说明3扇区10°~60°1扇区260°~120°5扇区3120°~180°4扇区4180°~240°6扇区5240°~300°2扇区6300°~360°这里有个坑要注意不同资料对αβ坐标系的方向定义不完全一样有些教材把β轴方向反过来或者扇区编号从30°起始导致查表表不同。如果你从网上抄一段SVPWM代码扇区判断和T1、T2查表必须成对使用混搭是最常见的翻车原因。2.3 作用时间计算伏秒平衡的落地版本一个PWM周期Ts内期望电压矢量Vref产生的伏秒等于两个相邻有效矢量的伏秒之和。以扇区1为例相邻矢量是V1(100)和V2(110)目标矢量在0°~60°之间。我用伏秒平衡直接推得到扇区1的本地公式T1作用V1 (1.5×Vα - 0.866×Vβ) × Ts / VdcT2作用V2 1.732×Vβ × Ts / Vdc这个公式对扇区1精确成立。对于其他扇区工程上更高效的做法不是每个扇区单独推公式而是用旋转坐标变换把当前扇区转到本地0°~60°再套用扇区1的公式。但如果每次计算都做sin/cos旋转MCU开销太大。所以我最终采用了纯查表的工程版本。定义四个公共量A (1.5*Valpha - 0.866*Vbeta) * Ts / Vdc B (1.5*Valpha 0.866*Vbeta) * Ts / Vdc C 1.732*Vbeta * Ts / Vdc D (-1.5*Valpha 0.866*Vbeta) * Ts / Vdc E (-1.5*Valpha - 0.866*Vbeta) * Ts / Vdc然后根据扇区从A~E中组合出T1、T2。这样三角函数全部消除只剩加减乘除在STM32上跑一个20kHz的电流环毫无压力。零矢量时间就是T0 Ts - T1 - T2如果T0算出来是负数说明已经进入过调制区需要等比例缩T1、T2。2.4 七段分布与比较器装载算出T1、T2、T0还不够关键在7段时间怎么排。以扇区1为例采用V0(000)在两头、V7(111)在中间的对称结构一个周期内开关顺序为000 → 100 → 110 → 111 → 110 → 100 → 000每段时间对应为T0/4、T1/2、T2/2、T0/2、T2/2、T1/2、T0/4。注意两个特点第一每次切换只有一相开关动作开关次数最少开关损耗最低第二零矢量被均摊到三个位置波形对称特性最好。换成STM32的PWM比较寄存器语言我们需要的不是段的时间而是三个比较点。在中心对齐模式下ARR决定周期CCR1/CCR2/CCR3决定每相上升沿时刻。通过给每相CCR赋值就能让硬件自动生成七段式波形CPU只需要更新一次比较值剩下交给定时器外设。这就是七段式SVPWM能低开销运行的核心MCU只负责每周期算一次T1/T2并换成CCR之后PWM时序完全由TIM1硬件完成。3. 在STM32上搭建SimpleFOC最小工程让SVPWM真正跑起来3.1 硬件清单与连线我手头用的主控是STM32F405RGT6板子自带一个高速定时器TIM1有互补PWM输出非常适合直接驱动三相逆变器。搭配的驱动板是一个自制的三相栅极驱动用了IR2101半桥驱动芯片配上6个MOS管母线电压给到24V。电机是额定电压24V的小型航模无刷电机7对极空载KV值标称750。SimpleFOC对硬件要求其实不高核心就三条路径三维PWM输出、两路电流采样第三相可以推算或者也直接采、一路编码器/磁编码器位置反馈。我的接线大致是这样TIM1_CH1/CH1N → PA8/PA7 → 驱动A相上下管TIM1_CH2/CH2N → PA9/PA10 → 驱动B相上下管TIM1_CH3/CH3N → PA10? 不TIM1_CH3在PA10但我这里用的是PB0/ PB? 后来发现CubeMX里TIM1通道需要对准引脚最终用PA10/PA9两路电流采样用ADC1的IN8、IN9正好对应PB0和PB1通过运放放大后进单片机编码器用AS5600磁编码器走I2CPA6/PA7模拟I2C如果你用的是SimpleFOCMini或者带SimpleFOC固件的开发板接线会更省事但调试思路一样。3.2 CubeMX关键配置TIM1互补PWM、ADC触发、编码器输入CubeMX里以下几个配置决定了后面能否顺利出波形TIM1配置时钟源选内部时钟通道1、2、3都选PWM Generation CHx and CHxN即同时使能互补通道频率设置20kHz即PWM周期50μs。对F405来说如果TIM1时钟是168MHzARR 168MHz/20kHz - 1 8399死区时间设成0.5μs左右。STM32CubeMX里设置死区是按一个位宽单位算的需要根据定时器时钟和分频确认。我直接填的DTG寄存器值先设成0.5μs实测温升可以接受死区时间是必须加的。如果完全没有死区半桥上下管在切换瞬间会同时导通直通短路炸MOS管。死区设置过大会导致电流波形产生明显畸变和电机噪音这是一个需要权衡的参数。ADC配置两个ADC采样通道对应两相电流。关键是把ADC触发源选为TIM1的TRGO事件并且让TIM1在主更新事件时产生TRGO。这样每次PWM周期开始硬件自动触发ADC采样不需要CPU干预采样点天然和PWM周期对齐。很多人调SimpleFOC时电流波形乱七八糟一个常见原因就是ADC采样没和PWM同步采样时刻落在开关噪声严重的边沿上。用硬件触发是正解。编码器输入编码器可以直接用定时器的正交编码器模式。比如把AS5600作为ABZ编码器接TIM3的CH1/CH2CubeMX里把TIM3设成Encoder Mode。但AS5600更常用I2C接口SimpleFOC库里有现成的MagneticSensorI2C类不需要占用定时器输入捕获。3.3 初始化代码与调制模式选择SimpleFOC的代码风格很简洁。我的初始化代码大致是这样#include SimpleFOC.h BLDCMotor motor BLDCMotor(7); // 7对极 BLDCDriver3PWM driver BLDCDriver3PWM(PA8, PA9, PA10, PB1); // 第四个参数是使能引脚如果没有可以不用 MagneticSensorI2C sensor MagneticSensorI2C(AS5600_I2C); // 如果用的是ABZ编码器 // Encoder encoder Encoder(PA6, PA7, 4096, 0.5f); float target_velocity 5.0f; void setup() { driver.voltage_power_supply 24.0f; driver.init(); sensor.init(); motor.linkSensor(sensor); motor.linkDriver(driver); motor.voltage_power_supply 24.0f; motor.voltage_limit 12.0f; motor.velocity_limit 20.0f; motor.controller MotionControlType::velocity; // 关键把调制方式设为SVPWM motor.foc_modulation FOCModulationType::SpaceVectorPWM; // PID参数 motor.PID_velocity.P 0.15f; motor.PID_velocity.I 0.5f; motor.PID_velocity.D 0.0f; motor.PID_velocity.output_ramp 1000.0f; motor.PID_current.P 8.0f; motor.PID_current.I 200.0f; motor.init(); motor.initFOC(); Serial.begin(115200); _delay(1000); } void loop() { motor.loopFOC(); motor.move(target_velocity); // SimpleFOC的motor.monitor()可以用于调参 }motor.foc_modulation FOCModulationType::SpaceVectorPWM;这一行就是在告诉SimpleFOC使用SVPWM。如果你不设置这一行SimpleFOC默认用的其实是正弦PWM。很多人在调SVPWM之前根本不知道SimpleFOC默认不是SVPWM这里需要留意。3.4 不接电机的开路验证法一个非常有用的调试技巧在还没有编码器反馈或者编码器没装好的阶段用一个开环电压模式直接驱动电机在示波器上验证SVPWM波形。SimpleFOC里有一个电压开环模式它不需要真实反馈角度只靠给定速度积分出一个虚拟电角度直接输出旋转电压矢量。这个模式对调试SVPWM特别友好因为你可以用固定幅值的旋转电压矢量去激励逆变器然后观察生成的PWM波形是不是标准的马鞍波。motor.controller MotionControlType::velocity_openloop; motor.target 6.0f; // 电角速度目标单位rad/s motor.voltage_limit 6.0f; // 电压幅值限制单位V // loop中不再调用loopFOC而是 motor.move(target_velocity);这样即使不接编码器电机也会以一个估计角度转起来。如果转起来角度不对或者不转先查电机相序如果电机转起来但示波器波形不对再查PWM配置。这个顺序能帮你快速隔离问题。我在这个阶段就把示波器探头接到电机相线上看到了完整的马鞍波确认SVPWM链路是通的再去接编码器调闭环省了很多排查时间。4. 示波器下的信号分析从理想波形到真实波形4.1 识别马鞍波的三个特征很多人第一次看到SVPWM的相电压波形时会愣住因为脑子里预期的是纯正弦结果示波器上是一个正弦三次谐波的马鞍形。记住三个特征顶部是平的底部也是平的中间像马鞍一样凹下去一点波形频率等于电机的电频率而不是开关频率波形没有负半周整体在0V以上这是相对GND看的相线电压如果看线电压就不是这样第一次看到这个波形时你可能会怀疑是不是死区补偿没做好或者采样有问题但实际上这只是SVPWM在正常工作的标志。要验证的话把调制模式切回SinePWM默认再看相电压就是标准正弦了。一对比立刻就能明白马鞍波是SVPWM特有的零序注入结果。4.2 六路PWM的死区检查波形确认后下一步检查六路PWM的死区时间。把示波器探头同时夹在A相上管和下管的栅极信号上或者IGBT模块的手册里标的HO和LO引脚触发方式设成上升沿触发调节时基到某一路PWM边沿附近放大观察。你会看到两个信号之间存在一个凹槽——上管已经关断但下管还没导通的这段时间就是死区。我的实测死区约0.5μs在20kHz开关周期里占比1%属于合理范围。如果死区太小上下管切换时会看到很窄的毛刺或重叠这是很危险的信号需要立刻断电检查驱动和接线。如果死区过大电机空载时就能明显听到嗞嗞的高频声因为相电流在死区期间被迫续流产生了很多低次谐波。4.3 相电流波形谐波分析三相电流波形是衡量SVPWM效果的最直观指标。在闭环稳定转速下电流波形应该是相对光滑的正弦波叠加的纹波频率等于开关频率20kHz附近。当转速稳定后用示波器FFT功能看电流频谱开关频率处的谐波尖峰是正常的但如果在低频段几百Hz到几kHz出现明显尖峰说明死区或采样时序还有问题。我实测中发现过一种典型问题电流波形整体正弦但每个PWM周期的上升/下降沿附近都带毛刺频率和开关频率一致。排查下来是ADC采样点卡在了死区边沿采到了开关噪声。调整方法是把ADC触发的时刻往PWM周期中心偏移让采样落在零矢量作用区间毛刺立刻消失。SimpleFOC文档里常提到一个概念如果电流采样点不对运行起来会看到电流波形抖动或者转矩明显脉动这时候很多人去调PI参数其实根源在ADC触发时刻调PI只能暂时掩盖症状。4.4 SimpleFOC的SpaceVectorPWM与传统七段式的等价性你可能已经注意到SimpleFOC的源码里其实没有扇区判断和T1/T2查表这些传统步骤。它的SpaceVectorPWM分支做的是另一套运算case FOCModulationType::SpaceVectorPWM: { // 由逆Park得到Ualpha、Ubeta float mod1 Ualpha; float mod2 (-0.5f * Ualpha 0.866f * Ubeta); float mod3 (-0.5f * Ualpha - 0.866f * Ubeta); float maximum max3(mod1, mod2, mod3); float minimum min3(mod1, mod2, mod3); float central 0.5f * (maximum minimum); Ua mod1 - central; Ub mod2 - central; Uc mod3 - central; // 之后统一除以电压母线并映射到0~1 }这个做法的本质是零序分量注入直接把三相电压减去它们的平均值即最大最小值的平均值等效于给三相同时叠加一个零序分量。我之前在2.4节讲的七段式SVPWM在数学上与这个钳位零序完全等价。区别在于传统SVPWM是先定扇区再算每一相的比较器值SimpleFOC是算出三相电压后直接做中点钳位不需要显式判断扇区。所以如果你看SimpleFOC源码找不到switch sector的代码千万不要觉得奇怪它只是用了另一条路走到了同一个终点。理解这一点你就能在SimpleFOC和传统SVPWM教材之间自由翻译不会迷失方向。5. 源码级微调在SimpleFOC中改七段式SVPWM的实战记录5.1 SimpleFOC的钳位零序实现解读理解2.4节七段式SVPWM之后再来看SimpleFOC的代码就会很清晰。它的SpaceVectorPWM分支做了一次最大最小中点钳位相当于在整个三相电压上叠加了一个零序信号这个零序信号正好把三相电压的平均值拉到中心。这个操作不需要扇区判断也不需要作用时间表代码量极少但效果和七段式SVPWM完全一致。如果你要在这个基础上做定制比如想改成五段式DPWM减小开关损耗可以直接在setPhaseVoltage函数里换掉中间的钳位逻辑。五段式的思路是把零矢量只放在000或111一侧不放在两侧。对应到钳位法就是把某相电压钳到母线或者GND一整段。具体实现可以修改central的计算方式例如只钳位于正半周或负半周。这种修改在低速大扭矩、对损耗敏感的场合有一定价值代价是电流波形谐波变大。我的建议是普通项目用默认的SpaceVectorPWM就很好除非你做了热仿真或者有明确的效率指标否则别为了炫技去改这个。5.2 把七段式改成五段式DPWM什么时候划算五段式SVPWM每相在一个开关周期内可以被钳位到0或1一段时间因此每60°扇区会有一相不动作开关损耗理论上比七段式降低约三分之一。但它有个致命短板相电流纹波增大特别是在低速轻载工况下更明显。对于追求效率的压缩机、大功率工业驱动器五段式配合低开关频率是主流选择。但对于小功率、低电压的SimpleFOC应用场景MOS管的开关损耗本来就不大省下的那点损耗不足以弥补谐波增大带来的负面影响。从代码层面改的话我做过一个简易版本在SimpleFOC算完Ua/Ub/Uc之后判断此刻哪一相电压绝对值最大就把它强制设为0或1钳位其余两相按比例修正。这么改完之后电机依然能转但电流FFT里低频谐波明显变多噪音也比七段式大。所以最后我放弃了决定保持默认七段式。5.3 三个最容易翻车的坑第一个CubeMX里TIM1的互补通道映射错位。F405的高级定时器每个通道有主输出和互补输出引脚映射不能想当然。我一开始把CH1N接到PA7结果CH2和CH1N占用了同一个引脚编译烧录后根本不出想要的波形。建议先在CubeMX的引脚图里确认每个通道的物理位置再画板或接线。第二个电流采样没和PWM同步。这个问题表现很隐蔽电机能转电流波形看起来也像正弦但只要你提高转速或者加大负载电流波形就开始毛糙FFT低频段出现一堆杂散分量。我用示波器对比过问题就出在ADC触发源选成了软件触发采样时刻漂移采到了开关噪声。把ADC触发改成TIM1的TRGO事件后问题立即消失。第三个编码器方向与电机相序不匹配。这是所有FOC调试中最常见的坑。如果SimpleFOC读到的电角度方向和电机的实际旋转方向不一致电流环会完全乱来表现为电机卡顿、反转、甚至炸管尤其是你已经关了限流。解决办法在initFOC()之后用声码器显示的电角度手动旋转电机轴观察电角度变化方向如果方向反了在sensor初始化里设置方向为-1。另外提醒一个很多人忽略的点SimpleFOC的motor.initFOC()会尝试对齐电角度如果用的是ABZ编码器这一步如果编码器偏移没设好会看到电机轴先猛地跳一下。很多新手以为这是程序故障其实是对齐动作正常现象只要幅度别太大就行。关于Vdc的设定也值得多说一句。driver.voltage_power_supply和motor.voltage_power_supply必须设成实际母线电压值否则SVPWM会给出错误的调制比。如果你用24V电源但代码里写的12VSimpleFOC算出来的占空比会偏小电机性能被限制得莫名其妙。曾经我调试一台电机怎么调都只能跑到平时速度的60%最后发现是变量定义处把电源电压写成了12V改过来之后满血复活。最后再分享一个小技巧调试SVPWM时我建议手头常备一个双通道示波器和一个电流探头。别嫌贵——一个便宜的单相电流探头能帮你省下大量排查时间。如果只有电压探头也能通过看相电压波形和母线电流波形间接判断问题但效率会低不少。在实测中我发现一个特别实用的组合首先用电压开环模式在空载下验证马鞍波接着切换到速度闭环但把电流环带宽调低观察稳态电流波形最后再逐步调高PI增益。这样一步步确认每一个环节都正常而不是上来就跑满转矩闭环否则一旦波形不对你根本不知道是SVPWM的问题、ADC的问题还是PID的问题。另外把工程里的所有常量和参数写成宏定义或者一个motor_config.h比如Vdc、开关频率、死区时间、电机极对数。调参时改一处全部生效避免出现改了一半参数的尴尬。这个习惯我在踩了几次坑之后才养成现在每次调试都会先整理好参数表再动硬件。后面对照着示波器波形调整SVPWM就顺手很多了。
返回列表