Commit Graph
12 Commits
Author SHA1 Message Date
LSMushui e3d432f3b5 refactor(modules): 按角色重排 modules/ 目录,消除命名不一致
原 modules/ 下目录大小写混用(BMI088/BUZZER/DR16/IST8310/LED/PID 为大写,
attitude/c_comm/filter/gimbal/ins/motor/referee/shoot 为小写),且把第三方
移植代码(QuaternionEKF/kalman_filter)与本工程封装(ins)混在同一目录。

新结构(全部小写,按角色分层,第三方代码单独隔离):

  modules/
    algorithm/   pid.c/h  filter.c/h          通用算法
    imu/         attitude.c/h  ins.c/h        姿态与惯导封装
    imu/qekf/    QuaternionEKF.c/h  kalman_filter.c/h
                                              (移植自 Wang Hongxi INS 例程)
    sensor/      bmi088.c/h  ist8310.c/h      板载传感器驱动
    comms/       dr16.c/h  c_comm.c/h  referee.c/h
    motor/       dji_motor.c/h                电机抽象
    control/     gimbal.c/h  shoot.c/h        整车控制逻辑
                 (底盘为 chassis.c/h)
    dev/         led.c/h  buzzer.c/h          板级指示/发声

共移动 58 个文件(每板 29 个),旧目录全部删除。

工程文件同步(每个工程各 2 个):
- .uvprojx: Modules 分组 16 个 <FilePath> 改为新路径;
  2 个非空 <IncludePath> 元素:15 个旧模块路径收敛为 8 个新目录,
  并顺带删除陈旧项 ../modules/AHRS(目录根本不存在,legacy 时代残留)
  与冗余项 ..\bsp(与 ../Bsp/Inc 重复)
- .uvoptx: 16 个 <PathWithFileName> 同步

关于"为什么不用改源码": Keil 的 include 靠 IncludePath 解析,本工程所有
#include 都是纯文件名形式(无路径限定),所以移动目录只需改工程配置。
另外 .h 完全不登记在工程里(只有 16 个 .c 被登记),故头文件只需移动。

回归验证(无法在此环境编译,用等价判据):
- 两个工程引用的 98 / 95 个 <FilePath> 全部真实存在
- 磁盘上 47 / 45 个 .c 全部仍被工程引用 —— 即没有任何文件掉出编译
- 2 个 IncludePath 元素均含全部 8 个新模块目录,无旧路径残留
- 所有 IncludePath 指向的目录均真实存在
- .uvprojx/.uvoptx XML 良构
- 两棵树结构一致(chassis/gimbal 等板级差异符合预期)

⚠️ 仍需在 Keil 里点一次编译确认(见 README 的构建章节)。
2026-09-12 11:51:41 +08:00
LSMushui 3e5e5e3f95 fix(bmi088): 数据路停摆的自恢复(任务上下文健康检查)
现场遥测(zo/ck 字段生效,说明新固件在跑):
  yaw -18.13 -> -18.61 / 0.7s , pitch/roll/an 全部纹丝不动
  ca=832 cg=1032 —— **8 行完全一致,采样计数停止增长**
  zo=-0.0009 ck=1 (零偏标定正常) , gx/gy/gz 也恒定

## 判定
ca/cg 不增长 = SPI 完成回调不再产生样本,IMU 数据已冻结在最后一个样本上。
此时 yaw 的"匀速自漂"只是 EKF 在忠实积分冻结值:
  gz - zo = -0.0117 - (-0.0009) = -0.0108 rad/s ≈ 观测 -0.0119 rad/s
所以姿态算法本身是对的,问题全在数据路。上一轮加的 z 标定健壮性生效了
(zo 从 0.067 降到 0.0009,ck=1),但数据路仍会停摆。

## 为什么必须放在任务上下文
停摆只有两类原因,而**中断回调里的兜底两类都救不了**:
  a) hspi1 卡在 BUSY(某次 SPI/DMA 出错后 HAL 没回到 READY):
     start_dma_read() 的 HAL_SPI_GetState 检查让每次调用都直接返回;
  b) BMI088 的 DRDY 是电平型信号:某次失败/被挡掉的读没把传感器的数据
     寄存器读走,INT 电平就保持住,之后既无下降沿也无上升沿 ——
     EXTI 永不触发,写在 EXTI 回调里的任何兜底都不会执行。
