Files
STM32F4-Base/App/util/task_watchdog.c
T
wandering1 7feeea6c56 实现独立看门狗: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。
2026-09-29 07:22:04 +08:00

202 lines
8.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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 对象,可安全用于内核启动前后的上下文。
*/
#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 已启动 */
/* 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 的时刻起算(避免启动后立即超时) */
IWDG->KR = 0x0000AAAAu; /* 重装计数器(喂狗) */
IWDG->KR = 0x0000CCCCu; /* 启动看门狗 */
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) {
static uint32_t s_last_tick;
uint32_t now;
uint32_t snapshot;
if (s_started == 0u) {
return; /* 未启动:不做任何判定 */
}
now = HAL_GetTick();
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;
IWDG->KR = 0x0000AAAAu; /* 全员健康:喂狗 */
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;
}