# 导航代码审查报告 日期: 2026-04-03 范围: - `App/nav/global_nav.c` - `App/nav/track_map.c` - `App/nav/track_map.h` - `App/robot_params.h` - `App/app_tasks.c` - `App/preproc/corridor_preproc.c` ## 结论 当前版本的 S 型拓扑和左右转向表整体上与地图理解一致,没有再发现明显的左右方向写反问题。 主要风险集中在以下几类: - 时间基准过度依赖 IMU 时间戳 - 连续确认逻辑复用了同一帧 VL53 数据 - 入场段过度依赖固定起始摆放位置 - 阶段切换存在 1 个控制周期的旧命令残留 - 连接段提前转向策略较激进 ## Findings ### 1. 高: IMU 时间戳卡住时,超时与里程都会冻结 位置: - `App/nav/global_nav.c:430-445` - `App/nav/global_nav.c:471` - `App/nav/global_nav.c:628-630` - `App/nav/global_nav.c:657-659` 现象: - `GlobalNav_Update()` 用 `board->imu_wz.timestamp_ms` 作为内部时间基准 - `odom_distance_accum` 的积分和 `elapsed_ms` 的推进都依赖这个时间戳 风险: - 如果 IMU 仍被判定为在线,但时间戳停更,导航状态机会继续输出控制命令 - 同时阶段超时保护不会推进 - 里程积分也不会推进 可能后果: - `ENTRY_STRAIGHT`、`LINK_STRAIGHT`、`EXIT_STRAIGHT` 长时间不退出 - 转向超时失效,机器人可能持续原地转 说明: - 这是行为级问题,不是单纯的调参问题 - 文档中也提到了该项仍是 TODO,但当前实现里确实已经构成运行风险 ### 2. 高: “连续 N 拍确认”实际在重复消费同一帧 VL53 数据 位置: - `App/app_tasks.c:285-286` - `App/app_tasks.c:299-347` - `App/nav/global_nav.c:516-523` - `App/nav/global_nav.c:615-620` - `App/robot_params.h:398-399` 现象: - 导航循环周期约为 `20ms` - VL53 任务推送周期约为 `100ms` - `REACQUIRE` 的连续 `5` 拍确认和 `LINK_STRAIGHT` 的连续 `2` 拍确认,都是按导航循环计数 风险: - 同一帧 VL53 观测会被导航层重复读取多次 - 于是“连续确认”并不等于“连续多个独立观测确认” 可能后果: - `REACQUIRE` 可能只靠 1 帧侧向数据就进入 `CORRIDOR_TRACK` - 沟口检测的 2 拍确认也可能只是一帧瞬时失效被重复消费 说明: - 这会削弱你现在新设计的联合判定可靠性 - 当前问题核心不是阈值,而是采样独立性不足 ### 3. 中: 入场段强依赖起始摆放位置,缺少几何确认 位置: - `App/nav/global_nav.c:484-495` - `App/robot_params.h:382-385` - `App/app_tasks.c:302-306` - `Doc/map.md:9` - `Doc/map.md:53-59` 现象: - `ENTRY_STRAIGHT` 现在只用 `里程 >= 0.30m 或 超时` 进入第一次右转 - 启动后直接 `GlobalNav_Start()`,没有专门的“出启动区口再开始计段”动作 风险: - 这要求机器人初始位置必须比较稳定,且接近你假设的起跑点 可能后果: - 如果车放在 100cm 深启动区内更靠后位置,可能在到达 `C1` 入口前就右转 - 如果车放得更靠前,也可能转得偏晚 说明: - 当前实现修掉了“侧墙始终有效导致误触发”的问题 - 但引入了“对起点一致性要求很高”的新假设 ### 4. 中: 阶段切换发生后,本周期仍可能执行旧阶段指令 位置: - `App/nav/global_nav.c:242-266` - `App/nav/global_nav.c:491-495` - `App/nav/global_nav.c:623-625` - `App/nav/global_nav.c:706-710` 现象: - 若本周期内先生成了旧阶段控制命令,再满足切段条件并 `transition_to()` - `out->stage` 在函数末尾会更新成新阶段 - 但本周期发出去的速度命令可能还是旧阶段的 风险: - 状态显示与实际执行在一个周期内不完全一致 可能后果: - 转向完成后多转一个控制周期 - 直行段满足切换条件后,当拍仍会继续向前推进一小段 说明: - 这通常是 20ms 量级的小偏差 - 但在靠近入口边缘、转向容差较紧时会放大几何误差 ### 5. 中: 连接段允许仅凭前激光位移提前触发下一次转向 位置: - `App/nav/global_nav.c:590-625` - `App/robot_params.h:406-409` - `App/nav/track_map.h:36-37` 现象: - `LINK_STRAIGHT` 的逻辑是 `B || (A && C)` - 其中 `A` 和 `B` 都采用 `0.70m * 0.85 = 0.595m` 作为触发阈值 - 也就是前激光变化量到达约 `59.5cm` 就可直接触发转向 风险: - 机器人可能在标称 `70cm` 沟间距之前约 `10.5cm` 就开始转向 可能后果: - 若前激光初值记录稍晚、转出后航向略偏、或前激光看到的并非理想正对围栏面,可能提前转向 - 提前量叠加转向半径误差后,可能更接近垄背边缘而非下一条沟中心 说明: - 这不是硬 bug,更像策略上偏激进 - 若实车转向余量很大,可能仍可工作;若几何余量小,则风险会明显上升 ## 正向观察 ### 1. 转向拓扑表当前与地图理解一致 位置: - `App/nav/track_map.c:34-47` 说明: - `C1` 右转入、左转出 - `C2` 左转入、右转出 - 奇偶沟交替 - `C6` 左转出场 这一版没有再看到此前那种第一条沟转向方向写反的问题。 ### 2. 连接段已经避免把“贴围栏侧 VL53 常亮”当作入口触发 位置: - `App/nav/global_nav.c:108-144` - `App/nav/global_nav.c:557-625` 说明: - 你已经把“非围栏侧 VL53 沟口检测”显式建模出来 - 同时结合前激光和里程计做联合判定 这比此前的 `side_walls_detected()` 直接触发要合理得多。 ## 开放问题 1. 比赛摆车是否保证机器人车头在启动区出口附近,而不是启动区任意位置? 2. 传感器黑板中的 `is_valid` 是否有基于时间戳的失效机制,还是生产者停更后仍可能保持有效? 3. 连接段的目标是“到下一沟中心线再转”,还是“进入下一沟开口就允许转”?当前 0.85 容差会显著影响这个定义。 ## 总体评价 当前版本比前一版明显更接近真实场地几何,尤其是: - 地图方向理解正确 - S 型左右转表正确 - 连接段不再依赖错误的侧墙常亮判据 但如果从比赛稳定性角度看,当前还存在两个最值得优先处理的问题: - 时间基准不能完全绑死在 IMU 时间戳上 - 连续确认不能重复消费同一帧侧向观测 如果这两点不处理,现场表现会比较依赖传感器健康状态与偶然时序,稳定性风险较高。