IWDG 延后启动改走宏改名(L3→L2):生成调用变空操作,重生成后不再需要手工删

This commit is contained in:
2026-10-10 01:30:47 +08:00
parent 63ee090fd1
commit e2e1d0fd42
6 changed files with 71 additions and 50 deletions
+21 -13
View File
@@ -880,7 +880,7 @@ external pin → "pin reset" (新增)
|---|---|---|---|
| **L1 写进 `.ioc`** | 让 CubeMX **自己生成**这份定制 | 凡是 CubeMX 界面能表达的配置 | ⭐⭐⭐ **根治**——配置成了事实源,重生成只会照着写 |
| **L2 搬进 `USER CODE` 区** | 把手工代码移到 `/* USER CODE BEGIN X */` 与 `/* USER CODE END X */` 之间 | 有现成标记区的文件(`Src/freertos.c` / `mpu_wrappers` 等),或 CubeMX 为该函数生成了标记区 | ⭐⭐ **有效**——`KeepUserCode=true` 时 CubeMX 逐字保留 |
| **L3 审计 + 基线** | 无法 L1/L2 的存量,用脚本定期核对 | 标记区外的存量、`Src/iwdg.c` 这类"生成区必须改动"的特例 | ⭐ **兜底**——不防丢失,但**丢了一定知道** |
| **L3 审计 + 基线** | 无法 L1/L2 的存量,用脚本定期核对 | 标记区外的存量(如 4 个 Fault Handler 实体) | ⭐ **兜底**——不防丢失,但**丢了一定知道** |
#### L1:哪些定制能写进 `.ioc`
@@ -963,24 +963,31 @@ external pin → "pin reset" (新增)
> → **只信 CubeMX 当前模板真实生成的区名**,用重生成后的文件核对,别凭记忆。
> → **宁可用文件头 `USER CODE BEGIN 0` 这类"区名从未变过"的标准位**。
>
> **② 生成区内无标记位可用的改动,只能 L3 兜底**。`Src/iwdg.c` 需要改的是
> `hiwdg.Init.*` 赋值与 `HAL_IWDG_Init()` 调用之间那一句,而 CubeMX 只在调用**之前**
> (`IWDG_Init 0/1`)和**之后**(`IWDG_Init 2`)生成标记区——**正中间没有**。
> 塞进 `IWDG_Init 1` 区也不能阻止区外那行被覆盖(HAL 调用在区外)。
> → 该改动**只能在生成区**,靠 `IWDG-DEFER-START-ANCHOR` 锚点注释 + 审计脚本检出。
> 同理 `HardFault_Handler` 本体(本代模板整函数重写,函数体外无区)。
> **② 生成区里那句"必须去掉"的调用,可用宏改名把它升级成 L2(2026-10-09)**。
> `Src/iwdg.c` 要处理的是 `hiwdg.Init.*` 赋值与 `HAL_IWDG_Init()` 调用之间那一句,而
> CubeMX 只在调用**之前**(`IWDG_Init 0/1`)和**之后**(`IWDG_Init 2`)生成标记区——
> **正中间没有**;塞进 `IWDG_Init 1` 区也阻止不了区外那行被覆盖(HAL 调用在区外)。
> 此前只能"每次重生成后手工删那一句"(L3)。现在的做法:在文件头 `USER CODE BEGIN 0`
> (标准保护位)放一个空实现 + `#define HAL_IWDG_Init(a_h) iwdg_deferred_start(a_h)`,
> 把**本编译单元里**那句生成调用改名到空实现 → 生成区保持原样、重生成**零 diff**,
> 不必再手工删。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`
> ——真 HAL 初始化根本没被链接进来,`MX_IWDG_Init` 只剩填结构体那几条指令。
> ⚠ **不能走"反初始化"**:IWDG 一旦启动就**无法停止**(唯一手段是复位),所以只能在
> 启动**之前**拦截,不能在 `IWDG_Init 2` 区里"撤销"。
> 同一技巧也用于 4 个 Fault Handler(`#define HardFault_Handler HardFault_Handler_Gen`
> 把**生成实体**改名,我们的实现放 `USER CODE BEGIN 0`)。区别:Handler 是"定义"而非
> "调用",所以我们的实现必须写在 `#define` **之前**,否则会被自己的宏改写。
#### L3:存量清单(标记区外,只能靠人记 + 脚本盯)
| 文件 | 内容 | 为什么无法 L1/L2 |
|---|---|---|
| `Src/iwdg.c` | 不调 `HAL_IWDG_Init()`(**P0:开机死循环防线**) | 生成区内无标记位可用(见上方硬教训②)。锚点 `IWDG-DEFER-START-ANCHOR` + 审计 FAIL 项 |
| `Src/stm32f4xx_it.c` | 4 个 Fault Handler 实体(`HardFault` / `MemManage` / `BusFault` / `UsageFault`) | 本代模板整函数重写、函数体外无区。各带独立锚点(`HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)+ 审计 FAIL 项 |
| `Src/freertos.c` | `.stack_size` 行内的中文注释 | 该行由 CubeMX 按 `.ioc` 生成,"值"受保护、"注释"不受。**注释是锦上添花**,丢失不影响功能(值在 `.ioc` 里) |
| `Src/stm32f4xx_it.c` | 4 个 Fault Handler 实体(`HardFault` / `MemManage` / `BusFault` / `UsageFault`) | 本代模板整函数重写、函数体外无区。各带独立锚点(`HARD-FAULT-ANCHOR` / `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)+ 审计 FAIL 项。⚠ 可用与 iwdg 同一招根治(把实现搬进 `USER CODE BEGIN 0`、写在 4 个 `#define` **之前**),尚未做 |
| `Src/freertos.c` | `.stack_size` 行内的中文注释 | 该行由 CubeMX 按 `.ioc` 生成,"值"受保护、"注释"不受。**注释是锦上添花**,丢失不影响功能(值在 `.ioc` 里);核算依据已集中到 `USER CODE BEGIN Variables` 区(L2) |
> **结论**:L3 清单只剩 2 项(iwdg 生成区改动、4 个 Fault Handler 实体)——**均为
> 生成区内无标记位可用的客观限制**,靠锚点注释 + 审计 FAIL 兜底(丢了一定知道)。
> 其余要么已在 `.ioc`(L1),要么已进 `USER CODE` 区(L2)。
> **结论**:L3 清单只剩 **1 项**(4 个 Fault Handler 实体)——`Src/iwdg.c` 已于
> 2026-10-09 用宏改名升级为 L2(见上方硬教训②)。其余要么已在 `.ioc`(L1),
> 要么已进 `USER CODE` 区(L2)。
> 剩余 1 项 WARN 是 `Src/freertos.c` 的 `timeSyncTask` 手工定义(属性块在
> `USER CODE BEGIN Variables` 区,本身受保护;WARN 只是提示"它不在 `.ioc` 里")。
@@ -1130,3 +1137,4 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动
| 2026-10-01 | **新增 §7.3 主机侧单元测试(L0.5)** | 硬件访问收敛到薄平台层(`App/util/task_watchdog.h` 末段声明 3 个 `__weak` 钩子:喂狗/启动/取 tick),MCU 端行为零变化(`@build` 0/0)。新建 `test/host/watchdog/`(`watchdog_test.c` 39 用例 + `run_unit_test.py` MSVC 运行器 + `stub/` 三桩头),`#include` 生产源码以保证测的是真代码。挂 pre-commit `watchdog-unit-test` 钩子。新增 `docs/测试说明/看门狗单元测试说明.md`;`App/util/README.md`、`test/host/README.md`、`测试开发标准流程.md`(新增 L0.5 层)同步。顺带改善:`s_last_tick` 由函数局部 static 提为文件 static(可测性 + 模块状态归一)。§5.1 文件表与 §10 步骤表登记;§7.2 S1 优先级降低(位图逻辑已由 U1 覆盖) |
| 2026-10-02 | **P0 根因修复:IWDG 超时被静默配成复位默认值(比设计短 5 倍)** | round 3~8 反复出现的"空 STOR(0 字节截断)→ IWDG 误复位"**根因不是"poller 自身停摆"**。真因:`Src/iwdg.c` 为实现**延后启动**(§4.5 决策 C)删掉了 `HAL_IWDG_Init()`(`IWDG-DEFER-START-ANCHOR`),而 `HAL_IWDG_Init()` 是**唯一**会把 `hiwdg.Init.Prescaler/Reload` 写进 IWDG 寄存器的路径——删掉后 `MX_IWDG_Init()` 只填结构体,`WD_IWDG_PRESCALER`/`WD_IWDG_RELOAD` 沦为**死宏**,寄存器停在**复位默认值** `PR=0`(÷4)/`RLR=4095`。实测(SWD 回读)实际超时 = 4×4096/LSI ≈ **349~512 ms**,而设计是 64×1250/LSI ≈ **1701~2498 ms**。两个后果:① `WD_MISS_TOLERANCE`(8×100ms=800ms)**永远等不到**就被 IWDG 复位 → **停喂判定分支成死代码**(这正是 §7"STALL 从未执行"的真正原因,当时据此误判为"poller 停摆");② 任何 >400ms 的心跳缺口都直接误复位(空 STOR 的 FTL flush 使 NET/FTP/UI 心跳缺 ~400ms)。**修复**:`wd_platform_start()`(`__weak`,**不在 CubeMX 生成区**,重生成不丢)补上按设计值配置:解锁(`KR=0x5555`) → `PR` → 等 `SR.PVU` 清 → `RLR` → 等 `SR.RVU` 清 → `KR=0xCCCC`(不等同步会静默丢弃后写的那只,SWD 实测)。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`,故这是全固件**唯一**写 PR/RLR 的点。**验证**:SWD 现场改回设计值后,同一空 STOR 由 **5/5 复位 → 100% 成功**;固件烧录后 3 次开机回读均为 `PR=4/RLR=1249`。`@build` 0/0;本地 CI L0+L0.5 全绿(278/278 断言) |
| 2026-10-03 | **V2/V4/V5 台架验收闭环(N-03 销项)** | 三用例全部通过。**V2 任务饿死**:`netTask` 循环插 `while(1)` → 800ms 声明 `[WD] task stall: mask=0x00000001 req=0x00000037`,≤2.5s 触发 IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。⚠️ 首轮实测**不复位**(stall 已声明、`miss` 累至 1157≈115s):根因是**停摆证据落盘自身喂狗**——`wd_stall_evidence()`→`sys_log_evidence()` 在停喂窗口里每 100ms 轮都写 NAND(`/log/wdev.log` + `SYSLOG_RUN` flush 诱导),经 `nand_ftl_wd_feed()` **直接重装 IWDG**,"停喂"语义被证据机制抵消。修复:加 `s_stall_evidenced` **每 episode 门闩**(每轮停摆只落盘一次,此后不再触碰 NAND)→ 复测复位正常。**V4 关中断死循环**:`__disable_irq()` 后插 `while(1)` → 设备每 ~4.9s 稳定复位(20+ 周期,`reset cause: IWDG`),证实 IWDG **独立于中断**(PRIMASK 屏蔽 TIM7/SysTick/PendSV 仍能复位)。**V5 长期不误杀**:FTP 512KB 上/下传 + `e02_probe` 网络探针持续 **30 min** → **120/120 次 FTP 传输全 PASS(MATCH)、零 IWDG 复位**(唯一复位为起始烧录的 `software (NVIC_SystemReset)`)。Step 4 堆告警全程未触发(`min_ever=4728 B` > 3072 B)。**遗留观察(已登记 `待处理内容.md`)**:V5 期间 44 次 `task stall`(`mask=0x00000001` 持续 ≥800ms)**100% 与 `e02_probe` 的 30s 挂起客户端→`time_sync` 掉线+监听重建(`listen FAIL`×2,约 4s)重合**;隔离实验证实「仅 FTP(无探针)120s = 0 次 stall」→ 该停摆为**探针场景专属**,非一般负载误杀。因缺口(~1s)< IWDG 最坏超时(1701ms)而未复位,与 §4.3 包含关系一致 |
| 2026-10-09 | **IWDG 延后启动由 L3 升级为 L2:不再需要每次重生成后手工删那一句** | 起因是提问"能否在初始化函数下方的自定义区里先反初始化"。查证:**反初始化不可行**——IWDG 一旦启动就**无法停止**(参考手册:唯一停止手段是复位),故不能在 `IWDG_Init 2` 区撤销。改用等价且免手工的做法:`Src/iwdg.c` 的 `USER CODE BEGIN 0`(标准保护位)放空实现 `iwdg_deferred_start()` + `#define HAL_IWDG_Init(a_h) iwdg_deferred_start(a_h)`,把**本编译单元里**那句生成调用 `HAL_IWDG_Init(&hiwdg)` 改名到空实现 → 生成区保持 CubeMX 原样、**重生成零 diff**。`.map` 证实 `Removing stm32f4xx_hal_iwdg.o(i.HAL_IWDG_Init), (94 bytes)`、`iwdg.o(i.MX_IWDG_Init)` 只引用 `.bss`(空实现被编译器消掉)。审计项 `iwdg-defer-start` 相应由 `wholefile`+锚点注释改为 `protected`(区 `0`)+ 代码符号断言(`iwdg_deferred_start` / `#define HAL_IWDG_Init`——`protected` 比对前会剥掉注释,锚点注释不能当断言)。§10.2 硬教训②与 L3 存量清单同步:L3 只剩 4 个 Fault Handler 实体(同一招可根治,尚未做)。`@build` 0 错误 0 警告;审计 21 通过 / 1 警告 / 0 失败 |