1. 为什么“从哪个项目开始读”本身就是个伪命题
先把结论摆在前面:没有唯一正确的入门项目,只有和你当前基础最匹配的切入路径。我见过太多人一上来就冲着 VESC 去,clone 下来打开一看,几万行 C 代码、跨了好几个抽象层、还夹着硬件配置和通信协议,三天后文件夹就再也没打开过。也见过有人从 SimpleFOC 入手,两周跑通开环,一个月调通电流环,然后回头看 VESC 的代码突然就“能读进去”了。
所以这篇东西不打算给你一个标准答案,而是把电机控制开源固件这个圈子里几个主流项目的代码结构、抽象层次、适合的读者画像拆开讲清楚,让你自己判断该从哪下手。核心关键词就几个:电机控制、开源固件、源码阅读、FOC、VESC。适合谁看?适合已经懂一点 C 语言、知道 PWM 和 ADC 是什么、但面对一堆开源仓库不知道从哪下手的嵌入式方向朋友。如果你连定时器输出互补 PWM 都没配过,建议先去把 STM32 的 TIM 模块玩明白再回来,不然读源码会变成纯看天书。
我自己的路径是这样的:先玩 STM32 的六步换相,然后被 SimpleFOC 的 Arduino 风格 API 吸引,用它跑通了第一个闭环,接着去啃 VESC 的固件,最后回头补了 Moteus 和 ODrive 的设计思路。这个顺序不是最优的,但它让我在每个阶段都有“能跑起来的东西”撑着,不至于纯读代码读到怀疑人生。
下面我按抽象层次从高到低的顺序,把几个项目逐个拆开。抽象层次高的,代码短、逻辑清晰、适合建立整体认知;抽象层次低的,贴近硬件、细节多、适合深入理解 FOC 的每一个环节。你可以根据自己的情况跳着看。
2. 先搞清楚 FOC 固件到底在干什么
在挑项目之前,得先知道你要读的代码到底在解决什么问题。不然读起来就是一堆数学公式和寄存器操作,完全不知道为什么要这么写。
2.1 FOC 控制的基本链路
FOC(Field-Oriented Control,磁场定向控制)的核心思想,用一句话说就是:把三相电流通过坐标变换,拆成两个直流量分别控制,一个管转矩,一个管磁场。这样控制起来就像控制直流电机一样简单。
整个链路大概是这样的:
- 采样三相电流(或者两相,第三相算出来)
- Clarke 变换:三相静止坐标系 → 两相静止坐标系(α-β)
- Park 变换:两相静止坐标系 → 两相旋转坐标系(d-q),需要转子电角度
- 电流环 PID:d 轴电流给定通常为 0,q 轴电流给定来自速度环或转矩指令
- 反 Park 变换:d-q 电压 → α-β 电压
- SVPWM:根据 α-β 电压计算三相占空比
- 写入定时器比较寄存器,输出 PWM
这七步里,第 3 步的转子电角度是整个 FOC 的命门。有传感器(编码器、霍尔)的直接读,无感的就得靠观测器估。第 6 步的 SVPWM是另一个容易卡住的地方,很多人看代码时搞不清那几个扇区判断和矢量作用时间是怎么推出来的。
2.2 为什么读源码比看论文有用
论文给你的是理想模型,源码给你的是工程妥协。比如论文里说“采样三相电流”,实际代码里可能只采两相,因为第三相可以通过基尔霍夫电流定律算出来,省一个 ADC 通道。论文里说“用编码器获取电角度”,实际代码里可能要做角度校准,因为编码器零点和电机电角度零点不一定对齐。这些东西论文不会写,但源码里全是。
所以读源码的正确姿势是:先知道理论上应该怎么做,然后看工程上是怎么打补丁的。补丁打得越多的项目,往往越成熟,但也越难读。这就是为什么我不建议一上来就啃 VESC。
3. 几个主流开源固件的代码结构对比
我把几个项目按“抽象层次”排了个序,从高到低。抽象层次高的,代码更接近人类思维,适合入门;抽象层次低的,更接近硬件,适合深入。
| 项目 | 语言/平台 | 抽象层次 | 代码量级 | 适合人群 |
|---|---|---|---|---|
| SimpleFOC | C++/Arduino | 高 | 中等 | 有 Arduino 基础,想快速理解 FOC 全貌 |
| Moteus | C++/Python | 中高 | 较大 | 有 Python 基础,想理解现代固件架构 |
| ODrive | C++/Python | 中 | 大 | 想理解双电机控制与通信协议 |
| VESC | C | 低 | 很大 | 有嵌入式基础,想深入理解量产级固件 |
| 各芯片厂商 SDK | C | 最低 | 大 | 想理解寄存器级实现 |
这个排序不是绝对的,但大致反映了读代码的难度曲线。下面逐个说。
3.1 SimpleFOC:最适合建立整体认知的起点
SimpleFOC 是我最推荐给纯新手的项目,没有之一。原因很简单:它的 API 设计就是照着“让人看懂”去的。
它的核心类就几个:BLDCMotor、BLDCDriver、Sensor。你创建一个电机对象,挂上一个驱动器对象和一个传感器对象,然后调init()和loop(),FOC 就跑起来了。代码里把 Clarke 变换、Park 变换、SVPWM 都封装成了独立的函数,命名也很直白,比如_electricalAngle()、_setPhaseVoltage()。
读 SimpleFOC 的源码,我建议按这个顺序:
- 先看
BLDCMotor::loopFOC(),这是整个 FOC 的主循环 - 再看
BLDCMotor::setPhaseVoltage(),这里包含反 Park 变换和 SVPWM - 然后看
BLDCMotor::initFOC(),理解角度校准的过程 - 最后看
Sensor和BLDCDriver的接口,理解硬件抽象层
它的 SVPWM 实现特别值得细看,因为写得很直观。它没有用查表法,而是直接根据 α-β 电压计算三个半桥的占空比,中间用到了扇区判断和矢量作用时间的计算。你如果对 SVPWM 的数学推导不熟,对着这段代码推一遍,比看十篇论文都管用。
注意:SimpleFOC 为了跨平台兼容,做了很多抽象,性能上不是最优的。但读源码阶段,可读性比性能重要得多。
3.2 Moteus:现代固件架构的教科书
Moteus 是 mjbots 出的,代码质量很高,架构清晰。它和 SimpleFOC 最大的区别是:它把实时控制部分和通信/配置部分彻底分开了。固件跑在 STM32 上,上位机用 Python 通过 CAN 或者 FDCan 通信。
读 Moteus 的源码,你会看到一个很典型的现代嵌入式固件架构:
firmware/目录下是跑在 MCU 上的 C++ 代码python/目录下是上位机工具和库- 实时控制部分用了一个叫
BldcServo的类,里面把 FOC 的各个环节拆成了独立的模块
它的 FOC 实现比 SimpleFOC 更接近量产级,比如它有电流采样校准、温度补偿、故障保护这些工程细节。如果你已经理解了 FOC 的基本原理,想看看真实产品里是怎么处理各种边界情况的,Moteus 是个很好的参考。
不过 Moteus 的代码量比 SimpleFOC 大不少,而且用了不少 C++ 模板和现代特性,如果你 C++ 基础一般,读起来会有点吃力。建议先把 SimpleFOC 读透再来。
3.3 VESC:量产级固件的代表,但不适合第一个读
VESC 是这个圈子里名气最大的项目,没有之一。它的硬件设计、固件代码、上位机工具都是开源的,而且经过了大量实际应用的验证。但正因为它是量产级的,代码里充满了历史包袱和硬件适配。
VESC 的固件是用纯 C 写的,代码结构大概是这样的:
mc_interface.c:电机控制的顶层接口mcpwm_foc.c:FOC 的核心实现mc_interface.c:底层定时器和 ADC 配置encoder/、hall/:传感器驱动comm_can.c、comm_usb.c:通信协议
它的 FOC 实现里,电流采样和 PWM 更新是紧耦合的,因为要保证采样时刻和 PWM 周期的严格同步。这种写法性能好,但可读性差。而且 VESC 支持很多种硬件版本,代码里到处是#ifdef,读起来很容易迷路。
我的建议是:把 VESC 当作第二个或第三个项目来读。等你对 FOC 的每个环节都清楚了,再来看 VESC 是怎么在资源受限的 MCU 上把这些环节塞进去的,会有很多收获。但如果你一上来就读 VESC,大概率会在mcpwm_foc.c那几千行代码里迷失。
3.4 ODrive:双电机控制与通信协议的好教材
ODrive 的定位和 VESC 不太一样,它更偏向机器人关节控制,所以它的固件里有很多和上位机通信、轨迹规划相关的东西。它的 FOC 实现也是 C++,但抽象层次比 VESC 高一些。
ODrive 比较有特色的是它的通信协议和状态机设计。如果你对“电机控制器怎么和上位机配合”这个问题感兴趣,ODrive 的代码值得细看。它的 FOC 部分反而没有 VESC 那么复杂,因为它的目标应用对动态性能的要求没有 VESC 那么极致。
4. 读源码的正确姿势:从能跑起来到能改起来
选好项目之后,怎么读也是个问题。我见过很多人把源码当小说看,从第一行看到最后一行,看完啥也没记住。读嵌入式源码,尤其是电机控制这种和硬件强相关的,必须动手。
4.1 第一步:让代码跑起来
不管你选哪个项目,第一件事都是让它在硬件上跑起来。没有硬件就用仿真,没有仿真就用开发板。总之,你得有一个能观察现象的环境。
以 SimpleFOC 为例,你需要:
- 一块支持 Arduino 的开发板(STM32、ESP32 都行)
- 一个 BLDC 电机
- 一个驱动器板(比如 DRV8313、L6234)
- 一个编码器或霍尔传感器
把这些东西连起来,烧录官方示例,让电机转起来。这一步看起来简单,但实际做的时候会遇到各种问题:接线错了、供电不足、传感器方向反了、极对数设错了。这些问题本身就是最好的学习材料,因为你在解决它们的过程中,会自然地去读代码里相关的部分。
4.2 第二步:带着问题读代码
电机转起来之后,不要急着从头读。先给自己提几个问题:
- 电机是怎么知道转子位置的?
- 电流环的 PID 参数在哪里设置的?
- PWM 频率是多少?在哪里配置的?
- 如果我想改控制模式,应该改哪个函数?
然后带着这些问题去代码里找答案。这种问题驱动的读法,比线性阅读效率高得多。因为电机控制固件的代码量很大,你不可能全部记住,但你可以记住“哪个功能在哪个文件里”。
4.3 第三步:改代码,观察现象
找到关键函数之后,试着改一改。比如:
- 把电流环的 Kp 调大,看电机会不会震荡
- 把 PWM 频率改高,看电流波形有什么变化
- 把角度校准的偏移量改掉,看电机会不会反转
改代码是检验你是否真正理解代码的最好方式。如果你改了一个参数,能预测出电机的行为,说明你读懂了。如果改完之后电机乱转,说明你还没搞懂这个参数的作用。
提示:改代码之前一定要备份,尤其是涉及硬件保护的参数。我见过有人把过流保护阈值改没了,结果烧了驱动器。
4.4 第四步:画流程图和信号流图
读到一个阶段,试着把 FOC 的整个信号流画出来。从电流采样开始,经过 Clarke、Park、PID、反 Park、SVPWM,最后到 PWM 输出。每个环节标注对应的函数名和文件位置。
这个图不用画得多漂亮,关键是强迫自己把各个环节串起来。画完之后,你会发现有些地方你其实没搞懂,比如“电角度是怎么从编码器读数算出来的”、“SVPWM 的扇区判断逻辑是什么”。这些就是下一步要重点攻克的地方。
5. 常见问题与排查技巧实录
读源码的过程中,会遇到一些典型问题。我把自己踩过的坑整理了一下,做成速查表。
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| 电机不转,但有电流声 | PWM 没输出,或占空比为零 | 检查定时器配置,看比较寄存器是否有值 |
| 电机抖动,无法闭环 | 角度方向反了,或极对数错了 | 手动转电机,看角度读数是否跟着变 |
| 电流采样值一直是零 | ADC 触发源配置错误 | 检查 ADC 是否由定时器触发,采样时刻是否在 PWM 中心 |
| 高速时电流波形畸变 | 电流环带宽不够,或采样延迟太大 | 提高电流环频率,或改用更快的采样方式 |
| 电机发热严重 | d 轴电流不为零,或 PID 参数太激进 | 检查 d 轴电流给定,降低 Kp |
| 无感启动失败 | 观测器参数不对,或启动策略有问题 | 先用有传感器跑通,再切无感 |
除了这些具体问题,还有几个通用的避坑技巧:
- 先看文档和 issue:很多问题别人已经遇到过了,GitHub 的 issue 区是个宝库
- 用调试器单步:不要只靠 printf,用 J-Link 或 ST-Link 单步调试,看变量变化
- 示波器是必备:看 PWM 波形、电流波形、编码器信号,比看代码直观得多
- 从开环到闭环:不要一上来就调闭环,先开环跑通,确认硬件没问题
我印象最深的一次踩坑,是调一个无感 FOC 的项目,电机低速时一直抖。查了两天,最后发现是滑模观测器的增益设得太高,导致估计角度在低速时噪声太大。把增益降下来,再加一个低速启动策略,问题就解决了。这种问题,光看代码是看不出来的,必须结合波形和实际现象去分析。
6. 不同基础的人该怎么选
最后给一个简单的选择建议,你可以对号入座。
如果你 C 语言刚入门,没碰过电机:先别读源码,去把 STM32 的定时器、ADC、PWM 玩明白,用六步换相让电机转起来。然后再看 SimpleFOC。
如果你有 Arduino 基础,想让电机闭环:直接上 SimpleFOC,它的文档和社区都很友好,遇到问题容易找到答案。
如果你有嵌入式基础,想理解 FOC 的每个细节:读 SimpleFOC 的 SVPWM 和电流环部分,然后读 Moteus 的架构,最后啃 VESC。
如果你想做产品,需要参考量产级设计:VESC 和 ODrive 的硬件设计和保护逻辑值得细看,但要有心理准备,代码量很大。
如果你对无感 FOC 特别感兴趣:先看 SimpleFOC 的有感实现,然后找一些专门讲观测器的开源项目,比如基于滑模观测器或龙伯格观测器的实现。VESC 的无感部分也可以参考,但它的实现比较紧凑,不适合第一个读。
我个人在实际操作中的体会是:读源码这件事,最怕的是“只读不跑”。你可以在纸上推一百遍 SVPWM 的公式,但不如让电机转起来,用示波器看一眼真实的 PWM 波形。代码里的每一个参数、每一个判断,背后都有实际的物理意义。把这些物理意义和代码对应起来,才算真正读懂了。
另外一个小技巧:给代码加注释,然后提交到自己的仓库。读的时候觉得懂了,写注释的时候往往会发现其实没懂。这个过程很痛苦,但效果很好。我读 VESC 的时候,给mcpwm_foc.c加了大概两百行注释,后来再回头看,那些注释就是我自己的学习笔记。