实现独立看门狗:CubeMX 配置 IWDG + 任务心跳位图,恢复三处被覆盖定制

经 CubeMX 图形界面启用 IWDG(Prescaler=64 / Reload=1249,标称 2498ms /
最坏 1701ms),并落地心跳位图判定、各任务埋点、RCC_CSR 复位原因上报与
configASSERT 改造。重生成覆盖的既有定制已全部恢复。

P0(开机死循环):HAL_IWDG_Init() 内部立即启动看门狗,而 CubeMX 把
MX_IWDG_Init() 插在 app_main_init() 之前——该段 NAND/RTC/ADC 初始化耗时
数秒无喂狗点,按 2.5s 超时必然复位。改 Src/iwdg.c 为只填参数不启动,
启动移交 task_wd_start(),由 defaultTask 在存储 bring-up 完成后调用。

恢复的三处定制(重生成覆盖):
- Src/stm32f4xx_it.c:HardFault_Handler 崩溃现场采集被打回空 while(1)
- Inc/FreeRTOSConfig.h:栈溢出检测与堆失败钩子两个宏被整块删除
- Src/freertos.c:三处任务栈实测记录中文注释丢失(数值未丢)

新增:
- App/util/task_watchdog.{c,h}:心跳位图 + 抽样判定 + IWDG 延后启动
- time_sync.c 长等待切片:net_recv_to 单次可睡满 30s 预算,切成 200ms 片
- Src/main.c 复位原因锁存(读后即清 RMVF,防残留误判)

埋点:defaultTask(唯一喂狗者)/ netTask(含 PHY 等待循环)/ uiTask /
timeSyncTask。ftpTask 首期剔除掩码,待二期改 lftpd。