故新增 bmi088_health_check(),由 attitude_update() 每 1ms 调用:采样计数
超过 BMI088_STALL_MS(5ms)没增长即判停摆,执行
  关 EXTI -> 复位 read_phase/片选 -> SPI 卡住则 abort,仍不 READY 就
  DeInit+Init(hspi1.Init 里仍是模式3/16分频,不会被改回)
  -> 清 EXTI 挂起标志 -> 阻塞读一次加速度+陀螺(顺带清传感器 DRDY 电平)
  -> 重新使能 EXTI
若中断路始终修不好,则退化为每 5ms 补读一次(约 200Hz)的轮询保底。因为
dt 固定 1ms、1kHz 调用,1 秒内积分出的总转角仍然正确(保持值×0.001×1000),
只是运动解析粒度变粗 —— 用于排障足够,正式使用应查清停摆根源。

## 附带
- 实现 HAL_SPI_ErrorCallback(HAL 里是 __weak):此前完全没实现,SPI/DMA
  出错后相位与片选无人复位;现在复位相位/片选并累加 bmi088_spi_errors。
- 新增 bmi088_spi_state() 暴露 HAL_SPI_GetState,debug_task 追加
  se=<SPI错误数> st=<SPI状态,1=READY>。若 st 长期非 1 即命中原因 (a);
  若 st 恒为 1 而 ca/cg 不涨,即命中原因 (b)。
- 已静态核对 DMA 流分配无冲突:SPI1 RX/TX = DMA2_Stream2/3 ch3,
  USART1 = Stream5/7, USART6 = Stream1/6, USART3_RX = DMA1_S1,
  I2C2_TX = DMA1_S7,互不重叠。

两个工程同步修改,内容一致。
2026-09-12 11:32:42 +08:00
LSMushui 9fc1575615 fix(ins/bmi088): 修复 yaw 匀速自漂(零偏标定被污染)与 IMU 数据冻结隐患
现场数据(yaw 以恒定角速度自漂,陀螺却不响应板子移动):
  yaw=-213.09 -> -223.10,2.60s 内 -10.01° => -3.8500 °/s = -0.06720 rad/s
  同窗口 gn = 0.017 rad/s = 0.9740 °/s          比值 3.95
  an=9.80 恒定、pitch/roll 基本不动、cvg=1、imu=1

## 根因一:gyro_z_offset 被运动样本污染(本次主因)
gn 打印的是 bmi088_gyro[] 原始模长(未减零偏),而实际进滤波器的是
gyro_z = gz - gyro_z_offset。漂移率 0.0672 rad/s 比原始 |gyro| 0.017
还大近 4 倍,只可能是 gyro_z_offset 本身是个约 +0.067 rad/s 的假零偏。
ins_init() 原来直接对前 1000 个样本取均值:上电瞬间 BMI088 的 SPI DMA
路刚起步,且车体常正被摆弄,运动样本一旦混进窗口,零偏就被标歪 ——
之后 yaw 便以该常值匀速自漂,与陀螺真实输出无关。

修: ins.c 的 z 标定改为
  - 先等采样路稳定(INS_Z_CAL_SETTLE_MS=50ms)
  - 单样本 |gyro| > 0.35rad/s(20°/s)视为无效,静止时不该出现
  - 一轮有效样本占比 <90% 视为"上电时车体在动",整轮作废重测,最多 3 轮
  - 单轮取 0.5s:LED_Task 的 IWDG 上电宽限期只有 5s,而 IMU_Task 在
    attitude_init() 期间不刷心跳,整个初始化必须留在 5s 内,可靠性靠
    "整轮重测"而非拉长单轮(3 轮最坏 1.55s,仅比原来 1s 略长)
  - 多轮都不静则用最后一轮均值并置 ins_z_cal_ok=0

## 根因二(顺带修):EXTI 触发沿与传感器/文档不符
bmi088.c 文件头与 bmi088.h 都写"数据就绪下降沿触发",legacy 用的也是
EXTI_Trigger_Falling,但 CubeMX 生成的 gpio.c 配的是 GPIO_MODE_IT_RISING
—— 会落在数据就绪电平的恢复沿上,采样时序整体错位。已改为
GPIO_MODE_IT_FALLING,并在 .ioc 里补 GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
(否则重新生成工程又会被改回 RISING)。

