实现独立看门狗: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:
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 模块名称: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;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 模块名称: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_ID_MAX
|
||||
} task_wd_id_t;
|
||||
|
||||
/*
|
||||
* 函数功能:任务心跳置位(各任务在自己主循环里调用)
|
||||
* 入口参数:id - 任务 ID
|
||||
* 返回值:无
|
||||
* 限定条件:任意上下文可调(单字原子写、无内核对象依赖)
|
||||
* 函数说明:仅 1~2 条指令,无锁、无阻塞,可放在中断外任意位置。
|
||||
*/
|
||||
void task_wd_beat(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 已启动 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TASK_WATCHDOG_H */
|
||||
Reference in New Issue
Block a user