# CAN/里程计频率修改建议报告 ## 1. 结论 建议采用以下组合修改,而不是单独只改一边: 1. 下位机 `FDR-Core` 将 `0x200 Odom Delta` 从当前约 `60ms` 提升到 `20ms` 固定发送。 2. 上位机 `ARES` 将 `0x200` 的消费与 `Odom_Update()` 从 `monitorTask(100ms)` 挪到 `navTask(20ms)`。 3. `0x182/0x183` 保持低频轮询即可,不需要跟着一起提到 `20ms`。 4. 当前阶段不建议把 `0x200` 进一步提到 `10ms`,先做到 `20ms` 即可。 一句话概括:当前 odom 不够实时,不是总线带宽不够,而是“下位机发送慢 + 上位机消费慢”叠加导致的;最合适的修法是把两端都对齐到 `20ms`。 ## 2. 本次核对到的事实 ### 2.1 下位机当前真实行为 下位机工程:`C:\Users\Falling_jasmine\CLionProjects\FDR-Core` 实际实现文件:`Core/Src/f4_can_app.c` 关键实现如下: 1. `CAN_Send_Telemetry_20ms()` 每 `20ms` 调一次。 2. `0x181` 状态帧固定每次都发。 3. `0x182 / 0x183 / 0x200` 通过 `s_telem_slot` 三选一轮询发送。 4. 所以 `0x200` 实际发送周期约为 `60ms`,不是 `20ms`。 5. `0x184` 通过 `s_comm_diag_divider` 每 `100ms` 发送一次。 对应代码位置: 1. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c:925-966` 2. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c:907-923` 另外,下位机头文件注释和实际实现已经出现漂移: 1. `f4_can_app.h` 注释写的是 `0x181/0x182/0x183/0x200` 都在 `20ms` 周期发送。 2. 但真实代码并不是这样,而是只有 `0x181` 固定 `20ms`。 对应位置: 1. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Inc\f4_can_app.h:165-173` 2. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c:925-966` ### 2.2 下位机 odom 数据源没有问题 下位机 `motor.c` 中,编码器增量在控制周期中持续累加到 `s_odom_acc_ticks[]`: 1. 每次电机更新时都会把四轮增量累加到 odom 累加器。 2. `CAN_Send_OdomFrame()` 发送 `0x200` 时,通过 `Motor_Get_And_Clear_Delta_Ticks()` 原子取走并清零。 这说明: 1. `0x200` 本质是“自上次发送以来的积分增量”。 2. 如果把 `0x200` 发送频率从 `60ms` 提高到 `20ms`,语义仍然正确,只是每帧覆盖的时间窗变短、实时性更高。 对应位置: 1. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\motor.c:225-245` 2. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\motor.c:263-295` 3. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c:907-923` ### 2.3 上位机当前真实行为 上位机工程:`D:\ARES` 当前 `0x200` 的消费模型是: 1. CAN ISR 收到每帧 `0x200` 后,不直接覆盖,而是累加到 `odom_accum`。 2. `monitorTask` 每 `100ms` 调用 `SNC_CAN_ConsumeOdomDelta()` 原子取走累计值。 3. 然后再调用 `Odom_Update()` 推送黑板。 4. `navTask` 本身跑在 `20ms`,但它读到的 `board.odom_vx` 很多拍都是旧值。 对应位置: 1. `D:\ARES\App\Can\snc_can_app.c:185-225` 2. `D:\ARES\App\app_tasks.c:145-167` 3. `D:\ARES\App\app_tasks.c:295-340` ## 3. 问题本质 当前 odom 实时性差,来自两个串联环节: 1. 下位机发送 `0x200` 只有约 `60ms` 一次。 2. 上位机又只在 `100ms` 的 `monitorTask` 中消费一次。 所以当前 `odom_vx` 的有效刷新节拍,不是 `navTask` 的 `20ms`,而是被这两个环节一起拖慢了。 这会直接影响: 1. `CorridorEKF_Predict()` 中 `odom_vx` 的实时性。 2. 沿程 `s` 的连续性。 3. 车体加减速或转向阶段时,状态传播的平滑度。 ## 4. 是否需要把 CAN 提得更高频 当前不建议先追求高于 `20ms` 的 CAN 频率。 原因: 1. 上位机 `navTask` / EKF 当前就是 `20ms / 50Hz`。 2. `0x100` 速度命令协议推荐节拍也是 `20ms`。 3. `0x200` 先做到 `20ms`,就已经与主导航闭环对齐。 4. 再往 `10ms` 提,边际收益明显变小,且会引入更多调度与验证成本。 在你当前系统中,更合理的目标是: 1. `0x100` 保持 `20ms` 2. `0x181` 保持 `20ms` 3. `0x200` 提到 `20ms` 4. `0x182/0x183/0x184` 维持低频 ## 5. 总线负载是否会成为问题 基于你补充的信息:总线上只有两个单片机,且 CAN 速率为 `1 Mbps`。 在这个前提下,把 `0x200` 提到 `20ms` 是合理的,风险较低。 主要原因: 1. 当前实时关键帧就只有 `0x100`、`0x181`、`0x200`。 2. 即便这三类帧都在 `20ms` 发送,总线占用通常仍远低于饱和。 3. 下位机当前发送代码本身也把遥测帧视为“允许丢”的低优先级消息,不会因短时发不出去而阻塞主循环。 对应代码: 1. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c:808-821` ## 6. 推荐修改方案 ### 6.1 下位机修改建议 修改文件: 1. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Src\f4_can_app.c` 2. `C:\Users\Falling_jasmine\CLionProjects\FDR-Core\Core\Inc\f4_can_app.h` 修改目标: 1. `0x181` 每 `20ms` 固定发送。 2. `0x200` 每 `20ms` 固定发送。 3. `0x182/0x183` 改成低频轮询发送。 4. `0x184` 继续 `100ms`。 最小改法建议如下: 1. 保留 `CAN_Send_StatusFrame()` 每拍都发。 2. 在 `CAN_Send_Telemetry_20ms()` 中,每拍额外固定调用 `CAN_Send_OdomFrame()`。 3. 把原来的 `s_telem_slot` 轮询从 `0x182/0x183/0x200` 三选一,改成只在 `0x182/0x183` 两者之间轮询。 4. 更新头文件中的注释,避免文档/实现再次漂移。 推荐把 `CAN_Send_Telemetry_20ms()` 的逻辑改成: 1. 先发 `0x181` 2. 再发 `0x200` 3. 再按 slot 发 `0x182` 或 `0x183` 4. 每 5 拍发一次 `0x184` 这样改的优点: 1. `0x200` 得到稳定 `20ms` 周期。 2. `0x182/0x183` 仍然保留,但不抢实时关键链路。 3. 代码改动很小,不需要碰控制内环和接收协议。 ### 6.2 上位机修改建议 修改文件: 1. `D:\ARES\App\app_tasks.c` 修改目标: 1. `SNC_CAN_ConsumeOdomDelta()` 不再由 `monitorTask(100ms)` 调用。 2. odom 消费与 `Odom_Update()` 挪到 `navTask(20ms)`。 具体建议: 1. 删除 `AppTasks_RunMonitorTask()` 中 `145-167` 这段 odom 消费代码。 2. 在 `AppTasks_RunNavTask_Impl()` 中、`Blackboard_GetSnapshot(&board)` 之前插入同样的 odom 消费逻辑。 3. 保持 `SNC_CAN_ConsumeOdomDelta()` 全系统只有一个消费者。 推荐新顺序: 1. `now_ms = HAL_GetTick()` 2. `SNC_CAN_ConsumeOdomDelta(...)` 3. `Odom_Update(...)` 或 `Odom_HandleTimeout(...)` 4. `Blackboard_GetSnapshot(&board)` 5. `CorridorPreproc_ExtractObs(...)` 6. `CorridorFilter_Update(...)` 这样可以保证: 1. `navTask` 每一拍都能第一时间消费已到达的 `0x200`。 2. 当下位机把 `0x200` 提到 `20ms` 后,上位机不会再被 `100ms monitorTask` 卡住。 ## 7. 不建议的修改方式 ### 7.1 只改上位机,不改下位机 这样只能把 odom 的消费延迟从 `100ms` 降下来,但 `0x200` 源头仍是约 `60ms`,收益有限。 ### 7.2 只改下位机,不改上位机 这样虽然 `0x200` 更快到达了,但上位机仍在 `100ms` 才取走一次,实时性改善不完整。 ### 7.3 直接把 `0x200` 提到 `10ms` 现阶段不建议。原因: 1. 上位机 `navTask` 仍是 `20ms`。 2. EKF 和控制链路当前主频也是 `20ms`。 3. 先把 `20ms` 跑顺、验证完,再决定是否需要更高频更合理。 ## 8. 推荐实施顺序 建议按下面顺序改: 1. 先改下位机 `f4_can_app.c`,让 `0x200` 固定 `20ms` 发送。 2. 同步修正下位机 `f4_can_app.h` 注释。 3. 再改上位机 `app_tasks.c`,把 odom 消费搬进 `navTask`。 4. 实车或日志确认 `0x200` 到达间隔、`odom_vx` 刷新节拍和 EKF 状态表现。 ## 9. 验收建议 改完后至少验证以下几点: 1. 上位机收到的 `0x200` 时间戳间隔是否接近 `20ms`。 2. `SNC_CAN_ConsumeOdomDelta()` 是否只在 `navTask` 被调用一次。 3. `board.odom_vx` 是否不再长时间停留旧值。 4. `EKF` 的 `s` 和 `e_y` 传播是否更连续。 5. `s_can_tx_drop_total` 是否没有明显上升。 6. `0x181`、`0x184`、`0x100` 协议行为是否保持兼容。 ## 10. 最终建议 最终建议如下: 1. 下位机把 `0x200` 改成 `20ms` 固定发送。 2. 上位机把 odom 消费改到 `navTask(20ms)`。 3. `0x182/0x183` 保持低频,不必提频。 4. 先不要把 `0x200` 提到 `10ms`。 这是当前这两套代码下,改动最小、收益最大、工程风险也最低的方案。