## 根因三(隐患):数据路可静默冻结
start_dma_read 里 read_phase 先置位、片选先拉低,之后若
HAL_SPI_TransmitReceive_DMA 启动失败就直接返回,完成回调永不到来,
read_phase 永久卡在非 NONE:此后每个数据就绪中断都被
if (read_phase == NONE) 挡掉,数据冻结在最后一个样本,而 bmi088_online
仍是 1、an/gn 看着还挺正常。修:失败则释放片选并回滚 read_phase;
另在 EXTI 回调加 20ms 超时兜底(一次 SPI 读远小于 1ms),回调丢失时
强行复位,保证能自恢复。

## 遥测:让这类问题一眼可见
bmi088.c 增加 bmi088_accel_cnt / bmi088_gyro_cnt(解析成功后自增),
ins.c 暴露 ins_gyro_z_offset / ins_z_cal_ok,debug_task 输出扩展为
  yaw= pitch= roll= cvg= rdy= imu= an= gn= gx= gy= gz= zo= ck= ca= cg=
其中 ca/cg 是否增长是判断"数据是否还在更新"的唯一可靠依据(数值冻结时
它们不涨);zo 与 gz 量级对比可直接看出零偏是否被标歪。新增
DEBUG_ATT_DIAG 开关(置 0 去掉这些排障字段)。

两个工程同步修改,内容一致。
2026-09-12 11:27:42 +08:00
LSMushui 72bf925f47 feat(buzzer): 台架调试模式把 CAN 掉线警告音换成循环旋律
台架调试时 CAN 设备(尤其 Pitch)掉线会按 legacy 语义每 2s 响 n 下,连续
滴滴声很吵且影响听其它声音,故加一个开关:

  robot_param.h -> 台架调试开关
  #define DEBUG_BUZZER_MELODY  1   // 1=警告音换旋律;0=legacy 报警(比赛用)

实现分三处:
- robot_param.h: 新增开关,与 DEBUG_SHOOT_WITHOUT_REFEREE 放在同一分区
- modules/BUZZER: 新增 buzzer_play_melody(step) —— 按固定 100ms/步 推进,
  内部对旋律总步数取模自动循环;旋律表 buzzer_melody[] 用"上行分解和弦 +
  回落"共 14 步(1.4s),空拍 BUZZER_P 作断音,选音限定中音3~高音2 避免刺耳。
  驱动层本身不判断开关,保持纯外设职责,只是多了一个音符序列接口。
- monitor_task.c: CAN 掉线分支按开关二选一。旋律模式下步进用本任务自身的
  100ms 节拍(与文件里已有的遥控器连接音效同一写法),不用 HAL_GetTick
  采样,避免周期抖动漏掉或重复音符。

设计取舍: 只替换"声音",不替换"灯语" —— 红灯常亮、遥控器掉线红灯双闪、
BMI088 失败绿灯双闪等 LED 语义完全不变,因此调试时仍能一眼看出是哪台
设备掉线;linkcheck_first_offline() 也保留调用,方便在调试器里直接看结果。
比赛前把 DEBUG_BUZZER_MELODY 改回 0 即恢复 legacy 报警。

两个工程同步修改,内容保持一致。
2026-09-12 11:19:59 +08:00
LSMushui 3e1bae6b98 chore: 同步 Keil/VSCode 本地状态(非本次代码改动)
- 底盘 .uvoptx: DSP Library 分组展开状态 tvExp 0 -> 1(Keil 中展开工程树
  所致,不影响编译)
- 云台 .vscode/settings.json: K230 Pylance stub 路径随工具链更新

两处均为工具自动写入,一并入库以保持工作区干净。
2026-09-12 11:15:24 +08:00
LSMushui 4f18ada2f7 fix(ins): 修复 QEKF 矩阵内存分配失败(堆 512B -> 8KB),并恢复姿态诊断位
## 根因: 堆太小导致卡尔曼滤波静默失效
modules/ins/kalman_filter.h 的分配宏是

    #ifdef _CMSIS_OS_H
    #define user_malloc pvPortMalloc
    #else
    #define user_malloc malloc
    #endif

而 kalman_filter.c 整个编译单元只 include 了 kalman_filter.h,从未引入
cmsis_os.h,因此 _CMSIS_OS_H 未定义 -> user_malloc 展开为 libc malloc,
FreeRTOS 那 32KB(configTOTAL_HEAP_SIZE=32768)堆完全用不上。

