已知陷阱
super_agent_bot edited this page 2026-11-02 03:34:57 +08:00

已知陷阱

本页只收“高频、难查、后果重”的坑。每条给出规则与详情入口; 改代码前先扫一遍, 尤其是驱动、启动、存储和烧录。

详细台账: docs/问题记录/CH395F_Trap_Records.md、GD5F2GQ5UE_Trap_Records.md、SD2506_Trap_Records.md、错误码传播审计.md。

1. 中文源码与 BOM(最容易被工具误伤)

规则:

  • 改含中文的源码只用 Edit 工具, 绝不用 Get-Content / Set-Content / cmd 整文件重写;
  • 带 BOM 的文件(Drivers/BSP/UI/*、App/task/ui_task.c、App/cfg/cfg_file.c、Lib/lv_conf.h)被 Edit 后会静默丢 BOM;
  • 丢 BOM 后 ARMCC 按 CP936 解码, 中文字面量报 #870-D: invalid multibyte character sequence;
  • 只补 3 字节 EF BB BF, 不要整文件重写;
  • 怀疑乱码先统计 EF BF BD 字节数, 不要急着 git checkout(会丢未提交工作)。

字符串字面量必须纯 ASCII: 无 BOM 文件里奇数个汉字会吞掉结束引号。中文只写注释。

2. osKernelStart 之前不能创建 RTOS 对象

症状: 启动直接死掉, 或行为不可预测。

规则: osKernelStart() 之前禁止任何 FreeRTOS / CMSIS-RTOS 创建类 API(osMessageQueueNew、osThreadNew 等)。内核前只能用纯寄存器/HAL 轮询 + 无锁静态缓冲; 内核启动后再惰性创建真队列。

详见 docs/架构设计/项目启动顺序管理.md。

3. 就绪位被消费 → FTP 静默不监听

症状: ftpTask 永久阻塞, 21 端口从未监听, 零日志零报错。

根因: osEventFlagsWait 默认消费所等位(xClearOnExit=pdTRUE)。netTask / uiTask / timeSyncTask 先消费掉 NET_READY / STORAGE_READY, ftpTask 的 WaitAll(bit0|bit1) 永不成立。

规则:

  • 状态位(NET_READY / STORAGE_READY)等待必须 osFlagsNoClear;
  • 命令位(CFG_SAVE / NET_RECONFIG)保持消费型。

4. 自研代码禁用 C 库堆分配

症状: 模拟器正常, 真机 malloc 返回 NULL。

根因: MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 限定为 512 B; KB 级分配必然失败, 而 PC 模拟器用宿主 CRT 堆, 照不出来。

规则: 只用 pvPortMalloc/vPortFree, 或把分配收进平台后端(*_mcu.c / *_sim.c)。

5. FatFs 追加写与短写

陷阱 A: 该移植的 FA_OPEN_APPEND 不含 FA_WRITE。只带 FA_OPEN_APPEND 打开的文件, f_write 恒返 FR_DENIED。

f_open(&fp, path, FA_WRITE | FA_OPEN_APPEND);

陷阱 B: f_write 簇分配失败时可能返回 FR_OK + 短写(无错误码)。必须校验写回字节数:

UINT wrote = 0;
if (f_write(&fp, buf, len, &wrote) != FR_OK || wrote != len) { /* 失败 */ }

6. FatFs 只支持 8.3 短名

FF_USE_LFN=0 → 只支持严格 8.3 短名, 不得以点开头。.fschk 这类探测名会恒返 FR_INVALID_NAME。自愈逻辑严禁把名字/参数类错误码归入“卷损坏触发集”, 否则会误格式化。

7. CH395F SPI 事务必须成对包裹, 分频固定为 8

规则:

  • 每次 SPI 事务用 ch395f_spi_begin() / ch395f_spi_end() 包裹;
  • SPI 分频系数必须为 8(PCB 设计约束, 勿“优化”);
  • 所有 CH395F 中断在 net_poll() 内经 GET_GLOB_INT_STATUS 集中读取清除, 不得在 ISR 或其他地方单独处理;
  • 非 netTask 任务只调 net_* API, 不直接碰 CH395F 驱动。

8. ADC 取数只能用原子快照

规则:

  • 只用 tpafe5160_snapshot()(临界区原子快照);
  • 旧的 get_buf() / data_ready() / clear_ready() 已删除(两步之间被抢占会拷回两帧混合数据, s_ready 有三写者竞态);
  • 消费者统一经 adc_input_poll() / adc_input_raw() 按语义取数, 不写死下标;
  • ISR 侧只能走 adc_tick_irq_notify()。

