fix(protect): 采样陈旧时独立闭锁保护(判定层失明的兜底门)
问题:L5-4 闭锁读的 adc_health_flags() 只有 adcTask 每拍跑 adc_health_check() 才可能置位。TIM3 一停,adcTask 就永久阻塞在 osThreadFlagsWait(刻意的 fail-safe),check() 不再执行 → stall 位 永远是 0,而 protectTask 仍每 20 ms 拿冻在最后一帧的相量继续判据。 窗口最坏约 800 ms(停喂阈值)+ 2498 ms(IWDG 标称)≈ 3.3 s。 方案:保护侧建一条与消费者存活完全解耦的兜底新鲜度门。 - adc_health.c/h 新增 adc_health_age_stale():无模块状态的纯判据, 参照系 adc_health_age_t 由调用方自存。故意不让 protectTask 去调 adc_health_check()——那是「每拍一次、单写者」的契约,两个上下文 同写会互相刷新差值基线,把现有判定层一起弄坏。 - 新增 ADC_HF_STALE = bit8(与判定层 bit0..bit7 位段错开)。本位 不由判定层写、不进 sticky、不进编排层 logged 位图。 - protect_task.c 用自己的时基直接读两个**中断侧递增**的计数器 (tpafe5160_frame_seq / adc_input_stm32_seq),并入掩码。两条车道 各自一份 primed:共用会让先观测的那条把标志置 1,另一条首拍拿 adv_ms = 0 算年龄 → 开机即误锁保护。 - 门限 PROT_STALE_AGE_MS = 100 + 20 = 120 ms,买的是时序阶梯: 判定层活着时它先报 0x1/0x2(带日志与自检留痕),死了只出现 0x100。闭锁最迟 140 ms,仍远小于最小动作延时(默认 1000 ms)。 实测: - @build 0 错 0 警:Code 213424 → 213520(+96 B);ZI 131480 → 131504(+24 B = 两份 age 结构);RW-data 1184 不变 - stack_report:protectTask 静态最坏链 872 B 不变(余 1176 B / 57%)、 adcTask 1320 B 不变——新判据无日志,不进最坏链 - 主机单测 45/45(新增 TC-ADCHEALTH-012..017 共 14 条断言:首拍只建 基线 / 超拍不判老 / 门限含等号 / 跨 tick 回绕 / 倒退按分工不判老 / 双观察者互不污染 / 空参数不判老) - 台架注入(一次性停 TIM3 300 ms 探针,验完已撤;探针固件与最终 固件 Program Size 逐字节相同): [+5462ms] [ERR][PROT] sampling FAULT -> protection blocked (hf=0x00000100) [+5757ms] [PROT] sampling recovered -> protection resumed 同次开机 [ADC_TASK] health: 出现 0 次;MISS 快照缺的正是 bit6 (0x1F7 - 0x1B7 = 0x40,实证 adcTask 当时确实阻塞,不是"在跑但没报"); reset cause: 仅 1 次(300 ms 被 800 ms 容忍吸收,闭锁不靠复位) 证据 docs/测试说明/boot_log_data/20261021_probe_stale_gate_boot_log.txt - 字节卫生:BOM 无 / CR 0 / U+FFFD 0;新增行无非法字面量 gate: only mem-budget/mem-numbers pending lead ledger backfill pre-commit run --all-files 失败仅这两条(台账受管快照与本树 .map 的 Flash/RAM 水位漂移,即本次 +96 B Code、+24 B ZI)。按并行分支约定 本分支不跑 --write,台账由 Lead 合并后统一回填。 文档同步:设计说明新增 §15(原相关文档改 §16),§7 位表加 bit8 行、 §8 责任矩阵与「已知窗口」段改写为已闭合、§10 盲区第 3 条、§11 用例 表、§12 判读手册、§14 改前清单全部跟上;位图配图补 bit8 列,并修掉 生成器把列数硬编码成 8 列导致新列被备注框盖住的问题。
This commit is contained in:
@@ -25,6 +25,12 @@
|
||||
* 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 对象互不干扰;空指针不判老
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||
@@ -349,6 +355,125 @@ static void tc_reinit(void) {
|
||||
"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)");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== ADC Health Tests ===\n");
|
||||
|
||||
@@ -364,6 +489,12 @@ int main(void) {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user