【2026 机器人模块大赛】微型机器人单关节闭环驱动模块:MA600A 通信通了以后,才发现真正的坑刚开始

上一篇帖子里,我把 MA600A 的基础通信先跑通了:SPI 可以读 16-bit 角度,32-bit frame 可以同时拿 angle 和 speed,BCT、ZERO、correction table 这些接口也都先封好了。那一版看起来进度还挺顺,J-Link SWD + RTT + lkscope 也能把角度波形实时画出来。

当时我以为,磁编这块大概已经从“硬件 bring-up”进入“等后面标定”的阶段了。

后面实际往 FOC 里接的时候才发现:通信是通了,但是数据怎么这么离谱, 离“距离能用于foc闭环控制”还差好多…

这篇就记录这中间踩到的一些坑,以及为什么我最后决定重新 layout 一版板子,把 MA600A 从旁轴布局改成同轴正对布局。

1. 能读到角度,不等于能用于 FOC

第二篇里,MA600A 的协议层和 AT32M412 的 SPI2 适配层已经基本跑通。普通角度读取就是主机发 0x0000,MA600A 返回一帧 16-bit angle;如果一次 /CS 拉低期间连续发 32 个 clock,还可以在后 16 bit 返回 speed 或 multi-turn。

从“验证通信”的角度看,这些都没问题:


1. raw angle 可以连续读取

2. 手动转动磁铁时 raw 会变化

3. speed raw 能体现方向

4. sample count 持续增加

5. lkscope 可以实时看内部变量

但 FOC 对角度的要求会更苛刻。它不是偶尔读一下看看数值对不对,而是每个控制周期都要相信这个角度。角度数据至少要满足几个条件:


1. 连续:不能突然跳到不合理的位置

2. 可信:异常数据要能识别,不能直接送进控制环

3. 及时:采集和处理时间不能吃掉太多 ISR 预算

4. 稳定:速度估计不能乱跳,否则速度环/位置环会被带偏

5. 可标定:机械布局带来的误差要能被补偿

后面的问题,基本都围绕这几个点展开。

2. 第一个坑:SPI 流程看似成功,但 raw 恒为 0

刚开始把 MA600A 接进电机控制工程时,遇到过一个很迷惑的现象:SPI 调用流程看起来是成功的,sample count 也在增加,但读出来的角度 raw 一直是 0。

这种情况最容易误判成协议问题,比如:


是不是 16-bit frame 时序不对?

是不是 CPOL/CPHA 配错?

是不是 /CS 保持时间不够?

是不是 8-bit 拼 16-bit 的方式有问题?

后面台架对比确认,真正的根因是 SPI2 GPIO mux 配错了。PB3/PB4/PB5 虽然接到了 SPI2,但对应的复用号必须按 AT32M412 的数据手册和 Workbench 生成结果来核对。mux 错了以后,软件层面的 SPI transfer 仍然可能“跑完”,但 MISO 实际没有拿到正确数据,于是就出现了“通信成功但数据全 0”的假象。

这个坑的教训挺直接:在没有排除引脚复用、接线、片选和示波器波形前,不要太快把问题归到协议层。

3. 第二个坑:旁轴标定不是采够点就行

V1 板子上,MA600A 和磁铁采用的是旁轴布局。这个选择一开始不是拍脑袋决定的,主要是为了节省轴向空间。小型关节模块里,轴向高度很宝贵,中心位置还可能要留给轴承、螺丝、磁铁、出线或者结构件。把磁编放到侧边,机械上会自由很多。

第二篇里我也用 MPS Magnetic Simulation Tool 做过 Side-Shaft 仿真。仿真结果已经提示旁轴结构会有明显角度非线性,需要 BCT 和 correction table 这类补偿手段。

所以后面做旁轴标定是预期内的事情。但真正写标定流程时,又踩了几个软件算法坑。

3.1 机械圈和电角度圈混了

FOC 里常用的是电角度,编码器 raw 对应的是机械角度。这个项目里的电机按 7 对极处理,那么:


1 机械圈 = 7 电角度圈

早期标定参数里写“转 5 圈”,但实现时按电角度圈去跑,结果就是:


5 电角度圈 / 7 对极 = 0.71 机械圈

也就是说,程序以为自己已经转够了,实际上编码器机械角度还没覆盖完整一圈。这样生成 correction table 时,很多角度区间根本没有采到数据。