9. F4 ADC 的四个硬约束

  1. ADC1 外部触发只认 TIM1/2/3/4/5/8 的 CC 或 TRGO; TIM6/TIM7 的 TRGO 只接 DAC → 用 TIM3。
  2. ADC1 在 DMA2 只挂 Stream0 或 Stream4 → 选 Stream4, 避免挤走 SPI1/USART1 RX; 换流后优先级会被重置为 0, 必须回 NVIC 核对(外设中断 ≥5)。
  3. DMAContinuousRequests 必须 ENABLE; 关掉会一轮 9 通道只发一次请求, 静默丢 8 路。
  4. DMA 目的缓冲必须在 SRAM(DMA2 不可达 CCM); ADC 上电必须自校准(CubeMX 不生成, adc1_calibrate() 手写)。

10. V0.1 的 8 路电气量不能跨转换器运算

8 路电气量分属 TPAFE5160 与 STM32 ADC1 两片转换器, 没有共同采样触发、精度/量程不一致:

  • V0.1 上禁止跨转换器做相位、功率、功率因数、矢量运算;
  • 主板侧只做逐通道独立监视;
  • 功率/功率因数/相角取子板计量模块结果;
  • V0.2 要求 8 路全部接 TPAFE。

11. net_msg_t 错误码要靠任务通知回传

net_msg_t 是按值拷入队列的, netTask 改的是自己那份副本, 调用方看不到。错误码必须搭 xTaskNotify() 通知值回来; 不要给 net_msg_t 加“回传用”字段(调用方只会读到栈垃圾)。

12. EOF 时不要 net_close 监听 fd

CH395F 单连接模型下, net 层在 EOF 已 auto_relisten。应用层若在 EOF 时 net_close 该 fd, 会把监听一起毁掉, 端口变成“连接被拒”。仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关。

13. CubeMX 重生成会覆盖 USER CODE 区外的一切

  • 自定义代码只写 USER CODE BEGIN/END 之间;
  • 重生成后立刻跑 scripts/check_cubemx_survival.py;
  • .ioc 的 KeepUserCode=true 只保护 USER CODE 区;
  • 4 个 Fault Handler、IWDG 延后启动等生成区外定制靠 cubemx-survival 钩子审计。

14. 烧录必须 -Rst -Run, 烧后必须回读校验

  • 裸 -P 可能显示 Programming Complete, 但目标核处于死循环/异常时写入不落地;
  • 烧后必须 -CmpFile 回读, No difference found 才算真正写入;
  • -CmpFile 连接时会复位目标, 必须排在采日志之后;
  • 固件身份戳用于确认“芯片里跑的就是这份构建”。

15. FTP 单客户端与默认口令

  • 同一时刻只服务 1 个会话; 旧会话异常断开后约 30 s 才恢复监听;
  • 不要用裸 TCP 工具探测 21 端口(会占掉唯一会话);
  • 测试脚本必须内置重连;
  • 出厂默认 admin/admin, 现场必须修改; FTP 明文传口令, 不挡网络嗅探。

16. RS-485 尚未接线, 不要假设它可用

  • rs485_init() 已调用, 但 rs485_task 未创建;
  • RS-485 采集按用户决定冻结(2026-10-12);
  • 接线前还有单缓冲包撕裂、发送超时未等发送完成就拉低方向控制两条驱动问题未修;
  • 接线前必须确认驱动契约已修, 并在建任务前后各跑一次 stack_report.py。

17. 新增任务/栈/缓冲先过内存预算

  • 新增任务、抬栈、加大缓冲前先核算 RAM;
  • 改栈前后各跑一次 scripts/stack_report.py;
  • scripts/ram_report.py 看 .map 静态占用;
  • mem-budget 钩子拦截 RW_IRAM1 / RW_CCM >92%;
  • 新增任务还要纳入 IWDG required_mask 与心跳点检。

18. 新源文件必须双端注册

  • Keil .uvprojx + simulator/CMakeLists.txt 都要注册;
  • 新文件落在新目录时, 还要把目录加进 uvprojx 的 <IncludePath>;
  • 否则报错会落在消费者 TU 上(cannot open source input file), 容易误判;
  • 用 new-source-file skill 并跑 registration-check。

19. uvprojx 抖动不要提交

每次 @build 后 UV4 可能自动改写 MDK-ARM/*.uvprojx(XML 归一化、PackID 降版本)。这类改动不入库; 只有真正增删源文件时才提交工程文件。

20. 启动段没有看门狗保护

app_main_init() + bring-up 阶段不喂狗, 也没有看门狗保护。启动卡死时先看最后一条 DBG_INFO, 启动段每一步都有结果日志。