feat(V4B): V4.24 — 环境重校准:永久环境变化自动恢复基线(金属板A拿走场景)

现场(2026-09-02实测): 线圈旁金属板A上电把Origin学低(128096),A拿走CAPVD弹回
真空场(128856) dev=+761(0.59%),被36321a1双向保护"dev>0永不更新"永久冻结
→ B触发需809cnt(正常54cnt) 灵敏度假性降低且不恢复(V4.20分钟级可自愈)

修复: dev>+4dlt高位两段式受控重校准
  ① 60s(ENV_RESYNC_WAIT)高位连续未回摆 → 判定永久环境变化(瞬态铁块类不污染)
  ② 学习基线B: 连续空闲窗200tick均值 + settle判稳(连续2窗漂移≤0.1%)
  ③ 空闲原子替换Origin, 清freeze/ORG/entry残留, slow snap, 黄灯恢复
- 学习期打断(回窗/dev<0/断线重连)→重算; 30s预算超时放弃保留旧Origin
- 黄灯模式4: 环境学习中常亮(优先级: 断开快闪>学习常亮>N短闪>灭)
- tests/test_env_resync.c 5场景ALL PASS(恢复66s/瞬态不学/打断重算/放弃/不震荡)
- docs: devlog V2.22 + spec V2.3 + 产品手册 V2.7 同步
This commit is contained in:
wangfq
2026-09-02 18:10:28 +08:00
parent cd0fd90770
commit 672f143e99
6 changed files with 477 additions and 8 deletions
+2 -2
View File
@@ -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
+127 -3
View File
@@ -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");
}