看门狗判定状态机增加主机侧单元测试(L0.5 层)
为 task_watchdog.c 的判定逻辑(位图取走清零 / 必需掩码 0x37 判定 / miss_streak 连续 8 次停喂 / 100ms 抽样节流 / tick 回绕安全)补一层 主机侧纯逻辑单元测试,弥补此前只能靠真机或模拟器才能发现回归的空白。 架构(沿用项目既有"共享核心 + 平台后端"做法,参照 cfg_file_store): - task_watchdog.h 末段新增"平台后端(内部)"声明 3 个钩子 (wd_platform_feed / _start / _tick_ms),MCU 端以 __weak 提供默认实现, 行为与重构前逐字一致(@build 0 错误 0 警告,Flash +20B)。 - 主机侧单测以同名强符号覆盖三钩子,即可离线注入/观测,无需碰 MCU 代码。 - 测试文件 #include 生产源文件本身,保证测的是真代码而非副本。 - 测试专用复位 task_wd_test_reset() 仅 TASK_WATCHDOG_UNIT_TEST 下编入, MCU 构建零代码零符号。 测试(test/host/watchdog/,MSVC 离线编译,纯标准库驱动): - 39 项断言全过(退出码 0);注入故障可复现退出码 1。 - run_unit_test.py 直接拼 INCLUDE/LIB 调 cl.exe,绕开本机被禁的 reg.exe。 - stub/ 三桩头覆盖 main.h / iwdg.h / dbg_log.h。 门禁:pre-commit 新增 watchdog-unit-test 本地钩子(L0.5 层),仅被测文件 或其测试变动时触发;测试开发标准流程.md 新增 L0.5 层说明。 顺带改善:task_watchdog.c 的节流基线 s_last_tick 由函数内 static 提为文件 static(可测性 + 模块状态归一)。
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
| `crc.c/h` | 校验算法(CRC16/CRC32 等,供 RS-485 协议与数据校验使用) |
|
||||
| `ringbuf.c/h` | 环形缓冲(SPSC 无锁环形队列,用于串口/数据流收发) |
|
||||
| `sys_clock.c/h` | **墙钟时间管理**:RTC 基准、tick 回绕防护、每 30 天惰性再基准化 |
|
||||
| `task_watchdog.c/h` | **独立看门狗(IWDG)判定状态机**:任务心跳位图 + defaultTask 单一喂狗者 |
|
||||
|
||||
## 设计要点
|
||||
|
||||
@@ -24,6 +25,16 @@
|
||||
- 单生产者单消费者(SPSC)无锁设计,配合 DMA/中断收发
|
||||
- 容量为 2 的幂,用掩码取模(避免除法开销)
|
||||
|
||||
### task_watchdog
|
||||
|
||||
- **任务心跳位图**:各任务只置位(`task_wd_beat`),校验者取走并清零(单字原子,无锁)
|
||||
- **单一喂狗者**:`defaultTask` 每 100 ms 抽样判定,仅当位图等于必需掩码时才喂狗
|
||||
- **硬件访问收敛**:`IWDG` 寄存器与 `HAL_GetTick()` 经 `wd_platform_*` 弱钩子访问
|
||||
(声明在 `task_watchdog.h` 末段),MCU 端为 `__weak` 默认实现,主机侧单测以同名强符号覆盖 → 可在 PC 上跑纯逻辑回归
|
||||
- 设计详见 `docs/子系统设计/看门狗设计说明.md`;单测见 `docs/测试说明/看门狗单元测试说明.md`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `docs/子系统设计/系统墙钟时间维护说明.md`
|
||||
- `docs/子系统设计/看门狗设计说明.md`
|
||||
- `docs/测试说明/看门狗单元测试说明.md`
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
* - §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"
|
||||
@@ -54,6 +61,7 @@ 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 -----------------------------------------------*/
|
||||
|
||||
@@ -121,8 +129,8 @@ void task_wd_start(void) {
|
||||
|
||||
/* 启动前先喂一次:让首个抽样窗口从"启动时刻"起算,
|
||||
* 而非从 MX_IWDG_Init 的时刻起算(避免启动后立即超时) */
|
||||
IWDG->KR = 0x0000AAAAu; /* 重装计数器(喂狗) */
|
||||
IWDG->KR = 0x0000CCCCu; /* 启动看门狗 */
|
||||
wd_platform_feed(); /* 重装计数器(喂狗) */
|
||||
wd_platform_start(); /* 启动看门狗 */
|
||||
|
||||
DBG_INFO("[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms");
|
||||
}
|
||||
@@ -139,7 +147,6 @@ void task_wd_start(void) {
|
||||
* IWDGRSTF,而不是 NVIC_SystemReset 留下的 SFTRSTF —— §5.3)。
|
||||
*/
|
||||
void task_wd_poll(void) {
|
||||
static uint32_t s_last_tick;
|
||||
uint32_t now;
|
||||
uint32_t snapshot;
|
||||
|
||||
@@ -147,7 +154,7 @@ void task_wd_poll(void) {
|
||||
return; /* 未启动:不做任何判定 */
|
||||
}
|
||||
|
||||
now = HAL_GetTick();
|
||||
now = wd_platform_tick_ms();
|
||||
if ((now - s_last_tick) < WD_POLL_PERIOD_MS) {
|
||||
return; /* 节流:非 100ms 边界直接返回 */
|
||||
}
|
||||
@@ -159,7 +166,7 @@ void task_wd_poll(void) {
|
||||
|
||||
if (snapshot == wd_required_mask()) {
|
||||
s_miss_streak = 0u;
|
||||
IWDG->KR = 0x0000AAAAu; /* 全员健康:喂狗 */
|
||||
wd_platform_feed(); /* 全员健康:喂狗 */
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,3 +206,69 @@ uint32_t task_wd_miss_count(void) {
|
||||
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
|
||||
|
||||
@@ -69,6 +69,55 @@ uint32_t task_wd_last_mask(void); /* 上次抽样到的位图(0 位 = 缺
|
||||
uint32_t task_wd_miss_count(void); /* 累计未达标轮数 */
|
||||
uint32_t task_wd_is_armed(void); /* 1 = IWDG 已启动 */
|
||||
|
||||
/*
|
||||
* ============================ 平台后端(内部) ============================
|
||||
* 把判定状态机中的硬件访问收敛到三个薄钩子,使"取走清零 / 掩码判定 / 缺失
|
||||
* 累计 / 节流"成为纯逻辑,可在主机侧用桩替换后离线穷举验证
|
||||
* (见 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 默认实现 = `IWDG->KR = 0x0000CCCCu;`
|
||||
*/
|
||||
void wd_platform_start(void);
|
||||
|
||||
/*
|
||||
* 函数功能:读单调毫秒时基(用于 100ms 抽样节流)
|
||||
* 返回值:自启动以来的毫秒计数(32 位回绕安全,用无符号差比较)
|
||||
* 函数说明:MCU 默认实现 = `HAL_GetTick()`
|
||||
* 主机侧单测覆盖此符号以驱动时间推进。
|
||||
*/
|
||||
uint32_t wd_platform_tick_ms(void);
|
||||
|
||||
/*
|
||||
* 函数功能:复位判定状态机(**仅主机侧单元测试使用**)
|
||||
* 返回值:无
|
||||
* 函数说明:仅当定义 TASK_WATCHDOG_UNIT_TEST 时编入(MCU 构建无此符号)。
|
||||
* 用于在用例之间清空位图/计数/节流基线,保证用例互不污染。
|
||||
*/
|
||||
#ifdef TASK_WATCHDOG_UNIT_TEST
|
||||
void task_wd_test_reset(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user