fix(V4B): V4.25 黄灯预判改锁存语义 — 触发后回摆保持常亮, 环境空闲稳定5s才灭

原(V4.25首个提交): 黄灯条件 wait_cnt>=5s, 回摆(wait清零)即灭 — 用户反馈应锁存:
一旦5s黄灯常亮, 高位回摆/目标靠近也保持常亮; 只有"新的没有变化的环境"
超过5s 确认恢复, 黄灯才灭。

改动:
- 新增 g_env_warn 预判锁存标志(只增不清): dev>+4dlt 高位累计满
  ENV_WARN_WAIT=500tick(~5s) → g_env_warn=1, 黄灯常亮
- 新增 g_env_recover_cnt 恢复确认: 环境回窗口内且 dev 未进进入线(>-dlt)
  连续 ENV_RECOVER_WAIT=500tick(~5s) → 解除锁存, 黄灯灭
  - 目标进确认区(dev<-dlt)/深压(dev<-4dlt) → 恢复计时清零
  - 高位重现 → 恢复计时清零, 黄灯保持
- abort/apply 均不清 g_env_warn (替换后也需恢复确认5s才灭)
- 黄灯模式4条件 = g_env_resync || g_env_warn
tests 5 场景: 回摆保持常亮→稳定5s灭→高位重现再锁存; 学习打断/放弃后锁存保持
docs: devlog V2.23 + spec V2.4 + 手册 V2.8 同步锁存语义
This commit is contained in:
wangfq
2026-09-02 19:03:49 +08:00
parent 7fd120fdf3
commit 47cb1d944e
6 changed files with 111 additions and 41 deletions
+11 -9
View File
@@ -15,21 +15,23 @@ V4.24 环境重校准的黄灯常亮只在**进入学习后**(dev>+4dlt 高位
### 改动 ### 改动
- 黄灯模式4 条件放宽为两段poll_yellow_led): - 黄灯模式4 条件(poll_yellow_led):`g_env_resync || g_env_warn`
`g_env_resync || g_env_wait_cnt >= ENV_WARN_WAIT` - 新增 `ENV_WARN_WAIT = 500 tick~5 s` + `ENV_RECOVER_WAIT = 500 tick~5 s`TaskLoop.h 文件级宏)
- 新增 `ENV_WARN_WAIT = 500 tick~5 s`TaskLoop.h 文件级宏) - **预判锁存状态机(g_env_warn**
- 高位(dev>+4dlt**5 s** 未回摆 → 黄灯**预判常亮**(提示"环境已重大变化,将进入重校准学习" - 高位(dev>+4dlt**5 s**`g_env_warn=1`黄灯**预判常亮**(提示"环境已重大变化")
- 累计满 **60 s** 才真正进入学习(学习期间黄灯保持常亮,学完替换 Origin 后灭) - **锁存**:回摆/目标靠近(wait 清零)**不灭灯**——只有环境回到正常空闲(dev 未进进入线)**连续 5 s**(恢复确认)才解除、黄灯灭
- 预判 ≠ 学习:回摆/目标靠近(wait 清零)→ 黄灯立即灭,不触发学习 - 累计满 **60 s** 才真正进入重校准学习(学习期间黄灯保持常亮,学完替换 Origin 后仍需恢复确认 5 s 才灭)
- 高位重现 → 恢复确认计时清零,黄灯继续常亮
- 预判 ≠ 学习:短时瞬态(60 s 内回摆)只亮黄灯、不触发学习
### 验证 ### 验证
- tests/test_env_resync.c 扩展:黄灯预判点亮时刻 = 500 tick瞬态高位 4000 tick 预判亮但未学习;打断回摆后预判灭 - tests/test_env_resync.c 扩展:预判点亮时刻 = 500 tick;回摆后黄灯**保持常亮**;稳定 5 s 后解除;高位重现再锁存;打断/放弃后锁存保持
- 5 场景 ALL PASSgcc `-fsyntax-only` 0 error - 5 场景 ALL PASSgcc `-fsyntax-only` 0 error
### 待现场验证 ### 待现场验证
A 拿走场景:约 5 s 黄灯常亮 → 60 s 自动重学(黄灯保持常亮数秒)→ 替换完成黄灯灭、检测高度恢复。 A 拿走场景:约 5 s 黄灯常亮(此后回摆也不灭)~60 s 自动重学 → 替换完成 → 环境稳定再 5 s 黄灯灭、检测高度恢复。
--- ---
@@ -1063,7 +1065,7 @@ if (dev >= dlt_ORG × 4) {
| 版本 | 时间 | 说明 | | 版本 | 时间 | 说明 |
|------|------|------| |------|------|------|
| V2.23 | 2026-09-02 | V4.25 环境异常黄灯预判:dev>+4dlt 高位连续 5sENV_WARN_WAIT=500tick)即黄灯常亮预判,60s 才真正学习;预判≠学习,回摆即灭 | | V2.23 | 2026-09-02 | V4.25 环境异常黄灯预判(锁存)dev>+4dlt 高位连续 5s 触发 g_env_warn 锁存常亮;回摆/目标靠近保持,环境空闲稳定 5s(ENV_RECOVER_WAIT)才解除;60s 才真正学习 |
| V2.20 | 2026-09-02 | V4.22 DEBUG 改编译器 -DDEBUG 控制(cmcng.h DEBUG_ENABLE 删除) | | V2.20 | 2026-09-02 | V4.22 DEBUG 改编译器 -DDEBUG 控制(cmcng.h DEBUG_ENABLE 删除) |
| V2.19 | 2026-09-02 | V4.20 负variation铁块锁死修复:基线跟踪双向对称保护(±4×dlt 对称窗口) | | V2.19 | 2026-09-02 | V4.20 负variation铁块锁死修复:基线跟踪双向对称保护(±4×dlt 对称窗口) |
| V2.18 | 2026-09-02 | V4.20 Debug 串口打印重组:频率/采样/基准全量输出(DBG/EVT 前缀) | | V2.18 | 2026-09-02 | V4.20 Debug 串口打印重组:频率/采样/基准全量输出(DBG/EVT 前缀) |
+3 -3
View File
@@ -185,10 +185,10 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈
| 状态 | 红灯 | 绿灯 | 黄灯 | 说明 | | 状态 | 红灯 | 绿灯 | 黄灯 | 说明 |
|------|------|------|------|------| |------|------|------|------|------|
| 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):高位持续 **5 s** 黄灯预判常亮,累计 **60 s** 后自动学习新基线并替换,恢复灵敏度 | | 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):高位持续 **5 s** 黄灯预判**锁存**常亮,累计 **60 s** 后自动学习新基线并替换,恢复灵敏度 |
- 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起自动恢复;V4.25 起环境变化 **~5 s** 黄灯即常亮预判(提示"检测器已感知环境变化、即将进入重学" - 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起自动恢复;V4.25 起环境变化 **~5 s** 黄灯即常亮预判(提示"检测器已感知环境变化")
- **黄灯常亮 = 环境异常预判/学习中**请勿移动线圈附近的目标(铁块静止 60s+ 也会被学入);回摆(干扰消失)黄灯自动灭,不触发学习 - **黄灯常亮(锁存)= 环境异常预判/学习中**一旦点亮,目标移走/回摆**不会立即灭灯**——需环境恢复正常(空闲)且稳定 **~5 s** 才解除。请勿在黄灯常亮期间移动线圈附近的金属目标(铁块静止 60 s+ 也会被学入)
- 黄灯优先级:断开快闪 > 环境预判/学习常亮 > N 短闪(断开史)> 灭 - 黄灯优先级:断开快闪 > 环境预判/学习常亮 > N 短闪(断开史)> 灭
- **快闪**200ms 亮 / 200ms 灭 - **快闪**200ms 亮 / 200ms 灭
+3 -2
View File
@@ -449,7 +449,8 @@ if (dev < dlt_ORG * 4) {
| STABLE_SETTLE_WINDOWS | 2 | ~2 s | 连续窗口漂移达标数 | | STABLE_SETTLE_WINDOWS | 2 | ~2 s | 连续窗口漂移达标数 |
| STABLE_MAX_SAMPLES | 500 tick | **~5 s** | 自检硬兜底上限(超时强制判稳) | | STABLE_MAX_SAMPLES | 500 tick | **~5 s** | 自检硬兜底上限(超时强制判稳) |
| FREEZE_TIMEOUT | 1000 | **~10 s** | 基线冻结超时(连续稳定后更新 Origin) | | FREEZE_TIMEOUT | 1000 | **~10 s** | 基线冻结超时(连续稳定后更新 Origin) |
| ENV_WARN_WAIT | 500 tick | **~5 s** | 环境预判黄灯V4.25):dev>+4×dlt 高位连续 5s 黄灯常亮预判(将进入重学);预判≠学习,回摆即灭 | | ENV_WARN_WAIT | 500 tick | **~5 s** | 环境预判锁存V4.25):dev>+4×dlt 高位连续 5s 黄灯常亮预判(锁存) |
| ENV_RECOVER_WAIT | 500 tick | ~5 s | 恢复确认(V4.25):环境回正常空闲连续 5s 才解除预判锁存、黄灯灭(回摆/目标靠近保持常亮) |
| ENV_RESYNC_WAIT | 6000 tick | **~60 s** | 环境重校准等待:dev>+4×dlt 高位连续 60s 判定永久环境变化(V4.24) | | ENV_RESYNC_WAIT | 6000 tick | **~60 s** | 环境重校准等待:dev>+4×dlt 高位连续 60s 判定永久环境变化(V4.24) |
| ENV_LEARN_WINDOW | 200 tick | ~2 s | 环境学习窗口(基线 B 均值窗,学习期黄灯常亮) | | ENV_LEARN_WINDOW | 200 tick | ~2 s | 环境学习窗口(基线 B 均值窗,学习期黄灯常亮) |
| ENV_LEARN_MAX_TICKS | 3000 tick | **~30 s** | 学习预算:窗口不稳超时放弃,保留旧 Origin(防死锁) | | ENV_LEARN_MAX_TICKS | 3000 tick | **~30 s** | 学习预算:窗口不稳超时放弃,保留旧 Origin(防死锁) |
@@ -758,4 +759,4 @@ DLD154V4B V1.4 起实现该专利的整数化版本,用于大车通行时的
| V2.1 | 2026-06-29 | 更新至 V2.6:单路 IIR ALFA_CAP1=79, WINDOW_ORIGIN=500, 两阶段基线 | | V2.1 | 2026-06-29 | 更新至 V2.6:单路 IIR ALFA_CAP1=79, WINDOW_ORIGIN=500, 两阶段基线 |
| V2.2 | 2026-09-02 | 更新至 V4.23:上电稳定期判稳语义(基准漂移≤0.1%×2窗 + 5s兜底)、红灯呼吸周期≈2.6s、绿灯自检描述 | | V2.2 | 2026-09-02 | 更新至 V4.23:上电稳定期判稳语义(基准漂移≤0.1%×2窗 + 5s兜底)、红灯呼吸周期≈2.6s、绿灯自检描述 |
| V2.3 | 2026-09-02 | 更新至 V4.24:环境重校准(dev>+4dlt 高位连续 60s → 学习基线 B → 空闲原子替换 Origin,黄灯常亮指示;瞬态 60s 内不污染、30s 学习预算兜底) | | V2.3 | 2026-09-02 | 更新至 V4.24:环境重校准(dev>+4dlt 高位连续 60s → 学习基线 B → 空闲原子替换 Origin,黄灯常亮指示;瞬态 60s 内不污染、30s 学习预算兜底) |
| V2.4 | 2026-09-02 | 更新至 V4.25:环境异常黄灯预判(ENV_WARN_WAIT=5s 常亮,预判≠学习,回摆即灭) | | V2.4 | 2026-09-02 | 更新至 V4.25:环境异常黄灯预判锁存ENV_WARN_WAIT=5s 触发;回摆保持,ENV_RECOVER_WAIT 空闲 5s 才解除);预判≠学习 |
+59 -20
View File
@@ -21,7 +21,8 @@
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗漂移达标 #define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗漂移达标
#define FREEZE_TIMEOUT 1000 // 打印饱和(dev>0 不再超时更新 Origin) #define FREEZE_TIMEOUT 1000 // 打印饱和(dev>0 不再超时更新 Origin)
#define ENV_RESYNC_WAIT 6000 // V4.24: dev>+4dlt 高位连续 60s → 判定永久环境变化 #define ENV_RESYNC_WAIT 6000 // V4.24: dev>+4dlt 高位连续 60s → 判定永久环境变化
#define ENV_WARN_WAIT 500 // V4.25: 高位连续 ~5s → 黄灯预判常亮 (TaskLoop.h) #define ENV_WARN_WAIT 500 // V4.25: 高位连续 ~5s → 黄灯预判锁存 (TaskLoop.h)
#define ENV_RECOVER_WAIT 500 // V4.25: 环境回正常空闲连续 ~5s → 解除预判 (TaskLoop.h)
#define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s #define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s
#define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃 #define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃
@@ -33,6 +34,8 @@ static uint16_t g_env_cnt = 0;
static uint32_t g_env_prev = 0; static uint32_t g_env_prev = 0;
static uint8_t g_env_settle = 0; static uint8_t g_env_settle = 0;
static uint16_t g_env_tick = 0; static uint16_t g_env_tick = 0;
static uint8_t g_env_warn = 0;
static uint16_t g_env_recover_cnt = 0;
/* 场景镜像 (loop1_* 对应量) */ /* 场景镜像 (loop1_* 对应量) */
static uint32_t sim_Origin; static uint32_t sim_Origin;
@@ -53,17 +56,20 @@ static void env_reset(void)
g_env_prev = 0; g_env_prev = 0;
g_env_settle = 0; g_env_settle = 0;
g_env_tick = 0; g_env_tick = 0;
g_env_warn = 0;
g_env_recover_cnt = 0;
} }
/* V4.25: 黄灯模式4 条件镜像 (TaskLoop.c poll_yellow_led) */ /* V4.25: 黄灯模式4 条件镜像 (TaskLoop.c poll_yellow_led) — 预判锁存 */
static int env_warn_active(void) static int env_warn_active(void)
{ {
return g_env_resync || g_env_wait_cnt >= ENV_WARN_WAIT; return g_env_resync || g_env_warn;
} }
/* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */ /* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */
static void env_resync_abort(void) static void env_resync_abort(void)
{ {
/* V4.25: abort 只退出学习, 不清预判锁存 g_env_warn (环境未恢复前黄灯保持) */
g_env_resync = 0; g_env_resync = 0;
g_env_sum = 0; g_env_sum = 0;
g_env_cnt = 0; g_env_cnt = 0;
@@ -91,15 +97,31 @@ static void sim_tick(void)
g_env_wait_cnt = 0; g_env_wait_cnt = 0;
if (g_env_resync) ev_abort++; if (g_env_resync) ev_abort++;
env_resync_abort(); env_resync_abort();
/* V4.25: 恢复确认 — 空闲(dev>-dlt)连续 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) { } else if (dev < 0) {
/* 车方向 → 冻结 (env 相关) */ /* 车方向 → 冻结 (env 相关) */
g_env_wait_cnt = 0; g_env_wait_cnt = 0;
if (g_env_resync) ev_abort++; if (g_env_resync) ev_abort++;
env_resync_abort(); env_resync_abort();
g_env_recover_cnt = 0; /* 目标深压 → 恢复计时清零 */
} else { } else {
/* dev>0 高位 — V4.24 两段式 */ /* dev>0 高位 — V4.24 两段式 / V4.25 预判锁存 */
g_env_recover_cnt = 0; /* 高位异常中 → 恢复确认失效 */
if (!g_env_resync) { if (!g_env_resync) {
g_env_wait_cnt++; g_env_wait_cnt++;
if (g_env_wait_cnt >= ENV_WARN_WAIT)
g_env_warn = 1; /* 预判锁存 (只增不清) */
if (g_env_wait_cnt >= ENV_RESYNC_WAIT) { if (g_env_wait_cnt >= ENV_RESYNC_WAIT) {
g_env_resync = 1; g_env_resync = 1;
g_env_wait_cnt = 0; g_env_wait_cnt = 0;
@@ -176,13 +198,15 @@ static void test_permanent_change_recovers(void)
assert(ev_applied == 1); assert(ev_applied == 1);
assert(tick == 6600); /* 6000 等待 + 3×200 窗 */ assert(tick == 6600); /* 6000 等待 + 3×200 窗 */
assert(sim_Origin == 128856UL); assert(sim_Origin == 128856UL);
assert(g_env_resync == 0); /* 替换后复位, 黄灯灭 */ assert(g_env_resync == 0); /* 学习结束 */
assert(env_warn_active() == 1);/* V4.25: 替换后黄灯保持 (恢复确认前不灭) */
/* 替换后 dev≈0 → 窗口分支, 不再进 freeze/学习 */ /* 替换后 dev≈0 → 窗口分支; 空闲连续 5s 恢复确认 → 黄灯灭 */
for (int i = 0; i < 500; i++) for (int i = 0; i < 500; i++)
sim_tick(); sim_tick();
assert(ev_applied == 1); /* 不再重复替换 */ assert(ev_applied == 1); /* 不再重复替换 */
printf(" 场景1 PASS — 永久环境变化 ~66s 自动恢复, Origin 128095->128856\n\n"); assert(env_warn_active() == 0);/* 恢复确认 5s 后黄灯灭 */
printf(" 场景1 PASS — 永久环境变化 5s 预判锁存, ~66s 自动恢复, 恢复确认后灭\n\n");
} }
static void test_transient_never_learns(void) static void test_transient_never_learns(void)
@@ -196,21 +220,28 @@ static void test_transient_never_learns(void)
assert(env_warn_active() == 1); /* 黄灯预判已亮 (wait=4000) */ assert(env_warn_active() == 1); /* 黄灯预判已亮 (wait=4000) */
printf("瞬态高位短停: 4000tick 时黄灯预判亮=%d (未学习)\n", env_warn_active()); printf("瞬态高位短停: 4000tick 时黄灯预判亮=%d (未学习)\n", env_warn_active());
/* 回摆进窗口 (CAPVD 回 Origin 附近), wait 清零 → 黄灯预判灭 */ /* 回摆进窗口 (CAPVD 回 Origin 附近): wait 清零, 但预判锁存保持 (V4.25 核心语义) */
sim_CAPVD = 128100UL; /* dev=+5 窗口内 */ sim_CAPVD = 128100UL; /* dev=+5 窗口内 */
sim_tick(); sim_tick();
assert(g_env_wait_cnt == 0); assert(g_env_wait_cnt == 0);
assert(env_warn_active() == 0); /* 回摆解除, 黄灯灭 */ assert(env_warn_active() == 1); /* 回摆不清黄灯, 保持常亮 */
/* 再次高位 59s(5900tick) — 仍不足 60s 连续 → 不学 */ /* 窗口内连续 499 tick (累计 500 恢复确认) → 环境稳定, 解除预判 */
for (int i = 0; i < 499; i++)
sim_tick();
assert(env_warn_active() == 0); /* 新环境稳定 5s, 黄灯灭 */
assert(g_env_recover_cnt == 0);
/* 再次高位 59s(5900tick): 5s 后预判重新锁存, 但 <60s 不学习 */
sim_CAPVD = 128856UL; sim_CAPVD = 128856UL;
for (int i = 0; i < 5900; i++) for (int i = 0; i < 5900; i++)
sim_tick(); sim_tick();
assert(ev_learn_start == 0); assert(ev_learn_start == 0);
assert(ev_applied == 0); assert(ev_applied == 0);
assert(sim_Origin == 128095UL); assert(sim_Origin == 128095UL);
printf("瞬态高位短停: 不学习不替换, Origin 保持 (等待计数=%d)\n", g_env_wait_cnt); assert(env_warn_active() == 1); /* 高位重现 → 预判重新锁存 */
printf(" 场景2 PASS — 铁块类瞬态(60s内回摆)不触发重校准\n\n"); printf("瞬态高位短停: 回摆保持常亮→稳定5s灭→高位重现再锁存 (未学习)\n");
printf(" 场景2 PASS — 铁块类瞬态(60s内回摆)不触发重校准, 预判锁存语义正确\n\n");
} }
static void test_learn_interrupted_restarts(void) static void test_learn_interrupted_restarts(void)
@@ -226,9 +257,10 @@ static void test_learn_interrupted_restarts(void)
sim_tick(); /* 学习窗1 未完 */ sim_tick(); /* 学习窗1 未完 */
sim_CAPVD = 128000UL; /* dev=-95 窗口内(B 目标) */ sim_CAPVD = 128000UL; /* dev=-95 窗口内(B 目标) */
sim_tick(); sim_tick();
assert(g_env_resync == 0); /* 打断退出 */ assert(g_env_resync == 0); /* 打断退出学习 */
assert(ev_abort == 1); assert(ev_abort == 1);
assert(env_warn_active() == 0); /* wait 清零, 黄灯预判灭 */ assert(env_warn_active() == 1); /* 预判锁存保持 (环境未确认恢复) */
assert(g_env_recover_cnt == 0); /* 目标在进入确认区, 恢复计时清零 */
/* 目标离开, 高位重新 60s → 学习 3 窗 → 替换 */ /* 目标离开, 高位重新 60s → 学习 3 窗 → 替换 */
sim_CAPVD = 128856UL; sim_CAPVD = 128856UL;
@@ -240,7 +272,11 @@ static void test_learn_interrupted_restarts(void)
assert(ev_applied == 1); assert(ev_applied == 1);
assert(ev_learn_start == 2); assert(ev_learn_start == 2);
assert(sim_Origin == 128856UL); assert(sim_Origin == 128856UL);
printf(" 场景3 PASS — 学习期任何打断都重算, 完成后恢复\n\n"); assert(env_warn_active() == 1); /* 替换后黄灯保持到恢复确认 */
for (int i = 0; i < 500; i++)
sim_tick(); /* 窗口恢复确认 5s */
assert(env_warn_active() == 0); /* 黄灯灭 */
printf(" 场景3 PASS — 学习期打断重算+锁存保持, 恢复确认后灭\n\n");
} }
static void test_unstable_learn_gives_up(void) static void test_unstable_learn_gives_up(void)
@@ -259,13 +295,14 @@ static void test_unstable_learn_gives_up(void)
sim_tick(); sim_tick();
} }
printf("学习期不稳: 预算放弃 tick=%d applied=%d resync=%d Origin=%lu\n", printf("学习期不稳: 预算放弃 tick=%d applied=%d resync=%d warn=%d Origin=%lu\n",
tick, ev_applied, g_env_resync, (unsigned long)sim_Origin); tick, ev_applied, g_env_resync, g_env_warn, (unsigned long)sim_Origin);
assert(ev_applied == 0); assert(ev_applied == 0);
assert(g_env_resync == 0); /* 超时放弃, 黄灯灭 */ assert(g_env_resync == 0); /* 超时退出学习 */
assert(env_warn_active() == 1); /* 环境仍异常 → 黄灯保持锁存 */
assert(ev_abort >= 1); assert(ev_abort >= 1);
assert(sim_Origin == 128095UL); /* Origin 不被污染 */ assert(sim_Origin == 128095UL); /* Origin 不被污染 */
printf(" 场景4 PASS — 学习不稳定 30s 放弃, 宁可灵敏度低不冒险污染\n\n"); printf(" 场景4 PASS — 学习不稳定 30s 放弃保留旧 Origin, 黄灯保持异常指示\n\n");
} }
static void test_no_learning_after_replace(void) static void test_no_learning_after_replace(void)
@@ -280,7 +317,9 @@ static void test_no_learning_after_replace(void)
for (int i = 0; i < 6000; i++) for (int i = 0; i < 6000; i++)
sim_tick(); /* 新 Origin 下高位条件消失 */ sim_tick(); /* 新 Origin 下高位条件消失 */
assert(ev_learn_start == before); /* 无新学习 */ assert(ev_learn_start == before); /* 无新学习 */
printf("替换后稳态: 高位条件消失, 无重复学习 (Origin=%lu)\n", (unsigned long)sim_Origin); assert(env_warn_active() == 0); /* 恢复确认 5s 后黄灯已灭 */
printf("替换后稳态: 高位条件消失, 无重复学习, 黄灯灭 (Origin=%lu)\n",
(unsigned long)sim_Origin);
printf(" 场景5 PASS — 恢复后进入正常跟踪, 不震荡\n\n"); printf(" 场景5 PASS — 恢复后进入正常跟踪, 不震荡\n\n");
} }
@@ -46,7 +46,8 @@
#define ENTRY_CONFIRM 3 // 进入确认: 连续 N 次低于阈值才判定有车 #define ENTRY_CONFIRM 3 // 进入确认: 连续 N 次低于阈值才判定有车
#define FREEZE_TIMEOUT 1000 // 基线冻结超时: ~10s @ 10ms/tick, 持续偏高且稳定后强制更新基线 #define FREEZE_TIMEOUT 1000 // 基线冻结超时: ~10s @ 10ms/tick, 持续偏高且稳定后强制更新基线
#define FREEZE_STABILITY_RATE 2 // 冻结稳定性窗口: 参考值的 ±2% (CAPVD 需在此范围内才累计超时) #define FREEZE_STABILITY_RATE 2 // 冻结稳定性窗口: 参考值的 ±2% (CAPVD 需在此范围内才累计超时)
#define ENV_WARN_WAIT 500 // 环境预判 (V4.25): dev>+4dlt 高位连续 ~5s 未回摆 → 黄灯常亮预判提示 #define ENV_WARN_WAIT 500 // 环境预判 (V4.25): dev>+4dlt 高位连续 ~5s → 黄灯预判锁存(回摆保持)
#define ENV_RECOVER_WAIT 500 // 恢复确认 (V4.25): 环境回正常(空闲)连续 ~5s 才解除预判, 黄灯灭
/*=========================================================================== /*===========================================================================
* *
@@ -86,6 +86,11 @@ uint32_t g_env_prev = 0; // 上一窗口均值 (B 候选)
uint8_t g_env_settle = 0; // 连续稳定窗口计数 (窗内均值漂移 ≤0.1% 则+1) uint8_t g_env_settle = 0; // 连续稳定窗口计数 (窗内均值漂移 ≤0.1% 则+1)
uint16_t g_env_tick = 0; // 学习预算 (超时放弃防死锁) uint16_t g_env_tick = 0; // 学习预算 (超时放弃防死锁)
/* V4.25+: 环境异常预判锁存 — 一旦触发黄灯常亮, 回摆/目标靠近不灭;
* (, dev 线) ENV_RECOVER_WAIT */
uint8_t g_env_warn = 0; // 1=环境异常预判锁存 (黄灯常亮)
uint16_t g_env_recover_cnt = 0; // 恢复确认计数 (空闲持续 5s → 解除预判)
#if USE_FLATNESS_EXIT #if USE_FLATNESS_EXIT
uint8_t g_exit_state; uint8_t g_exit_state;
uint16_t g_max_slope; uint16_t g_max_slope;
@@ -322,10 +327,11 @@ void poll_yellow_led(void)
} }
/*--- V4.25 模式4: 环境异常预判/重校准学习 → 黄灯常亮 /*--- V4.25 模式4: 环境异常预判/重校准学习 → 黄灯常亮
* g_env_wait_cnt >= ENV_WARN_WAIT(~5s): (, ) * g_env_warn == 1: ( ~5s ; /,
* g_env_resync == 1: ( 60s , Origin ) * ~5s )
* g_env_resync == 1: ( 60s , Origin)
* : > / > N短闪 > ---*/ * : > / > N短闪 > ---*/
if (g_env_resync || g_env_wait_cnt >= ENV_WARN_WAIT) { if (g_env_resync || g_env_warn) {
LED_YELLOW_ON; LED_YELLOW_ON;
g_fault_tick = 0; g_fault_tick = 0;
g_fault_phase = 0; g_fault_phase = 0;
@@ -433,6 +439,8 @@ void INIT_VD(void)
g_env_prev = 0; g_env_prev = 0;
g_env_settle = 0; g_env_settle = 0;
g_env_tick = 0; g_env_tick = 0;
g_env_warn = 0; // V4.25+: 环境预判锁存复位
g_env_recover_cnt = 0;
/* M4 优化: 快速 IIR + 进入确认 */ /* M4 优化: 快速 IIR + 进入确认 */
loop1_entry_cnt = 0; loop1_entry_cnt = 0;
@@ -881,12 +889,26 @@ void vd1_task(void)
loop1_freeze_cnt = 0; loop1_freeze_cnt = 0;
loop1_freeze_ref = 0; // V4.20: 清残留参考, 打印 FRZ 干净 loop1_freeze_ref = 0; // V4.20: 清残留参考, 打印 FRZ 干净
g_env_wait_cnt = 0; // V4.24: 高位回摆/目标靠近 → 等待重来 g_env_wait_cnt = 0; // V4.24: 高位回摆/目标靠近 → 等待重来
env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复) env_resync_abort(); // V4.24: 学习中被打断 → 重算
/* V4.25: 恢复确认 — 环境回正常(dev 未进进入线以下)连续 5s → 解除预判锁存
* (, /; ) */
if (dev > -(int32_t)loop1_dlt_ORG) {
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; // 目标进入确认区 → 恢复计时重来
}
update_moving_average(&loop1_ORG_SUM, &loop1_ORG_CNT, update_moving_average(&loop1_ORG_SUM, &loop1_ORG_CNT,
&loop1_Origin, loop1_CAPVD, WINDOW_ORIGIN); &loop1_Origin, loop1_CAPVD, WINDOW_ORIGIN);
} else if (dev < 0) { } else if (dev < 0) {
g_env_wait_cnt = 0; // V4.24: 目标靠近(车方向) → 等待重来 g_env_wait_cnt = 0; // V4.24: 目标靠近(车方向) → 等待重来
env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复) env_resync_abort(); // V4.24: 学习中被打断 → 重算
g_env_recover_cnt = 0; // V4.25: 目标深压 → 恢复确认计时清零
/* 正variation (CAPVD低于Origin, 疑似车辆) → 冻结, 保留原超时兜底更新 */ /* 正variation (CAPVD低于Origin, 疑似车辆) → 冻结, 保留原超时兜底更新 */
if (loop1_freeze_cnt == 0) { if (loop1_freeze_cnt == 0) {
loop1_freeze_ref = loop1_CAPVD; loop1_freeze_ref = loop1_CAPVD;
@@ -930,8 +952,13 @@ void vd1_task(void)
loop1_ORG_SUM = 0; loop1_ORG_SUM = 0;
if (!g_env_resync) { if (!g_env_resync) {
/*--- ① 等待确认: dev>+4dlt 高位连续累计 (回摆清零见窗口/dev<0分支) ---*/ /*--- ① 等待确认: dev>+4dlt 高位连续累计 (回摆清零见窗口/dev<0分支)
* V4.25: 5s(ENV_WARN_WAIT) g_env_warn=1 (),
* /; ---*/
g_env_wait_cnt++; g_env_wait_cnt++;
if (g_env_wait_cnt >= ENV_WARN_WAIT)
g_env_warn = 1; // 预判锁存 (只增不清)
g_env_recover_cnt = 0; // 高位异常中 → 恢复确认失效
if (g_env_wait_cnt >= ENV_RESYNC_WAIT) { if (g_env_wait_cnt >= ENV_RESYNC_WAIT) {
g_env_resync = 1; g_env_resync = 1;
g_env_wait_cnt = 0; g_env_wait_cnt = 0;