
做FOC这件事最容易被劝退的往往不是算法本身而是你踩完一圈坑回来发现问题全出在采样、时序和调参这些“脏活累活”上。这篇文章想做的就是把我从零把一个STM32F103的无刷电机FOC驱动跑起来的完整过程拆给你看从为什么FOC能解决方波驱动解决不了的问题到电流采样怎么搭、Clarke/Park变换到底在干什么再到代码怎么组织、电流环怎么调出来最后是几个我往返修过好几次的实测坑。全程附关键代码片段和可直接照搬的调试流程适合手里已经有开发板、想从“能转”进阶到“能控”的读者。1. 先搞清楚一件事FOC到底解决的是哪个问题很多朋友一开始接触无刷电机用的是最暴力的方式六步换相或者直接开环给三相PWM。电机的确能转但痛点也很明显——低速抖、带载掉速、噪音大、发热高稍微碰一下负载转速就飘。根本原因在于这种控制方式始终在“盲推”转子走并没有闭环观测转子的实时位置和力矩状态。FOCField Oriented Control磁场定向控制干的事情说白了就是把一台三相交流无刷电机在数学上等效成一台“直流电机”来控制。它通过实时采集三相电流结合转子位置把定子电流分解成“励磁分量”和“转矩分量”两个独立的直流分量然后像控制直流电机那样分别用两个PID环去稳住它们。这样一来电机在任意转速、任意负载下转矩输出都是连续可控的低速不抖、高速不飘、噪音能压得很低。1.1 方波驱动和FOC的本质差异方波驱动也叫六步换相默认转子位置每60度电角度换一种导通组合一个电周期内用六个离散状态去“追”转子。它的优点是控制简单、对位置精度要求低但代价是换相瞬间的力矩波动没法消除电机听起来像在“咳嗽”。FOC则是连续计算转子位置对应的最优电压矢量让定子磁场始终垂直于转子磁场力矩波动被压到很小。低频段的静音效果、低速平稳性、高速弱磁能力都是方波驱动很难做到的。代价也很直接需要至少两相电流采样、需要较精确的角度反馈、需要更高的PWM-ADC同步精度计算量也会上一个台阶。1.2 FOC的适用范围和“不要用它”FOC不是万能的。如果应用场景是航模电调、电风扇、简单水泵方波驱动或者单电阻采样FOC已经足够成本和复杂度都低得多。FOC真正发挥价值的地方是伺服、机械臂关节、AGV驱动轮、电动工具、高端家电压缩机这类对力矩波动、动态响应、噪音控制有明确要求的场景。我个人的建议是如果你是第一次做FOC直接用一个带编码器反馈的电机套件起步先把电流环在“有感”状态下调通再考虑无感方案。一上来就做无感FOC同时面对位置观测器、反电动势采样、启动策略一堆变量排查问题时会非常痛苦。2. 硬件选型F103够用但电流采样要吃透标题里说的是STM32很多人的第一反应是“F103这么入门级的芯片跑FOC行不行”。结论是完全够用但前提是你把外设资源卡得刚刚好。FOC的核心计算并不重也就是Clarke、Park、SVPWM加上两个PID在10kHz频率下跑72MHz主频的F103大概占用20%~30%的CPU完全扛得住。2.1 主控和驱动板的配合我用的主控是STM32F103RCT6选它主要是手里正好有而且它的高级定时器TIM1输出六路互补PWM带硬件死区ADC的注入组用定时器TRGO触发这两个特性正好是FOC的刚需。驱动板用的是带三相全桥的通用无刷驱动模块类似DRV8301、IR2101MOS的板子重点是确认板上有没有电流采样电阻和运放电路。如果没有你还需要外接电流传感器或采样模块否则FOC最关键的电流环无从谈起。选板时我总结了三件事母线电压采样是否引出调试和过压保护都要用。三相下桥电流采样信号是否已经放大到0~3.3V别指望自己再搭运放。驱动板的PWM输入电平是否兼容3.3V不兼容得上电平转换。2.2 电流采样方案的对比与选择FOC至少需要两相电流第三相可以由基尔霍夫定律算出。市面上常见三种方案适合不同场景方案优点缺点适用场景三电阻低端采样成本低、精度较好、支持单电阻省成本低压降、PWM占空比受限入门首选两相隔离运放/霍尔无低压降问题、抗干扰强成本高、带宽要求高中大功率驱动单电阻采样成本最低、PCB省面积需要重构电流、算法复杂高功率密度场景入门强烈建议用三电阻或两相电阻的低端采样。需要注意低端采样运放放大后的信号必须送进ADC的采样通道而且ADC采样时刻必须避开PWM开关边沿附近的振荡区否则采到的是毛刺电流环会莫名其妙地啸叫。这个我在后面“实测踩坑”里详细说。2.3 编码器从ABZ到AS5600转子位置反馈选择上我推荐两种按成本递进磁编码器AS560012bit输出、SPI/IIC接口、绝对角度成本二十来块钱非常适合入门调电流环。它输出的就是绝对电角度省去上电找零的麻烦。光电ABZ增量编码器精度更高但需要处理Z脉冲找零和方向判断问题。如果你要做有感FOC一开始别用无感的滑模观测器方案。AS5600 一个简单的直流无刷电机或云台电机是成本最低、见效最快的学习组合。记得把编码器的角度换算成“电角度”而不是“机械角度”乘以电机极对数即可。3. 三个变换的直觉Clarke、Park、SVPWM不是数学题FOC被劝退的高发区就是这三个变换。其实它们没有一个是数学考试全是物理直觉。我给每个变换都总结了“一句话版本”你看完再结合代码就通了。3.1 Clarke变换从三相到两相三相定子电流在空间上相隔120度但它们在同一个电机里叠加产生合成磁场。Clarke变换干的事就是把这“三根棒子”投影到互相垂直的两个坐标轴α、β轴上。一句话把三个120度方向的电流折算成两个90度方向的电流。公式上只要知道两相电流第三相用IaIbIc0反推就行。3.2 Park变换把交流变成直流αβ轴上的电流仍然是正弦波PID不喜欢追正弦直流量才是它的舒适区。Park变换把αβ坐标系旋转到与转子同步的dq坐标系旋转角度就是编码器读到的电角度。旋转之后原来随时间变化的正弦电流变成了缓慢变化的直流分量Id是励磁分量Iq是转矩分量。对表贴式永磁同步电机控制目标就是Id0、Iq目标转矩。一句话把旋转坐标系的“交变难题”转成静止坐标系的“直流控制”。3.3 SVPWM7段式发波是底线获得Ud和Uq之后还要逆Park变换回αβ坐标然后用SVPWM生成三相PWM波。SVPWM的核心思想是把三相全桥的八种开关状态六个有效矢量两个零矢量按平行四边形法则合成任意方向、任意大小的电压矢量。7段式发波指的是在一个PWM周期内分段插入有效矢量和零矢量让三相波形中心对齐谐波小、母线利用率高。实现时可以直接用查表判断扇区、计算相邻矢量的作用时间。网上有大量现成代码我贴一下我用的核心函数注意处理好浮点和溢出。void svpwm_calc(SVPWM_TypeDef* sv, float Ualpha, float Ubeta) { float U1 Ubeta; float U2 -0.5f * Ubeta 0.8660254f * Ualpha; float U3 -0.5f * Ubeta - 0.8660254f * Ualpha; uint8_t sector 0; if (U1 0) sector 1; if (U2 0) sector 2; if (U3 0) sector 4; // 根据扇区查表得到相邻矢量的作用时间T1、T2 // 7段式调制零矢量时间对半分 // 具体查表逻辑各家代码一致注意归一化到定时器ARR }代码细节不展开但原理要记住SVPWM不是“算出来的公式”而是“矢量合成 时间分配”。把扇区判断和时间计算先捋清楚再抄代码才不会抄完一改就炸。4. 软件架构把电流环、速度环、状态机拆开写代码组织混乱是很多FOC项目调不出效果的隐性原因。我看过太多人把电流环、速度环、编码器读取全部塞进一个定时器中断里变量满天飞最后改一个参数要全局搜索半天。想稳定跑起来分三层最舒服。4.1 中断划分10kHz电流环 1kHz速度环FOC电流环的带宽决定了力矩响应的速度一般设置10kHz~20kHz。我选10kHz也就是100us进一次中断在中断里完成读编码器角度、读ADC电流、Clarke、Park、电流PID、逆Park、SVPWM输出。速度环不需要那么快放在1kHz即可也就是每10个电流环周期更新一次。中断里不要放串口打印、不要放延时、不要放浮点printf这些操作会导致电流环抖动表现出来就是电机噪声大、发热快。调试数据可以用DMA 环形缓冲导出或者等停止后再通过串口回读。4.2 状态机从Idle到Run电机控制必须有状态机不要用“一个flag一堆if”糊弄。我的状态定义大致如下IDLE所有PWM关断等待指令。CALIB采样零漂校准读取编码器零位。ALIGN转子预定位使用id正向小电流把转子吸到固定角度。OPENLOOP开环启动用于无感或低速过渡。CLOSEDLOOP切入闭环电流环/速度环开始工作。FAULT过流、过压、堵转等故障态封锁PWM。每个状态转换要有明确的触发条件和退出条件尤其FAULT态任何异常都优先跳进去先封波再上报错误码。这样调程序的时候看状态码就知道卡在哪一步比看波形猜快得多。4.3 可复用的代码框架我建议把代码至少拆成四个模块方便后面换电机、换板子复用hardware_adc.cADC初始化、采样值读取、零漂校准。hardware_pwm.cTIM1 PWM初始化、死区配置、占空比更新。hardware_encoder.c编码器读取、角度换算、Z脉冲处理。foc_core.cClarke/Park/SVPWM/PID这些纯算法不依赖硬件。motor_ctrl.c状态机、速度环、指令解析。纯算法模块里不要出现任何寄存器操作这样才能换平台复用。我后来把foc_core.c原封不动搬到一个国产Cortex-M0的板子上只改了硬件层半天就能转起来这就是拆分的价值。5. 启动前的必修课转子初始位置检测很多刚接触FOC的人上来就写一个“上电直接跑闭环”的代码结果电机要么不动要么猛转一下然后报错。原因很简单不知道转子初始在哪你算出来的Iq方向可能和真实转子的力矩方向对不上还谈什么控制。这步在行业里叫“转子初始定位”或“初始位置检测”是模拟量和数字量之间的第一道坎。5.1 有编码器方案的做法如果你用了AS5600这类绝对编码器上电直接读角度即可但要注意多极对电机角度映射。我家云台电机是7对极机械角度一圈对应7个电周期所以电角度 机械角度 × 7 % 360°。如果你拿到的编码器是增量式的上电后角度位置是未知的这时最稳妥的办法是用“预定位”让Id一个小电流比如额定电流的5%~10%给定Iq0转子会被吸到某个固定电角度。这个状态保持几百毫秒然后以此角度为0点再切入闭环。有个细节预定位电流太大会导致转子“咔嗒”一下猛动太小又吸不准。实测从额定电流的5%开始往上加能稳定吸住且不猛跳就是合适值。5.2 无感启动方案无感方案没有位置传感器启动时只能用开环强拖或者高频注入。高频注入法在零低速能观测凸极位置但对表贴电机效果一般我用的比较多的是“开环强拖切换闭环”给一个固定频率、固定幅值的旋转电压矢量把转子拖到一定转速等反电动势足以支撑观测器工作时再切换到闭环。这个方法实现不复杂但对负载突变比较敏感适合风机、泵类这种轻载启动场景。切换时机是核心难点切早了会掉步失步切晚了电流会很大。我的经验是先看速度估计值是否跟上给定速度再看电流是否有收敛趋势两个条件都满足再切换。6. 电流环调参手记从波形“开花”调到收敛调参是FOC最考验耐心、也最依赖“手感”的部分。电流环是内环调不好外环全是白搭。我会把完整流程拉一遍你自己复现的时候照着做就行。6.1 电流采样校准是调参前提零漂校准不做你调的每一个PID都是在放大噪声。电机不动、PWM不输出时采集100次三相电流取平均把这个值作为零点偏移。ADC的参考电压误差、运放的偏置电压在这步全部被吸收掉。实测中没校准前开环给个小电流电机会缓慢漂移校准后纹丝不动就是这个步骤的差别。校准完成后还要做一次“给定电流-反馈电流”的标定检查。给一个固定Id/Iq指令看反馈电流的稳态误差是否在合理范围。如果偏差很大先查电流采样增益是否正确再谈调参。6.2 从对拖台架开始的参数辨识电流环能不能调好和电机参数关系很大。如果不知道电阻和电感建议先用直流伏安法或短路法粗测。直流伏安法很简单给电机施加一个已知直流电压测稳态电流RU/I。电感则可以用电压阶跃后的电流变化率来算L≈U×dt/di。有了R和L电流环的开环截止频率通常设计在1kHz左右。比例系数Kp可以按KpL×ωc来估算其中ωc2π×1000。积分Ki可以按KiKp×R/L来估算。这只是一个起点最终要在实际波形上微调。6.3 PID参数调整的完整流程我的调试流程是这样的先把速度环、位置环全部失能只留电流环。给一个恒定Iq指令用调试器或串口看电流反馈波形。只加Kp从“估算值的一半”开始加看到电流反馈对给定跟随变快且没有明显振荡为止。再加Ki用来消除稳态误差注意Ki别加到波形出现低频振荡。用阶跃响应评估给定从0跳到额定Iq看上升时间和超调量超调控制在10%以内就算可用。具体参数给一个参考起点针对一个额定电流约3A的无人机电机参数初始参考值观察现象调整方向Kp0.2电流响应慢/跟随差逐渐增大Ki0.05稳态误差无法消除逐渐增大Kd0高频噪声被放大一般不用这里强调一点电流环响应快、噪声大Kd几乎不用加。如果你发现波形上叠加了很多毛刺先查采样和布线不要靠调Kd把它压下去Kd压完问题还在只是被掩盖了。7. 实测踩坑这五个问题救了我好几次FOC跑通之后的工程化拼的就是踩坑经验。这几个问题每个我都花了不少时间排查写出来帮你提前绕开。7.1 PWM和ADC不同步一开始我把ADC采集放在中断开头直接读结果电流波形全是尖刺电机声音像在“锯木头”。原因是开关管开关瞬间的电流尖峰被采了进来。后来改成TIM1边沿对齐模式下让定时器溢出事件触发ADC注入组采样并且把采样时间点放在PWM中心比如7段式波形的中间时刻此时电流纹波最小。改完之后波形肉眼可见地变干净。7.2 上电瞬间MOS炸管的隐性原因我烧过两次MOS管一次是驱动板上电时控制板还没初始化IO默认输出低电平把下桥全打开了另一次是PWM初始化顺序不对死区还没配置就输出了。这两个都怪“时序”解决办法是单片机上电先把PWM输出全部禁用初始化完成后先配置死区再使能PWM输出。另外推荐在PWM输出通道上加RC滤波避免上电瞬间的毛刺误触MOS栅极。7.3 编码器毛刺和Z脉冲增量编码器如果布线不好会引入毛刺导致角度跳变电流环直接跟着抖。建议使用正交编码器接口STM32的TIM编码器模式而不是外部中断计数硬件自带滤波能扛掉大部分毛刺。Z脉冲的寻找也别在中断里循环等我见过有人用while等Z脉冲结果电机卡死。正确做法是预定位后让电机慢慢转起来找Z找到Z再闭环保留。AS5600这类磁编码器注意要远离强磁体和电机绕组否则角度会发生非线性的失真表现为在某些位置电流环怎么调都发散。7.4 母线电压纹波带来的低频抖动现象是电机在某个转速段来回抖频率大概十几赫兹查了电流环、速度环都没问题最后示波器一测母线电压电源带的压降波动挺大。原因是母线电压直接参与SVPWM的过调制处理电压波动会让Ud/Uq的计算基准产生偏差。解决方法是ADC同步采样母线电压在SVPWM计算时用实际母线电压做归一化而不是死用额定电压。这个点非常容易被忽略。7.5 死区时间和电流零点失真死区补偿是另一个被忽略的坑。死区时间是为了防止上下桥直通但它也会让电流在过零点附近发生失真低速和轻载时尤其明显表现为电流波形有“台阶”或力矩脉动。如果你对低速性能要求高就需要在电流采样和PID之间加入死区补偿用电流方向和占空比估算死区造成的电压误差再加回到电压指令里。不追求极致性能的话可以先跳过但当电机在低速下出现周期性抖动时记得回头查这个点。最后分享一个我自己的习惯每次改完一个参数我都会在笔记本上记录当时的电流波形特征和听感调得多了你会发现FOC好不好耳朵先知道。顺畅的声音是均匀的“嗡嗡”声刺耳尖啸多半是采样或参数除了问题。这个项目做到后面F103上的FOC驱动已经被我精简到核心算法不到两百行硬件层随便换电机随便换这套思路也跟着走了好几个板子。希望能帮你少走一点弯路。