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
+52 -1
View File
@@ -4,6 +4,57 @@
--- ---
## 2026-09-02 — 环境重校准:现场永久环境变化自动恢复基线(固件 V4.24)
> **固件版本:V4.24**cmcng.hFIRMWARE_VER="4.24" + MAIN=4/SUB=24)。devlog 修订 V2.22。
### 现场现象(V4.22 实测复现,DBG/EVT 串口日志)
线圈旁金属板 A 上电 → 稳定后 Origin 学低;A 拿走 → CAPVD 弹回真空场,
相对 Origin 恒高 dev=+761 cnt0.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 sENV_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=300030 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) ## 2026-09-02 — 红灯呼吸对齐 vd960Loop + 上电自检基准判稳(固件 V4.23)
> **固件版本:V4.23**cmcng.hFIRMWARE_VER="4.23" + MAIN=4/SUB=23)。devlog 修订 V2.21。 > **固件版本:V4.23**cmcng.hFIRMWARE_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.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 前缀) |
+11 -1
View File
@@ -1,6 +1,6 @@
# DLD154V4B 产品手册 # DLD154V4B 产品手册
> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.6 > 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.7
> 适用于停车场、收费站、交通卡口等场景的车辆到达/离开检测 > 适用于停车场、收费站、交通卡口等场景的车辆到达/离开检测
--- ---
@@ -181,6 +181,16 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈
| 线圈重连,断开 2 次 | 呼吸 | 正常 | **2 短闪** + 1.2s 灭 | 发生过 2 次断开,已恢复 | | 线圈重连,断开 2 次 | 呼吸 | 正常 | **2 短闪** + 1.2s 灭 | 发生过 2 次断开,已恢复 |
| 线圈重连,断开 3+ 次 | 呼吸 | 正常 | **3 短闪** + 1.2s 灭 | 多次断开,建议检查接头 | | 线圈重连,断开 3+ 次 | 呼吸 | 正常 | **3 短闪** + 1.2s 灭 | 多次断开,建议检查接头 |
### 5.3 环境校准(V4.24 新增)
| 状态 | 红灯 | 绿灯 | 黄灯 | 说明 |
|------|------|------|------|------|
| 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):先高位冻结 60s 确认,再自动学习新基线并替换,恢复灵敏度 |
- 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起:高位持续 60s 判定永久变化 → 黄灯常亮数秒(学习中)→ 自动恢复,无需断电
- **看到黄灯常亮表示正在学习新基线,请勿移动线圈附近的目标**(铁块静止 60s+ 也会被学入)
- 黄灯优先级:断开快闪 > 学习常亮 > N 短闪(断开史)> 灭
- **快闪**200ms 亮 / 200ms 灭 - **快闪**200ms 亮 / 200ms 灭
- **N 短闪**N × (80ms 亮 + 200ms 灭),循环间隔 1.2s - **N 短闪**N × (80ms 亮 + 200ms 灭),循环间隔 1.2s
- **呼吸**:PWM 渐变,系统运行指示(周期≈2.6sV4.23 起对齐 vd960Loop 观感) - **呼吸**:PWM 渐变,系统运行指示(周期≈2.6sV4.23 起对齐 vd960Loop 观感)
+6 -1
View File
@@ -1,6 +1,6 @@
# DLD154V4B 技术规格书 # DLD154V4B 技术规格书
> 单路线圈车辆检测器 | 固件版本: V4.23 | 文档版本: V2.2 > 单路线圈车辆检测器 | 固件版本: V4.24 | 文档版本: V2.3
> 本文档面向工程开发、系统集成及故障深度分析 > 本文档面向工程开发、系统集成及故障深度分析
--- ---
@@ -364,6 +364,7 @@ if (!VD_FLAG) {
| 4× 冻结阈值 | 防止异常 CAPVD 上升污染基线(V1.5 | | 4× 冻结阈值 | 防止异常 CAPVD 上升污染基线(V1.5 |
| 5% 斜率限幅 | 过滤 EMI/闪电尖峰,保护 IIRV2.0 | | 5% 斜率限幅 | 过滤 EMI/闪电尖峰,保护 IIRV2.0 |
| 冻结超时 + 稳定性检查 | 防止永久冻结死锁(V2.3~2.5 | | 冻结超时 + 稳定性检查 | 防止永久冻结死锁(V2.3~2.5 |
| 环境重校准(V4.24 | dev>+4×dlt 高位连续 60sENV_RESYNC_WAIT)→ 判定永久环境变化 → 学习基线 B(空闲窗均值+判稳,黄灯常亮)→ 原子替换 Origin;铁块类瞬态 60s 内仍只冻结不污染 |
#### 4.4.3 基线更新速率(两阶段) #### 4.4.3 基线更新速率(两阶段)
@@ -448,6 +449,9 @@ 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_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。 > **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.0 | 2026-06-29 | 更新至 V2.5 固件:双路 IIR、斜率限幅、进入确认、冻结超时+稳定性 |
| 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 学习预算兜底) |
+279
View File
@@ -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 <assert.h>
#include <stdio.h>
#include <stdint.h>
/*========== 与固件一致的参数宏 (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;
}
+2 -2
View File
@@ -14,10 +14,10 @@
#define __CMCNG_H__ #define __CMCNG_H__
#define PRODUCT_MODEL "DLD154" #define PRODUCT_MODEL "DLD154"
#define FIRMWARE_VER "4.23" #define FIRMWARE_VER "4.24"
#define HARDWARE_VER "3.00" #define HARDWARE_VER "3.00"
#define FIRMWARE_VER_MAIN 4 #define FIRMWARE_VER_MAIN 4
#define FIRMWARE_VER_SUB 23 #define FIRMWARE_VER_SUB 24
#define HARDWARE_VER_MAIN 3 #define HARDWARE_VER_MAIN 3
#define HARDWARE_VER_SUB 0 #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_stable_cnt = 0; // 稳定期累计样本 (每次 CAP_OK +1)
uint16_t g_settle_cnt = 0; // 基准稳定连续窗口计数 (窗口=100样本≈1s) 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 #if USE_FLATNESS_EXIT
uint8_t g_exit_state; uint8_t g_exit_state;
uint16_t g_max_slope; uint16_t g_max_slope;
@@ -310,6 +321,14 @@ void poll_yellow_led(void)
return; 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; uint8_t N = g_disconnect_count;
if (N == 0) { if (N == 0) {
@@ -403,6 +422,15 @@ void INIT_VD(void)
g_stable_cnt = 0; // V4.23: 自检计数从零开始 g_stable_cnt = 0; // V4.23: 自检计数从零开始
g_settle_cnt = 0; 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 + 进入确认 */ /* M4 优化: 快速 IIR + 进入确认 */
loop1_entry_cnt = 0; loop1_entry_cnt = 0;
loop1_freeze_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; 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 * 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_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×1/1000 (0.1%)
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 个窗口(每窗100样本≈1s)漂移达标 → 判稳 #define STABLE_SETTLE_WINDOWS 2 // 连续 2 个窗口(每窗100样本≈1s)漂移达标 → 判稳
#define STABLE_MAX_SAMPLES 500 // 硬兜底: 5s 未判稳强制结束自检(防绿灯无限闪) #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; if (loop1_Origin == 0) return;
@@ -812,9 +877,13 @@ 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: 高位回摆/目标靠近 → 等待重来
env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复)
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: 目标靠近(车方向) → 等待重来
env_resync_abort(); // V4.24: 学习中被打断 → 重算 (黄灯恢复)
/* 正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;
@@ -839,8 +908,13 @@ void vd1_task(void)
loop1_ORG_SUM = 0; loop1_ORG_SUM = 0;
} }
} else { } else {
/* 负variation (CAPVD高于Origin, 特殊铁块磁导率主导/异常高) → 只冻结, 永不更新 Origin /* 负variation (CAPVD高于Origin, 特殊铁块磁导率主导/异常高)
* 防 Origin 污染锁死; 权衡: 长时间负温漂基线不跟随(安全方向, 检测仍正常) */ * V4.24 两段式:
* ① 前 60s(ENV_RESYNC_WAIT): 只冻结, 不更新 Origin
* (36321a1 双向保护原语义: 防铁块污染 Origin → 离开假进入锁死)
* ② 高位连续 ≥60s 未回摆 → 判定"永久环境变化"(如金属板A拿走、基准被学低)
* → 进入受控重校准: 连续空闲窗口均值 B + settle 判稳 → 空闲原子替换 Origin
* 权衡: 铁块静止 60s+ 也会被学入 → 靠等待时长 + 黄灯常亮提示(操作接口) */
if (loop1_freeze_cnt == 0) { if (loop1_freeze_cnt == 0) {
loop1_freeze_ref = loop1_CAPVD; loop1_freeze_ref = loop1_CAPVD;
PRINT("EVT|neg_var_freeze CAPVD:%d Origin:%d dev:%d dlt:%d f:%dkHz\n", 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)); calc_freq_khz(loop1_CAPVD, loop1_LPCNT));
} }
loop1_freeze_cnt++; 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_CNT = 0;
loop1_ORG_SUM = 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; // 重连,清除断开标记 g_disconnect_active = 0; // 重连,清除断开标记
loop1_CAPVD = 0; // 强制 IIR 从首个有效值重新收敛 loop1_CAPVD = 0; // 强制 IIR 从首个有效值重新收敛
loop1_CAPVD_slow = 0; // 方案A: 释放慢滤波同步收敛 loop1_CAPVD_slow = 0; // 方案A: 释放慢滤波同步收敛
g_env_wait_cnt = 0; // V4.24: 断开重连后环境学习状态复位
env_resync_abort();
PRINT("EVT|loop_reconnect\n"); PRINT("EVT|loop_reconnect\n");
} }