看门狗判定状态机增加主机侧单元测试(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:
@@ -10,7 +10,7 @@
|
||||
"assert-not-deadloop": "d4a00718ab2bd071",
|
||||
"assert-impl": "b7fd1fadb4522edf",
|
||||
"stack-overflow-hook-impl": "b7fd1fadb4522edf",
|
||||
"watchdog-module": "3c70ea43ff03523e",
|
||||
"watchdog-module": "e8fb07637a214d03",
|
||||
"reset-cause-latch": "52fc71acbb0f1aed",
|
||||
"iwdg-hal-driver": "4b902875a84554e9",
|
||||
"hal-iwdg-enabled": "c8c9b916b05464b9",
|
||||
|
||||
@@ -111,6 +111,13 @@ simulator/SDL2/
|
||||
|
||||
# Binary / large files
|
||||
*.bin
|
||||
|
||||
# 主机侧单元测试产物(MSVC 中间物;test/host/**/run_unit_test.py 正常写 %TEMP%,
|
||||
# 但手工直接调 cl.exe 调试时可能落在 CWD。统一兜底忽略)
|
||||
*.obj
|
||||
*.exe
|
||||
*.pdb
|
||||
*.ilk
|
||||
# 内容页黄金参考(ref/*.bin)与目检 PNG 均为本地生成物,不入库:
|
||||
# 首次/换机后先跑 check_content_pages.py --update 生成 ref/*.bin,再跑 regen_ref_png.py 出 PNG
|
||||
simulator/tests/ref/
|
||||
|
||||
@@ -148,3 +148,16 @@ repos:
|
||||
language: system
|
||||
files: ^(STM32F407-Demo\.ioc|Src/.*\.(c|h)|Inc/.*\.h|App/util/task_watchdog\.(c|h)|MDK-ARM/.*\.uvprojx|scripts/check_cubemx_survival\.py)$
|
||||
pass_filenames: false
|
||||
|
||||
# 7) 看门狗判定状态机主机侧单元测试(2026-10-01 加)
|
||||
# 背景:task_watchdog.c 的判定逻辑(位图取走清零 / 掩码 / miss_streak
|
||||
# 累计 / 100ms 节流 / tick 回绕)是纯逻辑,板端套件无法穷举边界;本钩子
|
||||
# 用 MSVC 离线编译真实源文件(#include 纳入)并用可观测桩驱动,秒级、零
|
||||
# 硬件、不占 MCU RAM。仅在被测文件或其平台层 / 测试变动时触发。
|
||||
# 详见 docs/测试说明/看门狗单元测试说明.md
|
||||
- id: watchdog-unit-test
|
||||
name: 看门狗判定状态机单测(主机侧)
|
||||
entry: python test/host/watchdog/run_unit_test.py
|
||||
language: system
|
||||
files: ^(App/util/task_watchdog\.(c|h)|test/host/watchdog/.*)$
|
||||
pass_filenames: false
|
||||
|
||||
@@ -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
|
||||
|
||||
+284
-95
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" noNamespaceSchemaLocation="project_projx.xsd">
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project_projx.xsd">
|
||||
|
||||
<SchemaVersion>2.1</SchemaVersion>
|
||||
|
||||
<Header>### uVision Project, (C) Keil Software</Header>
|
||||
|
||||
<Targets>
|
||||
<Target>
|
||||
<TargetName>STM32F407-Demo</TargetName>
|
||||
@@ -16,28 +19,28 @@
|
||||
<PackID>Keil.STM32F4xx_DFP.3.1.1</PackID>
|
||||
<PackURL>https://www.keil.com/pack/</PackURL>
|
||||
<Cpu>IRAM(0x20000000-0x2001BFFF) IRAM2(0x2001C000-0x2001FFFF) IROM(0x8000000-0x80FFFFF) CLOCK(25000000) FPU2 CPUTYPE("Cortex-M4") TZ</Cpu>
|
||||
<FlashUtilSpec />
|
||||
<StartupFile />
|
||||
<FlashDriverDll />
|
||||
<FlashUtilSpec></FlashUtilSpec>
|
||||
<StartupFile></StartupFile>
|
||||
<FlashDriverDll></FlashDriverDll>
|
||||
<DeviceId>0</DeviceId>
|
||||
<RegisterFile />
|
||||
<MemoryEnv />
|
||||
<Cmp />
|
||||
<Asm />
|
||||
<Linker />
|
||||
<OHString />
|
||||
<InfinionOptionDll />
|
||||
<SLE66CMisc />
|
||||
<SLE66AMisc />
|
||||
<SLE66LinkerMisc />
|
||||
<RegisterFile></RegisterFile>
|
||||
<MemoryEnv></MemoryEnv>
|
||||
<Cmp></Cmp>
|
||||
<Asm></Asm>
|
||||
<Linker></Linker>
|
||||
<OHString></OHString>
|
||||
<InfinionOptionDll></InfinionOptionDll>
|
||||
<SLE66CMisc></SLE66CMisc>
|
||||
<SLE66AMisc></SLE66AMisc>
|
||||
<SLE66LinkerMisc></SLE66LinkerMisc>
|
||||
<SFDFile>$$Device:STM32F407ZGTx$CMSIS\SVD\STM32F407.svd</SFDFile>
|
||||
<bCustSvd>0</bCustSvd>
|
||||
<UseEnv>0</UseEnv>
|
||||
<BinPath />
|
||||
<IncludePath />
|
||||
<LibPath />
|
||||
<RegisterFilePath />
|
||||
<DBRegisterFilePath />
|
||||
<BinPath></BinPath>
|
||||
<IncludePath></IncludePath>
|
||||
<LibPath></LibPath>
|
||||
<RegisterFilePath></RegisterFilePath>
|
||||
<DBRegisterFilePath></DBRegisterFilePath>
|
||||
<TargetStatus>
|
||||
<Error>0</Error>
|
||||
<ExitCodeStop>0</ExitCodeStop>
|
||||
@@ -52,15 +55,15 @@
|
||||
<CreateHexFile>1</CreateHexFile>
|
||||
<DebugInformation>1</DebugInformation>
|
||||
<BrowseInformation>1</BrowseInformation>
|
||||
<ListingPath />
|
||||
<ListingPath></ListingPath>
|
||||
<HexFormatSelection>1</HexFormatSelection>
|
||||
<Merge32K>0</Merge32K>
|
||||
<CreateBatchFile>0</CreateBatchFile>
|
||||
<BeforeCompile>
|
||||
<RunUserProg1>0</RunUserProg1>
|
||||
<RunUserProg2>0</RunUserProg2>
|
||||
<UserProg1Name />
|
||||
<UserProg2Name />
|
||||
<UserProg1Name></UserProg1Name>
|
||||
<UserProg2Name></UserProg2Name>
|
||||
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
|
||||
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
|
||||
<nStopU1X>0</nStopU1X>
|
||||
@@ -69,8 +72,8 @@
|
||||
<BeforeMake>
|
||||
<RunUserProg1>0</RunUserProg1>
|
||||
<RunUserProg2>0</RunUserProg2>
|
||||
<UserProg1Name />
|
||||
<UserProg2Name />
|
||||
<UserProg1Name></UserProg1Name>
|
||||
<UserProg2Name></UserProg2Name>
|
||||
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
|
||||
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
|
||||
<nStopB1X>0</nStopB1X>
|
||||
@@ -79,15 +82,15 @@
|
||||
<AfterMake>
|
||||
<RunUserProg1>0</RunUserProg1>
|
||||
<RunUserProg2>1</RunUserProg2>
|
||||
<UserProg1Name />
|
||||
<UserProg2Name />
|
||||
<UserProg1Name></UserProg1Name>
|
||||
<UserProg2Name></UserProg2Name>
|
||||
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
|
||||
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
|
||||
<nStopA1X>0</nStopA1X>
|
||||
<nStopA2X>0</nStopA2X>
|
||||
</AfterMake>
|
||||
<SelectedForBatchBuild>1</SelectedForBatchBuild>
|
||||
<SVCSIdString />
|
||||
<SVCSIdString></SVCSIdString>
|
||||
</TargetCommonOption>
|
||||
<CommonProperty>
|
||||
<UseCPPCompiler>0</UseCPPCompiler>
|
||||
@@ -101,8 +104,8 @@
|
||||
<AssembleAssemblyFile>0</AssembleAssemblyFile>
|
||||
<PublicsOnly>0</PublicsOnly>
|
||||
<StopOnExitCode>3</StopOnExitCode>
|
||||
<CustomArgument />
|
||||
<IncludeLibraryModules />
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>0</ComprImg>
|
||||
</CommonProperty>
|
||||
<DllOption>
|
||||
@@ -136,10 +139,10 @@
|
||||
<bUseTDR>1</bUseTDR>
|
||||
<Flash2>BIN\UL2CM3.DLL</Flash2>
|
||||
<Flash3>"" ()</Flash3>
|
||||
<Flash4 />
|
||||
<pFcarmOut />
|
||||
<pFcarmGrp />
|
||||
<pFcArmRoot />
|
||||
<Flash4></Flash4>
|
||||
<pFcarmOut></pFcarmOut>
|
||||
<pFcarmGrp></pFcarmGrp>
|
||||
<pFcArmRoot></pFcArmRoot>
|
||||
<FcArmLst>0</FcArmLst>
|
||||
</Utilities>
|
||||
<TargetArmAds>
|
||||
@@ -172,7 +175,7 @@
|
||||
<RvctClst>0</RvctClst>
|
||||
<GenPPlst>0</GenPPlst>
|
||||
<AdsCpuType>"Cortex-M4"</AdsCpuType>
|
||||
<RvctDeviceName />
|
||||
<RvctDeviceName></RvctDeviceName>
|
||||
<mOS>0</mOS>
|
||||
<uocRom>0</uocRom>
|
||||
<uocRam>0</uocRam>
|
||||
@@ -183,6 +186,7 @@
|
||||
<RvdsVP>2</RvdsVP>
|
||||
<RvdsMve>0</RvdsMve>
|
||||
<RvdsCdeCp>0</RvdsCdeCp>
|
||||
<nBranchProt>0</nBranchProt>
|
||||
<hadIRAM2>1</hadIRAM2>
|
||||
<hadIROM2>0</hadIROM2>
|
||||
<StupSel>8</StupSel>
|
||||
@@ -306,7 +310,7 @@
|
||||
<Size>0x0</Size>
|
||||
</OCR_RVCT10>
|
||||
</OnChipMemories>
|
||||
<RvctStartVector />
|
||||
<RvctStartVector></RvctStartVector>
|
||||
</ArmAdsMisc>
|
||||
<Cads>
|
||||
<interw>1</interw>
|
||||
@@ -335,7 +339,7 @@
|
||||
<VariousControls>
|
||||
<MiscControls>--diag_suppress=1,68,111,188,191,546,1293</MiscControls>
|
||||
<Define>USE_HAL_DRIVER,STM32F407xx,NDEBUG</Define>
|
||||
<Undefine />
|
||||
<Undefine></Undefine>
|
||||
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/cfg;../App/log;../App/sync;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib</IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
@@ -351,9 +355,9 @@
|
||||
<useXO>0</useXO>
|
||||
<ClangAsOpt>1</ClangAsOpt>
|
||||
<VariousControls>
|
||||
<MiscControls />
|
||||
<Define />
|
||||
<Undefine />
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath>../Drivers/CMSIS/Include</IncludePath>
|
||||
</VariousControls>
|
||||
</Aads>
|
||||
@@ -364,15 +368,15 @@
|
||||
<noStLib>0</noStLib>
|
||||
<RepFail>1</RepFail>
|
||||
<useFile>1</useFile>
|
||||
<TextAddressRange />
|
||||
<DataAddressRange />
|
||||
<pXoBase />
|
||||
<TextAddressRange></TextAddressRange>
|
||||
<DataAddressRange></DataAddressRange>
|
||||
<pXoBase></pXoBase>
|
||||
<ScatterFile>STM32F407-Demo\STM32F407-Demo.sct</ScatterFile>
|
||||
<IncludeLibs />
|
||||
<IncludeLibsPath />
|
||||
<Misc />
|
||||
<LinkerInputFile />
|
||||
<DisabledWarnings />
|
||||
<IncludeLibs></IncludeLibs>
|
||||
<IncludeLibsPath></IncludeLibsPath>
|
||||
<Misc></Misc>
|
||||
<LinkerInputFile></LinkerInputFile>
|
||||
<DisabledWarnings></DisabledWarnings>
|
||||
</LDads>
|
||||
</TargetArmAds>
|
||||
</TargetOption>
|
||||
@@ -417,6 +421,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -444,6 +450,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -465,6 +477,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -492,6 +506,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -513,6 +533,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -540,6 +562,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -561,6 +589,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -588,19 +618,13 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
<interw>2</interw>
|
||||
<Ropi>2</Ropi>
|
||||
<Rwpi>2</Rwpi>
|
||||
<thumb>2</thumb>
|
||||
<SplitLS>2</SplitLS>
|
||||
<SwStkChk>2</SwStkChk>
|
||||
<NoWarn>2</NoWarn>
|
||||
<uSurpInc>2</uSurpInc>
|
||||
<useXO>2</useXO>
|
||||
<ClangAsOpt>1</ClangAsOpt>
|
||||
</Aads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
</File>
|
||||
@@ -621,6 +645,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -648,6 +674,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -669,6 +701,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -696,6 +730,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -727,6 +767,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -754,6 +796,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -840,6 +888,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -867,6 +917,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -888,6 +944,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -915,6 +973,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1001,6 +1065,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1028,6 +1094,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1049,6 +1121,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1076,6 +1150,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1097,6 +1177,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1124,19 +1206,13 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
<interw>2</interw>
|
||||
<Ropi>2</Ropi>
|
||||
<Rwpi>2</Rwpi>
|
||||
<thumb>2</thumb>
|
||||
<SplitLS>2</SplitLS>
|
||||
<SwStkChk>2</SwStkChk>
|
||||
<NoWarn>2</NoWarn>
|
||||
<uSurpInc>2</uSurpInc>
|
||||
<useXO>2</useXO>
|
||||
<ClangAsOpt>1</ClangAsOpt>
|
||||
</Aads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
</File>
|
||||
@@ -1157,6 +1233,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1184,6 +1262,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1205,6 +1289,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1232,6 +1318,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1338,6 +1430,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<GroupArmAds>
|
||||
@@ -1365,6 +1459,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
<interw>2</interw>
|
||||
@@ -1377,6 +1477,12 @@
|
||||
<uSurpInc>2</uSurpInc>
|
||||
<useXO>2</useXO>
|
||||
<ClangAsOpt>1</ClangAsOpt>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Aads>
|
||||
</GroupArmAds>
|
||||
</GroupOption>
|
||||
@@ -1398,6 +1504,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1425,6 +1533,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1446,6 +1560,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1473,6 +1589,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1494,6 +1616,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1521,6 +1645,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1542,6 +1672,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1569,6 +1701,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1590,6 +1728,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1617,6 +1757,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1638,6 +1784,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1665,6 +1813,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1686,6 +1840,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1713,6 +1869,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1734,6 +1896,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1761,6 +1925,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1782,6 +1952,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1809,6 +1981,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1830,6 +2008,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<FileArmAds>
|
||||
@@ -1857,6 +2037,12 @@
|
||||
<v6Lto>2</v6Lto>
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
</FileArmAds>
|
||||
</FileOption>
|
||||
@@ -1878,8 +2064,8 @@
|
||||
<AssembleAssemblyFile>2</AssembleAssemblyFile>
|
||||
<PublicsOnly>2</PublicsOnly>
|
||||
<StopOnExitCode>11</StopOnExitCode>
|
||||
<CustomArgument />
|
||||
<IncludeLibraryModules />
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>1</ComprImg>
|
||||
</CommonProperty>
|
||||
<GroupArmAds>
|
||||
@@ -1909,9 +2095,9 @@
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls>--diag_suppress=177</MiscControls>
|
||||
<Define />
|
||||
<Undefine />
|
||||
<IncludePath />
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
@@ -1926,10 +2112,10 @@
|
||||
<useXO>2</useXO>
|
||||
<ClangAsOpt>1</ClangAsOpt>
|
||||
<VariousControls>
|
||||
<MiscControls />
|
||||
<Define />
|
||||
<Undefine />
|
||||
<IncludePath />
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Aads>
|
||||
</GroupArmAds>
|
||||
@@ -2017,8 +2203,8 @@
|
||||
<AssembleAssemblyFile>0</AssembleAssemblyFile>
|
||||
<PublicsOnly>0</PublicsOnly>
|
||||
<StopOnExitCode>3</StopOnExitCode>
|
||||
<CustomArgument />
|
||||
<IncludeLibraryModules />
|
||||
<CustomArgument></CustomArgument>
|
||||
<IncludeLibraryModules></IncludeLibraryModules>
|
||||
<ComprImg>0</ComprImg>
|
||||
</CommonProperty>
|
||||
<GroupArmAds>
|
||||
@@ -2047,10 +2233,10 @@
|
||||
<v6WtE>2</v6WtE>
|
||||
<v6Rtti>2</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls />
|
||||
<Define />
|
||||
<Undefine />
|
||||
<IncludePath />
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
@@ -2065,10 +2251,10 @@
|
||||
<useXO>2</useXO>
|
||||
<ClangAsOpt>0</ClangAsOpt>
|
||||
<VariousControls>
|
||||
<MiscControls />
|
||||
<Define />
|
||||
<Undefine />
|
||||
<IncludePath />
|
||||
<MiscControls></MiscControls>
|
||||
<Define></Define>
|
||||
<Undefine></Undefine>
|
||||
<IncludePath></IncludePath>
|
||||
</VariousControls>
|
||||
</Aads>
|
||||
</GroupArmAds>
|
||||
@@ -3177,18 +3363,20 @@
|
||||
</Groups>
|
||||
</Target>
|
||||
</Targets>
|
||||
|
||||
<RTE>
|
||||
<apis />
|
||||
<apis/>
|
||||
<components>
|
||||
<component Cclass="CMSIS" Cgroup="CORE" Cvendor="ARM" Cversion="5.5.0" condition="ARMv6_7_8-M Device">
|
||||
<package name="CMSIS" schemaVersion="1.3" url="http://www.keil.com/pack/" vendor="ARM" version="5.8.0" />
|
||||
<package name="CMSIS" schemaVersion="1.3" url="http://www.keil.com/pack/" vendor="ARM" version="5.8.0"/>
|
||||
<targetInfos>
|
||||
<targetInfo name="STM32F407-Demo" />
|
||||
<targetInfo name="STM32F407-Demo"/>
|
||||
</targetInfos>
|
||||
</component>
|
||||
</components>
|
||||
<files />
|
||||
<files/>
|
||||
</RTE>
|
||||
|
||||
<LayerInfo>
|
||||
<Layers>
|
||||
<Layer>
|
||||
@@ -3197,4 +3385,5 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
</LayerInfo>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 265,016 B (258.80 KB) | 1,048,576 B (1024 KB) | 25.3% |
|
||||
| ER_IROM1 | 265,036 B (258.82 KB) | 1,048,576 B (1024 KB) | 25.3% |
|
||||
| RW_IRAM1 | 87,080 B (85.04 KB) | 131,072 B (128 KB) | 66.4% |
|
||||
| RW_CCM | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
|
||||
| **Flash (ROM 合计)** | **265,772 B (259.5 KB)** | 1,048,576 B (1024 KB) | **25.3%** |
|
||||
| **Flash (ROM 合计)** | **265,792 B (259.6 KB)** | 1,048,576 B (1024 KB) | **25.3%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
+26
-1
@@ -438,6 +438,7 @@ IWDG 复位常伴随 `LPWRRSTF` 若同时发生低压)。按**因果强度**
|
||||
|---|---|---|
|
||||
| `App/util/task_watchdog.c` | 新建 | 心跳位图、喂狗判定、**IWDG 寄存器级初始化与刷新**(§2.2 路径 C) |
|
||||
| `App/util/task_watchdog.h` | 新建 | 任务 ID 枚举、`task_wd_beat()` / `task_wd_poll()` / `task_wd_start()` 接口 |
|
||||
| `test/host/watchdog/{watchdog_test.c,run_unit_test.py,stub/*}` | 新建 | 主机侧单元测试:`#include` 生产源码 + 强符号覆盖钩子(§7.3) |
|
||||
| `Src/main.c` | 修改 | 复位原因锁存(`HAL_Init()` 后,`USER CODE` 区内) |
|
||||
| `Src/freertos.c` | 修改 | ◆ `vApplicationAssertFailed()` 实现 + `configASSERT` 改造;◆ 见下方"埋点方式" |
|
||||
| `App/task/*.c` | 修改 | 各任务主循环插入 `task_wd_beat(TASK_WD_XXX)` |
|
||||
@@ -703,6 +704,28 @@ external pin → "pin reset" (新增)
|
||||
|
||||
> IWDG 硬件行为在模拟器不可建模,S1 只验证**位图与判定逻辑**(纯 RAM 部分)。
|
||||
|
||||
### 7.3 主机侧单元测试(L0.5,**2026-10-01 已落地**)
|
||||
|
||||
在不依赖真机与模拟器的前提下,把判定状态机**直接编进主机进程**做纯逻辑回归:
|
||||
|
||||
| # | 场景 | 命令 | 期望 |
|
||||
|---|---|---|---|
|
||||
| U1 | 判定状态机全用例 | `python test/host/watchdog/run_unit_test.py` | 39 项断言全过,退出码 0 |
|
||||
| U2 | 门禁接入 | 改动 `App/util/task_watchdog*.{c,h}` 或 `test/host/watchdog/**` 后提交 | pre-commit `watchdog-unit-test` 钩子触发并通过 |
|
||||
|
||||
**架构**:`App/util/task_watchdog.c` 的硬件访问经 3 个 `__weak` 钩子收敛到
|
||||
`task_watchdog.h` 末段"平台后端(内部)"(`wd_platform_feed` / `wd_platform_start` /
|
||||
`wd_platform_tick_ms`)。主机侧测试文件 `#include "task_watchdog.c"` 原样纳入生产源码,
|
||||
并提供同名**强符号**覆盖这三个钩子(注入 `IWDG->KR` 影子寄存器与虚拟 tick),
|
||||
故测的是**生产代码本身**而非副本。
|
||||
|
||||
**覆盖范围**:置位/清零语义、必需掩码 `0x37` 判定、`WD_MISS_TOLERANCE=8`(800 ms)
|
||||
停喂、`WD_POLL_PERIOD_MS=100` 节流、tick 回绕安全、`task_wd_start()` 先喂一次等。
|
||||
|
||||
> 用例清单与扩展方法见 [`../测试说明/看门狗单元测试说明.md`](../测试说明/看门狗单元测试说明.md)。
|
||||
> 本层取代(并优于)原 S1 计划中"位图与判定逻辑"部分——**无需模拟器即可回归**;
|
||||
> S1 保留意义仅剩"在模拟器全链路中联调",优先级可降。
|
||||
|
||||
---
|
||||
|
||||
## 8. 内存与资源预算
|
||||
@@ -760,8 +783,9 @@ external pin → "pin reset" (新增)
|
||||
| **4** | 各任务埋点(含 `timeSyncTask` **切片**而非拆 delay) | 编译 0/0;串口无 `miss` | ✅ 完成 |
|
||||
| **5** | `task_wd_start()` 由 `defaultTask` 在 bring-up 完成后调用 | `[WD] armed:` 出现 | ✅ 完成 |
|
||||
| **6** | `configASSERT` 改造 + `vApplicationAssertFailed()` | 编译 0/0 | ✅ 完成 |
|
||||
| **6.5** | **(新增)** 硬件访问收敛到薄平台层(`task_watchdog.h` 末段,3 个 `__weak` 钩子)+ 主机侧单元测试(`test/host/watchdog/`,39 用例)+ pre-commit 钩子 | `run_unit_test.py` 退出码 0;`@build` 0/0(行为不变) | ✅ **2026-10-01 完成** |
|
||||
| **7** | 故障注入(V2/V4):人为制造任务卡死 | 复位 + `reset cause: IWDG` | ⏳ **待台架** |
|
||||
| **8** | 模拟器 `--watchdogtest` + stub | S1 | ⏳ 待做 |
|
||||
| **8** | 模拟器 `--watchdogtest` + stub | S1 | ⏳ 待做(位图逻辑已由 U1 覆盖,优先级降低) |
|
||||
| **9** | 长跑(V5,≥30 min,真实负载) | **零复位** | ⏳ **待台架** |
|
||||
| **10** | 本文档改写为"现状设计说明";`待处理内容.md` N-03 销项;`README.md` 登记 | 门禁 | ⏳ 待第 7~9 步结论 |
|
||||
|
||||
@@ -1078,3 +1102,4 @@ python scripts/check_cubemx_survival.py --write # 重建基线(确认改动
|
||||
| 2026-09-29 | **第二次重生成验证 + 机制修正** | 审计脚本**实测有效**(准确检出 5 处覆盖)。⚠️ **关键发现**:CubeMX 换了一代中断函数模板(旧 `HardFault_Handler`/`HardFault_Report` 自造区 → 新函数体内 `HardFault_IRQn 0`/`W1_HardFault_IRQn 0`),**"生成区外的自造标记区"同样不被保留**——上一轮假设被证伪。据此升级恢复方案:报告实现迁入 `USER CODE BEGIN 0`(标准保护位);Handler 本体改为生成区整函数替换(`#define` 改名保留生成实体,链接器自动移除,`.map` 已验证)+ `HARD-FAULT-ANCHOR` 锚点。同步修正审计脚本(`hardfault-handler` 改 `wholefile` + 锚点)、`UNPROTECTED_LEGACY` 清单、§10.1/§10.2 全文。`@build` 0 错误 0 警告 |
|
||||
| 2026-09-29 | **3 个 Fault 升级为崩溃采集** | `MemManage` / `BusFault` / `UsageFault` 由空 `while(1)`(死机静默)升级为与 `HardFault` 同一套采集:生成区整函数替换(各带独立锚点 `MEMFAULT-ANCHOR` / `BUSFAULT-ANCHOR` / `USAGEFAULT-ANCHOR`)+ `#define X X_Gen` 改名保留生成实体(链接器各移除 2 字节)。新增 `fault_handler_enable()` 使能 `SHCSR` 后三者(默认只开 HardFault,不开则故障被升级、看不出根因),声明入 `Inc/stm32f4xx_it.h`、调用在 `app_main_init()` 第一行。因 ARMCC `__asm` 取不到 C 数据符号,改为 per-fault C 入口 `fault_entry_{hard,mem,bus,usage}` 带故障名调 `hard_fault_report(tag, frame)`。**刻意不启用** `UNALIGN_TRP` / `DIV_0_TRP`(避免把普通 bug 升级成死机)。审计脚本新增 `fault-handlers-3` / `fault-handler-enable` 两项、`UNPROTECTED_LEGACY` 清掉 3 个 Fault(WARN 2→1)、清单 16→18 项。`@build` 0 错误 0 警告;审计 18 通过 / 1 警告 / 0 失败 |
|
||||
| 2026-10-01 | **查证 `fault_handler_enable()` 的 CubeMX 可行性** | 用户问"能否通过 CubeMX 配置"→ **否**。查 CubeMX 安装目录模板源码得三重证据:① `tpl_main_c.ftl` 的 `MX_NVIC_Init()` 只在 `systemHandler=="false"` 时生成 `HAL_NVIC_EnableIRQ`,而 `NVIC-STM32F417_Modes.xml` 中三个 Fault 都标 `Y`(=systemHandler)→ 永不生成使能代码;② **全部模板中 `SHCSR` 只出现 1 次**且是 MPU 清理的 `&= ~MEMFAULTENA`(禁用),**无任何 `\|=` 置位**;③ `.ioc` 无 MPU(`Mcu.IP` 列表),且 `HAL_MPU_Enable` 只置 `MEMFAULTENA` 一位。实测印证:`HAL_MspInit()` 里三个 Fault 一行代码都没生成。结论:只能手工写,但属 **L2**(`USER CODE BEGIN 1` 区)→ 重生成不丢,有审计项兜底。另新增警示:`.ioc` 的 `NVIC.MemoryManagement_IRQn=true` **不代表已使能**(第 1 段只是"向量可见")。写入 §10.2 与四个 Fault 采集小节 |
|
||||
| 2026-10-01 | **新增 §7.3 主机侧单元测试(L0.5)** | 硬件访问收敛到薄平台层(`App/util/task_watchdog.h` 末段声明 3 个 `__weak` 钩子:喂狗/启动/取 tick),MCU 端行为零变化(`@build` 0/0)。新建 `test/host/watchdog/`(`watchdog_test.c` 39 用例 + `run_unit_test.py` MSVC 运行器 + `stub/` 三桩头),`#include` 生产源码以保证测的是真代码。挂 pre-commit `watchdog-unit-test` 钩子。新增 `docs/测试说明/看门狗单元测试说明.md`;`App/util/README.md`、`test/host/README.md`、`测试开发标准流程.md`(新增 L0.5 层)同步。顺带改善:`s_last_tick` 由函数局部 static 提为文件 static(可测性 + 模块状态归一)。§5.1 文件表与 §10 步骤表登记;§7.2 S1 优先级降低(位图逻辑已由 U1 覆盖) |
|
||||
|
||||
@@ -34,4 +34,5 @@
|
||||
| N-03a | `configASSERT(x)` = `taskDISABLE_INTERRUPTS(); for(;;);`(`Inc/FreeRTOSConfig.h`)→ 断言时无任何输出 | ✅ **已修(2026-09-29)**:改为 `vApplicationAssertFailed(__FILE__, __LINE__)`,打印文件名+行号后主动复位(`Src/freertos.c`);宏已迁入 `USER CODE BEGIN 1` 区防重生成丢失 | 同上 N-03b |
|
||||
| N-03b | 启动段(`app_main_init` + `defaultTask` bring-up)在新方案下**无看门狗保护** | 显式代价(未变):`看门狗设计说明.md` §4.5 决策 C + §9 R1;该段死机表现为"无输出 + LED 不闪" | 同上 N-03c |
|
||||
| N-03c | **CubeMX 重生成会覆盖 `USER CODE` 标记区之外的定制** | ⚠️ **2026-09-29 两次重生成实测确认**:首次覆盖 4 处、第二次覆盖 5 处(P0 `Src/iwdg.c` 自动启动、P1 `HardFault_Handler` 打回空死循环、P1 `FreeRTOSConfig.h` 两个加固宏被删、P2 `freertos.c` 中文注释)。✅ **已建立自动检出**:`scripts/check_cubemx_survival.py`(18 项清单 + 哈希基线,已挂 pre-commit)——第二次实测**准确检出全部 5 处**。⚠️ 关键发现:CubeMX **换了一代中断模板**,"生成区外的自造标记区"同样不被保留(详见设计文档 §10.1)。**今后每次重生成后跑一次该脚本即可** | 本次实施记录 |
|
||||
| N-03e | 判定状态机**缺主机侧纯逻辑回归**(改动后只能靠真机/模拟器才发现回归) | ✅ **已落地(2026-10-01)**:硬件访问收敛到薄平台层(`App/util/task_watchdog.h` 末段声明 3 个 `__weak` 钩子),新建 `test/host/watchdog/` 单元测试(39 用例,`#include` 生产源码 + 强符号覆盖钩子),挂 pre-commit `watchdog-unit-test` 钩子。MCU 端行为零变化(`@build` 0/0)。新增测试分层 **L0.5** | 见 [`测试说明/看门狗单元测试说明.md`](测试说明/看门狗单元测试说明.md) |
|
||||
| N-03d | 3 个次要 Fault(`MemManage`/`BusFault`/`UsageFault`)为空 `while(1)`,死机静默 | ✅ **已修(2026-09-29)**:升级为与 `HardFault` 同一套崩溃采集(手工 `__asm` Handler → per-fault C 入口 → `hard_fault_report(tag, frame)`,日志打 `[ERR] MEMFAULT/BUSFAULT/USAGEFAULT + 全量现场`);新增 `fault_handler_enable()` 使能 `SHCSR` 后三者(默认只开 HardFault,否则故障被升级、看不出根因),在 `app_main_init()` 第一行调用。**刻意不启用** `UNALIGN_TRP`/`DIV_0_TRP`(避免把普通 bug 升级成死机)。`@build` 0/0;审计 18 通过 / 1 警告 / 0 失败 | 见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md) §10.2 |
|
||||
|
||||
+28
-1
@@ -25,16 +25,23 @@
|
||||
| 层 | 名称 | 载体 | 抓住什么 |
|
||||
|---|---|---|---|
|
||||
| L0 | 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `@build` | 编码规范、静态缺陷、任何编译警告 |
|
||||
| **L0.5** | **主机侧纯逻辑单测** | `test/host/<模块>/run_unit_test.py` + MSVC 离线编译真实源码(`#include` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 |
|
||||
| L1 | 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) |
|
||||
| L2 | 主机侧回归 | `@test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 |
|
||||
| L3 | 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 |
|
||||
|
||||
> **L0.5 与 L2 的区别**:L2 的模拟器回归依赖 SDL / LVGL 与图形输出;L0.5 是**纯计算**单测
|
||||
> (无窗口、无硬件、秒级),专测"判定逻辑"。目前仅看门狗模块使用,见
|
||||
> [`看门狗单元测试说明.md`](看门狗单元测试说明.md)。适格对象:算法 / 状态机 / 协议编解码 /
|
||||
> 参数校验等**无副作用**的模块。
|
||||
|
||||
### 1.1 门禁决策表(改动类型 → 必跑门禁)
|
||||
|
||||
| 改动类型 | 必跑门禁 |
|
||||
|---|---|
|
||||
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `@build` |
|
||||
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `@test`(模块标记) |
|
||||
| **有 L0.5 单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `@build`(+ 台架项另按需 G2) |
|
||||
| UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 |
|
||||
| UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** |
|
||||
| `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) |
|
||||
@@ -187,11 +194,31 @@ static void phaseN_run(void) {
|
||||
| Python 串口助手(serial_tool) | `tools/serial_tool/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle |
|
||||
| `test/host/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 |
|
||||
| `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL) |
|
||||
| **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:MSVC 离线编译真实 `task_watchdog.c` + 可观测桩,穷举判定状态机(零硬件、秒级) |
|
||||
| `.opencode/serial_config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 |
|
||||
| `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) |
|
||||
| `simulator/tests/*.py`(4 个) | `simulator/tests/` | LVGL 模拟器回归脚本(见 §5) |
|
||||
|
||||
### 4.2 新工具约定
|
||||
### 4.2 L0.5 主机侧单元测试(纯逻辑,无硬件)
|
||||
|
||||
**适用对象**:**无副作用**的纯逻辑模块——算法、状态机、协议编解码、参数校验等
|
||||
(例:看门狗的位图判定 / 掩码 / 计数阈值 / 整数回绕)。
|
||||
|
||||
**做法**(以看门狗为范式,见 [`看门狗单元测试说明.md`](看门狗单元测试说明.md)):
|
||||
|
||||
1. 模块的**硬件访问**收敛到薄平台层(`__weak` 钩子或后端函数)——参照
|
||||
`App/cfg/cfg_file_store.h` 的"共享核心 + 平台后端";
|
||||
2. 测试单元 `#include` **生产源文件**(保证测的是真代码,非副本),用桩替换平台层;
|
||||
3. 用 MSVC 离线编译运行(`run_unit_test.py` 自动探测工具链);
|
||||
4. 挂 pre-commit 钩子,被改动时自动跑(**只 commit 不烧录**,秒级)。
|
||||
|
||||
**约束**:
|
||||
|
||||
- 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`@build` 须 0/0);
|
||||
- 测试专用钩子(如静态态复位)用 `#ifdef <模块>_UNIT_TEST` 守卫,MCU 构建不编入(零符号);
|
||||
- 单测**不能替代**板端验证:真机寄存器 / 复位原因 / 时序仍须 L1/L2 台架闭环。
|
||||
|
||||
### 4.3 新工具约定
|
||||
|
||||
- **test/ 自动化脚本为纯 Python 标准库**(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;`requirements.txt` 仍须纯 ASCII——pip 按 GBK 代码页解码);
|
||||
- **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII);
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
# 看门狗单元测试说明(主机侧)
|
||||
|
||||
> **模块**:Task Watchdog(IWDG + 任务心跳位图,`App/util/task_watchdog.c`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**(L0.5 层,见 §1.5)——在 PC 上离线验证
|
||||
> 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
||||
> **状态**:✅ 已落地(39 用例,`@build` 0/0,已挂 pre-commit 钩子)
|
||||
> **日期**:2026-10-01
|
||||
> **关联文档**:
|
||||
> [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、
|
||||
> [`测试开发标准流程.md`](测试开发标准流程.md)(测试分层与门禁总览)、
|
||||
> [`测试文档模板规范.md`](测试文档模板规范.md)(本文命名规范)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 为什么单独做主机侧单测
|
||||
|
||||
`task_watchdog.c` 分两半:
|
||||
|
||||
| 半边 | 内容 | 可测性 |
|
||||
|---|---|---|
|
||||
| **判定状态机**(被测) | 位图取走清零、必需掩码比较、`miss_streak` 累计(容忍 8 次)、100 ms 抽样节流、tick 回绕安全 | 纯逻辑,与硬件无关 → **可在 PC 上位穷举** |
|
||||
| **硬件副作用** | `IWDG->KR` 写(喂狗 / 启动)、`HAL_GetTick()` | 需真机或桩 |
|
||||
|
||||
板端套件(`test/board/`)只能"在真机上完整跑一遍",**无法穷举边界**——例如:
|
||||
|
||||
- 节流窗口边界 99 ms / 100 ms / 199 ms / 200 ms 的分野;
|
||||
- 连续缺席第 7 次 / 第 8 次 / 第 9 次的行为差异(停喂触发点);
|
||||
- `HAL_GetTick()` 跨越 2^32 回绕后是否仍正确节流。
|
||||
|
||||
这些用主机侧单测一次性钉死,且**不占 MCU 的 RAM**(本项目 RAM 已 95%,见
|
||||
`docs/内存管理/`)。设计上参照项目既有的"共享核心 + 平台后端"做法
|
||||
(`App/cfg/cfg_file_store.h`:MCU 端 `cfg_file_store_mcu.c` / 模拟器端
|
||||
`cfg_file_store_sim.c`)。
|
||||
|
||||
### 1.2 被测对象与桩
|
||||
|
||||
被测源文件以 **`#include` 原样纳入**测试单元,保证测的是**生产代码**而非副本:
|
||||
|
||||
```
|
||||
test/host/watchdog/watchdog_test.c
|
||||
├── #include "main.h" → stub/main.h (HAL_GetTick 声明)
|
||||
├── #include "iwdg.h" → stub/iwdg.h (IWDG_TypeDef + IWDG 外部声明)
|
||||
├── #include "dbg_log.h" → stub/dbg_log.h (DBG_* 映射 + 错误计数)
|
||||
└── #include "task_watchdog.c" ← 生产源文件(App/util/),原样纳入
|
||||
```
|
||||
|
||||
三个**平台钩子**(声明在 `App/util/task_watchdog.h` 末段"平台后端(内部)")在主机侧被替换为可观测桩:
|
||||
|
||||
| 钩子 | MCU 默认实现(`__weak`) | 主机侧桩 |
|
||||
|---|---|---|
|
||||
| `wd_platform_feed()` | `IWDG->KR = 0x0000AAAAu` | 计数 `s_feed_count++` + 写影子寄存器 |
|
||||
| `wd_platform_start()` | `IWDG->KR = 0x0000CCCCu` | 计数 `s_start_count++` + 写影子寄存器 |
|
||||
| `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` |
|
||||
|
||||
> **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致,
|
||||
> 无额外分支、无额外符号(`@build` 0/0 已验)。平台默认实现由
|
||||
> `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()`
|
||||
> 仅在主机侧编译(MCU 构建零代码、零符号)。
|
||||
|
||||
### 1.3 测试环境
|
||||
|
||||
| 依赖 | 版本 / 说明 |
|
||||
|---|---|
|
||||
| 编译器 | 本机 MSVC(VS2019 BuildTools 14.29 / VS2022 任一),由 `run_unit_test.py` 自动探测 |
|
||||
| Python | 仓库 `.venv` 或任意 python3(**纯标准库**) |
|
||||
| 硬件 | 无(不需要开发板 / 串口 / ST-LINK) |
|
||||
|
||||
> **为何不调 `vcvars64.bat`**:它内部调用 `reg.exe` 探测环境,本机安全策略禁止
|
||||
> `reg.exe`。`run_unit_test.py` 因此**直接拼 `INCLUDE`/`LIB` 环境变量**并用绝对
|
||||
> 路径调 `cl.exe`(只需 MSVC 版本目录 + Windows SDK 的 include/lib),无副作用、可重复。
|
||||
|
||||
### 1.4 运行方式
|
||||
|
||||
```powershell
|
||||
# 仓库根目录(统一用 .venv)
|
||||
& '.venv\Scripts\python.exe' test/host/watchdog/run_unit_test.py
|
||||
```
|
||||
|
||||
退出码:`0`=PASS / `1`=测试失败 / `2`=环境错误(找不到编译器 / 编译失败)。
|
||||
|
||||
### 1.5 回归接入
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| **触发范围** | `App/util/task_watchdog.c` / `task_watchdog.h`,或 `test/host/watchdog/**` 变动 |
|
||||
| **门禁层级** | **L0.5 层**(介于 L0 静态门禁与 L1 板端套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
|
||||
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
|
||||
| **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` |
|
||||
| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `@test` 故障注入(`docs/待处理内容.md` N-03 V2/V4)保证**真机复位链路**正确 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试用例
|
||||
|
||||
> 命名 `TC-WD-NNN`(WD = Watchdog)。全部在 `watchdog_test.c` 内,主机侧运行。
|
||||
|
||||
| 用例 | 断言 | 验证的设计点 |
|
||||
|---|---|---|
|
||||
| **TC-WD-001** | 未启动时 `poll` 不喂狗、`is_armed()==0` | 启动前不判定(`s_started` 门卫) |
|
||||
| **TC-WD-016** | 未启动时即使全员置位也不喂、不更新 `last_mask` | 同上(位图无关) |
|
||||
| **TC-WD-014** | 重复 `task_wd_start()` 只启动 IWDG 一次 | 启动幂等(IWDG 不可逆,重复启动无意义) |
|
||||
| **TC-WD-015** | `start` 内部先喂一次(`feed_count==1`) | 首个抽样窗口从启动时刻起算,避免启动即超时 |
|
||||
| **TC-WD-002** | 50 ms 内 `poll` 不喂;累计 100 ms 喂 1 次 | 100 ms 抽样节流 |
|
||||
| **TC-WD-003** | 全员置位 → 喂狗 1 次;`last_mask==0x37` | 全员到位判定 + 掩码取值 |
|
||||
| **TC-WD-004** | 不重新置位则第二轮不喂 | **取走清零**语义(旧位不残留) |
|
||||
| **TC-WD-005** | 缺 UI(bit4)→ 不喂;`last_mask` 精确记录缺失位 | 必需掩码比较 + 缺失定位 |
|
||||
| **TC-WD-006** | 连续 1..7 次缺席不报;**第 8 次**打 `[WD] task stall`;缺席期间不喂 | `WD_MISS_TOLERANCE=8`(800 ms)停喂触发点 |
|
||||
| **TC-WD-007** | 停喂后**每 100 ms 复报**一条 stall(现状行为) | 见 §4 已知行为("最后遗言"持续到复位) |
|
||||
| **TC-WD-008** | 恢复全员到位 → 立即喂狗 + `streak` 归零 | 停顿可恢复(非锁死状态机) |
|
||||
| **TC-WD-009** | 99 ms 不判 / 100 ms 判 / 再 99 ms 不判 / 累计 100 ms 判 | 节流窗口**边界**(易错点) |
|
||||
| **TC-WD-010** | `tick` 跨 2^32 回绕后 100 ms 仍正确判 | 无符号差比较(回绕安全) |
|
||||
| **TC-WD-011** | `is_armed`/`last_mask`/`miss_count` 语义 | 观测接口正确性 |
|
||||
| **TC-WD-013** | 非法任务 ID(`>= TASK_WD_ID_MAX`)被忽略,不污染位图 | 防位图越界(`task_wd_beat` 的守卫) |
|
||||
|
||||
**当前结果**:39/39 PASSED(`failed=0`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 如何扩展用例
|
||||
|
||||
1. 在 `watchdog_test.c` 加一个 `static void tc_0NN_xxx(void)`;
|
||||
2. 用 `fresh()` 开头(复位模块静态态 + 清观测计数 + 时钟归零);
|
||||
3. 用 `start_and_clear()` 启动(并清掉 `start` 自身那一次喂狗);
|
||||
4. `beat_all()` / `task_wd_beat(...)` 置位,`poll_advance(ms)` 推进时间并抽样;
|
||||
5. `CHECK(cond, "描述")` 断言;
|
||||
6. 在 `main()` 里 `fresh(); tc_0NN_xxx();` 注册。
|
||||
|
||||
> ⚠ **别忘 `fresh()`**:`task_wd_poll` 的节流基准 `s_last_tick` 与位图 / 计数都是模块
|
||||
> 静态态,跨用例不隔离会导致假失败(本项目开发时即踩到,见 §4)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 已知行为与可优化点
|
||||
|
||||
### 4.1 stall 日志在停喂后复报(TC-WD-007,现状)
|
||||
|
||||
实现(`task_watchdog.c`):
|
||||
|
||||
```c
|
||||
if (s_miss_streak < WD_MISS_TOLERANCE) { s_miss_streak++; }
|
||||
if (s_miss_streak >= WD_MISS_TOLERANCE) { DBG_ERROR("[WD] task stall: ..."); }
|
||||
```
|
||||
|
||||
`streak` 封顶 8 后,判定条件 `streak >= 8` 在**后续每轮**仍成立 → 每 100 ms 复报一条,
|
||||
直到 IWDG 约 900 ms 后复位(约 9 条/停顿周期)。
|
||||
|
||||
**评估**:这是**刻意的"最后遗言"**——复位前日志窗口内信息不丢。代价是日志冗余。
|
||||
若改为"每停顿周期仅报一次",需加一个 `s_stall_reported` 标志(在 `streak` 归零时清除)。
|
||||
**当前保留现状**(单测按现状断言,行为变更须同步改 TC-WD-007 与本文)。
|
||||
|
||||
### 4.2 测试隔离依赖 `task_wd_test_reset()`
|
||||
|
||||
`task_wd_poll` 的节流基准 `s_last_tick` 曾是**函数内 `static`**,`task_wd_test_reset()`
|
||||
够不到它 → 跨用例基线残留 → 假失败。已改为**文件静态**并纳入复位。
|
||||
(这同时是更好的生产设计:模块状态应归于模块。)
|
||||
|
||||
---
|
||||
|
||||
## 5. 与板端验证的分工
|
||||
|
||||
| 层 | 覆盖 | 状态 |
|
||||
|---|---|---|
|
||||
| **本单测(L0.5)** | 判定状态机全部逻辑分支与边界 | ✅ 已落地 |
|
||||
| 板端故障注入(L1/L2) | 真机:单任务卡死 → 停喂 → IWDG 真复位 → `RCC_CSR` 记 `IWDGRSTF` | ⏳ 待台架(`docs/待处理内容.md` N-03 V2/V4) |
|
||||
| 长跑(L2) | ≥30 min 无假复位(`miss_count` 稳定) | ⏳ 待台架(N-03 V5) |
|
||||
|
||||
> 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因
|
||||
> 寄存器的真机行为,仍需 `@test` 台架闭环确认。
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-01 | 创建:看门狗判定状态机主机侧单元测试(39 用例)+ 平台层抽取(`task_watchdog.h` 末段三个 `__weak` 钩子)+ pre-commit 钩子 `watchdog-unit-test`;记录两处已知行为(stall 复报、测试隔离) |
|
||||
+18
-2
@@ -30,11 +30,24 @@
|
||||
| `latency_peer.py` | 链路时延测试的 PC 对端(peer) |
|
||||
| `time_sync_server.py` | 时间同步推送服务(对时源) |
|
||||
|
||||
### 主机侧单元测试(纯逻辑,无硬件)
|
||||
|
||||
| 脚本 | 作用 |
|
||||
|---|---|
|
||||
| `watchdog/run_unit_test.py` | **看门狗判定状态机单测**:用 MSVC 离线编译真实 `App/util/task_watchdog.c`(可观测桩驱动),39 用例穷举边界。零硬件、秒级(退出码 0=PASS/1=FAIL/2=环境错误) |
|
||||
| `watchdog/watchdog_test.c` | 测试单元本体(`#include` 生产源码 + 平台钩子桩) |
|
||||
| `watchdog/stub/` | 桩头文件(`main.h`/`iwdg.h`/`dbg_log.h`),覆盖 MCU 版以在 PC 上编译 |
|
||||
|
||||
> 详见 `docs/测试说明/看门狗单元测试说明.md`。已挂 pre-commit 钩子 `watchdog-unit-test`。
|
||||
|
||||
## 运行方式
|
||||
|
||||
```bash
|
||||
# @test 一键闭环(推荐)
|
||||
# @test 一键闭环(推荐,需真机)
|
||||
& '.venv\Scripts\python.exe' test/host/run_boot_test.py
|
||||
|
||||
# 看门狗判定状态机单测(纯逻辑,无需真机)
|
||||
& '.venv\Scripts\python.exe' test/host/watchdog/run_unit_test.py
|
||||
```
|
||||
|
||||
> 串口端口配置在 `.opencode/serial_config.json`(单一配置源,当前 COM6/115200)。
|
||||
@@ -42,11 +55,14 @@
|
||||
## ⚠️ 注意
|
||||
|
||||
- **PYTHONPATH/路径敏感**:脚本按仓库根相对路径读写,**从仓库根执行**
|
||||
- 本目录**被 pre-commit 门禁排除**
|
||||
- 本目录**被 pre-commit 门禁的 cppcheck / 代码规范排除**(`test/` 豁免);
|
||||
但 `watchdog/run_unit_test.py` 自身**作为独立钩子**参与门禁(见上)
|
||||
- 台架探针族(TCP 复连 / FTP 中止等)在 `MDK-ARM/*.py`,与本目录分工不同
|
||||
- `watchdog/` 单测需要本机装有 MSVC(VS2019/2022 的 C++ 工具链);无则脚本报环境错误(退出码 2)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `docs/测试说明/自动化测试流程.md` — `@test` 细节与失败模式
|
||||
- `docs/测试说明/测试开发标准流程.md` — 测试分层与门禁
|
||||
- `docs/测试说明/看门狗单元测试说明.md` — 主机侧单测(用例 / 平台层 / 扩展方法)
|
||||
- `docs/测试说明/CH395F_Test_Guide.md`、`FTP_Test_Guide.md`
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""看门狗判定状态机的主机侧单元测试运行器(纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/watchdog/watchdog_test.c(它以 #include 纳入
|
||||
**生产源文件** App/util/task_watchdog.c),在 PC 上离线跑完整判定状态机,
|
||||
失败即非 0 退出。零硬件、零烧录、毫秒级。
|
||||
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
task_watchdog.c 的判定逻辑(位图取走清零 / 掩码 / miss_streak 累计 / 节流)
|
||||
是纯逻辑,与硬件无关;板端套件只能"在真机上跑一遍",无法穷举边界(节流窗口
|
||||
99/100ms、tick 回绕、连续 8 次缺席的第 8/9 次分界)。主机侧单测把这些一次性
|
||||
钉死,且不占 MCU 的 RAM(项目 RAM 已 95%)。
|
||||
|
||||
实现要点(为何不用 vcvars64.bat):
|
||||
vcvars64.bat 内部调用 reg.exe 探测环境;本机策略禁止 reg.exe。故这里**直接
|
||||
拼 INCLUDE/LIB 环境变量**并绝对路径调用 cl.exe——只需 3 个已知目录
|
||||
(MSVC 版本 / Windows SDK include / SDK lib),无副作用、可重复。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/watchdog/run_unit_test.py
|
||||
或统一用 .venv: & '.venv\\Scripts\\python.exe' test/host/watchdog/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败)。
|
||||
|
||||
设计依据:docs/测试说明/看门狗单元测试说明.md
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# ----------------------------- 路径 -----------------------------
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
STUB = os.path.join(HERE, "stub")
|
||||
SRC = os.path.join(HERE, "watchdog_test.c")
|
||||
APP_UTIL = os.path.join(ROOT, "App", "util") # task_watchdog.c 所在目录
|
||||
|
||||
VS_ROOTS = [
|
||||
r"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools",
|
||||
r"C:\Program Files\Microsoft Visual Studio\2022\BuildTools",
|
||||
r"C:\Program Files\Microsoft Visual Studio\2022\Community",
|
||||
r"C:\Program Files\Microsoft Visual Studio\2022\Professional",
|
||||
]
|
||||
SDK_ROOTS = [r"C:\Program Files (x86)\Windows Kits\10"]
|
||||
|
||||
|
||||
def _first(pattern):
|
||||
hits = sorted(glob.glob(pattern))
|
||||
return hits[-1] if hits else None
|
||||
|
||||
|
||||
def _decode(b):
|
||||
"""把 subprocess 字节输出解码为字符串:本机 MSVC 输出为 GBK(代码页 936),
|
||||
测试程序自身输出为 UTF-8;按 UTF-8 优先、失败回退 GBK 处理。"""
|
||||
if isinstance(b, str):
|
||||
return b
|
||||
try:
|
||||
return b.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return b.decode("gbk", errors="replace")
|
||||
|
||||
|
||||
def locate_toolchain():
|
||||
"""返回 (cl.exe, include_dirs, lib_dirs);找不到返回 (None, [], [])。"""
|
||||
cl = None
|
||||
for vs in VS_ROOTS:
|
||||
c = _first(os.path.join(vs, r"VC\Tools\MSVC\*\bin\Hostx64\x64\cl.exe"))
|
||||
if c:
|
||||
cl = c
|
||||
break
|
||||
if not cl:
|
||||
return None, [], []
|
||||
|
||||
msvc_ver_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(cl))))
|
||||
# cl.exe = <vs>\VC\Tools\MSVC\<ver>\bin\Hostx64\x64\cl.exe
|
||||
msvc_ver_dir = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.dirname(cl)))) # -> ...\MSVC\<ver>
|
||||
inc = [
|
||||
os.path.join(msvc_ver_dir, "include"),
|
||||
]
|
||||
lib = [
|
||||
os.path.join(msvc_ver_dir, "lib", "x64"),
|
||||
]
|
||||
|
||||
for sdk in SDK_ROOTS:
|
||||
sdk_inc = _first(os.path.join(sdk, r"Include\*"))
|
||||
sdk_lib = _first(os.path.join(sdk, r"Lib\*"))
|
||||
if sdk_inc:
|
||||
inc += [
|
||||
os.path.join(sdk_inc, "ucrt"),
|
||||
os.path.join(sdk_inc, "shared"),
|
||||
os.path.join(sdk_inc, "um"),
|
||||
]
|
||||
if sdk_lib:
|
||||
lib += [
|
||||
os.path.join(sdk_lib, "ucrt", "x64"),
|
||||
os.path.join(sdk_lib, "um", "x64"),
|
||||
]
|
||||
return cl, inc, lib
|
||||
|
||||
|
||||
def main():
|
||||
cl, inc, lib = locate_toolchain()
|
||||
if not cl:
|
||||
print("[ENV] 找不到 MSVC cl.exe(需要 VS2019/2022 的 C++ 工具链)",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
tmp = tempfile.gettempdir()
|
||||
objdir = os.path.join(tmp, "wd_test_obj")
|
||||
os.makedirs(objdir, exist_ok=True)
|
||||
exe = os.path.join(tmp, "watchdog_unit_test.exe")
|
||||
|
||||
env = dict(os.environ)
|
||||
env["INCLUDE"] = os.pathsep.join(inc)
|
||||
env["LIB"] = os.pathsep.join(lib)
|
||||
env["PATH"] = os.path.dirname(cl) + os.pathsep + env.get("PATH", "")
|
||||
|
||||
cmd = [
|
||||
cl, "/nologo", "/W4", "/std:c11", "/utf-8",
|
||||
# C4819("源代码包含不能在当前代码页中表示的字符")是**纯编码诊断**:
|
||||
# 本文件确为 UTF-8(带 BOM,已核验),且已显式传 /utf-8,字节解码无误、
|
||||
# 编译产物正确。但在"只设 INCLUDE/LIB、不调 vcvars"的调用方式下,
|
||||
# MSVC 的代码页探测会退化为 `代码页(0)` → 恒定误报 10 条。
|
||||
# 该文件仅主机侧编译(ARMCC 从不编译 test/),故显式屏蔽,保持门禁输出干净。
|
||||
"/wd4819",
|
||||
"/I", STUB, # stub 头优先于真实头(覆盖 main.h/iwdg.h/dbg_log.h)
|
||||
"/I", APP_UTIL, # task_watchdog.c / task_watchdog.h
|
||||
"/I", HERE, # (保留)本目录
|
||||
SRC,
|
||||
"/Fe:" + exe,
|
||||
"/Fo:" + objdir + os.sep,
|
||||
]
|
||||
|
||||
print("=== 编译并运行看门狗单元测试 ===")
|
||||
cp = subprocess.run(cmd, env=env, capture_output=True)
|
||||
cout = _decode(cp.stdout)
|
||||
cerr = _decode(cp.stderr)
|
||||
if cout:
|
||||
sys.stdout.write(cout)
|
||||
if cerr:
|
||||
sys.stderr.write(cerr)
|
||||
if cp.returncode != 0:
|
||||
print("[ENV] 编译失败", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
run = subprocess.run([exe], env=env, capture_output=True)
|
||||
out = _decode(run.stdout)
|
||||
sys.stdout.write(out)
|
||||
errout = _decode(run.stderr)
|
||||
if errout:
|
||||
sys.stderr.write(errout)
|
||||
|
||||
if "=== Task Watchdog Tests:" not in out:
|
||||
print("[ENV] 未产生测试汇总行", file=sys.stderr)
|
||||
return 2
|
||||
if run.returncode == 0:
|
||||
print("[OK] 单元测试全部通过")
|
||||
return 0
|
||||
print("[FAIL] 有测试用例未通过", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 主机侧单测桩:dbg_log.h
|
||||
* 模块功能:为 task_watchdog.c 提供主机可编译的日志宏(映射到 printf / 计数)。
|
||||
*
|
||||
* ⚠ 仅供 test/host/watchdog/ 使用,不参与 MCU 构建。
|
||||
*
|
||||
* 与 simulator/dbg_log.h 同思路:MCU 版依赖 sys_clock(RTC 时间戳)与 USART,
|
||||
* 主机侧替换为 printf,并把 DBG_ERROR 计入计数器供单测断言("停喂时是否打了
|
||||
* [WD] task stall" 是可观测行为的一部分)。
|
||||
*
|
||||
* 创建日期:2026-10-01
|
||||
*/
|
||||
|
||||
#ifndef DBG_LOG_H
|
||||
#define DBG_LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* 测试期望的"最后遗言"计数(在 watchdog_test.c 实现,单测断言其增量) */
|
||||
extern int g_wdtest_err_count;
|
||||
|
||||
/* 断言:停喂时应恰好出现若干条 DBG_ERROR([WD] task stall) */
|
||||
#define DBG_INFO(fmt, ...) do { } while (0)
|
||||
#define DBG_DEBUG(fmt, ...) do { } while (0)
|
||||
#define DBG_ERROR(fmt, ...) do { g_wdtest_err_count++; } while (0)
|
||||
|
||||
#endif /* DBG_LOG_H */
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 主机侧单测桩:iwdg.h
|
||||
* 模块功能:为 task_watchdog.c 提供主机可编译、可观测的 IWDG 模型。
|
||||
*
|
||||
* ⚠ 仅供 test/host/watchdog/ 使用,不参与 MCU 构建。
|
||||
*
|
||||
* 说明:真实代码写 `IWDG->KR = 0xAAAAu;`(重装/喂狗)与 0xCCCC(启动)。
|
||||
* 本桩把 IWDG 指向 watchdog_test.c 中的影子对象,写入落到普通内存;
|
||||
* 单测通过平台钩子(wd_platform_feed/start)计次观测,无需读寄存器。
|
||||
*
|
||||
* 创建日期:2026-10-01
|
||||
*/
|
||||
|
||||
#ifndef IWDG_H
|
||||
#define IWDG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 与真实芯粒同名的结构(本模块仅用到 KR) */
|
||||
typedef struct {
|
||||
volatile uint32_t KR;
|
||||
} IWDG_TypeDef;
|
||||
|
||||
extern IWDG_TypeDef *IWDG;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* IWDG_H */
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 主机侧单测桩:main.h
|
||||
* 模块功能:为 task_watchdog.c 提供主机可编译的最小替代,仅需 HAL_GetTick() 声明。
|
||||
*
|
||||
* ⚠ 仅供 test/host/watchdog/ 使用,不参与 MCU 构建。
|
||||
* MCU 构建走真实 Inc/main.h(含 stm32f4xx_hal.h);本文件因 include 路径
|
||||
* 优先级被优先命中(见 run 脚本 -I 顺序)。
|
||||
*
|
||||
* 创建日期:2026-10-01
|
||||
*/
|
||||
|
||||
#ifndef __MAIN_H
|
||||
#define __MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* HAL 毫秒时基(MCU 端 = TIM7 中断累加;主机侧由桩提供可控时钟) */
|
||||
uint32_t HAL_GetTick(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MAIN_H */
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 模块名称:Task Watchdog 主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/util/task_watchdog.c 的**判定状态机**做穷举验证。
|
||||
*
|
||||
* 被测对象:真实源文件 ../App/util/task_watchdog.c(以 #include 原样纳入,
|
||||
* 保证测的是生产代码而非副本)。硬件访问由本文件提供的三个强符号
|
||||
* 平台钩子(wd_platform_feed/start/tick_ms)替换为可观测桩。
|
||||
*
|
||||
* 隔离:每个用例前调用 task_wd_test_reset()(生产文件中由
|
||||
* TASK_WATCHDOG_UNIT_TEST 守卫的测试专用复位,MCU 构建零开销)
|
||||
* 把模块静态态恢复到"刚上电"。
|
||||
*
|
||||
* 覆盖点(对应 docs/测试说明/看门狗单元测试说明.md 的 TC-WD-xxx):
|
||||
* TC-WD-001 未启动时 poll 不做任何判定(也不喂狗)
|
||||
* TC-WD-002 节流:< 100ms 的 poll 直接返回
|
||||
* TC-WD-003 全员到位置位 → 喂狗一次,且位图取走清零
|
||||
* TC-WD-004 取走清零语义:旧位不残留(下一轮必须重新置位)
|
||||
* TC-WD-005 掩码判定:缺任一位不喂,last_mask 精确记录
|
||||
* TC-WD-006 连续 1..7 次缺席不喂不报;第 8 次打 [WD] task stall
|
||||
* TC-WD-007 停喂后 stall 日志持续复报(现状行为,见用例内说明)
|
||||
* TC-WD-008 恢复:重新全员到位 → 喂狗 + streak 归零
|
||||
* TC-WD-009 节流窗口边界 99/100/199/200
|
||||
* TC-WD-010 tick 回绕安全(无符号差)
|
||||
* TC-WD-011 观测接口:last_mask / is_armed
|
||||
* TC-WD-013 非法任务 ID 被忽略(防位图越界)
|
||||
* TC-WD-014 start 幂等
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||
*
|
||||
* 创建日期:2026-10-01
|
||||
*/
|
||||
|
||||
/* 打开生产文件里的测试专用复位钩子(MCU 构建不定义此宏) */
|
||||
#define TASK_WATCHDOG_UNIT_TEST 1
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* 先纳入桩头文件,再纳入生产源码——保证 IWDG_TypeDef / HAL_GetTick 等
|
||||
* 类型与声明在使用前可见(生产源码内部是同名 include,走 include guard 不再重复)。
|
||||
* - stub/main.h → HAL_GetTick 声明
|
||||
* - stub/iwdg.h → IWDG_TypeDef + IWDG 外部声明
|
||||
* - stub/dbg_log.h→ DBG_* 映射与 g_wdtest_err_count
|
||||
*
|
||||
* 注意:生产源码里的三个 __weak 平台默认实现由 TASK_WATCHDOG_UNIT_TEST 守卫
|
||||
* 而不编入(避免与本文件桩重名),故主机侧无需处理 __weak 关键字。
|
||||
*/
|
||||
#include "main.h"
|
||||
#include "iwdg.h"
|
||||
#include "dbg_log.h"
|
||||
|
||||
/* ===================== 平台桩:时间与 IWDG ===================== */
|
||||
|
||||
/* 可控毫秒时钟(单测手动推进;真实 MCU 由 TIM7 累加) */
|
||||
static uint32_t s_tick = 0u;
|
||||
|
||||
/* IWDG 影子寄存器:真实代码写 IWDG->KR = x,这里落到本对象 */
|
||||
static IWDG_TypeDef s_iwdg_shadow;
|
||||
IWDG_TypeDef *IWDG = &s_iwdg_shadow;
|
||||
|
||||
/* 喂狗 / 启动 计数(平台钩子观测) */
|
||||
static int s_feed_count = 0;
|
||||
static int s_start_count = 0;
|
||||
|
||||
/* DBG_ERROR 计数(停喂时应打 [WD] task stall) */
|
||||
int g_wdtest_err_count = 0;
|
||||
|
||||
/* ---- 强符号覆盖:三个平台钩子(覆盖 task_watchdog.c 的 __weak 默认实现) ---- */
|
||||
void wd_platform_feed(void) { s_feed_count++; s_iwdg_shadow.KR = 0x0000AAAAu; }
|
||||
void wd_platform_start(void) { s_start_count++; s_iwdg_shadow.KR = 0x0000CCCCu; }
|
||||
uint32_t wd_platform_tick_ms(void) { return s_tick; }
|
||||
|
||||
/* main.h 声明的 HAL 时基 */
|
||||
uint32_t HAL_GetTick(void) { return s_tick; }
|
||||
|
||||
/* ===================== 测试框架(极简) ===================== */
|
||||
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
|
||||
#define CHECK(cond, name) do { \
|
||||
s_total++; \
|
||||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||||
} while (0)
|
||||
|
||||
/* ===================== 被测代码 ===================== */
|
||||
/* 原样纳入生产源文件(stub/ 里的 main.h / iwdg.h / dbg_log.h 生效) */
|
||||
#include "task_watchdog.c"
|
||||
|
||||
/* ===================== 辅助 ===================== */
|
||||
|
||||
/* 每个用例前的干净起点:复位模块静态态 + 清空观测计数 + 时钟归零 */
|
||||
static void fresh(void) {
|
||||
task_wd_test_reset();
|
||||
s_tick = 0u;
|
||||
s_feed_count = 0;
|
||||
s_start_count = 0;
|
||||
g_wdtest_err_count = 0;
|
||||
s_iwdg_shadow.KR = 0u;
|
||||
}
|
||||
|
||||
/* 推进时间并触发一次 poll */
|
||||
static void poll_advance(uint32_t ms) {
|
||||
s_tick += ms;
|
||||
task_wd_poll();
|
||||
}
|
||||
|
||||
/*
|
||||
* 启动并清空喂狗计数:task_wd_start() 内部会先喂一次(让首个抽样窗口从启动
|
||||
* 时刻起算,见生产代码注释),该次喂狗由 TC-WD-015 单独断言;其余用例只关心
|
||||
* "启动之后 poll 的喂狗行为",故此处把计数归零。
|
||||
*/
|
||||
static void start_and_clear(void) {
|
||||
task_wd_start();
|
||||
s_feed_count = 0;
|
||||
}
|
||||
|
||||
/* 全员置位(默认必需掩码 0x37 = bit0|1|2|4|5) */
|
||||
static void beat_all(void) {
|
||||
task_wd_beat(TASK_WD_DEFAULT);
|
||||
task_wd_beat(TASK_WD_NET);
|
||||
task_wd_beat(TASK_WD_FTP);
|
||||
task_wd_beat(TASK_WD_UI);
|
||||
task_wd_beat(TASK_WD_TIMESYNC);
|
||||
}
|
||||
|
||||
/* ===================== 用例 ===================== */
|
||||
|
||||
static void tc_001_not_armed(void) {
|
||||
printf("TC-WD-001 未启动时 poll 不做判定\n");
|
||||
beat_all();
|
||||
poll_advance(1000);
|
||||
CHECK(s_feed_count == 0, "未启动时 poll 不喂狗");
|
||||
CHECK(task_wd_is_armed() == 0, "is_armed == 0(未启动)");
|
||||
}
|
||||
|
||||
static void tc_002_throttle(void) {
|
||||
printf("TC-WD-002 节流:< 100ms 直接返回\n");
|
||||
start_and_clear();
|
||||
beat_all();
|
||||
poll_advance(50); /* 50ms < 100ms → 不抽样 */
|
||||
CHECK(s_feed_count == 0, "50ms 内 poll 不喂狗");
|
||||
poll_advance(50); /* 累计 100ms → 抽样 */
|
||||
CHECK(s_feed_count == 1, "累计 100ms 后喂狗 1 次");
|
||||
}
|
||||
|
||||
static void tc_003_all_beat_feeds(void) {
|
||||
printf("TC-WD-003 全员到位 → 喂狗一次\n");
|
||||
start_and_clear();
|
||||
beat_all();
|
||||
poll_advance(100);
|
||||
CHECK(s_feed_count == 1, "全员到位喂狗 1 次");
|
||||
CHECK(task_wd_last_mask() == 0x37u, "last_mask == 0x37(bit0|1|2|4|5)");
|
||||
}
|
||||
|
||||
static void tc_004_take_and_clear(void) {
|
||||
printf("TC-WD-004 取走清零:旧位不残留\n");
|
||||
start_and_clear();
|
||||
beat_all();
|
||||
poll_advance(100); /* 消费位图并喂狗 */
|
||||
CHECK(s_feed_count == 1, "第一轮喂狗");
|
||||
poll_advance(100); /* 不重新置位 */
|
||||
CHECK(s_feed_count == 1, "未重新置位 → 第二轮不喂狗(位图已取走清零)");
|
||||
}
|
||||
|
||||
static void tc_005_mask_required(void) {
|
||||
printf("TC-WD-005 掩码判定:缺任一位不喂\n");
|
||||
start_and_clear();
|
||||
/* 故意缺 UI(bit4) */
|
||||
task_wd_beat(TASK_WD_DEFAULT);
|
||||
task_wd_beat(TASK_WD_NET);
|
||||
task_wd_beat(TASK_WD_FTP);
|
||||
task_wd_beat(TASK_WD_TIMESYNC);
|
||||
poll_advance(100);
|
||||
CHECK(s_feed_count == 0, "缺 UI 位 → 不喂狗");
|
||||
CHECK(task_wd_last_mask() == (0x37u & ~(1u << TASK_WD_UI)),
|
||||
"last_mask 精确记录缺失位");
|
||||
}
|
||||
|
||||
static void tc_006_stall_8(void) {
|
||||
printf("TC-WD-006 连续 8 次缺席 → 打 stall 并停喂\n");
|
||||
start_and_clear();
|
||||
beat_all(); poll_advance(100); /* streak 清零 */
|
||||
int err0 = g_wdtest_err_count;
|
||||
for (int i = 1; i <= 7; i++) {
|
||||
poll_advance(100);
|
||||
CHECK(g_wdtest_err_count == err0, "第 1..7 次缺席不报 stall");
|
||||
}
|
||||
poll_advance(100); /* 第 8 次 */
|
||||
CHECK(g_wdtest_err_count == err0 + 1, "第 8 次缺席打 [WD] task stall");
|
||||
CHECK(s_feed_count == 1, "全程仅在开始时喂过 1 次(缺席期间未喂)");
|
||||
}
|
||||
|
||||
static void tc_007_stall_repeats_until_reset(void) {
|
||||
printf("TC-WD-007 停喂后 stall 日志持续复报(当前设计:每 100ms 一条)\n");
|
||||
start_and_clear();
|
||||
for (int i = 0; i < 8; i++) poll_advance(100); /* 第 8 次触发 */
|
||||
int err8 = g_wdtest_err_count;
|
||||
CHECK(err8 == 1, "第 8 次缺席后已报 1 条 stall");
|
||||
poll_advance(100); /* 第 9 次 */
|
||||
poll_advance(100); /* 第 10 次 */
|
||||
/*
|
||||
* 当前实现:streak 封顶 8,但判定条件 `streak >= 8` 在后续每轮仍成立
|
||||
* → 每 100ms 复报一条("最后遗言"持续到 IWDG ~900ms 后复位)。
|
||||
* 这是**刻意记录**的现状行为:它让复位前的日志窗口内信息不丢;
|
||||
* 代价是日志冗余(约 9 条/停顿周期)。若改为"每停顿周期仅报一次",
|
||||
* 需加一个 s_stall_reported 标志,见文档"已知可优化点"。
|
||||
*/
|
||||
CHECK(g_wdtest_err_count == err8 + 2, "停喂后每次抽样都复报 stall(现状)");
|
||||
}
|
||||
|
||||
static void tc_008_recover(void) {
|
||||
printf("TC-WD-008 恢复:重新全员到位 → 喂狗 + streak 归零\n");
|
||||
start_and_clear();
|
||||
for (int i = 0; i < 8; i++) poll_advance(100); /* 触发 stall */
|
||||
beat_all();
|
||||
poll_advance(100);
|
||||
CHECK(s_feed_count == 1, "恢复后立即喂狗");
|
||||
int err0 = g_wdtest_err_count;
|
||||
poll_advance(100); /* 再缺一次 */
|
||||
CHECK(g_wdtest_err_count == err0, "streak 已归零:单次缺席不报 stall");
|
||||
}
|
||||
|
||||
static void tc_009_throttle_boundary(void) {
|
||||
printf("TC-WD-009 节流窗口边界 99/100/199/200\n");
|
||||
start_and_clear();
|
||||
beat_all();
|
||||
poll_advance(99); CHECK(s_feed_count == 0, "99ms 不判");
|
||||
poll_advance(1); CHECK(s_feed_count == 1, "100ms 判(喂狗)");
|
||||
beat_all();
|
||||
poll_advance(99); CHECK(s_feed_count == 1, "再 99ms 不判");
|
||||
poll_advance(1); CHECK(s_feed_count == 2, "累计 100ms 判");
|
||||
}
|
||||
|
||||
static void tc_010_tick_wrap(void) {
|
||||
printf("TC-WD-010 tick 回绕安全\n");
|
||||
start_and_clear();
|
||||
s_tick = 0xFFFFFF00u; /* 推到接近 2^32 回绕 */
|
||||
task_wd_poll(); /* 建立基线(此处可能不判,取决节流) */
|
||||
beat_all();
|
||||
s_feed_count = 0;
|
||||
poll_advance(0x100u); /* 跨越回绕:0xFFFFFF00 + 0x100 = 0 */
|
||||
CHECK(s_feed_count == 1, "回绕处 100ms 仍正确判(无符号差)");
|
||||
}
|
||||
|
||||
static void tc_011_observers(void) {
|
||||
printf("TC-WD-011 观测接口语义\n");
|
||||
start_and_clear();
|
||||
CHECK(task_wd_is_armed() == 1, "is_armed == 1(已启动)");
|
||||
beat_all();
|
||||
poll_advance(100);
|
||||
CHECK(task_wd_last_mask() == 0x37u, "last_mask 反映最近一次抽样");
|
||||
CHECK(task_wd_miss_count() == 0u, "全程达标 → miss_count == 0");
|
||||
}
|
||||
|
||||
static void tc_013_bad_id(void) {
|
||||
printf("TC-WD-013 非法任务 ID 被忽略\n");
|
||||
start_and_clear();
|
||||
task_wd_beat((task_wd_id_t)(TASK_WD_ID_MAX + 3)); /* 非法:应被忽略 */
|
||||
poll_advance(100);
|
||||
CHECK(s_feed_count == 0, "非法 ID 未污染位图(必需位全缺 → 不喂)");
|
||||
CHECK(task_wd_last_mask() == 0u, "位图保持 0(非法位未置)");
|
||||
}
|
||||
|
||||
static void tc_014_start_idempotent(void) {
|
||||
printf("TC-WD-014 start 幂等\n");
|
||||
task_wd_start();
|
||||
task_wd_start();
|
||||
CHECK(s_start_count == 1, "重复 start 只启动 IWDG 一次");
|
||||
CHECK(task_wd_is_armed() == 1, "is_armed == 1");
|
||||
}
|
||||
|
||||
static void tc_015_start_feeds_once(void) {
|
||||
printf("TC-WD-015 start 先喂一次(首个窗口从启动时刻起算)\n");
|
||||
task_wd_start();
|
||||
CHECK(s_start_count == 1, "start 启动 IWDG 一次");
|
||||
CHECK(s_feed_count == 1, "start 内部先喂狗一次(避免启动即超时)");
|
||||
}
|
||||
|
||||
static void tc_016_stall_not_armed_no_feed(void) {
|
||||
printf("TC-WD-016 未启动时即便全员到位也不喂\n");
|
||||
/* 未 start:poll 直接返回 */
|
||||
beat_all();
|
||||
poll_advance(500);
|
||||
CHECK(s_feed_count == 0, "未启动 → 不喂狗(无论位图)");
|
||||
CHECK(task_wd_last_mask() == 0u, "未启动 → 不更新 last_mask");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Task Watchdog 主机侧单元测试 ===\n");
|
||||
printf("被测:App/util/task_watchdog.c(判定状态机)\n\n");
|
||||
|
||||
fresh(); tc_001_not_armed();
|
||||
fresh(); tc_016_stall_not_armed_no_feed();
|
||||
fresh(); tc_014_start_idempotent();
|
||||
fresh(); tc_015_start_feeds_once();
|
||||
fresh(); tc_002_throttle();
|
||||
fresh(); tc_003_all_beat_feeds();
|
||||
fresh(); tc_004_take_and_clear();
|
||||
fresh(); tc_005_mask_required();
|
||||
fresh(); tc_006_stall_8();
|
||||
fresh(); tc_007_stall_repeats_until_reset();
|
||||
fresh(); tc_008_recover();
|
||||
fresh(); tc_009_throttle_boundary();
|
||||
fresh(); tc_010_tick_wrap();
|
||||
fresh(); tc_011_observers();
|
||||
fresh(); tc_013_bad_id();
|
||||
|
||||
printf("\n=== Task Watchdog Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user