后面修正为按机械圈规划,再换算成电角度圈,让标定真正覆盖完整机械圆周。

3.2 直方图只累加,没有除以采样次数

标定时我把一圈分成 256 个 bin,每个 bin 里累加采样偏差。这里的直方图里存的是“累加和”,不是“平均值”。

早期计算 correction table 时忘了除以每个 bin 的采样次数,导致误差被放大很多倍,有些表项直接接近饱和。这个 bug 很典型:数据结构名字叫 hist,很容易让人以为里面已经是统计结果,但其实只是中间累加值。

修复后,每个 bin 记录两类信息:


1. 偏差累加和

2. 采样次数

最后先做平均,再做正反转合并和去直流处理。

3.3 采样数量和物理旋转脱耦

还有一个更容易被忽略的问题:采样速度很快,但电机物理旋转很慢。

如果用“采样数达到多少”来决定标定结束,那么程序可能 1 秒多就采够了,但电机实际只转了很小一段角度。结果就是:


软件上看:样本数够了

机械上看:一圈根本没转完

这个坑后面改成按旋转持续时间推进状态。也就是采样函数只负责不断填直方图,标定状态机由预估物理旋转时长决定什么时候从正转切到反转,什么时候进入计算阶段。

4. 第三个坑:开环 enc 模式容易死锁

为了验证编码器电角度,曾经尝试过 mc_open ... enc 这类模式,也就是开环时直接用编码器角度作为电角度输入。

这个想法听起来合理,但从静止启动时会有一个死锁问题:


转子不动

-> 编码器角度不变

-> 电角度 theta 不变

-> 电压矢量固定

-> 转子被锁住或只产生静态力矩

-> 转子还是不动

所以 enc 模式更适合在电机已经转起来以后做跟踪验证,不适合直接从静止启动。真正要启动,还是应该先用 ramp 或对齐过程建立运动,再切到编码器角度闭环。

这也是后面我更倾向于把“角度读取”“角度跟踪”“控制模式切换”拆开的原因之一。编码器数据不是拿到就直接塞进控制环,中间需要一个更稳的状态估计层。

5. 第四个坑:编码器读取费时间,不适合继续放在 FOC ISR 里

这个问题是后面推动软件架构调整的关键。

我实际测了一下时间:在 motor_control_isr.c 里,编码器读取这部分大约要 18us,而 FOC 计算本身大约 8us。控制中断是 16kHz,一周期只有 62.5us。也就是说,如果每个 FOC tick 都直接读 SPI 编码器,光编码器读取就吃掉了很大一块预算。

更关键的是,SPI 读取不是纯计算,它还会受总线时序、片选、等待等影响。把这类操作放在最高优先级 FOC ISR 里,后面电流环、速度环、位置环都继续加上去以后,余量会越来越紧。

所以后面做了一个比较明确的调整:


FOC ISR 不再直接读 MA600A SPI

编码器采集改成由单独的硬件 timer 驱动,FOC ISR 只消费已经准备好的角度估计值。

6. spike/reject 和速度不稳

后续观察时,MA600A 原始数据总体是正常的,比前面一些毛刺状态也少了很多,但仍然能看到丢样和特定角度范围的冒尖。

当时 enc_status 里能看到类似这样的诊断信息:


=== encoder service ===

raw16 : 51145

delta : -29

unwrap : -210999

mech_mdeg : 18803

elec_mrad : 3596

speed_raw : 0

counts : sample=398160 accept=390856 bus=0 spike=7437 stale=0

last_rej : raw=5731 delta=-1807

zero_raw : 36423

cal_valid : 1

=== encoder tracker ===

trk_raw : 50310

trk_theta : 2919 mrad

trk_speed : -52744 mrad/s

trk_age : 1 ticks

trk_count : 390880

这里比较有价值的是几个计数:


sample:总采样次数

accept:被接受的样本数

bus:SPI/bus 错误数

spike:被判定为跳变的样本数

stale:快照被重复消费的次数

last_rej:最近一次被拒绝的 raw 和 delta

从这个现象看,通信总线本身不一定是主要矛盾,bus=0 说明当时不是 SPI 事务大量失败。但 spike 在增长,说明原始角度里存在不符合物理速度约束的跳变。

另外 MA600A 自带的 speed raw 在我的测试里并不如通过角度跟踪器估出来的速度稳定。后面软件上就更倾向于用 raw angle 做主输入,再用 tracker 做速度估计和角度预测。

