Files
ASER/改进建议报告.md
2026-04-04 23:24:41 +08:00

465 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EKF 改进建议报告
> **基于**: 审查报告 + HANDOFF_v2.md + 全部相关源码精读
> **约束条件**: 左右四个 VL53 侧向激光测距数据可信度不高IMU yaw 准确度非常高
> **目标**: 逐条回应审查报告发现,判断是否属实、是否需要修改,并给出具体改进方案
---
## 总体判断
审查报告整体质量很高8 条发现中 **7 条经代码验证属实**1 条需要在新约束条件下重新评估优先级。结合"VL53 可信度低、IMU yaw 准确度高"这一关键约束,改进策略需要做出重大调整:**不应恢复侧墙航向观测,而应进一步强化 IMU 主导地位,同时用分侧补偿和自适应噪声解决横向偏置问题**。
---
## 一、逐条回应审查报告
### 1. 横向偏置补偿模型不完整 — "持续偏右"头号嫌疑
**审查报告结论**: 属实
**是否需要修改**: **是,优先级最高**
**代码验证**:
```c
// app_tasks.c:422 — y_offset 硬编码为 0
.y_offset = 0.0f,
// corridor_ekf.c:382-396 — 只有单一 side_sensor_inset无分侧区分
float inset = s_cfg.side_sensor_inset;
float d_center = (W - Rw) / 2.0f + inset; // 左右共用同一个 d_center
```
```c
// vl53_calibration_config.h:23-51 — 四颗 VL53 的 offset 校准值各不相同
// 左前: 10000 um, 左后: 10000 um, 右前: 9000 um, 右后: 13000 um
```
**分析**: 四颗 VL53 的 offset 存在高达 4mm 的差异9mm vs 13mm但 EKF 只使用一个 `side_sensor_inset` 且设为 0.0。在 40cm 宽的走廊中每侧仅有 ~10cm 间距4mm 差异等于 **4% 的系统性横向偏置**。这个偏置会被 EKF 当作"真实居中位置"持续输出给控制器。
加上 VL53 数据本身可信度不高这一约束,这个问题的严重性进一步放大——噪声大的传感器+系统性偏置=控制器持续偏向一侧。
### 2. EKF 取消侧墙航向观测e_th 几乎完全依赖 IMU
**审查报告结论**: 属实
**是否需要修改**: **否,当前设计在新约束下是正确的**
**代码验证**:
```c
// corridor_ekf.c:345-350 注释明确说明了设计决策
// 设计决策 (方向 B — IMU 主导航向):
// 侧墙激光仅用于更新横向位置 e_y不再构建航向观测 z_eth_L/z_eth_R。
// 侧墙前后差分 (d_lr-d_lf) 的噪声在 ±2cm 误差下过大,不适合做航向主观测。
```
**分析**: 审查报告建议"恢复低权重的侧墙航向修正"。但在"VL53 可信度不高"的约束下,这个建议**不应采纳**。理由:
1. 侧墙航向观测依赖同侧前后 VL53 的差分:`atan2(d_lr - d_lf, L_s)`,传感器基线 `L_s = 12cm`。如果单颗 VL53 噪声在 ±2cm 量级,差分噪声将达 ±2.8cm,对应航向噪声 `atan2(0.028, 0.12) ≈ 13°`,这比 IMU yaw 的精度差了一个数量级。
2. IMU yaw "准确度非常高"已被确认,它是航向估计的最佳来源。
3. 引入低质量侧墙航向观测反而会**污染** EKF 中高精度的 e_th 估计。
**结论**: 当前"IMU 主导航向"的设计方向 B 在本项目约束下是正确的。不需要恢复侧墙航向观测。
### 3. IMU yaw 参考锁定过早
**审查报告结论**: 属实
**是否需要修改**: **需要调整,但策略不同于审查报告建议**
**代码验证**:
```c
// corridor_filter.c:96-100
if (!s_imu_yaw_ref_set && out_state->conf >= 0.3f) {
s_imu_yaw_ref_rad = imu_yaw_continuous_rad - out_state->ee_th;
s_imu_yaw_ref_set = true;
}
```
**分析**: 审查报告建议"要求双侧观测稳定、e_y 和 e_th 较小、连续若干拍后再锁"。但考虑到 VL53 可信度低,依赖"双侧观测稳定"来判断是否可以锁定 yaw_ref 本身就不可靠。
更好的方案是:既然 IMU yaw 准确度非常高,**yaw_ref 的锁定应该主要看入沟姿态是否对正**,而不是看 VL53 是否收敛。具体来说:
- 入沟转向阶段 IMU 已经转了 85°+,转向完成时 IMU 的绝对 yaw 值就是走廊方向的最佳估计
- 可以直接在 `transition_to(GNAV_REACQUIRE)` 时,用 IMU 当前 yaw 作为 yaw_ref而不是等 VL53 收敛后再锁)
- conf ≥ 0.3 这个门槛在 VL53 不可靠时意义不大
### 4. CorridorFilter_Update() 覆盖 EKF 的 conf 和诊断字段
**审查报告结论**: 属实,是确定性 bug
**是否需要修改**: **是**
**代码验证**:
```c
// corridor_filter.c:103-106
if (s_imu_yaw_ref_set) {
CorridorEKF_UpdateIMUYaw(imu_yaw_continuous_rad, s_imu_yaw_ref_rad, true);
CorridorEKF_GetState(out_state); // ← 覆盖了之前 Update() 写入的 conf、reject_mask、maha_d2
out_state->t_ms = obs->t_ms;
}
// corridor_ekf.c:568-587 — GetState() 重新计算 conf只看 P_trace
float P_trace = s_state.P[0][0] + s_state.P[1][1] + s_state.P[2][2];
out->conf = clampf(1.0f - P_trace * 0.5f, 0.0f, 1.0f);
out->mahalanobis_d2 = 0.0f; // ← 清零
out->obs_reject_mask = 0U; // ← 清零
```
**分析**: `CorridorEKF_Update()` 精心计算了包含 `valid_sides``reject_mask``side_factor` 的 conf`GetState()` 把这些全部丢弃,只用 P_trace 重算。这会让下游(安全层、重捕获判定)对 EKF 健康度的判断偏乐观。
### 5. 转向逻辑对 IMU 失效没有后备路径
**审查报告结论**: 属实
**是否需要修改**: **是,但优先级中等(因 IMU 准确度高,实际触发概率低)**
**代码验证**:
```c
// global_nav.c:223-224
float imu_yaw = board->imu_yaw_continuous.is_valid
? board->imu_yaw_continuous.value : 0.0f; // IMU 无效时用 0
// nav_script.c:259-265 — 注释写了 fallback但没有实现
// 后备:如果 IMU 离线,退化回 EKF e_th 差值判定。
float imu_delta_deg = imu_yaw_continuous_deg - s_internal.turn_start_imu_yaw_deg;
delta_turned = imu_delta_deg * 0.01745329252f; // 始终用 IMU无 fallback
```
**分析**: IMU 准确度高意味着此 bug 实际触发概率极低但代码逻辑确实存在问题。IMU 无效时 yaw 变成 0会导致转向角度计算完全错误。应该至少加一个 fail-safeIMU 无效时立即停车)。
### 6. NavScript 的 EXIT 阶段不可达
**审查报告结论**: 属实,是确定性 bug
**是否需要修改**: **是(单沟测试模式下需要修复)**
**代码验证**:
```c
// nav_script.c:34 — pass_count 在入沟时置为 1
s_internal.pass_count = 1;
// 后续 CORRIDOR_FORWARD → TURN → CORRIDOR_BACKWARD → TURN → CORRIDOR_FORWARD 循环
// pass_count 没有递增逻辑,也没有基于 pass_count 跳转到 EXIT 的代码
```
**分析**: 状态机在 FORWARD ↔ BACKWARD 之间无限循环,永远到不了 EXIT。在单沟测试模式下机器人会无限往返直到电量耗尽。与赛道模式无关赛道走 GlobalNav但作为测试工具应该修复。
### 7. HANDOFF_v2.md 与代码状态漂移
**审查报告结论**: 属实
**是否需要修改**: **是**
**代码验证**:
```c
// HANDOFF_v2.md:582 写的是 USE_GLOBAL_NAV = 1
// robot_params.h:385 实际值
#define USE_GLOBAL_NAV 0 // ← 文档说 1代码是 0
// HANDOFF_v2.md:635 写 kd_theta = 0.1
// robot_params.h:229 实际值
#define PARAM_CTRL_KD_THETA 0.4f // ← 文档说 0.1,代码是 0.4
// HANDOFF_v2.md:636 写 kp_y = 3.0
// robot_params.h:238 实际值
#define PARAM_CTRL_KP_Y 4.0f // ← 文档说 3.0,代码是 4.0
// HANDOFF_v2.md:647 写 d_front_stop = 0.08
// robot_params.h:282 实际值
#define PARAM_SAFE_D_FRONT_STOP 0.10f // ← 文档说 0.08,代码是 0.10
```
**分析**: 多个关键参数值已漂移。对于正在调试"持续偏右"问题的人来说,如果按文档值理解系统行为会导致误判。
### 8. EKF 接口与实现存在漂移
**审查报告结论**: 属实
**是否需要修改**: **是(代码质量问题,不影响功能但影响维护)**
**代码验证**:
```c
// corridor_msgs.h:13 — 仍声明 3 维观测
#define EKF_OBS_DIM 3 // [z_ey, z_eth_L, z_eth_R]
// corridor_ekf.h:12-17 — 头文件注释仍描述 3 维观测模型
// 观测向量z = [z_ey, z_eth_L, z_eth_R]^T
// corridor_ekf.c — 实际只做 1 维 e_y 更新 + 独立 1DOF IMU yaw 更新
// corridor_ekf.h:81 — K 矩阵仍按 3x3 分配
float K[EKF_STATE_DIM][EKF_OBS_DIM]; // 3x3实际只用 3x1
```
**分析**: `EKF_OBS_DIM` 仍为 3但实际只做 1DOF 侧墙更新。`innovation[3]` 数组从未被写入。`S[3][3]`, `S_inv[3][3]`, `K[3][3]` 分配了多余空间。不影响运行但浪费 RAM 且误导维护者。
---
## 二、基于"VL53 低可信度 + IMU yaw 高精度"的 EKF 改进方案
### 核心思路
既然 VL53 不可靠而 IMU 非常可靠,改进策略应该是:
1. **航向估计**: 完全信任 IMU不引入侧墙航向观测维持现状审查报告第 2 条建议不采纳)
2. **横向估计**: 降低 VL53 的信任度(增大 R但通过分侧补偿消除系统性偏置
3. **置信度**: 改用更保守的计算方式,不能因为 P_trace 小就认为状态好
### 改进 A: 分侧横向补偿 (解决"持续偏右")
**问题本质**: 左右 VL53 安装不对称+校准值不同,但 EKF 用同一个 `d_center` 处理左右观测。
**改进方案**: 在 `CorridorEKFConfig_t` 中新增 `left_sensor_inset``right_sensor_inset`,替代单一的 `side_sensor_inset`
```c
// corridor_ekf.h — 新增配置字段
typedef struct {
// ... 现有字段 ...
float left_sensor_inset; // 左侧 VL53 内缩距离 (实测)
float right_sensor_inset; // 右侧 VL53 内缩距离 (实测)
// 保留 side_sensor_inset 做向后兼容默认值
} CorridorEKFConfig_t;
```
```c
// corridor_ekf.c — Update() 中分别计算左右期望读数
float d_center_left = (W - Rw) / 2.0f + s_cfg.left_sensor_inset;
float d_center_right = (W - Rw) / 2.0f + s_cfg.right_sensor_inset;
if (left_ok) {
z_ey += d_center_left - ((d_lf + d_lr) / 2.0f) - yoff;
valid_sides++;
}
if (right_ok) {
z_ey += ((d_rf + d_rr) / 2.0f) - d_center_right - yoff;
valid_sides++;
}
```
**标定方法**: 将机器人手动放在走廊正中央(卷尺确认),记录四颗 VL53 的原始读数。左侧期望读数 = `(走廊宽 - 车宽)/2`,与实际读数的差就是 `left_sensor_inset`,右侧同理。
### 改进 B: 增大 VL53 观测噪声 R_ey (适配低可信度传感器)
**当前值**: `PARAM_EKF_R_EY = 0.002`(相当于 VL53 标准差 ~4.5mm,过于乐观)
**建议值**: 根据 VL53 "可信度不高"的实际表现,建议调大到 `0.01 ~ 0.02`(标准差 ~10-14mm
```c
// robot_params.h
#define PARAM_EKF_R_EY 0.015f // 从 0.002 提高到 0.015,降低对 VL53 的信任度
```
**效果**: EKF 会更缓慢地跟踪 VL53 噪声跳变,横向估计更平滑。配合 IMU 高精度航向,控制器输出更稳定。
### 改进 C: 降低 IMU 航向观测噪声 R_eth_imu (充分利用高精度 IMU)
**当前值**: `PARAM_EKF_R_ETH_IMU = 0.01`
**建议值**: 既然 IMU yaw "准确度非常高",应该给更低的 R 值,让 EKF 更信任 IMU
```c
// robot_params.h
#define PARAM_EKF_R_ETH_IMU 0.002f // 从 0.01 降低到 0.002
```
**注意**: 当前 R_eth_imu 的注释说"应明显大于侧墙航向观测 R_ETH",但既然侧墙航向观测已经被取消了,这个约束不再适用。
### 改进 D: 修复 conf 覆盖 bug
```c
// corridor_filter.c — 改进 CorridorFilter_Update()
if (s_imu_yaw_ref_set) {
CorridorEKF_UpdateIMUYaw(imu_yaw_continuous_rad, s_imu_yaw_ref_rad, true);
// 修改: 只更新状态值(e_y, e_th, s, P),不覆盖 conf 和诊断字段
CorridorState_t imu_updated;
CorridorEKF_GetState(&imu_updated);
out_state->e_y = imu_updated.e_y;
out_state->e_th = imu_updated.e_th;
out_state->s = imu_updated.s;
memcpy(out_state->P, imu_updated.P, sizeof(out_state->P));
// 保留 out_state->conf, obs_reject_mask, mahalanobis_d2 来自 CorridorEKF_Update()
out_state->t_ms = obs->t_ms;
}
```
### 改进 E: IMU yaw 参考锁定策略优化
既然 IMU yaw 准确度高,锁定策略应改为:
```c
// corridor_filter.c — 新的锁定策略
if (imu_yaw_valid) {
if (!s_imu_yaw_ref_set) {
// 方案: 不等 VL53 收敛,直接在 EKF reset 后的首拍用 IMU 当前 yaw 锁定
// 理由: IMU yaw 精度高,转向完成后 IMU 指向即走廊方向
// 此时 e_th 应为 0刚 reset所以 ref = imu_yaw - 0 = imu_yaw
s_imu_yaw_ref_rad = imu_yaw_continuous_rad; // 不减 e_th因为刚 reset 为 0
s_imu_yaw_ref_set = true;
}
if (s_imu_yaw_ref_set) {
CorridorEKF_UpdateIMUYaw(imu_yaw_continuous_rad, s_imu_yaw_ref_rad, true);
// ... (用改进 D 的方式更新 out_state)
}
}
```
**优势**: 消除了审查报告第 3 条中"入沟偏角被固化为参考"的风险。因为 yaw_ref 在 reset 后立即锁定为 IMU 当前值(即转向完成后的方向),不会等到 VL53 不靠谱地"收敛"后才锁。
### 改进 F: VL53 单侧可用时的自适应 R
当只有一侧 VL53 有效时,观测质量明显下降,应该动态增大 R:
```c
// corridor_ekf.c — Update() 中
float R_ey = s_cfg.r_ey;
if (valid_sides == 2) {
R_ey *= 0.5f; // 双侧观测,噪声更低(现有逻辑)
} else if (valid_sides == 1) {
R_ey *= 3.0f; // 新增: 单侧观测时大幅增大噪声,降低信任度
}
```
**理由**: 单侧 VL53 在可信度本就不高的情况下,缺少交叉验证,不应赋予和双侧同等的信任权重。
---
## 三、非 EKF 相关改进
### 改进 G: IMU 失效安全保护
```c
// global_nav.c — execute_turn() 中加入 IMU 有效性检查
static void execute_turn(...)
{
if (!board->imu_yaw_continuous.is_valid) {
// IMU 失效: 立即停车,不继续盲转
out->override_v = 0.0f;
out->override_w = 0.0f;
out->use_override = true;
out->safety_mode = SAFETY_MODE_IDLE; // 停车
// 超时后会被外部超时保护捕获进入 ERROR
return;
}
// ... 正常转向逻辑 ...
}
```
### 改进 H: NavScript EXIT 路径修复
```c
// nav_script.c — 在 CORRIDOR_BACKWARD 到端后,增加 pass_count 递增和退出判断
case SCRIPT_STAGE_CORRIDOR_BACKWARD: {
// ... 到端检测 ...
if (s_internal.end_armed && front_ok && obs->d_front <= s_cfg.d_entry_exit_front) {
s_internal.pass_count++; // ← 新增: 递增趟数
if (s_internal.pass_count >= 3) { // 例如走 3 趟后退出
s_stage = SCRIPT_STAGE_EXIT;
} else {
s_internal.turn_start_e_th = state->e_th;
s_internal.turn_start_imu_yaw_deg = imu_yaw_continuous_deg;
s_internal.turn_started = false;
s_internal.post_turn_stage = SCRIPT_STAGE_CORRIDOR_FORWARD;
s_stage = SCRIPT_STAGE_TURN_AT_END;
}
out->request_corridor = false;
}
break;
}
```
### 改进 I: EKF 接口维度清理
```c
// corridor_msgs.h — 更新为实际使用的维度
#define EKF_OBS_DIM 1 // 实际只做 1DOF 侧墙更新 (z_ey)
// 或者如果考虑 IMU 独立更新:
// #define EKF_OBS_DIM_WALL 1
// #define EKF_OBS_DIM_IMU 1
```
同时更新 `corridor_ekf.h` 的头部注释,去掉 `z_eth_L`, `z_eth_R` 的描述,标注当前实际是 1DOF 侧墙 + 1DOF IMU 独立更新。
### 改进 J: 文档同步
更新 HANDOFF_v2.md 中以下不一致的参数值:
| 参数 | 文档值 | 代码实际值 | 操作 |
|------|--------|-----------|------|
| `USE_GLOBAL_NAV` | 1 | 0 | 更新文档为 0 |
| `kd_theta` | 0.1 | 0.4 | 更新文档为 0.4 |
| `kp_y` | 3.0 | 4.0 | 更新文档为 4.0 |
| `d_front_stop` | 0.08 | 0.10 | 更新文档为 0.10 |
---
## 四、改进优先级排序
| 优先级 | 改进项 | 预期效果 | 工作量 |
|--------|--------|---------|--------|
| **P0** | **A: 分侧横向补偿** | 直接解决"持续偏右"根因 | 中 (改 EKF 配置 + 实测标定) |
| **P0** | **D: 修复 conf 覆盖 bug** | 消除置信度虚高,安全层能正确响应 | 小 (改几行) |
| **P1** | **B: 增大 R_ey** | 降低 VL53 噪声对控制的影响 | 小 (改一个参数) |
| **P1** | **C: 降低 R_eth_imu** | 充分利用 IMU 高精度 | 小 (改一个参数) |
| **P1** | **E: yaw_ref 锁定优化** | 消除入沟偏角固化风险 | 小 (改几行) |
| **P2** | **F: 单侧自适应 R** | 单侧退化时更鲁棒 | 小 |
| **P2** | **G: IMU 失效保护** | 防止 IMU 瞬断导致盲转 | 小 |
| **P2** | **H: NavScript EXIT 修复** | 单沟测试可正常收尾 | 小 |
| **P3** | **I: 接口维度清理** | 代码可维护性 | 中 |
| **P3** | **J: 文档同步** | 消除调试误判 | 小 |
---
## 五、建议调试验证流程
### 第 1 步: 实测标定 (P0-A)
1. 将机器人用卷尺**精确**放在走廊正中央
2. 记录四颗 VL53 的原始读数 (mm)
3. 计算 `left_sensor_inset` = 理论值 - 实测左侧平均值
4. 计算 `right_sensor_inset` = 理论值 - 实测右侧平均值
5. 填入代码
### 第 2 步: 参数调整 (P1-B,C)
1. 修改 `R_ey = 0.015`, `R_eth_imu = 0.002`
2. 单沟 `USE_GLOBAL_NAV=0` 模式验证
3. 观察 e_y 估计值是否居中、是否平滑
### 第 3 步: 修复 bug (P0-D, P1-E)
1. 修复 conf 覆盖
2. 优化 yaw_ref 锁定
3. 单沟验证:观察入沟后 conf 是否合理、e_th 是否稳定
### 第 4 步: 偏右验证
1. 完成以上修改后,重新做单沟测试
2. 如果偏右问题消失 → 核心问题已解决
3. 如果仍有轻微偏向 → 微调 `y_offset` 参数 (之前硬编码 0 的那个)
### 第 5 步: 赛道模式测试
1. 切换 `USE_GLOBAL_NAV=1`
2. 验证入沟重捕获是否正常
3. 验证沟内偏右问题是否在多沟遍历中复现
---
## 六、总结
| 审查报告条目 | 是否属实 | 是否采纳建议 | 说明 |
|-------------|---------|-------------|------|
| 1. 横向偏置不完整 | **属实** | **采纳并扩展** | 改为分侧补偿 |
| 2. 恢复侧墙航向观测 | 属实(现象) | **不采纳建议** | VL53 不可靠时恢复航向观测有害 |
| 3. yaw_ref 锁定过早 | **属实** | **部分采纳** | 改为 IMU 驱动的即时锁定 |
| 4. conf 覆盖 bug | **属实** | **采纳** | 确定性 bug 必须修复 |
| 5. IMU 失效无后备 | **属实** | **采纳** | 加 fail-safe 停车 |
| 6. EXIT 不可达 | **属实** | **采纳** | 确定性 bug |
| 7. 文档漂移 | **属实** | **采纳** | 对照代码更新 |
| 8. 接口维度漂移 | **属实** | **采纳** | 清理历史包袱 |
**一句话结论**: 在"VL53 可信度低 + IMU yaw 高精度"的约束下,**正确的改进方向是"强化 IMU 主导 + 分侧标定消偏 + 降低 VL53 权重",而不是审查报告建议的"恢复侧墙航向观测"**。EKF 的方向 B 设计决策在当前硬件条件下是正确的,只需要补齐横向偏置补偿和修复 conf 覆盖 bug 就能解决"持续偏右"的核心问题。