/* * 模块名称:Task Watchdog(IWDG + 任务心跳位图) * 模块功能:把独立看门狗(IWDG)与"全部任务存活"绑定,使单任务死锁/饿死/ * 断言挂死可自动复位恢复,而非人工断电。 * 核心语义:喂狗 = "所有被监视任务都在抽样窗口内动过"。 * 适用平台:STM32F407ZGTx + FreeRTOS(CMSIS-RTOS V2) * 作者:王建锋 * 创建日期:2026-09-29 * 修改记录: * v1.0 2026-09-29 王建锋 创建:心跳位图 + 抽样判定 + IWDG 延后启动 * * 设计依据:docs/子系统设计/看门狗设计说明.md * - §3.1 为何不用"每任务各自喂狗"(那只检测全系统同时挂死) * - §3.3 为何用"单字位图 + 取走清零"(免临界区,天然原子) * - §4.3 超时与窗口的取值推导(含 LSI 偏快 47kHz 的最坏情况校验) * - §4.5 为何由 defaultTask 在 bring-up 完成后启动 * - §5.3 为何"停喂"而不直接 NVIC_SystemReset(保留 IWDGRSTF 权威归因) * * ⚠ 本模块不创建任何 FreeRTOS/CMSIS-RTOS 对象,可安全用于内核启动前后的上下文。 * * === 可测性设计(2026-10-01)=== * 硬件访问(IWDG 寄存器读写 / HAL_GetTick)下沉到 task_watchdog.h 末段声明的 * 三个 __weak 钩子(wd_platform_feed / _start / _tick_ms);MCU 端默认实现见 * 文件末尾,行为与重构前逐字一致。判定状态机因此成为纯逻辑,主机侧单测 * (test/host/watchdog/)以强符号覆盖这三个钩子后即可离线穷举验证。 * 详见 docs/测试说明/看门狗单元测试说明.md。 */ #include "task_watchdog.h" #include "main.h" /* HAL_GetTick()(HAL 时基 = TIM7,1ms) */ #include "iwdg.h" /* hiwdg 参数来源(CubeMX 生成,见 Src/iwdg.c) */ #define DBG_TAG "[WD]" #include "dbg_log.h" /* Private defines -----------------------------------------------------------*/ /* 抽样周期:defaultTask 主循环为 50ms 基准,100ms 是其整数倍(见 §4.3) */ #define WD_POLL_PERIOD_MS 100u /* 连续未达标容忍次数:8 × 100ms = 800ms。 * 需容纳 NAND 擦写(最坏数百 ms)等长耗时操作,同时远小于 IWDG 最坏超时 * 1701ms,保证"停喂后仍能复上电"(§4.3 的包含关系) */ #define WD_MISS_TOLERANCE 8u /* IWDG 超时参数:PR=4 → 64 分频;RLR=1249 * 标称 64 × 1249 / 32000 ≈ 2498 ms * 最坏 64 × 1249 / 47000 ≈ 1701 ms(LSI 偏快时超时变短,按最坏校核) */ #define WD_IWDG_PRESCALER 4u #define WD_IWDG_RELOAD 1249u /* Private types -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ /* 心跳位图:各任务只置位,校验者"取走并清零"。 * 单字读-改-写在 Cortex-M4 上天然原子,无需临界区(§3.3) */ static volatile uint32_t s_hb_bits; static uint32_t s_miss_streak; /* 连续未达标轮数(达到容忍上限即停喂) */ static uint32_t s_miss_total; /* 累计未达标轮数(观测用) */ static uint32_t s_last_mask; /* 上次抽样到的位图(定位卡死任务用) */ static uint32_t s_started; /* 1 = IWDG 已启动 */ static uint32_t s_last_tick; /* 上次抽样时刻(100ms 节流基准) */ /* Private function prototypes -----------------------------------------------*/ static uint32_t wd_required_mask(void); /* Private functions ---------------------------------------------------------*/ /* * 函数功能:必需心跳位掩码(编译期按构建配置生成) * 入口参数:无 * 返回值:本次构建要求"必须动过"的任务位集合 * 限定条件:无 * 函数说明:按 §4.1/§4.2 的设计,监视范围随构建配置变化: * - testTask 仅在启用测试套件时创建 * - netTask 在 ENABLE_HW_LAYER_TESTS 下入口即自挂起(不干活), * 不要求其心跳,否则该构建永不喂狗 * 注:ftpTask 当前统一纳入(其主循环为 lftpd 事件循环,需在 * lftpd 侧埋点;若某构建下 ftpTask 不创建,句柄检查兜底)。 */ static uint32_t wd_required_mask(void) { uint32_t m = (1u << (uint32_t)TASK_WD_DEFAULT) | (1u << (uint32_t)TASK_WD_NET) | (1u << (uint32_t)TASK_WD_FTP) | (1u << (uint32_t)TASK_WD_UI) | (1u << (uint32_t)TASK_WD_TIMESYNC); /* netTask 自挂起(CH395F 由 testTask 独占):不要求心跳 */ #if defined(ENABLE_HW_LAYER_TESTS) m &= ~(1u << (uint32_t)TASK_WD_NET); #endif return m; } /* Public functions ----------------------------------------------------------*/ /* * 函数功能:任务心跳置位 * 入口参数:id - 任务 ID(见 task_watchdog.h) * 返回值:无 * 限定条件:可在任意上下文调用(单字写,无内核对象依赖) * 函数说明:各任务在自己的主循环里调用。仅 1~2 条指令。 */ void task_wd_beat(task_wd_id_t id) { if ((uint32_t)id < (uint32_t)TASK_WD_ID_MAX) { s_hb_bits |= (1u << (uint32_t)id); } } /* * 函数功能:启动 IWDG(延后启动,由 defaultTask 在 bring-up 完成后调用) * 入口参数:无 * 返回值:无 * 限定条件:仅 defaultTask 调用;须在 osKernelStart 之后(本函数只用 HAL/寄存器) * 函数说明:MX_IWDG_Init() 已把参数填入全局 hiwdg(Prescaler/Reload 由 CubeMX * 在 Src/iwdg.c 生成区维护),但不启动(见该文件 IWDG_DEFER_START 注释)。 * 此处才真正写 IWDG_Key 启动——使"启动段不喂狗"成立(§4.5)。 * IWDG 一旦启动不可停,只能靠复位终止。 */ void task_wd_start(void) { if (s_started != 0u) { return; } s_started = 1u; /* 启动前先喂一次:让首个抽样窗口从"启动时刻"起算, * 而非从 MX_IWDG_Init 的时刻起算(避免启动后立即超时) */ wd_platform_feed(); /* 重装计数器(喂狗) */ wd_platform_start(); /* 启动看门狗 */ DBG_INFO("[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms"); } /* * 函数功能:心跳抽样与喂狗判定(defaultTask 主循环调用) * 入口参数:无 * 返回值:无 * 限定条件:仅 defaultTask 主循环调用(唯一喂狗者);内部节流到 100ms 一次 * 函数说明:取走并清零位图 → 与必需掩码比较: * 全中 → 清零缺失计数并喂狗; * 有缺 → 累计;连续达到 WD_MISS_TOLERANCE 次后**停止喂狗**, * 让 IWDG 自然溢出复位(这样 RCC_CSR 记录的是权威的 * IWDGRSTF,而不是 NVIC_SystemReset 留下的 SFTRSTF —— §5.3)。 */ void task_wd_poll(void) { uint32_t now; uint32_t snapshot; if (s_started == 0u) { return; /* 未启动:不做任何判定 */ } now = wd_platform_tick_ms(); if ((now - s_last_tick) < WD_POLL_PERIOD_MS) { return; /* 节流:非 100ms 边界直接返回 */ } s_last_tick = now; snapshot = s_hb_bits; /* 读 */ s_hb_bits = 0u; /* 清:语义 = "自上次抽样以来谁动过" */ s_last_mask = snapshot; if (snapshot == wd_required_mask()) { s_miss_streak = 0u; wd_platform_feed(); /* 全员健康:喂狗 */ return; } /* 有任务缺席:累计(不喂狗) */ s_miss_total++; if (s_miss_streak < WD_MISS_TOLERANCE) { s_miss_streak++; } if (s_miss_streak >= WD_MISS_TOLERANCE) { /* 连续 800ms 有任务未动过 → 已停喂 → 约 2.5s 后 IWDG 复位。 * 最后遗言走裸 UART(不依赖内核、不遍历指针、有界长度)。 */ DBG_ERROR("[WD] task stall: mask=0x%08X req=0x%08X miss=%lu", (unsigned)snapshot, (unsigned)wd_required_mask(), (unsigned long)s_miss_total); } } /* * 函数功能:读上次抽样到的位图(观测/自检用) */ uint32_t task_wd_last_mask(void) { return s_last_mask; } /* * 函数功能:读累计未达标轮数(观测/自检用) */ uint32_t task_wd_miss_count(void) { return s_miss_total; } /* * 函数功能:查询 IWDG 是否已启动(观测/自检用) */ uint32_t task_wd_is_armed(void) { return (s_started != 0u) ? 1u : 0u; } /* Platform layer (MCU defaults, weak) ---------------------------------------*/ /* * 以下三个 __weak 为 MCU 端默认实现,行为与重构前逐字一致。 * 主机侧单测提供同名强符号覆盖(链接器优先取强符号)以实现离线可观测。 * 声明见 task_watchdog.h 末段"平台后端(内部)";详见 * docs/测试说明/看门狗单元测试说明.md。 * * 主机侧单测(同编译单元 #include 本文件)需回避这组默认定义与自身桩重名, * 故由 TASK_WATCHDOG_UNIT_TEST 守卫:该宏下不编入默认实现,仅留桩。 */ #ifndef TASK_WATCHDOG_UNIT_TEST /* * 函数功能:重装 IWDG 计数器(喂狗)——MCU 端默认实现 * 入口参数:无 * 返回值:无 * 函数说明:写 IWDG->KR = 0xAAAA。100ms 级高频路径,故不经 HAL 封装。 */ __weak void wd_platform_feed(void) { IWDG->KR = 0x0000AAAAu; /* 重装计数器(喂狗) */ } /* * 函数功能:启动 IWDG——MCU 端默认实现 * 入口参数:无 * 返回值:无 * 函数说明:写 IWDG->KR = 0xCCCC(不可逆,之后只能靠复位停止)。 */ __weak void wd_platform_start(void) { IWDG->KR = 0x0000CCCCu; /* 启动看门狗(不可逆) */ } /* * 函数功能:读单调毫秒时基——MCU 端默认实现 * 入口参数:无 * 返回值:HAL 时基毫秒计数(32 位回绕安全,调用方用无符号差比较) * 函数说明:HAL 时基 = TIM7(1ms),非 SysTick。 */ __weak uint32_t wd_platform_tick_ms(void) { return (uint32_t)HAL_GetTick(); } #endif /* !TASK_WATCHDOG_UNIT_TEST */ /* Test-only hooks (compiled out unless TASK_WATCHDOG_UNIT_TEST) --------------*/ /* * 函数功能:复位判定状态机到"刚上电"(仅主机侧单元测试使用) * 入口参数:无 * 返回值:无 * 限定条件:仅当定义 TASK_WATCHDOG_UNIT_TEST 时存在 * 函数说明:把 s_started / 位图 / 计数 / 节流基线全部清零,使各用例互相独立。 * MCU 构建不定义该宏 → 零代码、零符号。 */ #ifdef TASK_WATCHDOG_UNIT_TEST void task_wd_test_reset(void) { s_hb_bits = 0u; s_miss_streak = 0u; s_miss_total = 0u; s_last_mask = 0u; s_started = 0u; s_last_tick = 0u; } #endif