前阵子搭一个云台,电机选了无刷云台电机,驱动器一开始想用现成的电子调速器,结果发现电调只能油门调速,想要精准转角和力矩控制根本无从下手。搜了一圈,最后落在了 SimpleFOC 这个开源项目上。如果你也在用 Arduino 玩无刷电机、永磁同步电机(PMSM),或者被 FOC 那些坐标变换和电流环折腾到头大,这个项目值得你花一个下午完整跑一遍。这篇文章我会按实际踩坑的顺序,从环境搭建、开环验证、闭环校准、PWM 细节到无感控制,把 SimpleFOC 这套东西从头到尾捋一遍。
1. 为什么我推荐 SimpleFOC:让 Arduino 直接驱动无刷电机的完整方案
1.1 普通电调做不到的事,FOC 可以做到
无刷电机的传统驱动方式是六步换向:通过位置传感器或者反电动势过零检测,按照六个扇区给三相绕组通电,产生一个旋转磁场把转子拽着走。这种方式实现简单、转速范围宽,但力矩脉动大、噪音明显,低速时尤其难受,更不用说让电机在某个角度精确停住了。
FOC 的思路完全不一样。它把三相电流先通过 Clark 变换从静止坐标系转到两相静止坐标系,再通过 Park 变换转到随转子旋转的 d/q 坐标系。这样一来,力矩分量和磁场分量就被解耦了——q 轴电流管力矩,d 轴电流管磁通。调整 q 轴电流就能像控制直流电机那样线性地控制力矩,而且全程是正弦波连续调制,静音、平滑、响应快。
打个比方,六步换向像手动挡挂挡,只有几个固定挡位;FOC 像无级变速,随时都在最佳发力点。这也是为什么现代电动汽车、无人机、机器人关节普遍用 FOC,而 SimpleFOC 把整套算法封装成了 Arduino 生态里的库,你不需要从零写坐标变换和 SVPWM,调好参数就能用。
1.2 SimpleFOC 项目全景:库、驱动器和配套工具
SimpleFOC 严格来说是开源社区的整套 FOC 解决方案。核心代码在 simplefoc/Arduino-FOC 仓库,里面包含几个大块:
- BLDCMotor 类:负责 FOC 算法、运动控制模式、PID 和低通滤波。
- BLDCDriver3PWM / BLDCDriver6PWM 类:负责输出 PWM 波形,3PWM 对应三相下桥驱动,6PWM 对应上下桥互补驱动。
- 传感器类:磁编码器(AS5600、MT6701 等)、增量编码器、霍尔传感器,以及无感模式需要的反电动势观测。
- 电流检测类:InlineCurrentSense 和 LowsideCurrentSense,用采样电阻加运放采集相电流。
除了核心库,官方还做了 SimpleFOC Studio 这个 Web 调试工具,接上串口之后能实时看速度、角度、电流曲线,在线调 PID。整个项目文档在 simplefoc.com 上相当完整,例程覆盖 ESP32、STM32、Arduino Uno、Teensy、RP2040 等常见开发板。
1.3 它能做的项目类型
我见过有人拿 SimpleFOC 做云台、机械臂关节、自平衡杆、电动滑板,甚至还有 3D 打印机直驱挤出机。它的运动控制模式有 torque、velocity、angle,外加 openloop 两个开环模式,基本覆盖了大多数电机控制场景。仓库里的 issue 区域经常有人分享自己的作品,遇到问题搜一搜,很多坑都有人踩过并给出了解决方案。
对于学习 FOC 的人来说,这个项目的价值也很直接:你不需要昂贵的伺服驱动器,一块几十块的开发板加一块驱动板,就能把教科书上的磁场定向控制搬到桌面上实测。
2. 环境搭建第一关:Arduino IDE、ESP32 开发板包和硬件接线
2.1 硬件清单和推荐组合
SimpleFOC 只是软件,硬件需要自己配。我的建议是新手先选一套低成本但容错率高的组合,别一上来就上大功率电机,容易烧驱动板和传感器。
| 部件 | 推荐型号 | 备注 |
|---|---|---|
| 电机 | 云台无刷电机(如 2204、2804、4006 等) | 极对数常见为 7,12V 供电 |
| 驱动板 | L6234、DRV8313、SimpleFOC Mini Shield、BGC 驱动板 | 3PWM 驱动的 L6234 最省事 |
| 传感器 | AS5600 磁编码器(12bit,I2C 接口) | 校准简单,新手首选 |
| 主控 | ESP32(如 ESP32 Dev Module、ESP32-S3) | 性能足够,I2C 和 PWM 资源多 |
| 电源 | 12V 直流电源或可调电源 | 先电流限制在 1A 以内调试 |
我自己的组合是 ESP32 Dev Module + L6234 驱动板 + AS5600 + 云台电机。整套下来百来块钱,跑 SimpleFOC 完全够用。如果手头有 Arduino Uno 也行,但 AVR 的资源紧,做复杂控制容易碰壁。
2.2 Arduino IDE 安装和 ESP32 开发板包的手动处理
Arduino IDE 直接去官网下载新版,Windows、macOS、Linux 都有安装包。装好之后要加 ESP32 的支持,这一步是很多人卡住的地方。
在 IDE 的首选项里把 ESP32 的板管理器地址贴进去,然后在开发板管理器里搜索 esp32 安装。问题在于这个包比较大,网络下载经常中断或卡在 MD5 校验失败。我强烈建议在首次安装时直接采用离线包方式:官网或第三方整理好的 esp32 离线包下载后,解压到 Arduino 用户目录下的 hardware/espressif 文件夹,重启 IDE 就能识别到 ESP32 系列,不用等在线下载。
装好后在工具 -> 开发板里选择 ESP32 Dev Module,上传速度可以设成 921600 或 115200,波特率设低了上传会慢,但出错率也低。很多人遇到的"上传程序一直卡在 Connecting..."基本是板子没进入下载模式,按住板上的 BOOT 键再点上传,等开始写入后再松开就行。
2.3 接线要点:共地、PWM、I2C 一个都不能错
接线看似简单,但错误率很高。以 L6234 驱动板为例,逻辑引脚接 ESP32 的 GPIO,三个 PWM 输入对应 U、V、W,控制信号和电机主电源之间必须共地,否则驱动板逻辑高低电平判断会乱,电机表现为随机抖动或完全不动。
AS5600 磁编码器用 I2C 接口,SDA 和 SCL 分别接 ESP32 的 GPIO21 和 GPIO22(这是默认的,具体以开发板定义为准),注意供电用 3.3V 或者 5V——AS5600 数据手册对电压有要求,接 5V 时 I2C 上拉也要跟着接 5V,如果主控是 3.3V 逻辑就建议统一用 3.3V 供电,避免电平不匹配损伤传感器。
电源方面,电机电源和逻辑电源分开是安全习惯。调试阶段我习惯用可调电源限流,电机和驱动板之间的母线电容不要省略,否则上电瞬间的浪涌容易把驱动芯片击穿。驱动板的 Vm 电源输入就接可调电源正极,负极和逻辑电源负极连一起。
3. 第一次启动电机:参数识别、开环测试和"转起来"的完整步骤
3.1 认识核心类和必须知道的参数
SimpleFOC 里最核心的是 BLDCMotor、BLDCDriver3PWM 和传感器类。代码结构大致如下:
#include <SimpleFOC.h> BLDCMotor motor = BLDCMotor(7); // 7 是极对数 BLDCDriver3PWM driver = BLDCDriver3PWM(2, 4, 5, 8); // U、V、W、使能引脚 MagneticSensorI2C sensor = MagneticSensorI2C(AS5600_I2C); void setup() { sensor.init(); driver.init(); motor.linkDriver(&driver); motor.linkSensor(&sensor); motor.init(); motor.initFOC(); }BLDCMotor 构造函数的第一个参数是极对数(pole_pairs),这个值填错,FOC 完全没法用。极对数怎么数?拿一个小磁铁或者直接用改锥头沿着电机外壳外圈吸一圈,吸住的次数就是极对数的一半?不对——极对数是 N-S 的组数,云台电机通电后转动一圈时电子换向的次数除以一圈的电角度周期,实际用磁铁数最直观:你拿螺丝刀靠近转子,沿圆周感受吸附点,转一圈有多少个吸附位置,极数就是这个数,极对数是极数除以 2。但云台电机型号不同会有差异,常见的是 7 对极,这就是为什么构造函数里写 7。
如果不想数,还有一个土办法:把电机三相任意两相短接,用手转电机,会感觉一顿一顿的,一顿一个齿槽周期,转一圈的顿感次数就是极对数乘以某个系数。但这个方法对新手不太友好,数磁铁位置最靠谱。
3.2 相电阻和相电感:能测尽量测,测不了也能跑
相电阻(phase_resistance)和相电感(phase_inductance)在 SimpleFOC 里主要影响电流环和低速效果。如果只是验证能否转动,这两个参数不填也能跑,系统会走电压模式而不是电流闭环。
想测也比较简单。相电阻用万用表直接量电机两相线之间的电阻,再除以 2 就是单相电阻。云台电机一般在 10 到 30 欧姆左右。相电感需要 LCR 电桥,普通万用表量不出来,没有电桥就先不管,或者参考同型号电机的标称值。我的经验是先填相位电阻,电感留空,跑通之后再慢慢补。
3.3 开环测试:不依赖传感器的初始化验证
在接传感器之前,有一个特别好的验证手段——velocity_openloop 模式。这个模式不读取传感器,直接给定转速范围内的电压矢量旋转,相当于手动制造旋转磁场。如果电机能转起来,说明接线、驱动板、PWM 都是通的,问题只可能在传感器和闭环参数上。
void setup() { driver.init(); motor.linkDriver(&driver); motor.controller = MotionControlType::velocity_openloop; motor.voltage_limit = 5; // 限制最大电压,防止电流过大 motor.init(); } void loop() { motor.move(2); // 目标速度 2 rad/s }跑 openloop 时注意把电压限制调低一点。我调试时先限制到 3V 到 5V,只要电机轻负载能转就行。目标速度先从 1 rad/s 慢慢加到 10 rad/s,观察电机是否平滑加速。如果电机只振动不转,大概率是 PWM 引脚顺序不对,把 driver 的三个引脚换一下顺序基本能解决。
另一个常见现象是电机反转或者转得很弱,这时检查使能引脚。很多 L6234 板子需要把使能引脚拉高,SimpleFOC 的 BLDCDriver3PWM 构造函数第四个参数就是使能引脚,传 -1 代表不使用。
3.4 上电不转、抖动、异响的快速排查
我在群里见过太多人卡在这一步。这里写一下我认为最靠谱的排查顺序:
- 电机一点反应都没有:测量驱动板 Vm 电压、逻辑电压是否正常;串口打印是否正常输出;使能引脚电平是否正确。
- 电机只发出高频叫声但不转:三相 PWM 接线顺序不对,把 U、V、W 三根线依次交换试试;或者电压限制太低,推不动负载。
- 电机抖动且电流大:大概率是电源塌了。可调电源电流上限设太低或者线太细,换粗线、提高电流限制。
- 转速方向反了:不需要改硬件,把 PWM 引脚顺序在代码里对调,或者后面调 sensor_direction 即可。
开环模式能跑通之后,恭喜,硬件链路算是全线贯通了,接下来才进入真正有技术含量的闭环环节。
4. 从"转得动"到"转得准":编码器校准和三环 PID 整定
4.1 为什么闭环必须先知道电角度
FOC 里最重要的一句话:你控制的电流矢量必须时刻和转子磁极保持正确的空间角度关系。d 轴要跟转子磁极对齐,如果角度错了,电流就是白做功,电机会抖、啸叫甚至烧驱动。
所以接上编码器之后,第一件事不是调 PID,而是让系统把编码器的读数映射到真实的电角度上。motor.initFOC()会做两件关键事情:一个是 sensor_direction 校准,也就是判断传感器旋转方向和电机磁场方向是正还是反;另一个是 zero_electric_angle 校准,也就是编码器零点位置对应的电角度偏移。这两个值不对,后面的控制全是乱套的。
校准过程看起来很简单:系统会给一个固定电压矢量把转子拉到某个位置,然后记录传感器读数。所以 initFOC() 时电机猛转一下或者咔哒一声是正常的,不用慌。现场常见的问题是校准电压太高,转子会猛烈跳动甚至把云台甩出去。校准电压可以在代码里调,我一般设置 3V 到 5V,只要能带动转子就行。
4.2 编码器选型:AS5600 和增量编码器怎么选
磁编码器是用 I2C 或者 SPI 读绝对角度,上电就知道位置,校准一次之后每次启动都可以直接闭环。增量编码器需要初始找零位,断电再上电通常要重新对齐,麻烦一些。新手我无脑推荐 AS5600,12bit 分辨率、I2C 接口、便宜、资料多,SimpleFOC 直接支持。
用 AS5600 时有个细节:磁铁要和电机轴同轴安装,磁铁中心要尽量对准编码器芯片中心,不然读出来的角度会有周期性误差。磁铁用径向充磁的更好,轴向充磁也不是不能跑,但精度会受影响。SimpleFOC 官方文档里有传感器安装示意图,照着装就行。
4.3 三环 PID 的调参顺序与"带宽"的理解
位置、速度、电流三环,从内到外是电流环最快、速度环次之、位置环最慢。很多人一上来就调位置环 P,结果电机疯狂振荡,原因就是内环没有先稳定。
“带宽”这个词在 FOC 里高频出现,它的意思是这个环能跟上多快的指令变化。电流环带宽通常设计在 1kHz 以上,速度环几十到几百赫兹,位置环几赫兹到十几赫兹就够了。带宽太高会把噪声放大,系统容易啸叫;带宽太低则跟指令很迟钝。SimpleFOC 中 PID 的 P、I 值就是在调整各自的带宽。
我做调参的顺序是这样的:
- 先开速度环,位置环不管:把速度指令设成一个固定值,比如 5 rad/s,只调速度环 P,直到速度输出平稳不抖,再加 I 消掉稳态误差。
- 速度环稳了再开位置环:位置指令来回摆动,调位置环 P,从小往大加,观察有没有过冲和振荡。
- 电流环通常不用大调:如果开了相电流检测,电流环的 P、I 保持默认即可,除非发现相电流波形明显畸变。
经验数值参考:云台电机、12V 供电下,速度环 P 常见 0.1 到 0.5、I 在 0.5 到 2;位置环 P 常见 10 到 40。但这个范围只是起点,不同电机和负载差异很大。
4.4 调参崩坏现场的处理
我自己翻车最多的情况有两种。一种是电机低频嗡嗡响,停下来跟着振荡,多半是 zero_electric_angle 没校准对,重新跑 initFOC() 再看打印值;另一种是位置控制有明显的"呼吸感",就是到位之后还一直慢速来回飘,通常是速度环 I 太大或者位置环 P 太小。
另外,如果电机只能朝一个方向转、反向立刻啸叫,sensor_direction 大概率是反的,把 motor.sensor_direction 改成 -1 试试,或者在硬件上把编码器方向反过来。SimpleFOC 初始化时会在串口打印校准结果,READ 和 OK 就应该一眼看到,这样排查效率高很多。
5. PWM 波形、定时器和电流采样:调底层参数要懂的事
5.1 SimpleFOC 默认 PWM 频率为什么是 20kHz
很多人在群里问"FOC PWM 波形和定时器到底怎么回事"。SimpleFOC 对不同平台有默认 PWM 频率,ESP32 上通常是 20kHz 量级,理由是:人耳对 20kHz 以上不敏感,电机不会发出刺耳噪音;同时 20kHz 下电流纹波足够小,力矩平滑度够用。
但频率不是越高越好。开关频率越高,MOS 管和驱动芯片的开关损耗越大,发热越严重。我调试小车时如果把 PWM 频率从 20kHz 拉到 40kHz,驱动板温度明显升高;拉低到 10kHz 则电机会有点"气声"。所以选频率是折中。官方默认值我建议先不动,等遇到实际问题再改。
每个 PWM 输出背后是定时器通道。ESP32 的 LEDC 有通道数量限制,如果同时用太多 PWM 输出或者跟其他库打架,就会出现诡异现象——比如舵机库和 FOC 同时用某个定时器通道,启动时间错位。查这种问题要先确认引脚映射和通道分配,平台的手册和库源码里都能查到引脚与通道的对应关系。
5.2 3PWM 和 6PWM:两种驱动拓扑的区别
SimpleFOC 支持 3PWM 和 6PWM 两种驱动类型。3PWM 只有三个下桥 PWM,电机的三相被拉到地或者拉高到电源,需要驱动芯片自带续流和死区处理,L6234 就是这种,接线少、成本低,适合小功率。6PWM 则是上下桥互补输出,控制更灵活,能实现同步整流,效率高一点,适合大功率场景。
选硬件时注意:云台电机、小功率直接 3PWM 足够;想上大电流的,老老实实买带 6PWM 输入的驱动板。3PWM 驱动板的缺点是母线电压利用率低一些,但低速云台场景不敏感。
5.3 电流采样:和 PWM 波形的同步问题
如果上了电流环,就绕不开电流采样。SimpleFOC 的 InlineCurrentSense 用采样电阻加运放测量相电流,采样时刻必须避开 PWM 开关切换瞬间,否则采到的全是噪音。实现上通常是在 PWM 载波周期的中点采样,这时候开关已经稳定,相电流代表性最好。
这块很多人第一次看代码觉得头大,其实不用深挖。你只要知道:电流采样不是随便找个时间点读,采样窗口和 PWM 同步是 FOC 电流环能不能稳住的关键。如果发现电流波形满是毛刺,先检查采样电阻的运放带宽和采样时序,别急着调 PID。
5.4 定时器冲突的排查思路
调底层参数最烦的是"上一个程序好好的,改了一下配置,电机全乱"。这种基本是定时器通道被占用。排查思路就一条:把问题缩小到最小可复现状态。先只保留 FOC 的初始化,其他外设全部注释掉,一步步加回来,看哪个外设导致系统异常。这个排查法在 Arduino 生态里非常通用,比盯着代码猜高效得多。
6. 无感 FOC 与滑膜观测器:丢掉编码器后的启动策略
6.1 无感为什么难:反电动势在低速时太小
无感 FOC 意味着去掉编码器,靠电机本身的反电动势来推算转子位置。问题是反电动势的幅值和转速成正比,转速接近零的时候,反电动势几乎为 0,没有信号可观测。这就陷入了一个死循环:需要位置才能启动,但启动时测不到位置。
工程上的无感方案通常分两段:低速用开环强拉或者 I-F 启动,把转子带起来;中高速切到滑膜观测器或者磁链观测器,用反电动势闭环。SimpleFOC 生态里对这个方向的支持也在逐步完善,但要做好心理准备,无感的调试难度比带编码器高一个量级。
6.2 滑膜观测器原理简述
滑膜观测器(SMO)的核心思想是:利用电机的电压方程和电流模型,构造一个观测器,让观测电流和实际电流的误差进入一个开关函数(比如符号函数),这个开关函数的输出经过滤波后就是反电动势的估计值。再通过锁相环或者反正切运算,从反电动势里提取转子角度。
这里面最需要注意的就是滤波。反电动势信号经过低通滤波器后会有相位滞后,滞后量和转速有关,所以无感 FOC 在实际运行中往往要根据转速做相位补偿。补偿没做好,电机高速时角度偏差越来越大,轻则效率降低,重则失步。这也是很多人觉得无感调起来很"玄"的原因——它不是调一个 PID 那么简单,而是整个观测链路都要对齐。
6.3 更靠谱的启动策略:I-F 开环强拉再切换
实用的无感启动一般这样干:开始时把电流矢量设成一个固定幅值,频率从 0 开始缓慢增加,让电机先被"牵着"转起来,这个过程叫 I-F 控制或者电流频率注入。等转速超过某个阈值(比如额定转速的 5% 到 10%),反电动势足够强了,再无缝切换到滑膜观测器闭环。如果切换点的相位没对齐,电机就会顿一下甚至失步。
SimpleFOC 的 openloop 模式其实就承担了类似"启动牵引"的功能。你可以在低速段故意用 velocity_openloop 把电机带到某个转速,再切回闭环。我当时跑无感项目时,切换前我会打印观测器的角度和实际轨迹,确认角度平滑才有信心切。
6.4 什么场景别碰无感
无感 FOC 适合风机、水泵、无人机电调这类高速、轻载、不需要锁定位置的场合。像机械臂关节、云台这种需要低速大力矩精准定位的应用,老老实实带编码器,别折腾无感。SimpleFOC 这种级别的无感方案,做原理验证和学习可以,真上产品要评估清楚扭矩和可靠性。
7. 实战排错:串口没数据、电机抖动、用 Wokwi 仿真入门
7.1 串口没有任何输出:八成不是代码的问题
调 SimpleFOC 第一步永远是看串口打印。初始化过程会把校准信息、传感器状态一条条打印出来,这是最直接的诊断窗口。如果串口一片空白,先检查三件事:
- 端口选择:开发板有没有被电脑识别,驱动有没有装。Windows 下 ESP32 常见 COM 口,Linux/macOS 下是 /dev/ttyUSB 或 /dev/tty 设备,别选错。
- 波特率:代码里 Serial.begin(115200),串口监视器右下角也必须选 115200。这是最不起眼但最常见的坑。
- 监视器设置:不要开"自动换行"或者"时间戳",有些版本会干扰显示,看着像乱码。
如果代码正常但串口监视器一个字都没有,还有一个可能:程序根本没烧进去。上传时看 IDE 输出的日志,确认写入成功再跑。
7.2 电机抖动、啸叫、过热的通用排查链路
抖动的原因千奇百怪,但排查顺序有规律。我的做法是逐步回滚:先回到 openloop 模式,确认硬件链路没问题;再回到闭环但把速度环、位置环全部停掉,只初始化传感器和 FOC,观察电角度是否平滑变化;最后一步一步加控制环。这样任何一步出问题,问题范围立刻缩小。
实际遇到最多的几个原因:校准电压太高导致 initFOC() 抖动剧烈;编码器接线接触不良导致角度跳变;zero_electric_angle 校准存档失败,每次上电重跑;PID 增益太大导致高频振荡。其中"角度跳变"这个比较隐蔽,AS5600 的 I2C 线太长老化或者磁铁偏心都会造成角度噪声,调 PID 之前先看角度波形是明智的。
7.3 用 Wokwi 仿真入门:没有硬件也能学 API
硬件没到货或者暂时不方便接线的时候,Wokwi 是个好帮手。它是浏览器里的电路仿真器,支持 Arduino 系列和部分外设,社区里也有人放过 SimpleFOC 的仿真项目。你可以在里面模拟主控和电机模型,跑通代码逻辑、见识 FOC 的调参流程。
当然仿真和实物还是有差距:真实的电流噪声、电源压降、传感器安装误差,仿真都模拟不出来。但作为学 API、理解控制逻辑的入门工具,Wokwi 完全够用。我一般建议新手先在仿真里把示例代码跑熟,再碰实物,翻车率会低不少。
7.4 调参记录:比技术更重要的习惯
调 FOC 参数非常容易陷入"乱试"。我今天调好了,明天换块电池全部失效,又不知道动了哪里。我的个人习惯是把每次校准打印的 sensor_direction、zero_electric_angle、相电阻值全部存档,PID 每次只改一个变量,改完记一行日志。这样出了问题可以快速回滚,而不是靠记忆硬扛。
最后聊两句
跑完这套流程之后,我最大的感受是:SimpleFOC 的价值不只是让电机转起来,而是把 FOC 里那些抽象概念全部变成了可以动手验证的实物。每次 initFOC() 的咔哒声,都是在提醒你电角度对齐有多重要;每次调速度环时的啸叫,都在教你带宽不是越大越好。别急着抄参数,先把校准输出读一遍,再做任何调整。这是我踩过无数坑之后最想分享的一条经验。