94b0d79026
本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里, 拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔): 一、UI 补齐(清单清空) - 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk 复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存), 记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面; ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签 (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。 - 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用 boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据 (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。 - 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28; 字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。 - 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言 425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录 (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。 二、数据可信销项 + 「数据可信」任务卡号整族清退 - 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在 docs/硬件平台/ADC通道映射说明.md §8.1 与 docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。 - 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语, 代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。 这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。 验证(实测) - @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084; RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%, 体积同码凭证与 RAM 台账已回填。 - check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动); check_content_pages 60 页 rc=0;check_font_charset / registration / coding-standard / bom-drift / mojibake / doc-links --all / layer-deps / nested-comment 全 rc=0;check_ids --strict 无错误;主机单测 ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。 未做(不是遗漏) - 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、 拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1) 已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。 - docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含; 其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
620 lines
24 KiB
C
620 lines
24 KiB
C
/*
|
||
* 模块名称:ADC 采样有效性校验主机侧单元测试
|
||
* 模块功能:在 PC 上对 App/algo/adc_health.c 做离线验证。
|
||
*
|
||
* 被测对象:真实源文件(以 #include 原样纳入,保证测的是生产代码而非副本):
|
||
* ../../../App/algo/adc_health.c
|
||
* 本层为**纯逻辑**(仅 stdint.h),**无需任何桩**。
|
||
*
|
||
* 为什么要主机侧单测(而不是只用板端闭环):
|
||
* 1. 静默/倒退/错误增量在健康台架上**不可复现**(复现 = 拔芯片/改驱动),
|
||
* 板端只能证明"健康时不误报",证不了"故障时必报";
|
||
* 2. 边界(恰好 100ms 门限 / tick 回绕 / 序号倒退后再前进 / 事件只发一拍)
|
||
* 只有合成计数序列能精确构造;
|
||
* 3. 不占 MCU 的 RAM,毫秒级完成。
|
||
*
|
||
* 覆盖点(对应 docs/算法/继电保护功能复现规划.md §3.1 采样有效性校验):
|
||
* TC-ADCHEALTH-001 init + 首拍只建基线(不误报)
|
||
* TC-ADCHEALTH-002 正常 1 kHz 推进 2000 拍:零故障零事件(健康不误报)
|
||
* TC-ADCHEALTH-003 TPAFE 静默:99ms 不报 / 100ms 报 / 恢复清除电平位、锁存位留痕
|
||
* TC-ADCHEALTH-004 STM32 静默:TPAFE 正常时只报 STM32 侧
|
||
* TC-ADCHEALTH-005 TPAFE 丢帧:d=3 → tgap+=2、事件仅当拍(边沿语义)
|
||
* TC-ADCHEALTH-006 STM32 gap 计数增量 → 事件(不入锁存:超拍非故障)
|
||
* TC-ADCHEALTH-007 序号倒退 → SEQ_BACKWARD 锁存(前进后仍留痕)
|
||
* TC-ADCHEALTH-008 FRSTDATA / STM32_ERR / HW_ERR 增量 → 事件位与锁存位编号一致
|
||
* TC-ADCHEALTH-009 tick 回绕:跨 0xFFFFFFFF 的正常推进不误报静默;回绕后冻结仍报
|
||
* TC-ADCHEALTH-010 NULL 守卫:in=NULL 跳过;out=NULL 只更新内部状态
|
||
* TC-ADCHEALTH-011 init 幂等:故障后 re-init 全清
|
||
* TC-ADCHEALTH-012 帧年龄纯判据:首拍只建基线、不判老(开机不得误锁)
|
||
* TC-ADCHEALTH-013 帧年龄纯判据:正常推进恒不判老,一次前进 3 帧也不判老
|
||
* TC-ADCHEALTH-014 帧年龄纯判据:门限含等号(差 1 不判、到点判),恢复前进即清
|
||
* TC-ADCHEALTH-015 帧年龄纯判据:tick 与序号跨 0xFFFFFFFF 不误判、停滞仍判老
|
||
* TC-ADCHEALTH-016 帧年龄纯判据:序号倒退按「有变化」处理,不判老(判据分工)
|
||
* TC-ADCHEALTH-017 帧年龄纯判据:两个 age 对象互不干扰;空指针不判老
|
||
* —— 采样通道自检 残余「码值越界(贴轨)检出」(2026-10-25 道A,标定折算落地分支加入)——
|
||
* TC-ADCHEALTH-018 释放沿后连续 20 拍贴上轨 -> 锁存 + 边沿只报一次
|
||
* TC-ADCHEALTH-019 无释放沿(开机/空载恒 0)永不锁存(合法工况不误报)
|
||
* TC-ADCHEALTH-020 中带样本清零连续计数(19+19 不锁);触轨含等号边界
|
||
* TC-ADCHEALTH-021 上下轨交替互清对侧计数,永不锁存
|
||
* TC-ADCHEALTH-022 下轨带对称锁存(STM32 削顶 = 贴底签名)
|
||
* TC-ADCHEALTH-023 rail_init 清 sticky 与释放沿记录;health_init 内含复位;
|
||
* 通道越界忽略不污染
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||
*
|
||
* 创建日期:2026-10-11
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
|
||
/* ===================== 断言 ===================== */
|
||
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)
|
||
|
||
/* ===================== 被测代码 ===================== */
|
||
/* 原样纳入生产源文件(纯逻辑,无需桩) */
|
||
#include "adc_health.c"
|
||
|
||
/* ===================== 工具 ===================== */
|
||
|
||
/* 构造一份"健康"计数快照模板:两侧序号同拍号 seq,错误计数全 0 */
|
||
static adc_health_in_t mk_in(uint32_t seq, uint32_t now_ms) {
|
||
adc_health_in_t in;
|
||
|
||
in.tpafe_seq = seq;
|
||
in.tpafe_frst_err = 0u;
|
||
in.stm32_seq = seq;
|
||
in.stm32_errs = 0u;
|
||
in.stm32_gaps = 0u;
|
||
in.stm32_hw_errs = 0u;
|
||
in.now_ms = now_ms;
|
||
return in;
|
||
}
|
||
|
||
/* ===================== TC ===================== */
|
||
|
||
/* TC-ADCHEALTH-001:init + 首拍只建基线 */
|
||
static void tc_prime(void) {
|
||
adc_health_in_t in;
|
||
adc_health_status_t st;
|
||
|
||
adc_health_init();
|
||
in = mk_in(12345u, 1000u); /* 开机时序号已非 0 */
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.flags == 0u) && (st.events == 0u) && (st.sticky == 0u) &&
|
||
(st.tpafe_gaps == 0u),
|
||
"TC-ADCHEALTH-001 first check only primes: no flags/events/sticky");
|
||
/* 第二拍序号未动但仅 1ms(< 门限)→ 仍不报 */
|
||
in.now_ms = 1001u;
|
||
adc_health_check(&in, &st);
|
||
CHECK(st.flags == 0u, "TC-ADCHEALTH-001 1ms without advance is not a stall");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-002:正常 1 kHz 推进 2000 拍零误报 */
|
||
static void tc_healthy(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
uint32_t seq = 0u, now = 0u;
|
||
int i, ok = 1;
|
||
|
||
adc_health_init();
|
||
for (i = 0; i < 2000; i++) {
|
||
in = mk_in(seq, now);
|
||
adc_health_check(&in, &st);
|
||
if ((st.flags != 0u) || (st.events != 0u) || (st.sticky != 0u)) {
|
||
ok = 0;
|
||
}
|
||
seq++;
|
||
now++;
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-002 healthy 1kHz sweep 2000 beats: zero flags/events");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-003:TPAFE 静默 99/100ms 门限 + 恢复留痕 */
|
||
static void tc_tpafe_stall(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
uint32_t seq = 500u, now = 500u;
|
||
int i;
|
||
|
||
adc_health_init();
|
||
/* 先跑 10 拍健康节拍建立"前进中"基线 */
|
||
for (i = 0; i < 10; i++) {
|
||
in = mk_in(seq, now);
|
||
adc_health_check(&in, &st);
|
||
seq++; now++;
|
||
}
|
||
/* 仅 TPAFE 冻结(STM32 继续走):前 100 拍(elapsed 0..99ms)不报。
|
||
* 注意第 1 拍会消费掉 healthy 循环末尾"未消费的那次 +1",
|
||
* 故静默计时从 now=510 起算,第 i 拍 elapsed = i。 */
|
||
for (i = 0; i < 100; i++) {
|
||
in = mk_in(seq, now);
|
||
in.stm32_seq = seq + (uint32_t)i + 1u; /* STM32 侧照常前进 */
|
||
adc_health_check(&in, &st);
|
||
now++;
|
||
}
|
||
CHECK((st.flags & ADC_HF_TPAFE_STALL) == 0u,
|
||
"TC-ADCHEALTH-003 no TPAFE stall before 100ms");
|
||
/* 第 100ms:报 */
|
||
in = mk_in(seq, now);
|
||
in.stm32_seq = seq + 100u;
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.flags & ADC_HF_TPAFE_STALL) != 0u,
|
||
"TC-ADCHEALTH-003 TPAFE stall at 100ms");
|
||
CHECK((st.sticky & ADC_HF_TPAFE_STALL) != 0u,
|
||
"TC-ADCHEALTH-003 stall latched into sticky");
|
||
CHECK(st.tpafe_stall_ms >= ADC_HEALTH_STALL_MS,
|
||
"TC-ADCHEALTH-003 stall_ms reported >= threshold");
|
||
/* 恢复:TPAFE 序号重新前进 → 电平位清除、sticky 留痕 */
|
||
in = mk_in(seq + 1u, now + 1u);
|
||
in.stm32_seq = seq + 101u;
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.flags & ADC_HF_TPAFE_STALL) == 0u,
|
||
"TC-ADCHEALTH-003 stall level clears on recovery");
|
||
CHECK((adc_health_sticky() & ADC_HF_TPAFE_STALL) != 0u,
|
||
"TC-ADCHEALTH-003 sticky keeps the fault trace after recovery");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-004:STM32 静默(TPAFE 正常) */
|
||
static void tc_stm32_stall(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
uint32_t seq = 100u, now = 100u;
|
||
int i;
|
||
|
||
adc_health_init();
|
||
for (i = 0; i < 10; i++) {
|
||
in = mk_in(seq, now);
|
||
adc_health_check(&in, &st);
|
||
seq++; now++;
|
||
}
|
||
/* STM32 冻结(TPAFE 继续走)。⚠ 第 1 拍消费 healthy 循环末尾的"未消费 +1"
|
||
* 并把静默计时起点重置为该拍 now → 第 i 拍 elapsed = i,需 101 拍才到 100ms */
|
||
for (i = 0; i < 101; i++) {
|
||
in = mk_in(seq + (uint32_t)i + 1u, now + (uint32_t)i + 1u);
|
||
in.stm32_seq = seq; /* 冻结在旧值 */
|
||
adc_health_check(&in, &st);
|
||
}
|
||
CHECK((st.flags & ADC_HF_STM32_STALL) != 0u,
|
||
"TC-ADCHEALTH-004 STM32 stall at 100ms");
|
||
CHECK((st.flags & ADC_HF_TPAFE_STALL) == 0u,
|
||
"TC-ADCHEALTH-004 healthy TPAFE not falsely stalled");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-005:TPAFE 丢帧计数 + 边沿语义 */
|
||
static void tc_tpafe_gap(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
|
||
adc_health_init();
|
||
in = mk_in(10u, 10u);
|
||
adc_health_check(&in, &st); /* 基线 */
|
||
in.tpafe_seq = 13u; in.stm32_seq = 11u; in.now_ms = 11u; /* TPAFE d=3 */
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.events & ADC_HE_TPAFE_GAP) != 0u,
|
||
"TC-ADCHEALTH-005 gap event on d>1");
|
||
CHECK(st.tpafe_gaps == 2u, "TC-ADCHEALTH-005 d=3 accumulates 2 lost points");
|
||
in.tpafe_seq = 14u; in.stm32_seq = 12u; in.now_ms = 12u; /* 恢复正常 d=1 */
|
||
adc_health_check(&in, &st);
|
||
CHECK(st.events == 0u, "TC-ADCHEALTH-005 gap event is edge-only (next beat clean)");
|
||
CHECK(st.tpafe_gaps == 2u, "TC-ADCHEALTH-005 gap counter is cumulative");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-006:STM32 gap 计数增量 → 事件、不入锁存 */
|
||
static void tc_stm32_gap(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
|
||
adc_health_init();
|
||
in = mk_in(1u, 1u);
|
||
adc_health_check(&in, &st);
|
||
in.stm32_gaps = 1u; in.tpafe_seq = 2u; in.stm32_seq = 2u; in.now_ms = 2u;
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.events & ADC_HE_STM32_GAP) != 0u,
|
||
"TC-ADCHEALTH-006 STM32 gap increment raises event");
|
||
CHECK((st.sticky & (ADC_HF_FRSTDATA | ADC_HF_STM32_ERR |
|
||
ADC_HF_HW_ERR)) == 0u,
|
||
"TC-ADCHEALTH-006 gap does NOT latch sticky (overrun is not a fault)");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-007:序号倒退 → 锁存 */
|
||
static void tc_backward(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
uint32_t s, t;
|
||
int i;
|
||
|
||
adc_health_init();
|
||
in = mk_in(1000u, 100u);
|
||
adc_health_check(&in, &st);
|
||
in.tpafe_seq = 995u; in.stm32_seq = 995u; in.now_ms = 101u; /* 倒退 */
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.flags & ADC_HF_SEQ_BACKWARD) != 0u,
|
||
"TC-ADCHEALTH-007 backward seq raises latched flag");
|
||
CHECK((st.sticky & ADC_HF_SEQ_BACKWARD) != 0u,
|
||
"TC-ADCHEALTH-007 backward latched into sticky");
|
||
/* 恢复正常推进 50 拍:SEQ_BACKWARD 不清(锁存语义) */
|
||
s = 996u; t = 102u;
|
||
for (i = 0; i < 50; i++) {
|
||
in = mk_in(s, t);
|
||
adc_health_check(&in, &st);
|
||
s++; t++;
|
||
}
|
||
CHECK((adc_health_flags() & ADC_HF_SEQ_BACKWARD) != 0u,
|
||
"TC-ADCHEALTH-007 SEQ_BACKWARD persists after normal advance");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-008:三类错误计数增量 → 事件位与锁存位编号一致 */
|
||
static void tc_err_counters(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
|
||
adc_health_init();
|
||
in = mk_in(1u, 1u);
|
||
adc_health_check(&in, &st);
|
||
|
||
in.tpafe_seq = 2u; in.stm32_seq = 2u; in.now_ms = 2u;
|
||
in.tpafe_frst_err = 1u;
|
||
adc_health_check(&in, &st);
|
||
CHECK(((st.events & ADC_HE_FRSTDATA) != 0u) &&
|
||
((st.sticky & ADC_HF_FRSTDATA) != 0u),
|
||
"TC-ADCHEALTH-008 FRSTDATA increment: event + sticky (same bit number)");
|
||
|
||
in.tpafe_seq = 3u; in.stm32_seq = 3u; in.now_ms = 3u;
|
||
in.stm32_errs = 1u;
|
||
adc_health_check(&in, &st);
|
||
CHECK(((st.events & ADC_HE_STM32_ERR) != 0u) &&
|
||
((st.sticky & ADC_HF_STM32_ERR) != 0u),
|
||
"TC-ADCHEALTH-008 STM32 err increment: event + sticky (same bit number)");
|
||
|
||
in.tpafe_seq = 4u; in.stm32_seq = 4u; in.now_ms = 4u;
|
||
in.stm32_hw_errs = 1u;
|
||
adc_health_check(&in, &st);
|
||
CHECK(((st.events & ADC_HE_HW_ERR) != 0u) &&
|
||
((st.sticky & ADC_HF_HW_ERR) != 0u),
|
||
"TC-ADCHEALTH-008 HAL err increment: event + sticky (same bit number)");
|
||
|
||
/* 计数不再变化 → 事件不再重复触发 */
|
||
in.tpafe_seq = 5u; in.stm32_seq = 5u; in.now_ms = 5u;
|
||
adc_health_check(&in, &st);
|
||
CHECK(st.events == 0u, "TC-ADCHEALTH-008 steady error counters raise no repeat events");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-009:tick 回绕安全 */
|
||
static void tc_tick_wrap(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
uint32_t seq = 70000u, now = 0xFFFFFF00u;
|
||
int i, ok = 1;
|
||
|
||
adc_health_init();
|
||
/* 跨 0xFFFFFFFF 正常推进 512 拍:不得误报静默 */
|
||
for (i = 0; i < 512; i++) {
|
||
in = mk_in(seq, now);
|
||
adc_health_check(&in, &st);
|
||
if (st.flags != 0u) {
|
||
ok = 0;
|
||
}
|
||
seq++; now++; /* uint32 自然回绕 */
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-009 tick wrap sweep: no false stall");
|
||
/* 回绕后两侧同时冻结。⚠ 第 1 拍消费 sweep 末尾的"未消费 +1"并重置计时起点
|
||
* → 第 i 拍 elapsed = i,需 101 拍才到 100ms */
|
||
for (i = 0; i < 101; i++) {
|
||
in = mk_in(seq, now);
|
||
adc_health_check(&in, &st);
|
||
now++;
|
||
}
|
||
CHECK(((st.flags & ADC_HF_TPAFE_STALL) != 0u) &&
|
||
((st.flags & ADC_HF_STM32_STALL) != 0u),
|
||
"TC-ADCHEALTH-009 stall still detected after tick wrap");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-010:NULL 守卫 */
|
||
static void tc_null_guard(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
int i;
|
||
|
||
adc_health_init();
|
||
in = mk_in(1u, 1u);
|
||
adc_health_check(&in, &st);
|
||
st.flags = 0xA5A5A5A5u;
|
||
adc_health_check(NULL, &st);
|
||
CHECK(st.flags == 0xA5A5A5A5u, "TC-ADCHEALTH-010 in=NULL leaves out untouched");
|
||
|
||
/* out=NULL:内部状态照常更新(两侧冻结 101 拍 → 静默位应出现) */
|
||
for (i = 0; i < 101; i++) {
|
||
in = mk_in(1u, (uint32_t)(2 + i));
|
||
adc_health_check(&in, NULL);
|
||
}
|
||
CHECK((adc_health_flags() & ADC_HF_TPAFE_STALL) != 0u,
|
||
"TC-ADCHEALTH-010 out=NULL still updates internal state");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-011:init 幂等 / 故障后全清 */
|
||
static void tc_reinit(void) {
|
||
adc_health_status_t st;
|
||
adc_health_in_t in;
|
||
int i;
|
||
|
||
adc_health_init();
|
||
in = mk_in(1u, 1u);
|
||
adc_health_check(&in, &st);
|
||
for (i = 0; i < 150; i++) { /* 制造双侧静默 */
|
||
in = mk_in(1u, (uint32_t)(2 + i));
|
||
adc_health_check(&in, &st);
|
||
}
|
||
CHECK(st.sticky != 0u, "TC-ADCHEALTH-011 pre: sticky non-zero before re-init");
|
||
adc_health_init();
|
||
CHECK((adc_health_flags() == 0u) && (adc_health_sticky() == 0u) &&
|
||
(adc_health_tpafe_gaps() == 0u),
|
||
"TC-ADCHEALTH-011 re-init clears flags/sticky/gaps");
|
||
in = mk_in(9u, 9u); /* 序号跳变也无妨:首拍只建基线 */
|
||
adc_health_check(&in, &st);
|
||
CHECK((st.flags == 0u) && (st.events == 0u),
|
||
"TC-ADCHEALTH-011 re-prime after re-init is clean");
|
||
}
|
||
|
||
/* ===================== 帧年龄纯判据(保护侧兜底门专用) ===================== */
|
||
|
||
/* 构造一个"未建基线"的参照系(本测试只用 stdint/stdio,不引 string.h) */
|
||
static adc_health_age_t mk_age(void) {
|
||
adc_health_age_t a;
|
||
|
||
a.seq = 0u;
|
||
a.adv_ms = 0u;
|
||
a.primed = 0u;
|
||
return a;
|
||
}
|
||
|
||
/* TC-ADCHEALTH-012:首拍只建基线 */
|
||
static void tc_age_prime(void) {
|
||
adc_health_age_t age = mk_age();
|
||
|
||
CHECK(adc_health_age_stale(&age, 4242u, 1000u, 120u) == 0u,
|
||
"TC-ADCHEALTH-012 first observation only primes");
|
||
CHECK((age.primed == 1u) && (age.seq == 4242u) && (age.adv_ms == 1000u),
|
||
"TC-ADCHEALTH-012 baseline recorded by first call");
|
||
CHECK(adc_health_age_stale(&age, 4242u, 1001u, 120u) == 0u,
|
||
"TC-ADCHEALTH-012 1ms idle is not stale");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-013:正常推进与超拍都不该判老 */
|
||
static void tc_age_healthy(void) {
|
||
adc_health_age_t age = mk_age();
|
||
uint32_t seq = 0u, now = 0u;
|
||
int i, ok = 1;
|
||
|
||
for (i = 0; i < 2000; i++) {
|
||
if (adc_health_age_stale(&age, seq, now, 120u) != 0u) {
|
||
ok = 0;
|
||
}
|
||
seq += ((i % 7) == 6) ? 3u : 1u; /* 每 7 拍造一次「前进 3 帧」的超拍 */
|
||
now += 1u;
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-013 advancing (incl. d=3 overrun) never goes stale");
|
||
CHECK(now > 120u, "TC-ADCHEALTH-013 sweep exceeded the threshold span");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-014:门限含等号 + 恢复即清 */
|
||
static void tc_age_threshold(void) {
|
||
adc_health_age_t age = mk_age();
|
||
uint32_t t;
|
||
int ok = 1;
|
||
|
||
(void)adc_health_age_stale(&age, 10u, 0u, 120u); /* 建基线 */
|
||
(void)adc_health_age_stale(&age, 11u, 10u, 120u); /* 前进:adv_ms = 10 */
|
||
for (t = 11u; t < 130u; t++) {
|
||
uint8_t want = ((t - 10u) >= 120u) ? 1u : 0u;
|
||
if (adc_health_age_stale(&age, 11u, t, 120u) != want) {
|
||
ok = 0;
|
||
}
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-014 stale exactly at age_ms (inclusive boundary)");
|
||
CHECK(adc_health_age_stale(&age, 12u, 200u, 120u) == 0u,
|
||
"TC-ADCHEALTH-014 advancing again clears staleness");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-015:tick 与序号一起跨回绕 */
|
||
static void tc_age_wrap(void) {
|
||
adc_health_age_t age = mk_age();
|
||
uint32_t seq = 700000u, now = 0xFFFFFF00u;
|
||
int i, ok = 1;
|
||
|
||
for (i = 0; i < 512; i++) {
|
||
if (adc_health_age_stale(&age, seq, now, 120u) != 0u) {
|
||
ok = 0;
|
||
}
|
||
seq++; now++; /* uint32 自然回绕 */
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-015 sweep across 0xFFFFFFFF never goes stale");
|
||
seq--; /* 回到最后一次被观测到的值:此后两侧冻结 */
|
||
for (i = 0; i < 121; i++) {
|
||
now++;
|
||
}
|
||
CHECK(adc_health_age_stale(&age, seq, now, 120u) != 0u,
|
||
"TC-ADCHEALTH-015 still detected stale after wrap");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-016:倒退按「有变化」处理,不判老 */
|
||
static void tc_age_backward(void) {
|
||
adc_health_age_t age = mk_age();
|
||
|
||
(void)adc_health_age_stale(&age, 1000u, 100u, 120u);
|
||
CHECK(adc_health_age_stale(&age, 995u, 101u, 120u) == 0u,
|
||
"TC-ADCHEALTH-016 backward counts as change, not staleness");
|
||
/* 倒退已把参照系重新对齐到 995(adv_ms = 101):此后停在 995 上,
|
||
* 差 1 ms 不判老、到点才判老——把"重对齐后计时起点跟着走"钉住 */
|
||
CHECK(adc_health_age_stale(&age, 995u, 220u, 120u) == 0u,
|
||
"TC-ADCHEALTH-016 119ms after re-alignment is not stale yet");
|
||
CHECK(adc_health_age_stale(&age, 995u, 221u, 120u) != 0u,
|
||
"TC-ADCHEALTH-016 baseline re-aligned by backward step");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-017:两个观察者互不干扰 + 空指针守卫 */
|
||
static void tc_age_two_observers(void) {
|
||
adc_health_age_t a = mk_age(); /* 模拟 adcTask:每拍观测 */
|
||
adc_health_age_t b = mk_age(); /* 模拟 protectTask:每 20 拍观测 */
|
||
uint32_t seq = 500u;
|
||
uint32_t t;
|
||
int ok = 1;
|
||
|
||
(void)adc_health_age_stale(&a, seq, 0u, 100u);
|
||
(void)adc_health_age_stale(&b, seq, 0u, 120u);
|
||
for (t = 1u; t <= 200u; t++) {
|
||
(void)adc_health_age_stale(&a, seq + t, t, 100u);
|
||
if ((t % 20u) == 0u) {
|
||
if (adc_health_age_stale(&b, seq + t, t, 120u) != 0u) {
|
||
ok = 0;
|
||
}
|
||
}
|
||
}
|
||
CHECK(ok, "TC-ADCHEALTH-017 two independent observers do not pollute each other");
|
||
CHECK(adc_health_age_stale(0, 1u, 1u, 120u) == 0u,
|
||
"TC-ADCHEALTH-017 NULL age never reports stale (no false lock)");
|
||
}
|
||
|
||
/* ===================== 采样通道自检 贴轨观察器(2026-10-25 道A) ===================== */
|
||
|
||
/* 便捷喂数:连发 n 拍同值,返回最后一次 watch 的边沿结果 */
|
||
static uint32_t feed(uint8_t ch, int32_t code, int n) {
|
||
uint32_t edge = 0u;
|
||
int i;
|
||
|
||
for (i = 0; i < n; i++) {
|
||
edge = adc_health_rail_watch(ch, code, -32112, 32112);
|
||
}
|
||
return edge;
|
||
}
|
||
|
||
/* TC-ADCHEALTH-018:释放沿后连续 20 拍贴上轨 -> 锁存,边沿只报一次 */
|
||
static void tc_rail_latch_after_release(void) {
|
||
uint32_t edge;
|
||
|
||
adc_health_rail_init();
|
||
(void)feed(0, 0, 1);
|
||
edge = feed(0, 32500, 19);
|
||
CHECK(edge == 0u && adc_health_rail_sticky() == 0u,
|
||
"018: 19 hi beats after release -> no latch");
|
||
edge = feed(0, 32600, 1);
|
||
CHECK(edge == 1u, "018: 20th consecutive hi beat -> edge");
|
||
CHECK(adc_health_rail_sticky() == (1uL << 0), "018: sticky bit0");
|
||
edge = feed(0, 32700, 5);
|
||
CHECK(edge == 0u, "018: no repeat edge after latched");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-019:无释放沿(恒 0)永不锁存——空载/开机合法工况 */
|
||
static void tc_rail_static_zero_ok(void) {
|
||
adc_health_rail_init();
|
||
(void)feed(3, 0, 30);
|
||
CHECK(adc_health_rail_sticky() == 0u,
|
||
"019: static in-band zero (no release) never latches");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-020:计数被中带清零;再连续 20 才锁存(含触轨带含等号边界) */
|
||
static void tc_rail_reset_by_mid(void) {
|
||
uint32_t edge;
|
||
|
||
adc_health_rail_init();
|
||
(void)feed(1, 0, 1);
|
||
(void)feed(1, 32113, 19);
|
||
(void)feed(1, 0, 1);
|
||
edge = feed(1, 32112, 19);
|
||
CHECK(edge == 0u && adc_health_rail_sticky() == 0u,
|
||
"020: mid sample resets run counter (19+19 never hits 20)");
|
||
edge = feed(1, 32112, 1);
|
||
CHECK(edge == 1u && adc_health_rail_sticky() == (1uL << 1),
|
||
"020: fresh 20-run latches; boundary code == hi_th counts as rail");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-021:上下轨交替互不清锁存但各自清零对侧计数 */
|
||
static void tc_rail_alt_no_latch(void) {
|
||
int i;
|
||
|
||
adc_health_rail_init();
|
||
(void)feed(2, 0, 1);
|
||
for (i = 0; i < 60; i++) {
|
||
(void)adc_health_rail_watch(2, ((i & 1) != 0) ? 32500 : -32500, -32112, 32112);
|
||
}
|
||
CHECK(adc_health_rail_sticky() == 0u,
|
||
"021: alternating rails clear each other, never latch");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-022:下轨对称路径(STM32 削顶 = 贴底签名,同样先要释放沿) */
|
||
static void tc_rail_low_side(void) {
|
||
uint32_t edge = 0u;
|
||
int i;
|
||
|
||
adc_health_rail_init();
|
||
(void)adc_health_rail_watch(5, 2000, 81, 4013);
|
||
for (i = 0; i < 20; i++) {
|
||
edge |= adc_health_rail_watch(5, 0, 81, 4013);
|
||
}
|
||
CHECK(edge == 1u && adc_health_rail_sticky() == (1uL << 5),
|
||
"022: low rail band latches symmetrically (ch5)");
|
||
}
|
||
|
||
/* TC-ADCHEALTH-023:rail_init 全清(含释放沿记录);health_init 内含;越界通道忽略 */
|
||
static void tc_rail_reset_and_oob(void) {
|
||
adc_health_rail_init();
|
||
(void)feed(0, 0, 1);
|
||
(void)feed(0, 32500, 20);
|
||
CHECK(adc_health_rail_sticky() != 0u, "023: pre-check latch happens");
|
||
adc_health_rail_init();
|
||
CHECK(adc_health_rail_sticky() == 0u, "023: rail_init clears sticky");
|
||
(void)feed(0, 32500, 25);
|
||
CHECK(adc_health_rail_sticky() == 0u, "023: release history cleared too");
|
||
(void)feed(0, 0, 1);
|
||
(void)feed(0, 32500, 20);
|
||
CHECK(adc_health_rail_sticky() != 0u, "023: pre-check relatch for health_init test");
|
||
adc_health_init();
|
||
CHECK(adc_health_rail_sticky() == 0u, "023: adc_health_init() also clears rail");
|
||
CHECK(adc_health_rail_watch(9, 32767, -32112, 32112) == 0u,
|
||
"023: ch OOB ignored, no sticky pollution");
|
||
}
|
||
|
||
int main(void) {
|
||
printf("=== ADC Health Tests ===\n");
|
||
|
||
printf("-- prime / healthy --\n");
|
||
tc_prime();
|
||
tc_healthy();
|
||
printf("-- stall (TPAFE / STM32) --\n");
|
||
tc_tpafe_stall();
|
||
tc_stm32_stall();
|
||
printf("-- gap / backward / err counters --\n");
|
||
tc_tpafe_gap();
|
||
tc_stm32_gap();
|
||
tc_backward();
|
||
tc_err_counters();
|
||
printf("-- tick wrap / guards / re-init --\n");
|
||
tc_age_prime();
|
||
tc_age_healthy();
|
||
tc_age_threshold();
|
||
tc_age_wrap();
|
||
tc_age_backward();
|
||
tc_age_two_observers();
|
||
tc_tick_wrap();
|
||
tc_null_guard();
|
||
tc_reinit();
|
||
printf("-- 采样通道自检 rail watch --\n");
|
||
tc_rail_latch_after_release();
|
||
tc_rail_static_zero_ok();
|
||
tc_rail_reset_by_mid();
|
||
tc_rail_alt_no_latch();
|
||
tc_rail_low_side();
|
||
tc_rail_reset_and_oob();
|
||
|
||
printf("\n=== ADC Health Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|