libc malloc 的堆来自 MDK-ARM/startup_stm32f407xx.s 的 Heap_Size,CubeMX
默认值是 0x200 = 512 字节。而 Kalman_Filter_Init(6, 0, 3) 会发起 25 次
分配、合计约 1602 字节(P/Pminus/F/FT/Q/S/temp_matrix/temp_matrix1 各
144B,H/HT/K 各 72B,R 36B,Q 系列 24B 等),且全程没有一处 NULL 检查,
分配失败后紧接着 memset(NULL, 0, n) 并把 NULL 直接交给 arm_mat_* 使用:

  - 前半段小分配成功、后半段 144B 级分配返回 NULL
  - arm_mat_* 对 pData=NULL 只读写地址 0 附近,函数本身不报错、只是不改
    动输出 -> 滤波器不再更新状态,但 QEKF_INS.UpdateCount 照常自增
  - 表现为: att_ready=1(看起来"在跑")、欧拉角却恒定不动

对照原始例程(能正常工作的那版)RoboMaster-C-Board-INS-Example-master,
其 MDK-ARM/startup_stm32f407xx.s 的 Heap_Size 为 0x2000 = 8KB,是本工程
的 16 倍。本次将两个工程的 Heap_Size 一并提到 0x2000,与该例程一致。

注意: startup_stm32f407xx.s 由 CubeMX 生成,若日后重新生成工程,这行会
被改回 0x200,需再次修改(或改走 pvPortMalloc 路线)。注释用纯 ASCII,
避免 armasm 处理非 ASCII 源文件。

## 恢复姿态诊断位
上一版 debug_task.c 把 cvg 换成了 att_ready,丢掉了唯一的"是否真的收敛"
判据 —— 于是"IMU 没数据"和"滤波器没跑"都表现为 cvg=1 + 三个 0.00,无法
区分。本次:
  - cvg 恢复为 INS.ConvergeFlag(与原诊断输出同义)
  - 新增 rdy(=att_ready)、imu(=bmi088_online)、an(=|accel|)、
    gn(=|gyro|) 四个姿态链路健康位
  - 保留 DEBUG_ATT_HEALTH 开关,置 0 即只输出三个欧拉角
  - 文件头补注: yaw 无绝对基准,上电静止时恒为 0,不能作为故障判据
2026-09-12 11:15:16 +08:00
LSMushui 944d0201da chore(mdk): 对齐 uvoptx 的文件编号,消除删除条目留下的空档
删除 bsp_uart 的两个文件条目后,.uvoptx 的 <FileNumber> 序列在删除点后面
留下断号(云台 1..97 后直接跳到 100,底盘 1..94 后跳到 97)。Keil 重新保存
工程时会自行把末项改回连续编号(底盘已被 Keil 规范为 95),此处把云台
一并规范为 98,使两个工程的编号均为 1..N 连续。

仅改编号数值,无文件增删,不影响编译。
2026-09-12 11:03:44 +08:00
LSMushui 5e6c5068fa fix(dsp): 补回缺失的 CMSIS-DSP 预编译库
上一个提交放开了 *.lib 的忽略规则,但两个工程下
Middlewares/ST/ARM/DSP/Lib/ 目录本身就不在磁盘上(此前工作区异常中随
其他被忽略文件一并丢失),导致 .uvprojx 引用的
arm_cortexM4lf_math.lib 找不到,工程无法链接。

现从同源副本 Desktop/workspace 下的对应文件还原:
- 云台/Middlewares/ST/ARM/DSP/Lib/arm_cortexM4lf_math.lib
- 底盘/Middlewares/ST/ARM/DSP/Lib/arm_cortexM4lf_math.lib
两份 SHA-256 一致(89bd896a...),各 2,729,868 字节。

还原后重新校验:两个 .uvprojx 共 193 条 <FilePath> 引用,磁盘缺失 0 个。

同时把 .workbuddy/(助手工作区状态)加入 .gitignore,不纳入固件仓库。
2026-09-12 11:02:09 +08:00
LSMushui 3c95bb0887 feat(debug): USB CDC 输出改为 QuaternionEKF 欧拉角
原 debug_task 每 500ms 经 CDC 打印一行混装诊断信息(芯片 ID/帧计数/空闲中断
计数/链路状态/电机掉线/跟随误差等 18 个字段),稳定后已无持续观察价值。
现改为直接输出姿态解算结果,便于上位机/串口助手直接看欧拉角与验证 EKF 收敛。