门禁:build 0 错误 0 警告;pre-commit 10 钩子全通过;Flash +2956B / RAM +40B。
This commit is contained in:
2026-09-29 07:22:04 +08:00
parent e18e9f2cfd
commit 7feeea6c56
24 changed files with 1761 additions and 361 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ docs/
| [`系统墙钟时间维护说明.md`](子系统设计/系统墙钟时间维护说明.md) | `sys_clock`:RTC 基准、tick 回绕防护、30 天惰性再基准化 |
| [`控制台单写者日志设计说明.md`](子系统设计/控制台单写者日志设计说明.md) | 控制台日志单写者模型(避免多任务交错输出) |
| [`运行与自检记录设计说明.md`](子系统设计/运行与自检记录设计说明.md) | `/log/run.log`·`selftest.log` 定长记录与待机显示 |
| [`看门狗设计说明.md`](子系统设计/看门狗设计说明.md) | **调研+方案(待实施)**:IWDG 选型、任务心跳位图、复位原因分类、验证计划 |
| [`看门狗设计说明.md`](子系统设计/看门狗设计说明.md) | **实施中**:IWDG(CubeMX 配置)+ 任务心跳位图;代码已落地编译通过,待台架故障注入/长跑验证 |
| [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 |
| [`网络/BSD_Socket_API_函数调用关系.md`](网络/BSD_Socket_API_函数调用关系.md) | **内部视图**:netTask / 消息队列 / L3 挂起 / 重试对账的调用关系与设计意图 |
| [`网络/Net层阻塞模型架构设计.md`](网络/Net层阻塞模型架构设计.md) | **等待模型**:pending 请求架构、五路完成语义、设计边界与性能特征 |
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 261,932 B (255.79 KB) | 1,048,576 B (1024 KB) | 25.0% |
| RW_IRAM1 | 87,040 B (85.00 KB) | 131,072 B (128 KB) | 66.4% |
| ER_IROM1 | 264,860 B (258.65 KB) | 1,048,576 B (1024 KB) | 25.3% |
| RW_IRAM1 | 87,080 B (85.04 KB) | 131,072 B (128 KB) | 66.4% |
| RW_CCM | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
| **Flash (ROM 合计)** | **262,660 B (256.5 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
| **Flash (ROM 合计)** | **265,616 B (259.4 KB)** | 1,048,576 B (1024 KB) | **25.3%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
+132 -60
View File
@@ -1,9 +1,10 @@
# 看门狗设计说明
> **模块名称**:独立看门狗(IWDG)+ 任务心跳位图
> **文档定位**:**调研 + 设计方案**——记录选型依据、在既有平台上落地的约束、
> 推荐架构与分步实施路径。**尚未实施**,实施后本文件应改写为"现状设计说明"。
> **状态**:📋 设计完成,待实施(登记于 [`../待处理内容.md`](../待处理内容.md) N-03)
> **文档定位**:**实施中**——记录选型依据、在既有平台上落地的约束、实现现状与
> 待台架验证项。**代码骨架已落地并编译通过**(§10 第 0~6 步);第 7~10 步
> (故障注入 / 长跑 / 本文档转为"现状设计说明")待台架。
> **状态**:🚧 代码已落地(`@build` 0/0),**待台架故障注入与长跑验证**;登记于 [`../待处理内容.md`](../待处理内容.md) N-03
> **日期**:2026-09-29
> **相关文档**:
> [`../审查记录/项目审查与优化建议报告.md`](../审查记录/项目审查与优化建议报告.md)(N-03 缺陷原始登记)、
@@ -33,8 +34,7 @@
> ⚠️ **实施路径因此改变**:`HAL_IWDG_MODULE_ENABLED` 不是一个"取消注释即可"的开关——
> 取消后 `stm32f4xx_hal_conf.h` 会去 include 一个**不存在的头文件**,直接编译失败。
> 本方案因此**不走 HAL 驱动,改用直接寄存器操作**(见 §2.2)——这反而是更优路径,
> 因为 CMSIS 设备头**已提供完整的 IWDG 寄存器定义与实例指针**,零新增依赖。
> 故须**由 CubeMX 勾选 IWDG 后重生成**,让驱动源码与 HUD 开关一并就位(见 §2.2)。
### 1.2 现存的两处"假保护"
@@ -108,7 +108,14 @@ T_IWDG = (4 × 2^PR) × RLR / F_LSI
**本方案选定(见 §4.3)**:`PR=4`(64 分频)/ `RLR=1249` → 标称 **2498 ms**、
最坏 **≈1701 ms**。
### 2.2 实施路径:直接寄存器操作,不引入 HAL IWDG 驱动
### 2.2 实施路径:**走 CubeMX 生成**(原方案为直接寄存器,已变更)
> **变更说明(2026-09-29)**:本节初稿推荐"直接寄存器操作"(下称路径 C),依据是
> HAL IWDG 驱动源码不在仓库。经与项目负责人确认,**改走 CubeMX 图形界面生成**
> (路径 A)——让 `.ioc` 成为配置的唯一事实源、与其他外设保持一致。路径 A 的
> 原评估"高风险"已被本次实践证伪:重生成带来的定制覆盖是**有限且可枚举**的
> (实测见 §10 第 2 步),处理方式已固化为"重生成 → 逐项恢复 → 编译验证"。
> 以下保留路径比较,作为决策记录。
**约束事实**(§1.1 表的第 3、4 行):HAL IWDG 驱动源码**不在仓库**、也**不在 MDK 工程**中。
CubeMX 只在勾选该外设时才会把 `stm32f4xx_hal_iwdg.c/h` 拷进 `Drivers/STM32F4xx_HAL_Driver/`。
@@ -120,16 +127,44 @@ CubeMX 只在勾选该外设时才会把 `stm32f4xx_hal_iwdg.c/h` 拷进 `Driver
| 路径 | 做法 | 评估 |
|---|---|---|
| A. 从 CubeMX 重新生成(勾选 IWDG) | 用 `.ioc` 勾选 IWDG 后生成 | ❌ **高风险**:CubeMX 重生成会重写 `Src/`/`Inc/` 生成区,项目有大量既存定制(见 `AGENTS.md`「平台约束」),且会带回行尾空白抖动(2026-09-20 已记录过该问题) |
| B. 手工补入 `stm32f4xx_hal_iwdg.c/h` | 从 STM32CubeF4 包拷贝两文件 + 注册进 MDK 工程 | ⚠️ 可行但需**手改 `.uvprojx`**(XML 结构脆弱),且引入约 0.3 KB Flash 的 HAL 封装 |
| **C. 直接寄存器操作**(**本方案选定**) | 用 CMSIS 已提供的 `IWDG` 实例指针 + 寄存器位域宏 | ✅ **零新增依赖、零工程文件改动、代码量约 20 行** |
| **A. 从 CubeMX 重新生成(勾选 IWDG)**(**实际采用**) | 用 `.ioc` 勾选 IWDG 后生成 | ✅ **配置单一事实源、与其他外设一致**;代价 = 需恢复被覆盖的既有定制(实测 4 处,见 §10 第 2 步),且须按 `AGENTS.md` 处理行尾空白抖动 |
| B. 手工补入 `stm32f4xx_hal_iwdg.c/h` | 从 STM32CubeF4 包拷贝两文件 + 注册进 MDK 工程 | ⚠️ 需**手改 `.uvprojx`**(XML 结构脆弱),且引入约 0.3 KB Flash 的 HAL 封装;`.ioc` 与代码脱节 |
| C. 直接寄存器操作(初稿推荐,**已放弃**) | 用 CMSIS 已提供的 `IWDG` 实例指针 + 寄存器位域宏 | ✅ 零新增依赖、零工程文件改动、约 20 行;❌ 但**破坏了"外设配置统一由 CubeMX 表达"的约定**,后续维护者无处可查"这台设备的 IWDG 配了什么" |
**选 C 的依据**:CMSIS 设备头 `Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f407xx.h`
**已完整提供**所需的一切(已实测确认):
**选 A 的依据**:本项目的既有约定是"外设配置 = `.ioc`"(`AGENTS.md`「平台约束」:
`Inc/`/`Src/` 中 CubeMX 生成文件带 `USER CODE` 标记,自定义代码只写标记区内)。
IWDG 若用裸寄存器,则分频/重载值只散落在 `App/util/task_watchdog.c` 的宏里,
与 `.ioc` 无关联——下一个维护者改超时值时**不会想到去改代码宏**。
路径 A 让该配置出现在 CubeMX 界面里,符合项目惯例。
**CubeMX 侧配置值**(在 `.ioc` / CubeMX 图形界面填写):
| 项 | 值 | 说明 |
|---|---|---|
| Activated | ✅ 勾选 | Peripheral 树 → IWDG |
| IWDG counter clock prescaler | **64** | 对应 `IWDG_PRESCALER_64`,即 `PR=4` |
| IWDG down-counter reload value | **1249** | 12 位,范围 0~4095 |
| Window value | —— | ⚠️ **本 HAL 版本 `IWDG_InitTypeDef` 无此字段**(仅 `Prescaler` / `Reload`),CubeMX 界面亦无该项;窗口寄存器保持复位默认 `0xFFF` = 禁用窗口,符合预期 |
生成结果:`Src/iwdg.c` / `Inc/iwdg.h`(新文件)、`Drivers/STM32F4xx_HAL_Driver/{Inc,Src}/stm32f4xx_hal_iwdg.{h,c}`、
`Inc/stm32f4xx_hal_conf.h` 中 `HAL_IWDG_MODULE_ENABLED` 解除注释、`.uvprojx` 挂载驱动、
`Src/main.c` 的 `MX_*_Init()` 序列新增 `MX_IWDG_Init()`。
> ⚠️ **路径 A 的固有陷阱(本次实测踩中,P0)**:CubeMX 把 `MX_IWDG_Init()` 插在
> `app_main_init()` **之前**,而 `HAL_IWDG_Init()` 内部第一句即
> `__HAL_IWDG_START(hiwdg)`(立即启动、之后不可停)。启动段(NAND 初始化等)耗时
> 可达数秒且无喂狗点 → 按 2.5s 超时必然复位 → **开机死循环**。
> **CubeMX 界面无法关闭这个自动启动**(HAL 行为,无对应选项)。
> 解决办法见 §4.5 决策 C:把 `Src/iwdg.c` 里那一行 `HAL_IWDG_Init(&hiwdg)` 换成
> 只填参数的 USER CODE 注释块,启动动作移交 `task_wd_start()`。
> 这是路径 A 下**唯一必须手工修改生成文件**的地方,已在文件内留注释说明恢复方法。
**参考:CMSIS 设备头已提供的寄存器定义**(路径 C 曾依赖,现仅作 `task_wd_beat`/`task_wd_poll`
里裸写 `IWDG->KR` 的依据):
| 已有定义 | 位置 | 内容 |
|---|---|---|
| `IWDG_TypeDef` | `:573-579` | `KR` / `PR` / `RLR` / `SR` 四寄存器结构体 |
| `IWDG_TypeDef` | `stm32f407xx.h:573-579` | `KR` / `PR` / `RLR` / `SR` 四寄存器结构体 |
| `IWDG_BASE` | `:944` | `APB1PERIPH_BASE + 0x3000` |
| `IWDG` 实例指针 | `:1068` | `((IWDG_TypeDef *) IWDG_BASE)` |
| `IWDG_KR_KEY_Msk` | `:9331` | 键值字段掩码 |
@@ -137,7 +172,7 @@ CubeMX 只在勾选该外设时才会把 `stm32f4xx_hal_iwdg.c/h` 拷进 `Driver
| `IWDG_RLR_RL_Msk` | `:9344` | 重装值字段(12 位) |
| `IWDG_SR_PVU` / `IWDG_SR_RVU` | `:9349-9353` | 更新进行中标志(须轮询等它清零) |
**键值常量**(上游 HAL 在 `stm32f4xx_hal_iwdg.h` 定义,本方案需自行定义):
**键值常量**(由 HAL 定义在 `stm32f4xx_hal_iwdg.h`,本方案在 `task_watchdog.c` 内裸写):
| 用途 | 值 |
|---|---|
@@ -149,9 +184,10 @@ CubeMX 只在勾选该外设时才会把 `stm32f4xx_hal_iwdg.c/h` 拷进 `Driver
IWDG 是**"写键值"式外设**:写 `0x5555` 解锁 → 配 `PR`/`RLR` → 写 `0xCCCC` 启动
(启动后**不可停**,只能复位)→ 此后周期性写 `0xAAAA` 喂狗。
> **为何这会被称为"更优路径"**:HAL 的 `HAL_IWDG_Init()` 内部做的事就是上面这几步,
> 外加一个 `IWDG_HandleTypeDef` 状态机。本项目不需要那层封装——寄存器操作总共
> 约 20 行,且完全落在 `App/util/task_watchdog.c` 内,**不触碰任何 CubeMX 生成文件**。
> **喂狗为何仍走裸寄存器而非 `HAL_IWDG_Refresh()`**:喂狗是 100 ms 级高频操作,
> HAL 封装带 `IWDG_HandleTypeDef` 有效性检查(读 `State` 字段、判 `LOCKED`),
> 在高频路径上无收益。`Task_wd_poll()` 里写 `IWDG->KR = 0xAAAA` 是 1 条存储指令。
> 这不是"绕过 HAL",而是"HAL 提供了该寄存器定义,直接用"——与路径选择无关。
---
@@ -558,22 +594,24 @@ void task_wd_poll(void)
> 丢失"是看门狗抓到的"这一关键信息。**让 IWDG 自然溢出**,
> 复位原因才是权威的 `watchdog reset`。代价是额外的 ≤2.5 s 延迟(可接受)。
### 5.4 各任务的埋点位置
### 5.4 各任务的埋点位置(**2026-09-29 已全部落地**)
| 任务 | 埋点位置 | 备注 |
| 任务 | 实际埋点位置 | 备注 |
|---|---|---|
| `defaultTask` | `default_task_func` 的 `for(;;)` 循环首行 | 50 ms 节拍,天然满足 |
| `netTask` | 主循环 `for(;;)` 内(`net_poll` 之后) | 1 ms 节拍;另需在 `net_stack_bring_up` 的 PHY 等待循环内补埋点(§4.4) |
| `ftpTask` | ⚠️ 见 §4.3 —— 第一步**剔除掩码**;后续改 `Lib/lftpd` 时再补 | 已知缺口 |
| `uiTask` | `ui_task_func` 主循环(`osDelay(10)` 处) | 10 ms 节拍 |
| `timeSyncTask` | `time_sync_task` 主循环 | 最长 `vTaskDelay(2000)`——⚠️ **2 s > 800 ms 容忍窗口**,需拆分:在 `vTaskDelay` 前多次分段置位,或把该 timeout 缩短 |
| `testTask` | 由套件掩码决定 | 套件运行时可长阻塞,按 §4.1 排除 |
| `defaultTask` | `App/task/default_task.c`:`for(;;)` 首行 `task_wd_poll()` + `task_wd_beat(TASK_WD_DEFAULT)` | 50 ms 节拍,天然满足;同时是唯一喂狗者 |
| `netTask` | `App/task/net_task.c`:① 主循环 `for(;;)` 首行;② `net_stack_bring_up` 的 PHY 等待循环内 `osDelay(50)` 前;③ `ENABLE_HW_LAYER_TESTS` 自挂起分支(掩码已剔除,埋点无副作用) | ② 为 §4.4 白名单项:PHY 最长等待 3 s(60×50 ms),必须在其中补心跳 |
| `ftpTask` | ⚠️ **首期不埋点**——由 `wd_required_mask()` **剔除**(见 §4.3) | 已知缺口:`Lib/lftpd` 事件循环改动面大,二期再补 |
| `uiTask` | `App/task/ui_task.c`:`ui_port_step(10)` / `ui_net_sink_poll()` 之后 | 10 ms 节拍;等待 NET_READY 期间不喂属**预期**(该段卡死即应复位) |
| `timeSyncTask` | `App/sync/time_sync.c`:① `time_sync_task` 主循环首行;② 监听重试的 500 ms / 2 s 延时前;③ accept 轮询 200 ms 延时前;④ 收尾 50 ms 延时前;⑤ **`time_sync_handle_conn` 内的等待循环**(关键:把 30 s 总预算切成 `TIME_SYNC_WAIT_SLICE_MS`=200 ms 的片,每片先喂心跳) | ⑤ 是本案最要害的一处:对端连上但不发时间包是完全正常的场景,若不切片,30 s 等待必然被判卡死 |
| `testTask` | 不埋点——由套件掩码决定,按 §4.1 排除 | 套件运行时可长阻塞 |
> ⚠️ **`timeSyncTask` 是关键风险点**:其主循环有两处 `vTaskDelay(pdMS_TO_TICKS(2000))`
> (`App/sync/time_sync.c:68/75`)。若心跳位只在循环首行,则 2 s 内位图恒缺席,
> **必然触发停喂**。处理三选一:① 每 200 ms 分段置位;② 缩短该 delay;
> ③ 该任务单独用"宽松窗口"(增大容忍次数到 25 次 = 2.5 s,但会逼近 IWDG 超时,不推荐)。
> **推荐 ①**:把 2 s 拆成 10 × 200 ms 循环,每次置位并检查退出条件——语义不变,且提升响应性。
> ⚠️ **实施时的新发现(补充 §4.4 白名单)**:`timeSyncTask` 的风险**不止**
> 主循环里那两处 `vTaskDelay(2000)`——`time_sync_handle_conn` 内部的
> `net_recv_to(..., rem)` 单次可睡满 **30 s 总预算**。初稿只注意到 `vTaskDelay`
> 的两处,会漏掉这处。处理方式取"**切片**"而非"放宽窗口":
> 新增 `TIME_SYNC_WAIT_SLICE_MS = 200U`,把单次等待的 `rem` 夹到 ≤200 ms,
> 每片先 `task_wd_beat()`。总预算语义(≤30 s)完全不变,只是把一次长睡眠
> 拆成多次短睡眠——同时提升了"对端提前到达"的响应粒度。
---
@@ -581,26 +619,41 @@ void task_wd_poll(void)
### 6.1 串口标记(供 `@test` 采集)
**实际实现**(2026-09-29 落地,`App/app/app_main.c` + `App/util/task_watchdog.c` +
`Src/freertos.c`):
| 标记 | 时机 | 含义 |
|---|---|---|
| `[WD] reset cause=<类型>` | `app_main_init` 早期 | 复位原因(`power on` / `watchdog reset` / `soft reset` / `pin reset`) |
| `[WD] watchdog armed (timeout=2500ms)` | `task_wd_start()` | IWDG 已启动(真机自检必查标记) |
| `[WD] miss streak=<n> mask=0x%08X` | 每次未达标(限流打印) | 谁没动(`mask` 中 0 位 = 缺失任务) |
| `[ERR] WATCHDOG: task stall` | 停喂时 | 即将复位,最后遗言 |
| `[WD] beat ok` | 可选,调试期 | 心跳正常(默认关闭,避免刷屏) |
| `[APP] reset cause: <类型> (CSR=0x%08X)` | `app_main_init` 早期 | 复位原因,类型为 `IWDG (independent watchdog)` / `WWDG (window watchdog)` / `software (NVIC_SystemReset)` / `power-on (POR)` / `external pin (NRST)` / `brown-out (BOR)` / `unknown`;同时打印 `RCC_CSR` 原值便于事后核对 |
| `[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms` | `task_wd_start()` | IWDG 已启动(真机自检必查标记) |
| `[WD] task stall: mask=0x%08X req=0x%08X miss=%lu` | 连续 8 窗未达标时(每窗一次,共打印至复位) | 即将停喂 → 约 2.5 s 后 IWDG 复位。`mask` 的 0 位 = 缺席任务 |
| `[ERR] ASSERT FAILED <file>:<line> (resetting)` | `vApplicationAssertFailed()` | FreeRTOS 断言失败(`Src/freertos.c`),打印后主动复位 |
| `[ERR] STACK OVERFLOW task=<name>` | `vApplicationStackOverflowHook` | 既有钩子(栈溢出) |
| `[ERR] MALLOC FAILED (heap exhausted)` | `vApplicationMallocFailedHook` | 既有钩子(堆耗尽,只告警不复位) |
> 日志标签说明:`[APP]` 是 `App/app/app_main.c` 的 `DBG_TAG`;`[WD]` 是
> `App/util/task_watchdog.c` 的 `DBG_TAG`(定义在两个 include 之间,
> 是 `dbg_log.h` 的既有取标签方式)。全部为纯 ASCII,规避 `AGENTS.md` 记载的
> ARMCC CP936 字符串字面量陷阱。
### 6.2 与 `run.log` 的集成
`sys_log_mcu.c` 的 `syslog_mcu_mounted()` 当前只写 `power on` / `reboot`
(靠 `run.log` 末条 epoch 时间差推断)。**改为优先用硬件标志**:
(靠 `run.log` 末条 epoch 时间差推断)。**现状:未改**——本次实施选择
"先只在串口上报 `reset cause`",理由:
1. 串口标记已经能支撑 `@test` 判定与人工归因,`run.log` 的价值在于**长期累计**;
2. 写 `run.log` 涉及 UI 字符集("看门狗"三字是否在位)与既有 600 s 时间窗分类逻辑,
改动面比串口上报大,**留作二期**(`待处理内容.md` N-03 后续项)。
**二期计划**(复位原因映射表,优先级见 §4.8):
```
复位原因(§4.8 优先级)→ 描述串映射:
power on → "power on" (既有)
watchdog reset → "watchdog" (新增,UI 字符集需补"看门狗"三字)
soft reset → "soft reset" (新增)
pin reset → "pin reset" (新增)
其余 → 回退既有 600s 时间窗分类
power-on (POR) → "power on" (既有)
IWDG / WWDG → "watchdog" (新增,UI 字符集需补"看门狗"三字)
software → "soft reset" (新增)
external pin → "pin reset" (新增)
其余 → 回退既有 600s 时间窗分类
```
> ⚠️ **UI 字符集影响**:`docs/子系统设计/运行与自检记录设计说明.md` §6.1 记录了
@@ -685,24 +738,42 @@ void task_wd_poll(void)
## 10. 实施步骤(按序执行,每步端到端验证)
| 步 | 内容 | 验证 |
|---|---|---|
| **1** | 新建 `App/util/task_watchdog.c/h`;实现 IWDG 寄存器级 `wd_iwdg_init/feed` + 心跳位图;**登记进 MDK 工程** | `@build` 0/0 |
| **2** | `Src/main.c` 复位原因锁存 + 清标志;`syslog_mcu.c` 写描述串 | V7/V8(`cause` 正确) |
| **3** | 实现抽样判定 + 串口标记(**暂不启动 IWDG**) | V1 观察 `[WD] beat ok`(调试期) |
| **4** | 各任务埋点(含 `timeSyncTask` 拆分) | V5 短跑(5 min)无 `miss` 打印 |
| **5** | `task_wd_start()` 启动 IWDG(写 `0xCCCC`) | V1 + V5 |
| **6** | `configASSERT` 改造 + `vApplicationAssertFailed()` | V3 |
| **7** | 故障注入(V2/V4) | 复位 + `cause` 正确 |
| **8** | 模拟器 `--watchdogtest` + stub | S1 |
| **9** | 长跑(V5,≥30 min,真实负载) | **零复位** |
| **10** | 本文档改写为"现状设计说明";`待处理内容.md` N-03 销项;`README.md` 登记 | 门禁 |
| 步 | 内容 | 验证 | 状态 |
|---|---|---|---|
| **0** | **(新增)** CubeMX 勾选 IWDG(Prescaler=64 / Reload=1249)→ 重生成 → **恢复被覆盖的定制** | `@build` 0/0 + 启动日志齐全 | ✅ **2026-09-29 完成**,覆盖 4 处见下 |
| **1** | 新建 `App/util/task_watchdog.c/h`;实现心跳位图 + 抽样判定 + IWDG 延后启动;**登记进 MDK 工程**(`App` 组,`sys_clock.c` 之后) | `@build` 0/0 | ✅ 完成 |
| **2** | `Src/main.c` 复位原因锁存 + 清标志;`App/app/app_main.c` 打印描述串 | `[APP] reset cause:` 出现 | ✅ 完成(`run.log` 写入留二期) |
| **3** | `Src/iwdg.c` 阻止 `HAL_IWDG_Init()` 自动启动(P0) | 开机不死循环 | ✅ 完成 |
| **4** | 各任务埋点(含 `timeSyncTask` **切片**而非拆 delay) | 编译 0/0;串口无 `miss` | ✅ 完成 |
| **5** | `task_wd_start()` 由 `defaultTask` 在 bring-up 完成后调用 | `[WD] armed:` 出现 | ✅ 完成 |
| **6** | `configASSERT` 改造 + `vApplicationAssertFailed()` | 编译 0/0 | ✅ 完成 |
| **7** | 故障注入(V2/V4):人为制造任务卡死 | 复位 + `reset cause: IWDG` | ⏳ **待台架** |
| **8** | 模拟器 `--watchdogtest` + stub | S1 | ⏳ 待做 |
| **9** | 长跑(V5,≥30 min,真实负载) | **零复位** | ⏳ **待台架** |
| **10** | 本文档改写为"现状设计说明";`待处理内容.md` N-03 销项;`README.md` 登记 | 门禁 | ⏳ 待第 7~9 步结论 |
> **每步改完必须 `@build` 0 错误 0 警告**(`AGENTS.md` 铁律)。
>
> **第 1 步的风险点**:`App/util/task_watchdog.c` 需在 `MDK-ARM/*.uvprojx` 中挂载。
> 若挂载不顺,可临时把实现放进 `App/task/default_task.c`(已在工程内)验证逻辑,
> 待工程文件可改时再抽成独立模块。**两种形态的逻辑完全一致**。
### 10.1 第 0 步实测:CubeMX 重生成覆盖了哪 4 处
`.ioc` 的 `ProjectManager.DeletePrevious=true` + `KeepUserCode=true` 决定了
**只有 `USER CODE BEGIN/END` 标记区内的代码受保护**。实测被覆盖的定制:
| 级别 | 文件 | 被覆盖内容 | 恢复方式 |
|---|---|---|---|
| **P0** | `Src/iwdg.c`(新文件) | CubeMX 生成的 `MX_IWDG_Init()` 直接调 `HAL_IWDG_Init()` → 开机死循环 | 改为只填参数;插 `USER CODE BEGIN IWDG_DEFER_START` 注释块说明(**唯一必需的手工改动**) |
| **P1** | `Src/stm32f4xx_it.c` | `HardFault_Handler` 崩溃现场采集(91 行:PC/LR/xPSR/R0-R3/CFSR/HFSR/BFAR/MMFAR)被打回空 `while(1)` | 整块换回手工版(`__asm void` 汇编取 MSP/PSP + `hard_fault_report`) |
| **P1** | `Inc/FreeRTOSConfig.h` | `configCHECK_FOR_STACK_OVERFLOW 2` + `configUSE_MALLOC_FAILED_HOOK 1` **整个块被删除** | 插回 `USER CODE BEGIN FREE_RTOS_CONFIG_HARDENING` 块 |
| **P2** | `Src/freertos.c` | 三处中文注释丢失(任务栈大小的实测记录) | 恢复注释(栈**数值**未丢,`.ioc` 同步到位) |
> ⚠️ **P1 两处的教训**:它们都在 `USER CODE` 标记区**之外**(`stm32f4xx_it.c` 的
> `HardFault_Handler` 由 CubeMX 直接生成;`FreeRTOSConfig.h` 的加固宏原本写在
> 非 `USER CODE` 区)。**P1 的丢失不会立刻暴露**——栈溢出检测与堆失败钩子只在
> 真发生溢出/耗尽时才起作用,且 `HardFault_Handler` 被打回空死循环后
> **崩溃时连现场都打不出来**(只剩 IWDG 兜底复位,但归因信息全丢)。
> 故本次已把这三个宏搬进 `USER CODE BEGIN 1` 区,并加注释
> "⚠ 本段为手工添加,CubeMX 重生成会删除(2026-09-29 实测),改完须核对本块存在"。
> **每步改完必须 `@build` 0 错误 0 警告**(`AGENTS.md` 铁律)。第 0~6 步已一次性
> 验证通过:`BUILD_OK: 0 errors, 0 warnings`。
---
@@ -711,7 +782,7 @@ void task_wd_poll(void)
| 议题 | 结论 |
|---|---|
| **选型** | **IWDG**(独立 LSI),非 WWDG(依赖 APB1 + 中断,在关中断/HardFault 场景失效) |
| **实施路径** | **直接寄存器操作**(不引入 HAL IWDG 驱动)——驱动源码不在仓库、也未经由 CubeMX 生成,CMSIS 设备头已提供全部寄存器定义(§2.2) |
| **实施路径** | **CubeMX 勾选 IWDG 生成**(`.ioc` = 配置单一事实源,与其他外设一致);唯一手工改动 = `Src/iwdg.c` 中阻止 `HAL_IWDG_Init()` 立即启动(§2.2 / §4.5) |
| **架构** | **任务心跳位图(各自置位,无锁单字)+ defaultTask 单一喂狗者**——避免"有人跑就喂狗"的假保护 |
| **超时** | 2498 ms(PR=4 / RLR=1249);最坏(LSI 47 kHz)≈1701 ms;中间留 800 ms 停喂判定裕度 |
| **启动时机** | **defaultTask bring-up 完成后**——绕开初始化深链超时陷阱,代价是启动段无保护 |
@@ -768,4 +839,5 @@ void task_wd_poll(void)
| 日期 | 变更 | 说明 |
|---|---|---|
| 2026-09-29 | 创建 | 调研 + 设计方案:IWDG + 任务心跳位图;含选型、九项设计决策、验证计划与风险清单 |
| 2026-09-29 | 修正实施路径 | 实测发现 HAL IWDG 驱动源码不在仓库且未编入 MDK 工程 → 改为**直接寄存器操作**(§2.2 路径 C);超时数值按公式核算修正;补 `RCC_CSR`/`IWDG` 寄存器定义的实测行号 |
| 2026-09-29 | 修正实施路径 | 实测发现 HAL IWDG 驱动源码不在仓库且未编入 MDK 工程 → 初稿改为**直接寄存器操作**(§2.2 路径 C);超时数值按公式核算修正;补 `RCC_CSR`/`IWDG` 寄存器定义的实测行号 |
| 2026-09-29 | **路径再变更 + 实施落地** | 决策改走 **CubeMX 图形配置**(§2.2 路径 A):`HAL_IWDG_MODULE_ENABLED` 改为经 CubeMX 生成(避免"取消注释即编译失败")。同时落地 §5 骨架到 `App/util/task_watchdog.{c,h}`,各任务埋点(default/net/ui/time_sync),`RCC_CSR` 复位原因锁存(`Src/main.c` + `App/app/app_main.c`),`configASSERT` 改为 `vApplicationAssertFailed()`。实测 CubeMX 重生成覆盖 4 处既有定制(P0×1 + P1×2 + P2×1),已全部恢复,`@build` 0 错误 0 警告 |
+4 -3
View File
@@ -30,6 +30,7 @@
| # | 事项 | 说明 | 出处 |
|---|---|---|---|
| N-03 | **引入独立看门狗(IWDG)+ 任务心跳位图**——现状全库无看门狗(HAL 驱动源码也不在仓库),任何形态死机不可自恢复;另 `configASSERT` 为关中断死循环(连日志都不打)。**设计方案已完成**(含选型、九项决策、验证计划、风险清单),待实施 | **设计见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md)**;实施路径为直接寄存器操作(不引入 HAL IWDG 驱动);实施后本文档改写为现状设计说明并销项 | `审查记录/项目审查与优化建议报告.md` N-03a/b/c |
| N-03a | `configASSERT(x)` = `taskDISABLE_INTERRUPTS(); for(;;);`(`Inc/FreeRTOSConfig.h:163`)→ 断言时无任何输出 | 修复方案见 `看门狗设计说明.md` §4.6(改为"先记录后复位");N-03 实施时一并处理 | 同上 N-03b |
| N-03b | 启动段(`app_main_init` + `defaultTask` bring-up)在新方案下**无看门狗保护** | 显式代价:`看门狗设计说明.md` §4.5 决策 C + §9 R1;该段死机表现为"无输出 + LED 不闪" | 同上 N-03c |
| N-03 | **引入独立看门狗(IWDG)+ 任务心跳位图**——现状全库无看门狗(HAL 驱动源码也不在仓库),任何形态死机不可自恢复;另 `configASSERT` 为关中断死循环(连日志都不打)。**代码已落地并编译通过(2026-09-29)**:CubeMX 生成 IWDG(Prescaler=64/Reload=1249)+ `App/util/task_watchdog.{c,h}` 心跳位图 + 6 任务埋点 + `RCC_CSR` 复位原因锁存 + `configASSERT` 改造 | **剩台架验证**(故障注入 V2/V4、长跑 V5 ≥30 min);文档 §10 第 7~10 步待办 | **见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md)**(§10 有实施状态表) | `审查记录/项目审查与优化建议报告.md` N-03a/b/c |
| N-03a | `configASSERT(x)` = `taskDISABLE_INTERRUPTS(); for(;;);`(`Inc/FreeRTOSConfig.h`)→ 断言时无任何输出 | ✅ **已修(2026-09-29)**:改为 `vApplicationAssertFailed(__FILE__, __LINE__)`,打印文件名+行号后主动复位(`Src/freertos.c`);宏已迁入 `USER CODE BEGIN 1` 区防重生成丢失 | 同上 N-03b |
| N-03b | 启动段(`app_main_init` + `defaultTask` bring-up)在新方案下**无看门狗保护** | 显式代价(未变):`看门狗设计说明.md` §4.5 决策 C + §9 R1;该段死机表现为"无输出 + LED 不闪" | 同上 N-03c |
| N-03c | **CubeMX 重生成会覆盖 `USER CODE` 标记区之外的定制** | ⚠️ **2026-09-29 实测确认**:重生成覆盖 4 处(P0 `Src/iwdg.c` 自动启动、P1 `HardFault_Handler` 打回空死循环、P1 `FreeRTOSConfig.h` 两个加固宏被删、P2 `freertos.c` 中文注释);已全部恢复并加"重生成会删除"警示注释;**今后每次 CubeMX 重生成后须重跑本文档 §10.1 的核对清单** | 本次实施记录 |