diff --git a/docs/devlog.md b/docs/devlog.md index 6785ca4..199b2ac 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -4,6 +4,57 @@ --- +## 2026-09-02 — 环境重校准:现场永久环境变化自动恢复基线(固件 V4.24) + +> **固件版本:V4.24**(cmcng.h:FIRMWARE_VER="4.24" + MAIN=4/SUB=24)。devlog 修订 V2.22。 + +### 现场现象(V4.22 实测复现,DBG/EVT 串口日志) + +线圈旁金属板 A 上电 → 稳定后 Origin 学低;A 拿走 → CAPVD 弹回真空场, +相对 Origin 恒高 dev=+761 cnt(0.59% Origin,实测 CAPVD 128096→128856), +被 V4.20 双向保护(36321a1)"dev>0 永不更新"永久冻结 → Origin 卡 128095 → +再拿金属板 B 测试需下跌 ~809 cnt(正常 54 cnt)才触发 → 触发高度明显降低、不恢复。 + +- V4.20(双向保护前,278d470):dev>0 走正常窗口平均 → 数分钟缓慢恢复(现场"等一段时间能恢复") +- V4.22(含双向保护):dev>0 只冻结、永不更新 → 永久不恢复 + +### 根因 + +36321a1 为防"铁块靠近污染 Origin → 离开假进入锁死",把 dev>+4×dlt 高位的 +"无车环境异常"一律永久冻结。但"铁块靠近(瞬态,秒级回摆)"与"永久环境变化 +(基准被学低,如 A 拿走)"数值不可分,仅**时间维度**可分——永久冻结把后者也卡死。 + +### 修复(V4.24 两段式受控重校准) + +| 阶段 | 条件 | 动作 | +|------|------|------| +| ① 冻结等待 | dev>+4×dlt 高位连续 60 s(ENV_RESYNC_WAIT=6000 tick)未回摆 | 保持双向保护冻结语义(<60 s 瞬态不污染) | +| ② 学习基线 B | 连续空闲窗(ENV_LEARN_WINDOW=200 tick)均值 + settle 判稳(连续 2 窗漂移 ≤0.1%) | **黄灯常亮**指示学习过程 | +| ③ 原子替换 | B 可信且空闲 | Origin=B;清 freeze/ORG/entry 残留;slow snap(同 V4.04);黄灯恢复 | + +- 学习期任何打断(CAPVD 回窗、dev<0 目标靠近、线圈断开重连)→ 重算重来 +- 学习预算 ENV_LEARN_MAX_TICKS=3000(30 s)超时放弃,保留旧 Origin——宁可灵敏度低不冒险污染 +- 替换瞬间 B≈当前空闲 CAPVD → dev≈0 无跳变,不会误触发继电器 +- 黄灯优先级:断开快闪 > 学习常亮 > N 短闪(断开史)> 灭 + +### 验证 + +- `tests/test_env_resync.c`(新增):5 场景 ALL PASS + 1. 永久变化(常量高位)→ 6600 tick(~66 s)自动替换 Origin 128095→128856 + 2. 瞬态高位 60 s 内回摆 → 不学习不替换 + 3. 学习期打断 → abort 重算 → 最终恢复 + 4. 学习期窗口不稳 → 30 s 预算放弃,Origin 不被污染 + 5. 替换后 dev≈0 → 无重复学习不震荡 +- gcc `-fsyntax-only` TaskLoop.c 0 error + +### 权衡(待现场验证) + +- 铁块静止 60 s+ 也会被学入 → 靠等待时长 + 黄灯常亮提示(操作员看到黄灯别移走目标) +- 恢复总时长 ≈66 s(V4.20 分钟级),A 拿走场景现场复测确认手感 +- ENV_RESYNC_WAIT / ENV_LEARN_MAX_TICKS 为编译期宏,现场可调 + +--- + ## 2026-09-02 — 红灯呼吸对齐 vd960Loop + 上电自检基准判稳(固件 V4.23) > **固件版本:V4.23**(cmcng.h:FIRMWARE_VER="4.23" + MAIN=4/SUB=23)。devlog 修订 V2.21。 @@ -983,7 +1034,7 @@ if (dev >= dlt_ORG × 4) { | 版本 | 时间 | 说明 | |------|------|------| -| V2.21 | 2026-09-02 | V4.23 红灯呼吸对齐 vd960Loop(周期≈2.6s);上电自检基准判稳:窗口 Origin 漂移 ≤0.1%×连续 2 窗 + 5s 兜底 | +| V2.22 | 2026-09-02 | V4.24 环境重校准:dev>+4dlt 高位连续 60s 触发受控学习(黄灯常亮),连续空闲窗均值+判稳后原子替换 Origin,恢复金属板A拿走类永久环境变化;打断重算 + 30s 预算兜底 | | V2.20 | 2026-09-02 | V4.22 DEBUG 改编译器 -DDEBUG 控制(cmcng.h DEBUG_ENABLE 删除) | | V2.19 | 2026-09-02 | V4.20 负variation铁块锁死修复:基线跟踪双向对称保护(±4×dlt 对称窗口) | | V2.18 | 2026-09-02 | V4.20 Debug 串口打印重组:频率/采样/基准全量输出(DBG/EVT 前缀) | diff --git a/docs/product-manual.md b/docs/product-manual.md index eadb9ec..48f5413 100644 --- a/docs/product-manual.md +++ b/docs/product-manual.md @@ -1,6 +1,6 @@ # DLD154V4B 产品手册 -> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.6 +> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.7 > 适用于停车场、收费站、交通卡口等场景的车辆到达/离开检测 --- @@ -181,6 +181,16 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 | 线圈重连,断开 2 次 | 呼吸 | 正常 | **2 短闪** + 1.2s 灭 | 发生过 2 次断开,已恢复 | | 线圈重连,断开 3+ 次 | 呼吸 | 正常 | **3 短闪** + 1.2s 灭 | 多次断开,建议检查接头 | +### 5.3 环境校准(V4.24 新增) + +| 状态 | 红灯 | 绿灯 | 黄灯 | 说明 | +|------|------|------|------|------| +| 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):先高位冻结 60s 确认,再自动学习新基线并替换,恢复灵敏度 | + +- 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起:高位持续 60s 判定永久变化 → 黄灯常亮数秒(学习中)→ 自动恢复,无需断电 +- **看到黄灯常亮表示正在学习新基线,请勿移动线圈附近的目标**(铁块静止 60s+ 也会被学入) +- 黄灯优先级:断开快闪 > 学习常亮 > N 短闪(断开史)> 灭 + - **快闪**:200ms 亮 / 200ms 灭 - **N 短闪**:N × (80ms 亮 + 200ms 灭),循环间隔 1.2s - **呼吸**:PWM 渐变,系统运行指示(周期≈2.6s,V4.23 起对齐 vd960Loop 观感) diff --git a/docs/technical-spec.md b/docs/technical-spec.md index 53cfce3..bc4c064 100644 --- a/docs/technical-spec.md +++ b/docs/technical-spec.md @@ -1,6 +1,6 @@ # DLD154V4B 技术规格书 -> 单路线圈车辆检测器 | 固件版本: V4.23 | 文档版本: V2.2 +> 单路线圈车辆检测器 | 固件版本: V4.24 | 文档版本: V2.3 > 本文档面向工程开发、系统集成及故障深度分析 --- @@ -364,6 +364,7 @@ if (!VD_FLAG) { | 4× 冻结阈值 | 防止异常 CAPVD 上升污染基线(V1.5) | | 5% 斜率限幅 | 过滤 EMI/闪电尖峰,保护 IIR(V2.0) | | 冻结超时 + 稳定性检查 | 防止永久冻结死锁(V2.3~2.5) | +| 环境重校准(V4.24) | dev>+4×dlt 高位连续 60s(ENV_RESYNC_WAIT)→ 判定永久环境变化 → 学习基线 B(空闲窗均值+判稳,黄灯常亮)→ 原子替换 Origin;铁块类瞬态 60s 内仍只冻结不污染 | #### 4.4.3 基线更新速率(两阶段) @@ -448,6 +449,9 @@ if (dev < dlt_ORG * 4) { | STABLE_SETTLE_WINDOWS | 2 | ~2 s | 连续窗口漂移达标数 | | STABLE_MAX_SAMPLES | 500 tick | **~5 s** | 自检硬兜底上限(超时强制判稳) | | FREEZE_TIMEOUT | 1000 | **~10 s** | 基线冻结超时(连续稳定后更新 Origin) | +| ENV_RESYNC_WAIT | 6000 tick | **~60 s** | 环境重校准等待:dev>+4×dlt 高位连续 60s 判定永久环境变化(V4.24) | +| ENV_LEARN_WINDOW | 200 tick | ~2 s | 环境学习窗口(基线 B 均值窗,学习期黄灯常亮) | +| ENV_LEARN_MAX_TICKS | 3000 tick | **~30 s** | 学习预算:窗口不稳超时放弃,保留旧 Origin(防死锁) | > **M4 优化**:V2.0 将 tick 从 50ms (TMR15 5ms×10) 提升到 10ms (vTaskDelay 10ms)。IIR 系数同步调整(α 从 79/256→18/256),保持等效时间常数。进入确认 3×10ms + 500ms IN_DELAY = ~530ms,比旧设计 (50ms + 500ms = 550ms) 略快。基线更新从 5s 加速到 1s。 @@ -752,3 +756,4 @@ DLD154V4B V1.4 起实现该专利的整数化版本,用于大车通行时的 | V2.0 | 2026-06-29 | 更新至 V2.5 固件:双路 IIR、斜率限幅、进入确认、冻结超时+稳定性 | | 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.3 | 2026-09-02 | 更新至 V4.24:环境重校准(dev>+4dlt 高位连续 60s → 学习基线 B → 空闲原子替换 Origin,黄灯常亮指示;瞬态 60s 内不污染、30s 学习预算兜底) | diff --git a/tests/test_env_resync.c b/tests/test_env_resync.c new file mode 100644 index 0000000..45fcd4e --- /dev/null +++ b/tests/test_env_resync.c @@ -0,0 +1,279 @@ +/* + * test_env_resync.c — 环境重校准(受控基线更新) gcc 隔离单测 (DLD154V4B V4.24) + * + * 验证对象: TaskLoop.c vd1_task() 无车段 dev>0 分支 (V4.24 两段式) + * ─ 现场场景 (2026-09-02 实测): 线圈旁金属板A上电 → Origin 学低(128096) + * A 拿走 → CAPVD 弹回真空场(128856) dev=+761 → 双向保护(36321a1)永不更新 + * → B 触发需 809cnt (正常 54cnt) 灵敏度假性降低 + * ─ 修复: dev>+4dlt 高位连续 60s(ENV_RESYNC_WAIT) 未回摆 → 判定永久环境变化 + * → 学习基线 B (连续空闲窗均值 + settle 判稳) → 空闲原子替换 Origin + * + * 运行: gcc -Wall tests/test_env_resync.c && ./a.out + * 期望: 4 场景 ALL PASS (瞬态 60s 内短停不学; 永久变化 ~66s 恢复) + */ + +#include +#include +#include + +/*========== 与固件一致的参数宏 (TaskLoop.c vd1_task 内) ==========*/ +#define STABLE_ORIGIN_PPT 1 // 判稳带: 窗口均值漂移 ≤ Origin×1/1000 (0.1%) +#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗漂移达标 +#define FREEZE_TIMEOUT 1000 // 打印饱和(dev>0 不再超时更新 Origin) +#define ENV_RESYNC_WAIT 6000 // V4.24: dev>+4dlt 高位连续 60s → 判定永久环境变化 +#define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s +#define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃 + +/*========== 与固件同名的 env 全局镜像 (TaskLoop.c :80-90) ==========*/ +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; + +/* 场景镜像 (loop1_* 对应量) */ +static uint32_t sim_Origin; +static uint32_t sim_CAPVD; +static int32_t sim_dlt; /* 进阈 dlt */ + +/* 事件计数 */ +static int ev_learn_start = 0; +static int ev_applied = 0; +static int ev_abort = 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; +} + +/* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */ +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; +} + +static void env_resync_apply(uint32_t new_origin) +{ + (void)new_origin; + /* 固件此处打印 + 替换 Origin + 清 freeze/ORG/entry + slow snap */ + ev_applied++; + env_resync_abort(); +} + +/*========== 模拟 vd1_task 无车段基线块 env 逻辑 (逐行复刻 V4.24) ==========*/ +static void sim_tick(void) +{ + int32_t dev = (int32_t)sim_CAPVD - (int32_t)sim_Origin; + int32_t freeze_band = sim_dlt * 4; + + if (dev < freeze_band && dev > -freeze_band) { + /* 对称窗口内 → 正常基线跟踪 (env 相关) */ + g_env_wait_cnt = 0; + if (g_env_resync) ev_abort++; + env_resync_abort(); + } else if (dev < 0) { + /* 车方向 → 冻结 (env 相关) */ + g_env_wait_cnt = 0; + if (g_env_resync) ev_abort++; + env_resync_abort(); + } else { + /* dev>0 高位 — V4.24 两段式 */ + if (!g_env_resync) { + g_env_wait_cnt++; + if (g_env_wait_cnt >= ENV_RESYNC_WAIT) { + 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; + ev_learn_start++; + } + } else { + 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; + uint32_t _drift, _band; + g_env_sum = 0; + g_env_cnt = 0; + if (g_env_prev != 0) { + _drift = (_m > g_env_prev) ? (_m - g_env_prev) : (g_env_prev - _m); + _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_Origin = _m; /* env_resync_apply 替换 */ + env_resync_apply(_m); + return; /* 本 tick 不再走进入判定 */ + } + } + g_env_prev = _m; + } + if (g_env_tick >= ENV_LEARN_MAX_TICKS) { + ev_abort++; + env_resync_abort(); + } + } + } +} + +/*========== 测试工具 ==========*/ +static void t_set(uint32_t origin, uint32_t capvd, int32_t dlt) +{ + sim_Origin = origin; + sim_CAPVD = capvd; + sim_dlt = dlt; + env_reset(); + ev_learn_start = 0; + ev_applied = 0; + ev_abort = 0; +} + +/*========== 场景 ==========*/ +static void test_permanent_change_recovers(void) +{ + /* ① 常量高位稳定 (A 拿走): Origin=128095, 空场 CAPVD=128856, dev=+761>192 */ + t_set(128095UL, 128856UL, 48); + + int tick = 0; + for (; tick < 7000 && !ev_applied; tick++) + sim_tick(); + + printf("常量高位稳定: 学习启动=%d 替换=%d 替换tick=%d resync=%d\n", + ev_learn_start, ev_applied, tick, g_env_resync); + assert(ev_learn_start == 1); + assert(ev_applied == 1); + assert(tick == 6600); /* 6000 等待 + 3×200 窗 */ + assert(sim_Origin == 128856UL); + assert(g_env_resync == 0); /* 替换后复位, 黄灯灭 */ + + /* 替换后 dev≈0 → 走窗口分支, 不再进 freeze/学习 */ + for (int i = 0; i < 500; i++) + sim_tick(); + assert(ev_applied == 1); /* 不再重复替换 */ + printf(" 场景1 PASS — 永久环境变化 ~66s 自动恢复, Origin 128095->128856\n\n"); +} + +static void test_transient_never_learns(void) +{ + /* ② 瞬态高位短停 (铁块模拟): 40s(4000tick) 高位后回摆 → 不学习 */ + t_set(128095UL, 128856UL, 48); + for (int i = 0; i < 4000; i++) + sim_tick(); + assert(ev_learn_start == 0); + + /* 回摆进窗口 (CAPVD 回 Origin 附近), wait 清零 */ + sim_CAPVD = 128100UL; /* dev=+5 窗口内 */ + sim_tick(); + assert(g_env_wait_cnt == 0); + + /* 再次高位 59s(5900tick) — 仍不足 60s 连续 → 不学 */ + sim_CAPVD = 128856UL; + for (int i = 0; i < 5900; i++) + sim_tick(); + assert(ev_learn_start == 0); + assert(ev_applied == 0); + assert(sim_Origin == 128095UL); + printf("瞬态高位短停: 不学习不替换, Origin 保持 (等待计数=%d)\n", g_env_wait_cnt); + printf(" 场景2 PASS — 铁块类瞬态(60s内回摆)不触发重校准\n\n"); +} + +static void test_learn_interrupted_restarts(void) +{ + /* ③ 学习期打断重算: 60s 后进入学习, 途中目标靠近(回窗) → abort → 重来并最终恢复 */ + t_set(128095UL, 128856UL, 48); + + /* 第一次 60s + 学习 100 tick 后, B 靠近把 CAPVD 压回窗口 */ + for (int i = 0; i < 6000; i++) + sim_tick(); /* 进入学习 */ + assert(g_env_resync == 1); + for (int i = 0; i < 100; i++) + sim_tick(); /* 学习窗1 未完 */ + sim_CAPVD = 128000UL; /* dev=-95 窗口内(B 目标) */ + sim_tick(); + assert(g_env_resync == 0); /* 打断退出, 黄灯灭 */ + assert(ev_abort == 1); + + /* 目标离开, 高位重新 60s → 学习 3 窗 → 替换 */ + sim_CAPVD = 128856UL; + for (int i = 0; i < 7000 && !ev_applied; i++) + sim_tick(); + + printf("学习期打断: abort=%d 重新学习启动=%d 最终替换tick计数=%d Origin=%lu\n", + ev_abort, ev_learn_start, 0, (unsigned long)sim_Origin); + assert(ev_applied == 1); + assert(ev_learn_start == 2); + assert(sim_Origin == 128856UL); + printf(" 场景3 PASS — 学习期任何打断都重算, 完成后恢复\n\n"); +} + +static void test_unstable_learn_gives_up(void) +{ + /* ④ 学习期每窗均值漂移 >0.1% (CAPVD 高位大幅交替 128856±200..400) + * → settle 永远到不了 2 → 30s 预算超时放弃, 保留旧 Origin */ + t_set(128095UL, 128856UL, 48); + for (int i = 0; i < 6000; i++) + sim_tick(); /* 进入学习 */ + assert(g_env_resync == 1); + + int tick = 0; + for (; tick < 3500 && !ev_applied && g_env_resync; tick++) { + /* 交替高值: 窗与窗之间均值漂移 ~200 > band(~129), 且 dev 恒 >192 */ + sim_CAPVD = ((tick / ENV_LEARN_WINDOW) & 1) ? 129256UL : 129056UL; + sim_tick(); + } + + printf("学习期不稳: 预算放弃 tick=%d applied=%d resync=%d Origin=%lu\n", + tick, ev_applied, g_env_resync, (unsigned long)sim_Origin); + assert(ev_applied == 0); + assert(g_env_resync == 0); /* 超时放弃, 黄灯灭 */ + assert(ev_abort >= 1); + assert(sim_Origin == 128095UL); /* Origin 不被污染 */ + printf(" 场景4 PASS — 学习不稳定 30s 放弃, 宁可灵敏度低不冒险污染\n\n"); +} + +static void test_no_learning_after_replace(void) +{ + /* ⑤ 替换后 Origin≈CAPVD → dev≈0 正常窗口跟踪, 不再触发二次学习 */ + t_set(128095UL, 128856UL, 48); + for (int i = 0; i < 7000 && !ev_applied; i++) + sim_tick(); + assert(ev_applied == 1); + + int before = ev_learn_start; + for (int i = 0; i < 6000; i++) + sim_tick(); /* 新 Origin 下高位条件消失 */ + assert(ev_learn_start == before); /* 无新学习 */ + printf("替换后稳态: 高位条件消失, 无重复学习 (Origin=%lu)\n", (unsigned long)sim_Origin); + printf(" 场景5 PASS — 恢复后进入正常跟踪, 不震荡\n\n"); +} + +int main(void) +{ + test_permanent_change_recovers(); + test_transient_never_learns(); + test_learn_interrupted_restarts(); + test_unstable_learn_gives_up(); + test_no_learning_after_replace(); + printf("ALL PASS (5 scenarios)\n"); + return 0; +} diff --git a/utilities/at32f421_freertos_demo/inc/cmcng.h b/utilities/at32f421_freertos_demo/inc/cmcng.h index a1948de..efb7726 100644 --- a/utilities/at32f421_freertos_demo/inc/cmcng.h +++ b/utilities/at32f421_freertos_demo/inc/cmcng.h @@ -14,10 +14,10 @@ #define __CMCNG_H__ #define PRODUCT_MODEL "DLD154" -#define FIRMWARE_VER "4.23" +#define FIRMWARE_VER "4.24" #define HARDWARE_VER "3.00" #define FIRMWARE_VER_MAIN 4 -#define FIRMWARE_VER_SUB 23 +#define FIRMWARE_VER_SUB 24 #define HARDWARE_VER_MAIN 3 #define HARDWARE_VER_SUB 0 diff --git a/utilities/at32f421_freertos_demo/src/TaskLoop.c b/utilities/at32f421_freertos_demo/src/TaskLoop.c index 22cd8d1..3fe2796 100644 --- a/utilities/at32f421_freertos_demo/src/TaskLoop.c +++ b/utilities/at32f421_freertos_demo/src/TaskLoop.c @@ -75,6 +75,17 @@ uint8_t g_loop_stable; uint16_t g_stable_cnt = 0; // 稳定期累计样本 (每次 CAP_OK +1) uint16_t g_settle_cnt = 0; // 基准稳定连续窗口计数 (窗口=100样本≈1s) +/* V4.24: 环境重校准(受控基线更新)状态 — 现场永久环境变化(如金属板A拿走)自动恢复 + * 触发链: dev>+4dlt 高位连续 60s → 学习基线 B (连续空闲窗均值+判稳) → 空闲原子替换 Origin + * 黄灯常亮指示学习过程; 与双向保护并存: 60s 内瞬态污染仍只冻结不污染 Origin */ +uint8_t g_env_resync = 0; // 1=学习中 (黄灯常亮指示) +uint16_t g_env_wait_cnt = 0; // dev>+4dlt 高位连续累计 (≥ENV_RESYNC_WAIT 触发学习) +uint32_t g_env_sum = 0; // 学习窗口 CAPVD 累加 +uint16_t g_env_cnt = 0; // 学习窗口样本数 +uint32_t g_env_prev = 0; // 上一窗口均值 (B 候选) +uint8_t g_env_settle = 0; // 连续稳定窗口计数 (窗内均值漂移 ≤0.1% 则+1) +uint16_t g_env_tick = 0; // 学习预算 (超时放弃防死锁) + #if USE_FLATNESS_EXIT uint8_t g_exit_state; uint16_t g_max_slope; @@ -310,6 +321,14 @@ void poll_yellow_led(void) return; } + /*--- V4.24 模式4: 环境重校准学习中 → 黄灯常亮 (优先级: 断开快闪 > 学习常亮 > N短闪 > 灭) ---*/ + if (g_env_resync) { + LED_YELLOW_ON; + g_fault_tick = 0; + g_fault_phase = 0; + return; + } + uint8_t N = g_disconnect_count; if (N == 0) { @@ -403,6 +422,15 @@ void INIT_VD(void) g_stable_cnt = 0; // V4.23: 自检计数从零开始 g_settle_cnt = 0; + /* V4.24: 环境重校准复位 */ + 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; + /* M4 优化: 快速 IIR + 进入确认 */ loop1_entry_cnt = 0; loop1_freeze_cnt = 0; @@ -704,6 +732,40 @@ static uint32_t calc_freq_khz(uint32_t capvd, uint16_t lp_cnt) return (60000UL * lp_cnt) / capvd; } +/*=========================================================================== + * V4.24: 环境重校准 — 学习被打断/放弃时复位 (黄灯恢复) / 完成时原子替换 Origin + * + * 替换安全设计: + * - 仅空闲(无车, dev 高位)且学习 B 连续 2 窗稳定后才调用 + * - B ≈ 当前空闲 CAPVD → 替换瞬间 dev≈0, 无跳变不会触发继电器 + * - 同步清 freeze/ORG/entry 残留, slow 滤波 snap (同 V4.04 进入时) + *===========================================================================*/ +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; +} + +static void env_resync_apply(uint32_t new_origin) +{ + PRINT("EVT|env_resync Origin:%d->%d CAPVD:%d\n", + loop1_Origin, new_origin, loop1_CAPVD); + loop1_Origin = new_origin; + loop1_freeze_cnt = 0; + loop1_freeze_ref = 0; + loop1_ORG_CNT = 0; + loop1_ORG_SUM = 0; + loop1_entry_cnt = 0; +#if USE_SLOW_RELEASE + loop1_CAPVD_slow = loop1_CAPVD; // 防替换后瞬态假释放 (同 V4.04 snap) +#endif + env_resync_abort(); +} + /*=========================================================================== * vd1_task — 核心检测算法(对齐 M1H VD1_TASK) * @@ -719,6 +781,9 @@ void vd1_task(void) #define STABLE_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×1/1000 (0.1%) #define STABLE_SETTLE_WINDOWS 2 // 连续 2 个窗口(每窗100样本≈1s)漂移达标 → 判稳 #define STABLE_MAX_SAMPLES 500 // 硬兜底: 5s 未判稳强制结束自检(防绿灯无限闪) +#define ENV_RESYNC_WAIT 6000 // V4.24: dev>+4dlt 高位连续 60s → 判定永久环境变化 +#define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s (200×10ms) +#define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃 (防黄灯常亮死锁) if (loop1_Origin == 0) return; @@ -812,9 +877,13 @@ void vd1_task(void) /* 对称窗口内 → 正常基线跟踪 */ loop1_freeze_cnt = 0; loop1_freeze_ref = 0; // V4.20: 清残留参考, 打印 FRZ 干净 + g_env_wait_cnt = 0; // V4.24: 高位回摆/目标靠近 → 等待重来 + env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复) update_moving_average(&loop1_ORG_SUM, &loop1_ORG_CNT, &loop1_Origin, loop1_CAPVD, WINDOW_ORIGIN); } else if (dev < 0) { + g_env_wait_cnt = 0; // V4.24: 目标靠近(车方向) → 等待重来 + env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复) /* 正variation (CAPVD低于Origin, 疑似车辆) → 冻结, 保留原超时兜底更新 */ if (loop1_freeze_cnt == 0) { loop1_freeze_ref = loop1_CAPVD; @@ -839,8 +908,13 @@ void vd1_task(void) loop1_ORG_SUM = 0; } } else { - /* 负variation (CAPVD高于Origin, 特殊铁块磁导率主导/异常高) → 只冻结, 永不更新 Origin - * 防 Origin 污染锁死; 权衡: 长时间负温漂基线不跟随(安全方向, 检测仍正常) */ + /* 负variation (CAPVD高于Origin, 特殊铁块磁导率主导/异常高) + * V4.24 两段式: + * ① 前 60s(ENV_RESYNC_WAIT): 只冻结, 不更新 Origin + * (36321a1 双向保护原语义: 防铁块污染 Origin → 离开假进入锁死) + * ② 高位连续 ≥60s 未回摆 → 判定"永久环境变化"(如金属板A拿走、基准被学低) + * → 进入受控重校准: 连续空闲窗口均值 B + settle 判稳 → 空闲原子替换 Origin + * 权衡: 铁块静止 60s+ 也会被学入 → 靠等待时长 + 黄灯常亮提示(操作接口) */ if (loop1_freeze_cnt == 0) { loop1_freeze_ref = loop1_CAPVD; PRINT("EVT|neg_var_freeze CAPVD:%d Origin:%d dev:%d dlt:%d f:%dkHz\n", @@ -848,9 +922,57 @@ void vd1_task(void) calc_freq_khz(loop1_CAPVD, loop1_LPCNT)); } loop1_freeze_cnt++; - if (loop1_freeze_cnt > FREEZE_TIMEOUT) loop1_freeze_cnt = FREEZE_TIMEOUT; // 饱和, 不更新 + if (loop1_freeze_cnt > FREEZE_TIMEOUT) loop1_freeze_cnt = FREEZE_TIMEOUT; // 打印饱和, 不更新 loop1_ORG_CNT = 0; loop1_ORG_SUM = 0; + + if (!g_env_resync) { + /*--- ① 等待确认: dev>+4dlt 高位连续累计 (回摆清零见窗口/dev<0分支) ---*/ + g_env_wait_cnt++; + if (g_env_wait_cnt >= ENV_RESYNC_WAIT) { + 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; + PRINT("EVT|env_learn_start Origin:%d CAPVD:%d dev:%d\n", + loop1_Origin, loop1_CAPVD, (int)dev); + } + } else { + /*--- ② 学习基线 B: 连续空闲窗口均值 + settle 判稳 (同 V4.23 上电自检语义) ---*/ + g_env_tick++; + g_env_sum += loop1_CAPVD; + g_env_cnt++; + if (g_env_cnt >= ENV_LEARN_WINDOW) { + uint32_t _m = g_env_sum / g_env_cnt; + uint32_t _drift, _band; + g_env_sum = 0; + g_env_cnt = 0; + if (g_env_prev != 0) { + _drift = (_m > g_env_prev) ? (_m - g_env_prev) : (g_env_prev - _m); + _band = _m * STABLE_ORIGIN_PPT / 1000; + if (_drift <= _band) { + g_env_settle++; + } else { + g_env_settle = 0; // 窗口不稳 → 重算 + } + if (g_env_settle >= STABLE_SETTLE_WINDOWS) { + /* 连续 2 窗稳定 → B 可信; 空闲由上下文保证, 原子替换 */ + env_resync_apply(_m); + return; // 本 tick 不再走进入判定 + } + } + g_env_prev = _m; + } + if (g_env_tick >= ENV_LEARN_MAX_TICKS) { + /* 学习预算超时 → 放弃, 保留旧 Origin (宁可灵敏度低不冒险污染) */ + PRINT("EVT|env_learn_abort timeout Origin:%d CAPVD:%d\n", + loop1_Origin, loop1_CAPVD); + env_resync_abort(); + } + } } } @@ -1026,6 +1148,8 @@ void loop_task_function(void *pvParameters) g_disconnect_active = 0; // 重连,清除断开标记 loop1_CAPVD = 0; // 强制 IIR 从首个有效值重新收敛 loop1_CAPVD_slow = 0; // 方案A: 释放慢滤波同步收敛 + g_env_wait_cnt = 0; // V4.24: 断开重连后环境学习状态复位 + env_resync_abort(); PRINT("EVT|loop_reconnect\n"); }