资讯动态

先楫 HPM 与 STM32G491 CAN FD 通信延迟排查:从 4 个周期优化到同周期反馈

发布时间:2026/8/28 18:49:45 来源:尧图企业网站定制
先楫 HPM 与 STM32G491 CAN FD 通信延迟排查从 4 个周期优化到同周期反馈最近在人形机器人通信链路调试中发现了一个比较典型、也比较容易被忽略的 CAN FD 时序问题。先楫板周期下发控制指令关节模组接收控制量、完成控制处理后再通过 CAN FD 返回状态数据。最开始从抓包看一条控制指令从发送到在反馈中体现出来时间差达到了8.033 ms约 4 个 CAN FD 收发周期。单看 CAN FD 总线本身这个结果很容易让人先怀疑波特率、总线负载或者驱动性能。但后面的排查证明真正的问题不在“CAN 帧在线上跑得慢”而在接收 FIFO 中的旧数据积压以及控制处理和反馈发送之间的软件时序。这篇文章记录一下完整的定位过程。1. 先定义清楚这里测到的“延迟”是什么本文说的通信延迟不是 CAN FD 帧在总线上的物理传播时间也不是单纯的硬件收发时间。测试方法是在控制帧中携带frameNum模组处理这条控制帧后在反馈帧中返回对应的frameNum。通过比较主站当前发送的帧号和反馈中携带的帧号可以判断当前收到的反馈到底对应几个周期之前的控制指令。因此本文关注的更准确说法其实是控制数据的新鲜度data age或者控制—反馈软件流水线延迟。最初抓包中观察到16.661262 s - 16.653229 s 8.033 ms约等于 4 个 CAN FD 收发周期。对于机器人关节控制来说这种周期级延迟比单纯几十微秒的总线传输时间更值得关注因为它会直接增加闭环中的数据年龄。2. 一个很奇怪的现象换个上电顺序延迟就变了进一步测试时发现延迟和上电顺序存在明显关系。上电顺序观察到的控制—反馈延迟先给模组上电再给先楫板上电约 1 个周期先给先楫板上电再给模组上电约 4 个周期这个现象非常关键。如果问题主要来自 CAN FD 波特率、报文长度或者总线负载那么仅仅改变上电顺序不应该让延迟从 1 个周期突然变成 4 个周期。所以排查重点开始转向模组启动过程中CAN FD 接收链路什么时候开始收数据软件任务又是什么时候开始消费这些数据3. 问题一模组后上电Rx FIFO 里已经堆了旧控制帧模组使用 STM32G491。模组后上电时先楫 HPM 已经启动并且在持续周期发送控制帧。例如总线上可能已经依次出现frame 8 frame 9 frame 10 frame 11 ...问题在于STM32 上电初始化并不是“所有软件模块在同一个时刻一起开始工作”。FDCAN 外设可能已经初始化完成可以正常把总线报文放进 Rx FIFO但控制任务、反馈任务以及应用层处理流程还没有完全进入稳定运行状态。于是启动过程中会出现这样的情况总线已经发送frame 8、frame 9、frame 10 ... Rx FIFO 中仍然排着frame 8、frame 9 ... 应用层开始工作以后从 FIFO 头部按顺序读取此时模组处理到的是“旧控制帧”而不是总线上刚刚到达的最新控制量。3.1 为什么这个延迟不会自己追上来这才是这个问题最容易忽略的地方。假设进入稳定运行以后先楫板每个周期产生 1 条新控制帧模组每个周期也只消费 1 条控制帧。如果启动时 Rx FIFO 已经积压了若干帧那么后面虽然模组一直在处理数据但每个周期进来 1 帧同时只处理 1 帧。生产速度和消费速度相同之前形成的队列深度就不会自然减少。所以系统不会“运行一会儿自动恢复”。它会一直稳定地落后若干周期。文档里的frame 8 / frame 9 / frame 10是用来说明这个机制的示例。现场测试最终表现为总延迟从约 4 个周期降到 1 个周期说明启动阶段旧帧积压贡献了额外的周期级数据年龄。实际积压多少帧会受到 FDCAN 初始化完成时间、任务启动时间和控制周期相位的影响并不一定每次固定为 2 帧。4. 第一轮优化控制数据只处理 Rx FIFO 中的最新帧对于这种周期控制协议旧控制指令通常已经失去意义。比如主站连续发送frame 100目标位置 10.0° frame 101目标位置 10.2° frame 102目标位置 10.5°如果模组现在已经收到frame 102再花两个控制周期依次执行frame 100和frame 101不仅没有收益反而人为增加控制数据年龄。因此第一轮优化思路很直接收到一批积压帧时丢弃旧控制帧只保留并处理 Rx FIFO 中最新的一帧。修改前逻辑更接近if(FDCAN_RxFifoHasData()){read_one_frame_from_fifo();process_control_frame();}优化后的思想是while(FDCAN_RxFifoHasData()){read_one_frame_from_fifo();/* * 持续覆盖只保留最后读取到的最新帧 */latest_framerx_frame;}process_control_frame(latest_frame);具体工程实现可以根据 HAL/LL 或 MCAN 驱动接口调整核心不是这几行代码本身而是把接收语义从FIFO 消息队列改成最新控制状态缓存部署以后重新测试控制—反馈延迟从约4 个周期降低到 1 个周期。这说明启动阶段的旧帧积压确实是主要问题之一。4.1 这里有一个使用前提“丢掉旧帧只处理最新帧”并不是所有 CAN 协议都适用。它适合的是周期位置、速度、力矩目标值以及其他latest-value wins类型的数据。如果报文承载的是增量命令、轨迹分片、必须逐条执行的事件或者参数写入事务就不能简单丢弃旧帧。所以这次优化成立的根本原因是我们的 CAN FD 控制帧属于周期覆盖型控制量。5. 还有 1 个周期到底慢在哪里第一轮修改完成以后问题没有完全结束。抓包已经从 4 个周期降到了 1 个周期但frameNum仍然能观察到稳定的一拍延迟。这时再去检查 Rx FIFO 已经没有意义因为旧帧积压已经解决了。继续把模组内部的软件执行时序展开后发现剩下的 1 个周期来自CAN FD 反馈帧进入 Tx FIFO 的时机。6. 问题二反馈帧组织得太早发送的是“上一拍状态”原来的处理方式大致是CAN FD 接收中断到来在接收中断中更新新的模组控制参数退出 CAN FD 中断之前立即读取模组状态并填入 CAN FD Tx FIFO后续 FOC 中断才真正根据新的控制参数更新模组运行状态。问题就出在第 3 步。CAN FD 接收中断虽然已经收到了本周期最新控制指令但此时 FOC 控制还没有基于这个新控制量完成运算。所以填入 Tx FIFO 的状态本质上仍然对应上一个控制周期。可以把这个时序理解成收到控制帧 N CANFD Rx IRQ 更新控制参数 N 立即组织反馈 此时状态仍然是 N-1 FOC ISR 根据控制参数 N 完成控制处理 状态更新为 N等 CAN FD 回复发出去时Tx FIFO 里早已经放好了N-1的状态。于是从抓包看就表现为控制帧已经是 frame N但反馈数据仍然对应 frame N-1。这就是剩余 1 个周期延迟的来源。7. 第二轮优化等 FOC 更新完成后再把反馈数据放进 Tx FIFO解决思路不是让 CAN FD 发得更快而是重新安排**“什么时候生成反馈数据”**。优化后CAN FD 接收中断只负责两件事更新最新控制参数设置recvNewCanFdFrame true表示本周期收到了一条新的控制指令。不再在 CAN FD Rx ISR 退出前立即填充 Tx FIFO。例如voidCANFD_Rx_IRQHandler(void){update_control_parameter();recvNewCanFdFrametrue;/* * 这里不再立即组织 CAN FD 反馈数据 */}随后进入 FOC 中断根据最新控制参数完成本周期控制计算和状态更新。在 FOC ISR 退出前检查if(recvNewCanFdFrame){recvNewCanFdFramefalse;/* * 软件触发 UART4_IRQ */trigger_uart4_irq();}最后在UART4_IRQ中读取已经由 FOC 更新完成的最新模组状态再填充到 CAN FD Tx FIFOvoidUART4_IRQHandler(void){update_canfd_feedback_from_latest_motor_state();fill_canfd_tx_fifo();}这里UART4_IRQ是当前项目中用于承接这一发送处理的软件触发中断名称本身不是关键。真正关键的是执行顺序发生了变化CAN FD 收到最新控制量 FOC 使用最新控制量完成状态更新 再组织本周期反馈 最后放入 CAN FD Tx FIFO这样反馈帧中的状态数据才和本周期刚收到的控制指令对应。8. 为什么这个优化能消除最后 1 个周期修改前控制参数和反馈状态属于两个不同时间点控制参数N 反馈状态N-1所以即使 CAN FD 总线没有任何堵塞从frameNum看也天然落后一拍。修改以后反馈数据的采样点被移动到了 FOC 更新之后控制参数N FOC 处理N 反馈状态N因此反馈不再携带上一周期的旧状态。这次优化解决的其实不是传统意义上的“发送性能”而是一个很典型的数据采样时刻错误。在实时控制系统里“数据什么时候被读取和打包”经常和“数据传输用了多久”同样重要。9. 最终测试结果完成两轮优化以后重新抓包。测试时把控制模式从0切换为7可以看到先楫板在本周期发送模式7后模组在对应反馈帧中已经可以同步返回模式状态7。同时Tx frameNum Rx frameNum不再观察到之前的周期级滞后。速度参数测试也得到相同结果新的速度控制量可以在对应周期反馈中体现出来。最终整个问题可以概括成两部分问题表现根因优化启动阶段旧控制帧积压上电顺序不同延迟可从 1T 变为约 4TFDCAN 已收帧但应用处理尚未启动之后生产/消费速率相同积压无法自然消失清理旧帧只处理最新周期控制帧反馈生成时机过早清掉 FIFO 后仍稳定落后 1TCANFD Rx ISR 中过早把状态放入 Tx FIFOFOC 还没更新本周期状态FOC 更新完成后再组织反馈并填入 Tx FIFO10. 这次问题给我的几个启发10.1 周期通信延迟不一定是总线性能问题看到 8 ms 延迟第一反应很容易去看CAN FD 波特率 总线利用率 中断响应时间 发送 FIFO这些当然需要检查但这次真正占主要部分的是软件流水线。一条 CAN FD 帧可能只花很短时间就完成传输但如果应用层一直处理几周期前的数据对控制系统来说它仍然是“高延迟”。10.2 实时控制更关心数据年龄而不是消息有没有丢普通通信程序往往强调每一帧都不能丢。但实时控制有时恰恰相反。对于高频周期目标值旧数据最大的价值可能就是尽快被新数据覆盖。控制系统真正需要的是尽量使用最新的控制指令。所以 Rx FIFO 的使用策略必须和上层数据语义一致。10.3 中断里“马上回复”不一定延迟最低乍一看在 CAN FD 接收中断里立即把反馈放入 Tx FIFO似乎是最快的做法。但如果反馈内容依赖后续 FOC 运算那么“立即发送”只是更快地发送了一份旧数据。这种情况下更合理的目标不是越早放进 Tx FIFO 越好。而应该是在最新状态已经生成以后尽早把它放进 Tx FIFO。二者差一个状态更新边界结果就是一个完整控制周期。11. 总结这次 CAN FD 通信延迟问题最终不是一个单点故障而是两个软件时序问题叠加。第一部分来自启动时的Rx FIFO 旧控制帧积压。模组后上电时FDCAN 已经开始收帧但应用层还没有及时消费进入稳定运行后又因为生产速度和消费速度基本一致历史积压一直保留下来。第二部分来自反馈数据生成得太早。CAN FD Rx ISR 收到最新指令以后在 FOC 完成最新状态更新之前就把反馈装入了 Tx FIFO因此天然返回上一周期状态。最终的处理方式也对应两条原则对周期覆盖型控制量优先保证“最新数据”不要机械地逐帧补历史数据。对依赖控制计算结果的反馈量要在最新状态真正生成以后再进行采样和发送。从最后的抓包结果看控制模式和速度参数都已经能够在对应周期内完成更新frameNum也能保持一致。这次问题对我最大的提醒是做实时通信优化时不要只盯着总线。把“接收、缓存、任务消费、控制计算、状态采样、反馈入队”整个软件时序拉出来很多所谓的通信延迟其实藏在这里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价