0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
251 lines
12 KiB
C
251 lines
12 KiB
C
/*
|
||
* 模块名称:Task Watchdog(IWDG + 任务心跳位图)
|
||
* 模块功能:独立看门狗与"全部任务存活"绑定的对外接口
|
||
* 适用平台:STM32F407ZGTx + FreeRTOS(CMSIS-RTOS V2)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-29
|
||
* 修改记录:
|
||
* v1.0 2026-09-29 王建锋 创建
|
||
*
|
||
* 设计依据:docs/子系统设计/看门狗设计说明.md
|
||
*/
|
||
|
||
#ifndef TASK_WATCHDOG_H
|
||
#define TASK_WATCHDOG_H
|
||
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/*
|
||
* 任务 ID(位序号 = 心跳位图上的位置,与必需掩码一一对应)
|
||
* 顺序一旦确定不要插空/改动,掩码是编译期常量
|
||
*/
|
||
typedef enum {
|
||
TASK_WD_DEFAULT = 0,
|
||
TASK_WD_NET = 1,
|
||
TASK_WD_FTP = 2,
|
||
TASK_WD_TEST = 3,
|
||
TASK_WD_UI = 4,
|
||
TASK_WD_TIMESYNC = 5,
|
||
TASK_WD_ADC = 6, /* adcTask:1ms 采样节拍消费者(B1,2026-10-10) */
|
||
TASK_WD_IEC104 = 7, /* iec104Task:104 上行服务(B4,2026-10-12) */
|
||
TASK_WD_PROTECT = 8, /* protectTask:保护判据节拍(B5,2026-10-12) */
|
||
TASK_WD_ID_MAX
|
||
} task_wd_id_t;
|
||
|
||
/*
|
||
* 函数功能:任务心跳置位(各任务在自己主循环里调用)
|
||
* 入口参数:id - 任务 ID
|
||
* 返回值:无
|
||
* 限定条件:任意上下文可调(单字原子写、无内核对象依赖)
|
||
* 函数说明:仅 1~2 条指令,无锁、无阻塞,可放在中断外任意位置。
|
||
*/
|
||
void task_wd_beat(task_wd_id_t id);
|
||
|
||
/*
|
||
* 函数功能:把当前任务句柄登记进 FTL 心跳路由表(任务入口调用一次)
|
||
* 入口参数:id - 任务 ID
|
||
* 返回值:无
|
||
* 限定条件:任务上下文调用(内核启动后)
|
||
* 函数说明:nand_ftl_wd_beat() 强符号(本模块)按此表把 FTL 长耗时操作
|
||
* 的心跳路由到调用者任务的心跳位(见 gd5f2gq5ue.h 钩子说明)。
|
||
* testTask 刻意不登记:它不在必需掩码内(抽样为精确匹配),
|
||
* 其上下文置位会引入多余位反而误杀。
|
||
*/
|
||
void task_wd_bind(task_wd_id_t id);
|
||
|
||
/*
|
||
* 函数功能:启动 IWDG(**延后启动**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:仅 defaultTask 在存储 bring-up 完成后调用一次
|
||
* 函数说明:MX_IWDG_Init() 只填参数不启动(原因见 Src/iwdg.c 的
|
||
* IWDG_DEFER_START 注释:HAL_IWDG_Init 内部会立即启动,而它被
|
||
* CubeMX 插在 app_main_init 之前,那段无喂狗点且耗时可达数秒)。
|
||
* 本函数才真正启动,使"启动段不喂狗、主循环段才喂"成立。
|
||
*/
|
||
void task_wd_start(void);
|
||
|
||
/*
|
||
* 函数功能:心跳抽样与喂狗判定
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:仅 defaultTask 主循环调用(唯一喂狗者)
|
||
* 函数说明:内部节流至 100ms 一次。全员到位才喂狗;连续 800ms 有任务
|
||
* 缺席则停止喂狗,由 IWDG 自然溢出复位(保留 IWDGRSTF 权威归因)。
|
||
*/
|
||
void task_wd_poll(void);
|
||
|
||
/* 观测接口(自检 / 串口报告用) */
|
||
uint32_t task_wd_last_mask(void); /* 上次抽样到的位图(0 位 = 缺席任务) */
|
||
uint32_t task_wd_miss_count(void); /* 累计未达标轮数 */
|
||
uint32_t task_wd_is_armed(void); /* 1 = IWDG 已启动 */
|
||
|
||
/*
|
||
* ==================== 第 8 轮取证:复位不丢的"进段打点" ====================
|
||
* 背景(2026-10-02 台架第 7~8 轮取证,完整链条见
|
||
* docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录):误复位时判定分支(STALL)从未
|
||
* 执行,当时判为**喂狗点 task_wd_poll 自身停摆**(第 8b 轮已更正:真因是 IWDG 超时
|
||
* 被静默配成复位默认值),而非某个被监视任务饿死。RAM 态随复位消失、串口又可能来不及打,
|
||
* 故需要一条**复位后仍可读**的"defaultTask 主循环最后走到哪一段"轨迹。
|
||
*
|
||
* 实现:轨迹槽放在 .noinit(scatter 的 UNINIT 执行区,见
|
||
* MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct 的 UNINIT_CCM)——IWDG/引脚复位
|
||
* 不改变 SRAM 内容,故复位后仍能读出上一轮的最后进段。槽布局:
|
||
* [0]=magic(校验是否为本模块写过)[1]=段号 [2]=进段时刻(ms) [3]=循环序号
|
||
* 第 8b 轮追加"时间源存活性"槽(同样在 .noinit):
|
||
* cur = [magic, uwTick, 本轮 TIM7 计数, 0](TIM7 ISR 每 1ms 更新)
|
||
* prev = [magic, uwTick, 上一轮 TIM7 计数, 0](main() 在 HAL_Init 之前快照)
|
||
* 注意:**仅 MCU 构建存在**(TASK_WATCHDOG_UNIT_TEST 下不编入,主机单测不受影响)。
|
||
* ==========================================================================
|
||
*/
|
||
|
||
/* defaultTask 主循环"进段"编号(task_wd_seg 的入参;复位后从 PREVRUN 行读出) */
|
||
typedef enum {
|
||
TASK_WD_SEG_IDLE = 0, /* 未进入任何段(初值) */
|
||
TASK_WD_SEG_POLL = 1, /* task_wd_poll(喂狗判定) */
|
||
TASK_WD_SEG_EVTWAIT = 2, /* osEventFlagsWait(50ms 兜底等待) */
|
||
TASK_WD_SEG_DRAIN = 3, /* dbg_console_drain(控制台排空) */
|
||
TASK_WD_SEG_CFGSAVE = 4, /* net_cfg_save_run(配置保存) */
|
||
TASK_WD_SEG_CFGPOLL = 5, /* cfg_file_poll(2s 配置轮询) */
|
||
TASK_WD_SEG_LED = 6, /* LED 心跳 */
|
||
TASK_WD_SEG_SYSLOG = 7, /* syslog_drain(运行期日志落盘) */
|
||
TASK_WD_SEG_STACK = 8 /* stack_probe_poll(栈水位观测) */
|
||
} task_wd_seg_t;
|
||
|
||
/*
|
||
* 函数功能:记录"即将进入的主循环段"(第 8 轮取证;**仅 MCU 构建**)
|
||
* 入口参数:seg - 段号(task_wd_seg_t)
|
||
* 返回值:无
|
||
* 限定条件:defaultTask 主循环调用(任意上下文亦可,纯 SRAM 写)
|
||
* 函数说明:4 字写(magic/段/时刻/序号),进 .noinit 槽;复位不丢。
|
||
*/
|
||
void task_wd_seg(uint32_t seg);
|
||
|
||
/*
|
||
* 函数功能:开机上报上一轮最后进段 + 时间源存活性(第 8 轮取证;**仅 MCU 构建**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:启动早期调用(内核启动前亦可,裸 UART 直发)
|
||
* 函数说明:magic 命中才打一行 `PREVRUN seg=<段> seq=<序号> t=<ms>`(裸 UART,
|
||
* 不依赖环形缓冲写者),随后清 magic(一次复位只报一次)。
|
||
* 第 8b 轮:紧接着打一行 `TICKRUN uw= n= duw=`——uw 为 TIM7 在**上一轮**
|
||
* 最后一次记下的 HAL tick、n 为上一轮 TIM7 中断总数、duw = uw - t
|
||
* (相对进段时刻的增量),据此判定 TIM7 是否在冻结同刻停摆。
|
||
*/
|
||
void task_wd_seg_report(void);
|
||
|
||
/*
|
||
* 函数功能:开机快照上一轮的"时间源存活性"(第 8b 轮取证;**仅 MCU 构建**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:**必须在 HAL_Init() 之前调用**(main() 的 USER CODE BEGIN 1)
|
||
* 函数说明:TIM7 在 HAL_Init 里即开始 1ms 中断,而开机上报要到 app_main_init
|
||
* (≈117ms)才跑;本函数把上一轮的终值先搬到 prev 槽再清空 cur,
|
||
* 否则终值会被本轮开机瞬间的 tick 覆盖(2026-10-02 台架实测踩坑)。
|
||
*/
|
||
void task_wd_tick_snapshot(void);
|
||
|
||
/*
|
||
* 函数功能:记录"时间源存活性"到复位不丢的 .noinit 槽(第 8b 轮取证;**仅 MCU 构建**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:TIM7 中断上下文调用(`HAL_TIM_PeriodElapsedCallback` 内,每 1ms)
|
||
* 函数说明:更新 cur 槽 = [magic, uwTick, 本轮 TIM7 中断计数, 0](纯 SRAM 写,无阻塞)。
|
||
* 与 task_wd_seg 的"进段打点"配合,复位后由 task_wd_seg_report() 上报。
|
||
* 背景(2026-10-02 台架第 8b 轮取证,见 docs/子系统设计/看门狗设计说明.md
|
||
* 2026-10-02 变更记录):HAL_UART_Transmit 的超时判据
|
||
* 依赖 uwTick,时间源停摆即死等——必须先把"TIM7 是否停摆"钉死再定向修复。
|
||
* ⚠ **本函数内严禁调用任何 FreeRTOS/CMSIS-RTOS API**:TIM7 在 HAL_Init
|
||
* 阶段即开始中断(远早于 xPortStartScheduler),此时 ISR 版内核 API 会触发
|
||
* port.c 的优先级断言并把系统卡死在 TIM7 ISR 内(2026-10-02 台架实测根因)。
|
||
*/
|
||
void task_wd_tick_probe(void);
|
||
|
||
/*
|
||
* ============================ 平台后端(内部) ============================
|
||
* 把判定状态机中的硬件访问收敛到三个薄钩子,使"取走清零 / 掩码判定 / 缺失
|
||
* 累计 / 节流"成为纯逻辑,可在主机侧用桩替换后离线穷举验证
|
||
* (见 test/host/watchdog/ 与 docs/测试说明/看门狗单元测试说明.md)。
|
||
*
|
||
* 与 App/cfg/cfg_file_store.h 的"共享核心 + 平台后端"同款做法:
|
||
* - 三者由 task_watchdog.c 以 __weak 提供 MCU 默认实现(读写 IWDG 寄存器 /
|
||
* 调 HAL_GetTick),**行为与不抽钩子时逐字一致**;
|
||
* - 主机侧单测提供同名**强符号**覆盖 → 不碰 MCU 代码即可观测/注入;
|
||
* - MCU 构建零行为变化。
|
||
*
|
||
* ⚠ 非模块对外接口,业务代码**不要**直接调用(由 task_wd_* 间接使用)。
|
||
* ========================================================================
|
||
*/
|
||
|
||
/*
|
||
* 函数功能:重装 IWDG 计数器(喂狗)
|
||
* 返回值:无
|
||
* 函数说明:MCU 默认实现 = `IWDG->KR = 0x0000AAAAu;`
|
||
* 主机侧单测覆盖此符号以记录"何时被喂"。
|
||
*/
|
||
void wd_platform_feed(void);
|
||
|
||
/*
|
||
* 函数功能:按设计参数配置并启动 IWDG(不可逆,启动后只能靠复位停止)
|
||
* 返回值:无
|
||
* 函数说明:MCU 默认实现 = 解锁 PR/RLR → 写 WD_IWDG_PRESCALER/WD_IWDG_RELOAD
|
||
* (/64 + 1249 → 1701~2498ms,等 SR.PVU/RVU 同步)→ `IWDG->KR = 0xCCCC`。
|
||
* ⚠ 配置**必须**在此完成:`Src/iwdg.c` 为实现延后启动,把生成的
|
||
* `HAL_IWDG_Init(&hiwdg)` 经宏改名成空实现(见该文件 `USER CODE BEGIN 0`),
|
||
* 而 `HAL_IWDG_Init()` 是 HAL 里唯一把 `hiwdg.Init.*` 写进寄存器的路径;
|
||
* 缺了这一步 PR/RLR 停在复位默认值(PR=0/RLR=4095 ≈ 400ms),
|
||
* 超时比设计短约 5 倍 → 800ms 容忍分支成死代码、易 IWDG 误复位。
|
||
* 主机侧单测以强符号覆盖本钩子(不碰寄存器),故其行为不受影响。
|
||
*/
|
||
void wd_platform_start(void);
|
||
|
||
/*
|
||
* 函数功能:读单调毫秒时基(用于 100ms 抽样节流)
|
||
* 返回值:自启动以来的毫秒计数(32 位回绕安全,用无符号差比较)
|
||
* 函数说明:MCU 默认实现 = `HAL_GetTick()`
|
||
* 主机侧单测覆盖此符号以驱动时间推进。
|
||
*/
|
||
uint32_t wd_platform_tick_ms(void);
|
||
|
||
/*
|
||
* 函数功能:lftpd 看门狗心跳钩子(**本模块提供强符号实现**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:任意上下文(IWDG 启动前只置位图,不影响判定)
|
||
* 函数说明:驱动层(lftpd.c / lftpd_inet.c 的节拍点)只调用弱钩子,本模块以
|
||
* 强符号覆盖并路由到 TASK_WD_FTP 心跳位——驱动层不向上依赖应用层
|
||
* (分层铁律)。声明于 lftpd.h 供驱动侧引用,此处为"本模块提供"侧的声明。
|
||
*/
|
||
void lftpd_task_wd_beat(void);
|
||
|
||
/*
|
||
* 函数功能:NAND FTL 直接喂狗钩子(**本模块提供强符号实现**)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:任意上下文(IWDG 未启动时为空操作)
|
||
* 函数说明:FTL 长耗时路径(GC/块擦除等待)在调用者任务上下文**直接写 IWDG->KR**,
|
||
* 覆盖"喂狗点(defaultTask 主循环)自身被长 I/O 阻塞"的误复位形态。
|
||
* 声明于 gd5f2gq5ue.h 供驱动侧引用,此处为"本模块提供"侧的声明。
|
||
*/
|
||
void nand_ftl_wd_feed(void);
|
||
|
||
/*
|
||
* 函数功能:复位判定状态机(**仅主机侧单元测试使用**)
|
||
* 返回值:无
|
||
* 函数说明:仅当定义 TASK_WATCHDOG_UNIT_TEST 时编入(MCU 构建无此符号)。
|
||
* 用于在用例之间清空位图/计数/节流基线,保证用例互不污染。
|
||
*/
|
||
#ifdef TASK_WATCHDOG_UNIT_TEST
|
||
void task_wd_test_reset(void);
|
||
#endif
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* TASK_WATCHDOG_H */
|