7. 软件架构调整:encoder_service + TMR7 + tracker

前面这些坑叠在一起以后,编码器部分就不能再是“哪个模块想读就去读一下 SPI”的结构了。后面我做了三层拆分。

7.1 encoder_service:唯一快照源

encoder_service 负责维护编码器快照和诊断信息。它会处理:


1. raw16

2. delta / unwrap

3. mechanical angle

4. electrical angle

5. bus error count

6. spike reject count

7. stale count

8. last rejected sample

9. zero 和 calibration table

这样调试命令、标定流程、FOC 相关逻辑都不再各自维护一份“看起来像编码器状态”的变量,减少不同来源互相打架。

7.2 TMR7:4kHz 硬件定时采集

编码器采集放到了 TMR7 中断里,目前采集频率是 4kHz。这个频率比 FOC 16kHz 低,但对磁编角度采集已经足够用于当前调试。更重要的是,它把 SPI 事务从 FOC ISR 中挪出去了。

代码结构上大概是:


TMR7_GLOBAL_IRQHandler

-> encoder_service_acquire_once()

-> 读 MA600A raw angle

-> 通过物理速度阈值判断是否接受

-> 更新 encoder_tracker

这里还有一个细节:被拒绝的 spike 不会直接送进 tracker。这样即使原始角度偶尔冒尖,FOC 消费到的角度也不会立刻跟着跳。

7.3 encoder_tracker:FOC tick 只做预测

FOC ISR 里现在不再读 SPI,而是调用 tracker:


encoder_tracker_tick()

theta = encoder_tracker_get_electrical_angle_rad()

age = encoder_tracker_get_sample_age_ticks()

tracker 在新样本到来时更新角度和速度估计,在 FOC tick 中根据速度做轻量预测。这样 FOC 侧看到的是一个连续的角度估计,而不是每次都被 SPI 采样时序牵着走。

如果样本长时间没有更新,sample_age 会增长,超过阈值后再触发传感器故障。

这套结构的目标不是把坏数据“美化成功”,而是把几个问题分开:


采集是否成功 -> encoder_service 诊断

样本是否可信 -> spike/reject

FOC 是否有可用角度 -> tracker

传感器是否掉线 -> sample age

8. 回到硬件:为什么一开始选旁轴

说完软件,再回到硬件布局。

V1 用旁轴布局并不是因为同轴不知道更理想,而是因为旁轴在小型关节模块里确实有吸引力。

旁轴的优点主要是这些:


1. 节省轴向空间

2. 中心轴、轴承、螺丝、出线空间不用让给磁编芯片

3. PCB 可以放在侧边或结构边缘

4. 对薄型关节、云台、夹爪这类紧凑结构更友好

5. 对中空轴、穿线轴等结构也更容易布置

这个项目一开始追求的是一个小型单关节驱动模块,轴向高度和结构堆叠都比较敏感,所以旁轴方案很自然。

而且第二篇里用 MPS Magnetic Simulation Tool 做仿真时,也不是说旁轴完全不可用。工具本身就提供 Side-Shaft 模型,只是它也明确提示旁轴属于更复杂的安装方式,需要补偿。

所以更准确的说法是:旁轴不是不能用,而是它把机械空间省下来了,同时把磁场线性度、装配一致性和标定复杂度的账转移到了后面。

9. 旁轴的代价:磁场不对称会进入控制问题

旁轴布局下,磁铁不是在 MA600A 敏感区域正上方旋转,磁场矢量随角度变化时不再像同轴那样对称。这会带来几个实际代价:


1. 原始角度非线性更明显

2. 对磁铁和芯片的相对位置更敏感

3. 对气隙、偏心、倾角更敏感

4. 不同装配样本之间一致性可能更差

5. 更依赖 BCT 和 correction table

6. 特定机械角度可能更容易出现异常点

如果只是做低速位置显示,这些问题也许可以靠标定慢慢压下去。但这个项目的目标是做一个能进 FOC、电流环、速度环、位置环的小关节驱动。对闭环来说,角度异常不是简单的“显示不准”,而是会直接变成控制输入异常。

这也是我最后决定重画一版板子的原因。

10. V2:把 MA600A 改成同轴正对

上周我重新 layout 了一版硬件,把 MA600A 从旁轴位置移动到了同轴正对的位置,同时整体走线也做了调整。

同轴方案的目标很明确:


1. 让磁场更对称