输出格式(单位: 度): yaw=<...> pitch=<...> roll=<...> cvg=<0|1>
  yaw   = att_deg_yaw   (多圈连续偏航角,= INS.YawTotalAngle,云台 Yaw 环反馈)
  pitch = att_deg_pitch  (= QEKF_INS.Roll, 绕 y 轴角)
  roll  = att_deg_roll   (= QEKF_INS.Pitch,绕 x 轴角,云台 Pitch 环反馈)
  cvg   = att_ready      (0 表示 EKF 未收敛,此时角度不可信)
字段命名沿用工程侧语义,QEKF 内部 Pitch/Roll 与航空惯例互换的映射见
modules/attitude/attitude.c 文件头。

其他调整:
- 云台/底盘共用本文件,因两板 IMU_Task 都跑 attitude_update(),att_deg_*
  均有效,故移除原 ROBOT_TYPE_GIMBAL / ROBOT_TYPE_CHASSIS 分支;
- 输出周期 500ms -> 100ms(10Hz,宏 DEBUG_OUT_PERIOD_MS 可调),2Hz 下无法
  观察姿态动态响应;
- 清理因此不再需要的 include 与 dbg_* 外部声明(bmi088/ist8310/dr16/referee/
  c_comm/bsp_can/bsp_usart/usart.h/dbg_imu_loop/dbg_can_loop);
- 同步更新两份 debug_task.h 的注释说明。
2026-09-12 10:33:29 +08:00
LSMushui a9d3d97ed8 refactor(bsp): 删除无实际作用的 bsp_uart 模块
分析结论: Bsp 层并存 bsp_uart.{c,h} 与 bsp_usart.{c,h} 两套串口支持包,
其中 bsp_uart 是死代码,证据:

1. bsp_uart 只导出一个函数 UartSendTxMessage()(HAL_UART_Transmit 阻塞
   发送 + 最多 5 次重发),全工程检索无任何调用方;
2. #include "bsp_uart.h" 只出现在 bsp_uart.c 自身,Application/Core 层
   一律包含 bsp_usart.h;
3. bsp_uart.h 里的 UART1/UART2 宏也无人使用,仅被 bsp_usart.c 的注释提及;
4. 真正在用的是 bsp_usart.{c,h}(USART1/3/6 的 DMA 循环接收 + 空闲中断整帧
   回调 + TX DMA),main.c/stm32f4xx_it.c/referee_task/can_task 均依赖它。

变更:
- 删除 云台/底盘 各自的 Bsp/Src/bsp_uart.c 与 Bsp/Inc/bsp_uart.h;
- 从两个 Keil 工程的 .uvprojx(编译文件列表)与 .uvoptx(文件选项)中摘除
  对应条目,工程文件良构性已校验通过。

注: bsp_usart.c 中 usart1_tx_dma_enable()/usart6_tx_dma_enable() 目前也
无调用方(仅 usart1_tx_dma_init() 被 can_task 调用,为视觉自瞄预留),属接口
预留而非死模块,本次保留。
2026-09-12 10:33:20 +08:00
LSMushui 58aff16be1 build(gitignore): 保留 CMSIS-DSP 预编译库,不再被 *.lib 忽略
两个子工程的 .gitignore 来自通用 C 模板,其中的 *.lib 规则会把
Middlewares/ST/ARM/DSP/Lib 下的 arm_cortexM4lf_math.lib(CMSIS-DSP 预编译
库)一并排除。该文件是源码依赖而非构建产物,是工程链接 DSP 依赖时必须
的输入,一旦不入库,新克隆的仓库无法构建。故移除 *.lib 忽略规则。
2026-09-12 10:33:12 +08:00
LSMushui a4da5ad17c chore: 导入 Simba-OmniInfantry 基线(云台/底盘双工程)
- 云台(ROBOT_TYPE_GIMBAL)与底盘(ROBOT_TYPE_CHASSIS)完整固件源码
- 新增根级 .gitignore,排除 Keil MDK-ARM 构建产物(.o/.d/.crf/.axf/.lnp/.map/.htm/.sct/.dep/.iex/.lst 等)
  与 *.uvguix.* / DebugConfig/(与机器绑定的 UI 布局)
- core.autocrlf=false,避免固件源码在检出时被改动换行符
2026-09-12 10:28:21 +08:00