1. 默认低频档频率合理窗 20~120kHz, 出界即告警(不做档位判定/灰带):
TaskLoop.c 新增 coil_monitor() — 稳定+无车空闲由无车主路径调用,
Origin 反推空场 f, f<20kHz→L偏大/f>120kHz→L偏小, 1s 防抖置位/清除
2. 告警形式: 黄灯"一长一短" 200ms亮/100ms灭/100ms亮/800ms灭 循环 (模式5)
3. 黄灯优先级: 断开快闪 > 故障补偿(学习)常亮 > 电感量一长一短 > N短闪 > 灭
4. 故障补偿等待 ENV_RESYNC_WAIT 6000→1000 (60s→10s): 总补偿 ~66s→~21s
验证: tests/test_coil_range.c 5场景(边界19/20/120/121+防抖) ALL PASS;
test_env_resync.c 适配10s(applied_at=1600tick) 5场景 ALL PASS; 语法 0 error
文档: devlog V2.24/spec V2.5/manual V2.9/design doc v0.3(标记已实现)
修复: 源文件行尾多CR污染(\\r\\r\\r\\r\\n)统一规范化回标准CRLF
336 lines
12 KiB
C
336 lines
12 KiB
C
/*
|
|
* test_env_resync.c — 环境重校准(受控基线更新) gcc 隔离单测 (DLD154V4B V4.26)
|
|
*
|
|
* 验证对象: TaskLoop.c vd1_task() 无车段 dev>0 分支 (V4.24/25 两段式, V4.26 参数更新)
|
|
* ─ 现场场景 (2026-09-02 实测): 线圈旁金属板A上电 → Origin 学低(128096)
|
|
* A 拿走 → CAPVD 弹回 128856 dev=+761(0.59%) > +4dlt → 冻结累计
|
|
* ─ V4.26 拍板: ENV_RESYNC_WAIT 60s→10s(故障补偿提速), 其余语义不变
|
|
*
|
|
* 黄灯语义 (poll_yellow_led): 5s 预判锁存(g_env_warn, 回摆保持) →
|
|
* 10s 进入学习(g_env_resync, 常亮) → 空闲原子替换 Origin → 空闲 5s 恢复确认 → 灭
|
|
*
|
|
* 镜像宏与固件一致(改 ENV_RESYNC_WAIT 时同步此处!)
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
#define ENV_WARN_WAIT 500
|
|
#define ENV_RECOVER_WAIT 500
|
|
#define ENV_RESYNC_WAIT 1000 /* V4.26: 60s->10s (拍板4) */
|
|
#define ENV_LEARN_WINDOW 200
|
|
#define ENV_LEARN_MAX_TICKS 3000
|
|
#define STABLE_ORIGIN_PPT 1
|
|
#define STABLE_SETTLE_WINDOWS 2
|
|
|
|
#define FREEZE_BAND_MULT 4 /* freeze_band = dlt*4 */
|
|
|
|
/* --- 镜像状态 --- */
|
|
static uint32_t sim_Origin = 128095UL;
|
|
static uint32_t sim_CAPVD = 128856UL;
|
|
static uint32_t sim_dlt = 48UL;
|
|
static uint8_t sim_learn_start_cnt = 0;
|
|
static uint8_t sim_applied = 0;
|
|
static uint32_t sim_applied_origin = 0;
|
|
static uint32_t sim_applied_tick = 0;
|
|
static uint8_t sim_abort = 0;
|
|
static uint32_t sim_tick_cnt = 0;
|
|
|
|
/* --- g_env_* 镜像 (与固件同名) --- */
|
|
static uint8_t g_env_resync = 0;
|
|
static uint16_t g_env_wait_cnt = 0;
|
|
static uint32_t g_env_sum = 0;
|
|
static uint16_t g_env_cnt = 0;
|
|
static uint32_t g_env_prev = 0;
|
|
static uint8_t g_env_settle = 0;
|
|
static uint16_t g_env_tick = 0;
|
|
static uint8_t g_env_warn = 0;
|
|
static uint16_t g_env_recover_cnt = 0;
|
|
|
|
static void env_reset(void)
|
|
{
|
|
g_env_resync = 0;
|
|
g_env_wait_cnt = 0;
|
|
g_env_sum = 0;
|
|
g_env_cnt = 0;
|
|
g_env_prev = 0;
|
|
g_env_settle = 0;
|
|
g_env_tick = 0;
|
|
g_env_warn = 0;
|
|
g_env_recover_cnt = 0;
|
|
sim_learn_start_cnt = 0;
|
|
sim_applied = 0;
|
|
sim_abort = 0;
|
|
sim_tick_cnt = 0;
|
|
}
|
|
|
|
static int env_warn_active(void)
|
|
{
|
|
return g_env_resync || g_env_warn; /* poll_yellow_led 模式4 条件镜像 */
|
|
}
|
|
|
|
static void env_resync_abort(void)
|
|
{
|
|
g_env_resync = 0;
|
|
g_env_sum = 0;
|
|
g_env_cnt = 0;
|
|
g_env_prev = 0;
|
|
g_env_settle = 0;
|
|
g_env_tick = 0;
|
|
sim_abort++;
|
|
}
|
|
|
|
/* vd1_task 无车段 dev 三分支的 env 相关逻辑镜像 (V4.26) */
|
|
static void sim_tick(void)
|
|
{
|
|
int32_t dev;
|
|
int32_t freeze_band;
|
|
|
|
sim_tick_cnt++;
|
|
dev = (int32_t)sim_CAPVD - (int32_t)sim_Origin;
|
|
freeze_band = (int32_t)(sim_dlt * FREEZE_BAND_MULT);
|
|
|
|
if (dev < freeze_band && dev > -freeze_band) {
|
|
/* 对称窗口内 → 正常基线跟踪 (env 相关) */
|
|
g_env_wait_cnt = 0;
|
|
if (g_env_resync) env_resync_abort();
|
|
/* V4.25: 恢复确认 — 真空闲(未进进入线)连续 5s 解除预判锁存 */
|
|
if (dev > -(int32_t)sim_dlt) {
|
|
if (g_env_warn) {
|
|
g_env_recover_cnt++;
|
|
if (g_env_recover_cnt >= ENV_RECOVER_WAIT) {
|
|
g_env_warn = 0;
|
|
g_env_recover_cnt = 0;
|
|
}
|
|
}
|
|
} else {
|
|
g_env_recover_cnt = 0; /* 目标在进入确认区 → 恢复计时重来 */
|
|
}
|
|
} else if (dev < 0) {
|
|
/* 车方向 → 冻结 (env 相关) */
|
|
g_env_wait_cnt = 0;
|
|
g_env_recover_cnt = 0;
|
|
if (g_env_resync) env_resync_abort();
|
|
} else {
|
|
/* dev>0 高位 — 两段式 (V4.24) + 预判锁存 (V4.25) */
|
|
g_env_recover_cnt = 0;
|
|
if (!g_env_resync) {
|
|
g_env_wait_cnt++;
|
|
if (g_env_wait_cnt >= ENV_WARN_WAIT)
|
|
g_env_warn = 1; /* 5s 预判锁存 */
|
|
if (g_env_wait_cnt >= ENV_RESYNC_WAIT) {
|
|
sim_learn_start_cnt++;
|
|
g_env_resync = 1;
|
|
g_env_wait_cnt = 0;
|
|
g_env_sum = 0; g_env_cnt = 0; g_env_prev = 0;
|
|
g_env_settle = 0; g_env_tick = 0;
|
|
}
|
|
} else {
|
|
/* 学习中: 窗口均值 + settle 判稳 */
|
|
g_env_tick++;
|
|
g_env_sum += sim_CAPVD;
|
|
g_env_cnt++;
|
|
if (g_env_cnt >= ENV_LEARN_WINDOW) {
|
|
uint32_t _m = g_env_sum / g_env_cnt;
|
|
g_env_sum = 0;
|
|
g_env_cnt = 0;
|
|
if (g_env_prev != 0) {
|
|
uint32_t _drift = (_m > g_env_prev) ? (_m - g_env_prev) : (g_env_prev - _m);
|
|
uint32_t _band = _m * STABLE_ORIGIN_PPT / 1000;
|
|
if (_drift <= _band)
|
|
g_env_settle++;
|
|
else
|
|
g_env_settle = 0;
|
|
if (g_env_settle >= STABLE_SETTLE_WINDOWS) {
|
|
sim_applied = 1;
|
|
sim_applied_origin = _m;
|
|
sim_applied_tick = sim_tick_cnt;
|
|
sim_Origin = _m; /* 原子替换 (空闲由上下文保证) */
|
|
g_env_resync = 0;
|
|
g_env_wait_cnt = 0;
|
|
g_env_sum = 0; g_env_cnt = 0;
|
|
g_env_prev = 0; g_env_settle = 0; g_env_tick = 0;
|
|
return;
|
|
}
|
|
}
|
|
g_env_prev = _m;
|
|
}
|
|
if (g_env_tick >= ENV_LEARN_MAX_TICKS) {
|
|
env_resync_abort(); /* 预算超时放弃 (保留旧 Origin) */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ============ 场景 1: 永久变化 (A拿走) ~21s 恢复 ============ */
|
|
static void test_permanent_change_recovers(void)
|
|
{
|
|
int tick, warn_at = 0;
|
|
|
|
env_reset();
|
|
sim_Origin = 128095UL; sim_CAPVD = 128856UL;
|
|
|
|
for (tick = 0; tick < 4000 && !sim_applied; tick++) {
|
|
sim_tick();
|
|
if (!warn_at && env_warn_active())
|
|
warn_at = (int)sim_tick_cnt;
|
|
}
|
|
printf("1. 永久变化: warn_at=%d learn=%d applied_at=%d Origin=%u (期望 warn 500, learn>=1000, applied~1600)\n",
|
|
warn_at, sim_learn_start_cnt, (int)sim_applied_tick, sim_Origin);
|
|
|
|
assert(warn_at == 500); /* 5s 预判锁存 */
|
|
assert(sim_learn_start_cnt == 1);
|
|
assert(sim_applied == 1);
|
|
assert(sim_applied_origin == 128856UL); /* 替换到真空场 */
|
|
assert(sim_Origin == 128856UL);
|
|
assert(env_warn_active() == 1); /* 替换后黄灯保持 (等待恢复确认) */
|
|
|
|
/* 空闲 5s 恢复确认 → 灭 */
|
|
for (tick = 0; tick < 600; tick++)
|
|
sim_tick();
|
|
assert(env_warn_active() == 0);
|
|
printf(" 恢复确认后黄灯灭 OK\n");
|
|
}
|
|
|
|
/* ============ 场景 2: 瞬态高位 (<10s 回摆) 不学, 锁存保持到恢复确认 ============ */
|
|
static void test_transient_no_learn(void)
|
|
{
|
|
int i;
|
|
|
|
env_reset();
|
|
sim_Origin = 128095UL; sim_CAPVD = 128856UL;
|
|
|
|
/* 高位 9s (900tick): 预判 5s 亮, 但 <10s 不学习 */
|
|
for (i = 0; i < 900; i++)
|
|
sim_tick();
|
|
assert(env_warn_active() == 1);
|
|
assert(sim_learn_start_cnt == 0);
|
|
|
|
/* 回摆进窗口: wait 清零但预判锁存保持 */
|
|
sim_CAPVD = 128100UL; /* dev=+5 */
|
|
sim_tick();
|
|
assert(g_env_wait_cnt == 0);
|
|
assert(env_warn_active() == 1); /* 回摆不清黄灯 (拍板锁存语义) */
|
|
|
|
/* 窗口空闲 499tick (累计500恢复确认) → 灭 */
|
|
for (i = 0; i < 499; i++)
|
|
sim_tick();
|
|
assert(env_warn_active() == 0);
|
|
assert(g_env_recover_cnt == 0);
|
|
|
|
/* 再高位 9s → 又锁存, 仍不学习 */
|
|
sim_CAPVD = 128856UL;
|
|
for (i = 0; i < 900; i++)
|
|
sim_tick();
|
|
assert(env_warn_active() == 1);
|
|
assert(sim_learn_start_cnt == 0);
|
|
assert(sim_applied == 0);
|
|
assert(sim_Origin == 128095UL);
|
|
printf("2. 瞬态高位(<10s)不学, 锁存保持, 恢复确认后灭 OK\n");
|
|
}
|
|
|
|
/* ============ 场景 3: 学习期打断重算 + 最终恢复 ============ */
|
|
static void test_learn_interrupt_retry(void)
|
|
{
|
|
int i;
|
|
int learn_start_before;
|
|
|
|
env_reset();
|
|
sim_Origin = 128095UL; sim_CAPVD = 128856UL;
|
|
|
|
/* 10s 高位 → 学习开始 */
|
|
for (i = 0; i < 1000; i++)
|
|
sim_tick();
|
|
assert(sim_learn_start_cnt == 1);
|
|
assert(g_env_resync == 1);
|
|
|
|
/* 学习 100 tick 后目标靠近 (dev=-95 进入确认区) → 打断 */
|
|
for (i = 0; i < 100; i++)
|
|
sim_tick();
|
|
sim_CAPVD = 128000UL; /* dev=-95 */
|
|
sim_tick();
|
|
assert(g_env_resync == 0); /* 打断退出学习 */
|
|
assert(sim_abort >= 1);
|
|
assert(env_warn_active() == 1); /* 预判锁存保持 (环境未确认恢复) */
|
|
assert(g_env_recover_cnt == 0); /* 目标在进入确认区 → 恢复计时清零 */
|
|
|
|
/* 目标离开, 高位重现 → 重新等待 10s + 学习 3 窗 → 替换 */
|
|
learn_start_before = sim_learn_start_cnt;
|
|
sim_CAPVD = 128856UL;
|
|
for (i = 0; i < 4000 && !sim_applied; i++)
|
|
sim_tick();
|
|
assert(sim_learn_start_cnt == learn_start_before + 1);
|
|
assert(sim_applied == 1);
|
|
assert(sim_Origin == 128856UL);
|
|
assert(env_warn_active() == 1); /* 替换后保持到恢复确认 */
|
|
|
|
/* 空闲 5s → 黄灯灭 */
|
|
for (i = 0; i < 600; i++)
|
|
sim_tick();
|
|
assert(env_warn_active() == 0);
|
|
printf("3. 学习期打断重算, 最终恢复 OK\n");
|
|
}
|
|
|
|
/* ============ 场景 4: 学习期持续不稳 → 30s 预算放弃 ============ */
|
|
static void test_learn_unstable_abort(void)
|
|
{
|
|
int i;
|
|
|
|
env_reset();
|
|
sim_Origin = 128095UL;
|
|
|
|
/* 高位 10s → 学习开始; 然后窗口均值交替漂移 >0.1% (129056/129256) */
|
|
for (i = 0; i < 1000; i++) {
|
|
sim_CAPVD = 128856UL;
|
|
sim_tick();
|
|
}
|
|
assert(g_env_resync == 1);
|
|
|
|
for (i = 0; i < 3500; i++) {
|
|
/* 每窗 200 样本交替 129056 / 129256 → 窗间漂移 200 > band~129 → settle 恒 0 */
|
|
sim_CAPVD = ((i / 200) & 1) ? 129256UL : 129056UL;
|
|
sim_tick();
|
|
}
|
|
/* 1000(等待) + 3000(学习预算) ≈ 放弃于 tick 4000; 总 tick 4500 < 再学阈值(5000) */
|
|
assert(sim_learn_start_cnt == 1); /* 只有一次学习启动 */
|
|
assert(sim_applied == 0); /* 未替换 */
|
|
assert(sim_Origin == 128095UL); /* 旧 Origin 保留 (不冒险污染) */
|
|
assert(g_env_resync == 0); /* 预算 30s 放弃 */
|
|
assert(env_warn_active() == 1); /* 环境仍异常 → 黄灯保持 */
|
|
printf("4. 学习不稳 30s 放弃, 保留旧 Origin, 黄灯保持 OK\n");
|
|
}
|
|
|
|
/* ============ 场景 5: 替换后不震荡, 无重复学习 ============ */
|
|
static void test_no_oscillation_after_apply(void)
|
|
{
|
|
int i, lsc;
|
|
|
|
env_reset();
|
|
sim_Origin = 128095UL; sim_CAPVD = 128856UL;
|
|
|
|
for (i = 0; i < 4000 && !sim_applied; i++)
|
|
sim_tick();
|
|
assert(sim_applied == 1);
|
|
|
|
lsc = sim_learn_start_cnt;
|
|
for (i = 0; i < 6000; i++)
|
|
sim_tick(); /* 窗口内持续 (dev≈0) */
|
|
assert(sim_learn_start_cnt == lsc); /* 无重复学习 */
|
|
assert(sim_applied == 1); /* 未被再次改写 */
|
|
assert(sim_Origin == 128856UL);
|
|
assert(env_warn_active() == 0); /* 恢复确认后黄灯已灭 */
|
|
printf("5. 替换后不震荡无重复学习 OK\n");
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_permanent_change_recovers();
|
|
test_transient_no_learn();
|
|
test_learn_interrupt_retry();
|
|
test_learn_unstable_abort();
|
|
test_no_oscillation_after_apply();
|
|
printf("\nALL PASS (ENV_RESYNC_WAIT=%d)\n", ENV_RESYNC_WAIT);
|
|
return 0;
|
|
}
|