7feeea6c56
经 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。
202 lines
8.0 KiB
C
202 lines
8.0 KiB
C
/*
|
||
* 模块名称: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;
|
||
}
|