2. 让 raw angle 更接近理想线性

3. 降低 BCT/correction table 的压力

4. 减少特定角度冒尖的风险

5. 给后续闭环控制减少变量

当然,同轴不是没有代价。它会占用轴向空间,中心区域也会被磁铁、芯片、轴承、安装结构共同竞争。对于某些极薄结构,旁轴依然可能是更合理的选择。

所以这次改版不是为了得出“旁轴不行”的结论,而是为了做一个更清楚的工程取舍:旁轴用空间换复杂度,同轴用结构空间换传感稳定性。

目前软件上也已经单独开了 hw/v2-coaxial-encoder 分支,适配了 V2 的一些引脚变化,例如 PWMA/PWMC 对调、LED 改到 PB8 等。后面 V2 板回来以后,会在这个分支上继续验证。

11. 旁轴 vs 同轴:先列一个工程优劣对比

后续实测数据还没补完,但先从工程角度列一个对比表,方便后面用数据去验证。

| 项目 | 旁轴布局 | 同轴布局 |

| — | — | — |

| 轴向空间 | 占用小,适合薄型结构 | 占用较大,需要给磁铁和芯片留高度 |

| 中心区域 | 可避开轴承、螺丝、穿线孔 | 中心区域更紧张 |

| PCB 布局自由度 | 可以放侧边,机械自由度高 | 传感器位置受轴心约束 |

| 磁场对称性 | 较差,天然非线性更明显 | 较好,更接近理想角度输出 |

| 装配敏感度 | 对偏心、气隙、倾角更敏感 | 对称性更好,但仍需控制气隙和同心度 |

| 标定压力 | 更依赖 BCT/correction table | 标定压力相对小 |

| 闭环风险 | 异常角度点更容易影响控制 | 角度输入预期更稳定 |

| 适合场景 | 薄型、中空轴、侧装、空间极限场景 | 对精度、稳定性、闭环控制更敏感的场景 |

我目前的判断是:


旁轴:适合空间优先,但要接受标定和装配复杂度

同轴:适合控制稳定性优先,但要付出结构空间

最终到底差多少,还要等 V2 板回来以后用同一套测试流程说话。

12. 这次 AI 在里面帮了什么

这部分也继续按前两篇的方式记录一下。

AI 在这轮调试里主要做了这些事:


1. 帮忙把现象拆成 SPI、数据位宽、标定算法、ISR 时间预算、机械布局几个方向

2. 帮忙对照 MA600A 手册确认角度位宽和 SPI frame

3. 帮忙梳理 encoder_service / tracker / timer acquisition 的软件架构

4. 帮忙把每次调试记录整理成可追溯的日志

5. 帮忙提醒哪些结论还只是怀疑,不能提前写死

但最终判断还是靠实测。比如 raw 恒 0 是 mux 问题,角度冒尖是不是磁场布局导致,旁轴和同轴到底差多少,这些都不是 AI 能凭嘴确认的,还是要靠示波器、lkscope、串口日志和实物对比。

这次的感受是:AI 很适合帮忙把复杂调试过程拆成路径,防止脑子里全是散的现象;但每一个关键结论,都必须回到硬件证据。

14. 小结

MA600A 的通信本身不算复杂,第二篇里也已经证明了这一点。但把一个磁角度传感器真正接进 FOC 闭环,中间要处理的问题比“读寄存器”多很多:


1. SPI 和 GPIO 复用要可靠

2. 数据位宽和单位不能搞错

3. 标定算法要覆盖完整机械范围

4. 采样数量不能代替物理运动

5. 异常角度样本要识别和拒绝

6. 速度估计要足够稳定

7. ISR 时间预算不能被 SPI 读取吃掉

8. 机械磁场布局最终会影响控制质量

这也是为什么我没有直接在旁轴方案上继续硬调,而是重画了一版同轴 V2。旁轴方案不是完全不可用,它确实节省空间,也适合很多结构受限的场景。但在这个小型关节驱动项目里,我更希望先把闭环控制的不确定性降下来,让后面的电流环、速度环、位置环少背一个锅。

后面等 V2 板回来,如果同轴 raw 曲线、spike 计数、tracker 速度和标定残差都有明显改善,那这次改版就算有了比较硬的证据。如果改善不明显,那也同样值得记录,说明问题还要继续往磁铁、装配、采样或算法里挖。

总之,调电机这种事,最好还是让波形先说话。

1 个赞