一次CAN总线电机指令发送"必须加延时"问题的解决

个人的轮腿机器人用 FDCAN 挂六个达妙电机,每条总线三个。调车的时候发现一个怪现象:一轮里三条力矩指令连着发,电机经常莫名"没反应",然后机器人就各自抖动发疯。必须在每条之间插一个 osDelay(1) 才正常。当时本着能用就行的思想就不管了,顺带把控制周期钉死在 3ms 上。这次想把周期压到 2ms,于是重新翻出来查。最后发现:延时要解决的问题根本不在发送侧。

虚假的修复

一年前刚上手 CAN 的时候,想法很simple:三条指令发得太快,CAN 外设的发送 FIFO 被顶满了,后面的帧自然就丢了。于是每条指令之间加 1ms,让前一条先发出去再发下一条。

加完延时之后确实没问题了。然而它把周期卡死了,因为控制必须涉及到电机控制就导致了电机的控制周期被卡死在了3ms;

现状分析

出问题的发送链路大致是这样:

控制任务:

轮毂电机 CAN发送
osDelay(1)
髋关节A CAN发送
osDelay(1)
髋关节B CAN发送
osDelay(1)
(左右腿各一遍)

tx的FIFO深度是是4,rxFIFO深度8/
首先顺着原来的思路,可能是发送太快了,排查txFIFO的问题。
不过首先先查一下控制周期到底是不是严格的3ms。

第一次实验排查

实验一 测量周期

实验方法:用 DWT 周期计数器在控制循环入口打时间戳,统计相邻两次的间隔

实验结果:周期平均值 2.9995 ms,最小值 2.993 ms,最大值 3.006 ms;

实验结论:3ms 周期没啥问题,tick 锁定得非常准,抖动很小,几乎可以忽略了。同时也说明周期几乎全花在三个延时上,算法本身占用不多。所以压缩周期,必须改好这边can发送的延迟。接下来要排查can发送真正的问题

实验二 排查发送端

实验方法:去掉延时,把发送连在一起,然后这波控制一下变量,在任务屁股后面加个osDelay(3),保持原本你个的3ms周期,在发送任务和can驱动里加几个计数器放到全局结构体里,然后用keil的watch区查

实验结果:经过can驱动发送的没有出错的全都发出去了,统计出FIFO满的次数居然也是0,最大占用是2。然后诡异的现象出现了,复刻了当时有问题的现象,机器人出现了明显的抖动,发疯。

实验结论:太神秘了,全都发出了,与此同时还诡异的出现了抖动不受控的神秘现象,说明不加延时确实有问题。然后FIFO 从来没被占满过,发太快的假设直接不成立。简直离谱到家了。

实验分析

太神秘了,现在的现象好像和can发送有关系又没关系,说他有关系,一看数据百分百和1发送没关系,说没关系去掉延时又疯狂电机出问题。

这个时候有两条怀疑对象:

  1. 诡异的板子硬件问题(也是达妙的板子)
  2. 达妙电机有问题
  3. can的代码还是有问题

前两条,去达妙售后群里,达妙官方文档里讲达妙的技术用的是一样的电机和板子,不加延时能带高达9个电机跑满CANFD的8MBps上限,所以基本可以排除硬件问题咯

回过头重新检查数据,发现有一个地方不对劲:在TX的FIFO几乎闲的没事干的时候,RX的FIFO,一直是满的!占满了8个位置
首先4 vs 8,优势在RX,怎么也不会在tx不满的情况下占满八个,那就只有一个地方有问题,那就是接收处理的地方。

于是去看接收的代码,果然,接收处的代码:
回调函数HAL_FDCAN_RxFifo0Callback只调用一次次1 HAL_FDCAN_GetRxMessage
这就导致了接收的电机反馈基本上来不及处理,很多都丢掉了,LQR算法拿到的都是过时不稳定的消息,算出来的力矩不准,然后就滚雪球式的继续出错,怪不得会出问题!

到这里"必须加 1ms 延时"的真相就清楚了:延时把三条指令摊开发送,回帧也就摊开了,那个"一次只读一条"的中断刚好跟得上。延时不是解决方案,是把问题藏起来了。

第二次实验排查

实验三 中断里把 FIFO 读空

实验方法:把 HAL_FDCAN_RxFifo0Callback 从"读一条"改成 while (fill > 0) 循环读空,其他不改。

实验结果:接收 FIFO 最大堆积从 8 降到 3;车也不抖了。

实验结论:验证了前面的说法,已经找到了1核心原因。原来的中断只取一帧,在大量多个反馈接收的时候发生了溢出。

修改方案

关于中断:回调里不再只读一条,一次中断把积压的帧全处理掉。

关于发送:不需要任何延时,三条指令连发即可。TX FIFO 深度 4、三条指令最大占用 2 格,余量充足。

关于周期:延时去掉后,周期改由 vTaskDelayUntil 绝对节拍控制,而不是 osDelay 相对延时,确保周期严格执行。(用osDelay有时候会不准)

完成改动:CAN 接收中断读空 FIFO、发送去掉延时、周期改用 vTaskDelayUntil、把中断里的阻塞操作挪到任务。控制周期从 3ms 压到 2ms(经过了一番pid和卡尔曼滤波算法的跳整~)。实测周期严格,电机控制正常,没有复现发抖的情况。

总结

复盘原来的修法有两个问题:

  1. 把周期卡死了,因为控制必须涉及到电机控制就导致了周期被迫因为can发送绑死在3ms;
  2. 解决问题没有解决本质问题,只是表象修好了。排查问题只考虑了发送端,没验证过接收端,或者说压根没有深入思考去解决问题,纯粹是"加了就好"的经验主义,有点思维惯性加想当然了捏~
posted @ 2026-10-19 18:16  Hynaya  阅读(12)  评论(0)    收藏  举报