原 README.md 是上游框架模板的拷贝:两棵树内容完全相同,描述的是"计划要做的 模块"(一堆未勾选的 checkbox),且大量链接指向根本不存在的 ./doc/*.md (framework/注意事项/API/LOG/TODO/reference/appendix/questions 全都没有)。 它没有描述本项目实际重构后的代码。 本次文档落地为三份: 1. README.md(根,详细) - 项目概览(双板架构、两板同构、QEKF、双 INS 跟随方案) - 硬件与工具链、快速开始(编译烧录 + 上电前置检查) - 代码地图(Application/Bsp/Core/modules/MDK-ARM 逐层说明) - 运行架构(9 个任务的优先级/栈/职责表、控制周期、IWDG 看门狗) - 数据流与对外接口(CAN 分配、串口分配、CToC 帧表、姿态数据流) - 模块清单、USB CDC 遥测字段详解、调参入口与三个台架开关 - 上电时序与灯语/蜂鸣器语义 - 已知陷阱(整合了本次排障的全部硬知识) - 版本管理约定、致谢与联系 2. doc/已知问题与排障.md(新增) 排障总则(先看 ca/cg 而非数值;用算式交叉验证)+ 症状速查表 + 6 个典型 案例详解 + 提交前检查清单。把"踩过的坑"沉淀成可复查的手册。 3. 云台/README.md、底盘/README.md(重写为简短的板级说明) 只讲本板特有的职责、编译宏、独占模块、上电特有行为、蜂鸣器序号,并指回 根 README;顺便去掉指向不存在文件的死链。 另修一处文档缺陷:modules/comms/c_comm.h 里 0x1A9 帧被写了两遍,且两处对 Data[4] 的描述互相矛盾(一处说只有 att_ready,一处说还有编码器增向与自检位), 已合并为一份准确描述。
9.0 KiB
9.0 KiB
已知问题与排障手册
本文记录本项目实际踩过的坑、判断依据和处理方式。遇到姿态/通讯异常时先读这里。
0. 排障总则
0.1 先看 ca / cg,再看数值
USB 遥测里的 ca(加速度采样计数)和 cg(陀螺采样计数)是唯一能证明"数据还在更新"的指标。
数值是会骗人的 —— 数据路停摆时,an、gn、gx/gy/gz、pitch/roll 都会安静地停在最后一个样本上,看起来"挺正常"。
ca/cg 在涨 → 数据路活着,问题在算法或标定
ca/cg 不涨 → 数据路死了,先修数据路,别的都别管
0.2 用算式交叉验证,别凭感觉
姿态异常时,把观测到的现象和遥测里的量做量级核算,往往能直接定位层次。例:
- yaw 匀速自漂 → 算漂移率(rad/s)
- 若漂移率 >
gn⇒ 漂移来自被污染的 z 零偏zo,不是陀螺本身 - 若漂移率 ≈
gz - zo⇒ 算法没问题,是数据冻结了(EKF 在忠实积分冻结值)
- 若漂移率 >
- 角度恒定不动 → 看
cvg:cvg=0说明量测一直被卡方检验拒绝;cvg=1而角度不动 ⇒ 多半是ca/cg停了
0.3 症状速查表
| 症状 | 首先怀疑 | 去看 |
|---|---|---|
| 欧拉角恒为 0.00 | IMU 没起来,或 EKF 内存分配失败 | imu、an、cvg;§2.1 |
| 角度恒定不动(有值但不变) | 数据路停摆(数据冻结) | ca/cg;§2.2 |
| yaw 匀速自漂 | z 零偏标定被运动污染 | zo vs gz,漂移率 vs gn;§2.3 |
| 角度是垃圾值 / yaw≈180 | 矩阵运算静默失败 | cq 字段、Heap_Size;§2.1 |
| 蜂鸣器连续滴滴响 | CAN 设备掉线报警(正常行为) | 响几下 = 第几号设备掉线;§2.4 |
| 红灯常亮 | CAN 设备掉线 | 同上 |
| 绿灯连闪两下 | BMI088 失败 | imu |
| 反复重启(约 3~5 s 一次) | 初始化超时 / 任务卡死 | IWDG 5 s 预算;§2.5 |
| 串口收不到整帧 | 空闲中断 / DMA 未重启 | bsp_usart.c |
1. 工具与判据
1.1 USB CDC 遥测字段
完整字段说明见根 README §8。排障最常用的几个:
| 字段 | 关键读法 |
|---|---|
ca/cg |
是否在增长 = 数据路是否活着 |
cvg |
INS.ConvergeFlag,是否真收敛(不是"是否在跑") |
imu |
BMI088 是否初始化成功 |
an |
≈9.8 正常;≈0 说明没数据 |
gn |
原始陀螺模长(未减零偏) |
zo |
实际减掉的 z 零偏;与 gz 量级对比 |
ck |
zo 是否可信(标定时是否静止) |
se/st |
SPI 出错次数 / SPI 状态(1=READY) |
1.2 LED 与蜂鸣器
灯语见根 README §10。蜂鸣器的"响几下"直接对应掉线设备序号 + 1。
1.3 与参考工程对照
桌面上留有本项目的参照物,遇到"应该能跑却跑不起来"时,优先和它们逐行对照:
| 参考 | 位置 | 用途 |
|---|---|---|
| 原始 INS 例程 | Desktop/新建文件夹/RoboMaster-C-Board-INS-Example-master |
QEKF / kalman_filter 的移植来源;其 Heap_Size = 0x2000 |
| legacy 固件 | Desktop/new-infantry-c-board-legacy - 副本 |
StdPeriph 版、自带 AHRS.lib;引脚分配、EXTI 触发沿、寄存器序列的权威参照 |
| 本工程旧版 | Desktop/Simba-OmniTest/Simba-ELCT-FrameWork |
同一框架的历史快照 |
2. 典型案例详解
2.1 三个欧拉角恒为 0.00
两个不同原因都会长成这样,用 cvg 和 an 区分:
(a) EKF 矩阵内存分配失败 —— cvg=1、an≈9.8、角度恒 0
- 现象:
yaw=pitch=roll=0.00且cvg=1(注意cvg若是att_ready之类的量就永远看不出来) - 原因:
kalman_filter.c的user_malloc展开为 libcmalloc(它不 includecmsis_os.h),而 libc 堆来自startup_stm32f407xx.s的Heap_Size。CubeMX 默认0x200(512 B),远小于Kalman_Filter_Init(6,0,3)需要的约 1.6 KB,且全程无 NULL 检查 - 后果:后半段矩阵
pData = NULL,arm_mat_*只读写地址 0 附近、不报错也不改输出 ⇒ 滤波器不再更新状态,但UpdateCount照常自增 ⇒ 看起来"在跑"、"收敛了" - 处理:把
Heap_Size改为0x2000(与原始例程一致)。⚠️ 该行由 CubeMX 生成,重新生成工程会被改回
(b) IMU 没起来 —— cvg=0、an≈0、角度恒 0
- 现象:
imu=0或an≈0,且cvg=0(卡方检验一直不过) - 加速度全零 → 量测被拒绝 → 四元数停在初值
[1,0,0,0]反解就是三个 0.00 - 处理:查 SPI 通路 / 片选 / 传感器供电;并把
debug_task的imu、an和绿灯灯语一起看
2.2 数据路静默冻结
- 现象:
pitch/roll/an/gx/gy/gz全部纹丝不动,ca/cg停止增长,而imu仍是 1;yaw 可能还在匀速漂(积分冻结值) - 两类原因:
hspi1卡在 BUSY(SPI/DMA 出错后 HAL 没回到 READY)⇒start_dma_read()里HAL_SPI_GetState检查让每次调用直接返回- BMI088 的 DRDY 是电平型信号,某次失败或被挡掉的读没把数据寄存器读走 ⇒ INT 电平保持住 ⇒ 既无下降沿也无上升沿,EXTI 永不触发
- 为什么中断里的兜底没用:两种情况都会让 EXTI 回调压根不执行
- 处理:已实现任务上下文的
bmi088_health_check()(由attitude_update()每 1 ms 调用),计数 5 ms 不涨即重启中断路 + 阻塞读一次,修不好则退化为约 200 Hz 轮询保底 - 继续深挖:看
st与sest长期非 1 ⇒ SPI 卡死(原因 1)st恒为 1 但ca/cg不涨 ⇒ 数据就绪中断丢失(原因 2)
2.3 yaw 匀速自漂
- 现象:yaw 以恒定角速度漂移,
pitch/roll正常;移动板子时陀螺读数"不响应" - 判定算式:
实例:实测 -3.85 °/s = -0.0672 rad/s,而
漂移率(rad/s) = Δyaw / Δt 若 漂移率 > gn ⇒ 漂移来自被污染的 zo,不是陀螺 实际进滤波器的角速度 = gz - zogn = 0.017 rad/s—— 漂移率比整个陀螺模长还大近 4 倍,只可能是假零偏 - 原因:z 轴零偏标定窗口(~1 s)里混入了运动样本。上电瞬间 SPI 路刚起步、且车体常正被摆弄
- 处理:
ins.c已加健壮性(稳定等待 + 离群剔除 + 整轮重测 +ck标志);根治办法是上电后那 1.5 秒别碰车 - 注意
gn是未减零偏的原始模长,所以gn小并不代表进滤波器的值小
2.4 蜂鸣器一直响
- 这是正常报警,不是故障:某台 CAN 设备掉线,
monitor_task按 legacy 语义每 2 s 窗内响n下,n = 最先掉线的设备序号 + 1 - 云台序号:
0=摩擦轮L 1=摩擦轮R 2=Pitch 3=Yaw 4=拨弹 5=底盘回传;底盘:0~3=四轮 4=云台数据 - 响三下 ⇒ 第 3 号设备(云台板的 Pitch 电机 0x206)掉线
- 台架调试嫌吵:把
robot_param.h的DEBUG_BUZZER_MELODY置 1,警告音换成旋律;灯语不变,故障信息不丢。比赛前记得改回 0
2.5 反复重启
- 现象:约 3~5 s 复位一次,USB 输出每隔几秒重来
- 原因:IWDG 约 2.5 s 溢出;上电 5 s 内无条件喂狗,之后要求所有任务心跳都在 500 ms 内刷新过。因此
- 某个任务卡死(死循环 / 阻塞在无超时的等待)
- 或者整个初始化超过 5 s,
IMU_Task还没进主循环、心跳不刷新
- 处理:检查各初始化耗时(传感器重试次数、IMU 零偏标定轮数);
IMU_Task在attitude_init()期间不刷心跳,扩展初始化要算进这 5 s 预算
2.6 工程编译/链接相关的坑(非运行期)
| 症状 | 原因 | 处理 |
|---|---|---|
链接报找不到 arm_mat_* |
Middlewares/ST/ARM/DSP/Lib/*.lib 丢失 |
该目录是源码依赖,已入库;确认文件在 |
新加的 .c 没被编译 |
没加进 .uvprojx |
同步加 .uvprojx(和 .uvoptx) |
| Keil 每次保存工程文件都在变 | .uvoptx 的 <FileNumber> 断号 |
删除条目后把末尾编号改为连续值 |
| 移动目录后编译不过 | <IncludePath> 没跟着改 |
.h 不登记在工程里,靠 IncludePath 定位;移动目录时同步改 |
| CubeMX 重新生成后行为变了 | 手改过的生成文件被覆盖 | 复查 startup_stm32f407xx.s 的 Heap_Size 与 Core/Src/gpio.c 的 INT1 触发沿 |
3. 改动本仓库时的检查清单
改完代码准备提交前,过一遍:
- 两棵树都改了?(同构文件需要同步)
- 增删了
.c?→ 同步改.uvprojx与.uvoptx - 移动了目录?→ 同步改
.uvprojx/.uvoptx的文件路径与<IncludePath> - 工程引用的文件是否都真实存在?磁盘上的
.c是否都被工程引用(没有掉出编译)? - 动过
modules/imu/qekf/?→ 先读imu/attitude.c、imu/ins.c文件头约定 - 姿态相关改动?→ 在
README §8的字段里想清楚怎么验证(哪个字段能证明它对了) - 改了
robot_param.h里带"比赛请改 0"的开关?→ 记得改回来