From 92f675e2d3032e84686af16e96e7232d74ca7f02 Mon Sep 17 00:00:00 2001 From: wangfq Date: Thu, 3 Sep 2026 10:50:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(V4B):=20V4.26=20=E2=80=94=20=E7=BA=BF?= =?UTF-8?q?=E5=9C=88=E7=94=B5=E6=84=9F=E9=87=8F=E5=91=8A=E8=AD=A6(?= =?UTF-8?q?=E4=B8=80=E9=95=BF=E4=B8=80=E7=9F=AD)=20+=20=E6=95=85=E9=9A=9C?= =?UTF-8?q?=E8=A1=A5=E5=81=BF=2060s=E2=86=9210s=20(4=E7=82=B9=E6=8B=8D?= =?UTF-8?q?=E6=9D=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/coil-range-alarm-design.md | 8 +- docs/devlog.md | 40 ++ docs/product-manual.md | 10 +- docs/technical-spec.md | 44 +- tests/test_coil_range.c | 116 ++++++ tests/test_env_resync.c | 392 +++++++++--------- .../at32f421_freertos_demo/inc/TaskLoop.h | 3 + utilities/at32f421_freertos_demo/inc/cmcng.h | 4 +- .../at32f421_freertos_demo/src/TaskLoop.c | 76 +++- 9 files changed, 465 insertions(+), 228 deletions(-) create mode 100644 tests/test_coil_range.c diff --git a/docs/coil-range-alarm-design.md b/docs/coil-range-alarm-design.md index b85e772..8d8b463 100644 --- a/docs/coil-range-alarm-design.md +++ b/docs/coil-range-alarm-design.md @@ -1,7 +1,7 @@ # 线圈电感量估算与不合理范围告警 — 设计讨论稿 -> **状态:设计讨论稿(未实现)** — 本文仅存档讨论结论,代码未落地。 -> 固件版本基线:V4.25(cmcng.h);涉及产品:DLD154V4B(DLD154V4)。 +> **状态:已实现(V4.26,2026-09-03)** — 本文为设计讨论与实现归档;落地代码见 TaskLoop.c coil_monitor()(vd1_task 无车主路径调用)与 poll_yellow_led() 模式 5。 +> 固件版本基线:V4.26(cmcng.h);涉及产品:DLD154V4B(DLD154V4)。 **修订记录:** @@ -9,6 +9,7 @@ |------|------|------| | v0.1 | 2026-09-02 | 初稿(合理范围按 50~300 µH,三区制) | | v0.2 | 2026-09-03 | 合理范围改 **50 ~ 1000 µH(规格书口径,两档任意一档合法即不告警)**,三区窗口重算 | +| v0.3 | 2026-09-03 | **用户 4 点拍板收口(取代 v0.2 三区/灰带)**:默认低频档频率窗 **20~120 kHz** 出界即告警;黄灯**一长一短**;优先级 1 断开快闪 > 4 故障补偿常亮 > 5 电感一长一短 > 3 N短亮 > 2 灭;故障补偿 ENV_RESYNC_WAIT **60s→10s**。已实现于 V4.26(672f143 后) | ## 1. 背景 @@ -17,7 +18,8 @@ DLD154V4 线圈振荡电路:固定电容 **33 nF** + 拨码开关可并联另 需求(用户最终口径): 1. 由实测振荡频率推算线圈电感量 L(粗略值即可) -2. 对不合理电感量范围的线圈告警:**两档任意 L ∈ [50, 1000] µH 合法**(规格书范围),超出才告警 +2. 对不合理电感量范围的线圈告警(**v0.3 拍板,取代 §4 三区制**):按默认低频档计算,频率合理范围 + **20 ~ 120 kHz,不在合理范围内一律判定电感量不合理 → 告警**(不做档位判定、不做灰带提示) 3. 预留 C_eff 校准公式/系数,测试时微调 4. 持续监视 diff --git a/docs/devlog.md b/docs/devlog.md index 2d51580..4112272 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -4,6 +4,45 @@ --- +## 2026-09-03 — 线圈电感量告警 + 故障补偿提速(固件 V4.26) + +> **固件版本:V4.26**(cmcng.h:FIRMWARE_VER="4.26" + MAIN=4/SUB=26)。devlog 修订 V2.24。 + +### 背景 + +DLD154V4 线圈振荡两级频率(33/66nF 拨码,拨码不引 IO)。现场抓到 f=16.3kHz(Xn=3681) 超范围 +线圈(L≈1.44mH@66nF),检测异常难排查 → 要求对不合理电感量自动告警。 +另:环境重校准(V4.24/25)补偿总时长 ~66s 偏长 → 拍板提速。 + +### 拍板规格(2026-09-03) + +1. 按默认低频档计算,频率合理范围 **20 ~ 120 kHz**,出界即判定电感量不合理 → 告警(不做档位判定/灰带) +2. 告警形式:黄灯**一长一短**(长亮200ms/灭100ms/短亮100ms/灭800ms 循环) +3. 黄灯优先级:线圈断开快闪 > 故障补偿(学习补偿)常亮 > 电感量不合理一长一短 > 线圈断开 N 短亮 > 灭 +4. 故障补偿黄灯常亮(等待学习)时间 **60s → 10s**(ENV_RESYNC_WAIT 6000→1000) + +### 改动 + +- TaskLoop.c:新增 coil_monitor()(稳定+无车空闲由无车主路径调用;Origin 反推空场 f; + f<20kHz→L偏大 / f>120kHz→L偏小,1s 防抖置位/清除;EVT|coil_fault/coil_ok) +- poll_yellow_led():新增模式5(一长一短 40/20/20/160 tick);优先级 1>4>5>3>2 +- ENV_RESYNC_WAIT 6000→1000(5s 预判 → 10s 学习 → ~16s 替换 → 空闲 5s 确认灭,总 ~21s) +- TaskLoop.h:COIL_F_OK_MIN_KHZ=20 / COIL_F_OK_MAX_KHZ=120 / COIL_DEF_C_NF=66 +- 行尾修复:V4.24~25 编辑期间源文件行尾被写成多 CR(\r\r\r\r\n),本次统一规范化回标准 CRLF + +### 验证 + +- tests/test_coil_range.c 5 场景:L偏大/偏小 1s 防抖置位、恢复 1s 防抖清除、边界 19/20/120/121、抖动不误报 ALL PASS +- tests/test_env_resync.c(ENV_RESYNC_WAIT=1000):永久变化 applied_at=1600tick、瞬态<10s 不学、 + 锁存保持、打断重算、放弃不污染 ALL PASS +- gcc -fsyntax-only 0 error + +### 待现场验证 + +- 超范围线圈(如 f=16.3kHz)应黄灯一长一短;正常 100~300µH 线圈(f 20~120k 内)不误报 +- 故障补偿总时长 ~21s;环境高位 10s 即学(铁块静止 >10s 会被学入,黄灯常亮提示请勿移动目标) +- 电感告警阈值边界用标定线圈(100µH/300µH)实测微调 + ## 2026-09-02 — 环境异常黄灯预判:5s 常亮提示将进入重学(固件 V4.25) > **固件版本:V4.25**(cmcng.h:FIRMWARE_VER="4.25" + MAIN=4/SUB=25)。devlog 修订 V2.23。 @@ -1065,6 +1104,7 @@ if (dev >= dlt_ORG × 4) { | 版本 | 时间 | 说明 | |------|------|------| +| V2.24 | 2026-09-03 | V4.26 线圈电感量告警(频率窗 20~120kHz 一长一短)+ 故障补偿 60s→10s;行尾规范化 CRLF | | 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.19 | 2026-09-02 | V4.20 负variation铁块锁死修复:基线跟踪双向对称保护(±4×dlt 对称窗口) | diff --git a/docs/product-manual.md b/docs/product-manual.md index 8650950..f08f9d1 100644 --- a/docs/product-manual.md +++ b/docs/product-manual.md @@ -1,6 +1,6 @@ # DLD154V4B 产品手册 -> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.8 +> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.9 > 适用于停车场、收费站、交通卡口等场景的车辆到达/离开检测 --- @@ -177,6 +177,7 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 | 故障类型 | 红灯 | 绿灯 | 黄灯 | 说明 | |---------|------|------|------|------| | 线圈断开(当前) | 呼吸 | 灭 | **快闪** (200ms) | 检查线圈连接 | +| 线圈电感量不合理(V4.26) | 呼吸 | 灭(无车) | **一长一短** (200ms亮/100ms灭/100ms亮/800ms灭 循环) | 空场频率出 20~120kHz(默认低频档):L偏大(f<20k)/L偏小(f>120k),检查线圈匝数/尺寸 | | 线圈重连,断开 1 次 | 呼吸 | 正常 | **1 短闪** + 1.2s 灭 | 发生过 1 次断开,已恢复 | | 线圈重连,断开 2 次 | 呼吸 | 正常 | **2 短闪** + 1.2s 灭 | 发生过 2 次断开,已恢复 | | 线圈重连,断开 3+ 次 | 呼吸 | 正常 | **3 短闪** + 1.2s 灭 | 多次断开,建议检查接头 | @@ -185,14 +186,15 @@ DLD154V4B 是一款基于 ARM Cortex-M4 微控制器的**单通道环形线圈 | 状态 | 红灯 | 绿灯 | 黄灯 | 说明 | |------|------|------|------|------| -| 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):高位持续 **5 s** 黄灯预判**锁存**常亮,累计 **60 s** 后自动学习新基线并替换,恢复灵敏度 | +| 环境校准学习中 | 呼吸 | 灭(无车) | **常亮** | 检测到**永久环境变化**(如旁置金属板被移走使基准被学低):高位持续 **5 s** 黄灯预判**锁存**常亮,累计 **10 s**(V4.26 起,原 60s)后自动学习新基线并替换,恢复灵敏度 | -- 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起自动恢复;V4.25 起环境变化 **~5 s** 黄灯即常亮预判(提示"检测器已感知环境变化") +- 线圈旁金属等目标在**上电时**在场会把空车基准学低;目标移走后检测高度会明显变低(V4.22 及以前需断电重上电或等基线慢跟恢复)。V4.24 起自动恢复;V4.25 起环境变化 **~5 s** 黄灯即常亮预判;V4.26 起故障补偿提速:约 **5s 预判 → 10s 学习 → ~16s 替换 → 空闲 5s 恢复确认后灭(总 ~21s)** - **黄灯常亮(锁存)= 环境异常预判/学习中**:一旦点亮,目标移走/回摆**不会立即灭灯**——需环境恢复正常(空闲)且稳定 **~5 s** 才解除。请勿在黄灯常亮期间移动线圈附近的金属目标(铁块静止 60 s+ 也会被学入) -- 黄灯优先级:断开快闪 > 环境预判/学习常亮 > N 短闪(断开史)> 灭 +- 黄灯优先级(V4.26):断开快闪 > 故障补偿(环境预判/学习)常亮 > **线圈电感量不合理一长一短** > N 短闪(断开史)> 灭 - **快闪**:200ms 亮 / 200ms 灭 - **N 短闪**:N × (80ms 亮 + 200ms 灭),循环间隔 1.2s +- **一长一短**(V4.26):200ms 亮 + 100ms 灭 + 100ms 亮 + 800ms 灭,循环(线圈电感量不合理) - **呼吸**:PWM 渐变,系统运行指示(周期≈2.6s,V4.23 起对齐 vd960Loop 观感) - **绿灯慢闪(上电自检)**:200ms 亮/灭,持续至基准值稳定(连续窗口 Origin 漂移 ≤0.1%)才停止 (V4.23) diff --git a/docs/technical-spec.md b/docs/technical-spec.md index dc42c1e..fa7d574 100644 --- a/docs/technical-spec.md +++ b/docs/technical-spec.md @@ -1,6 +1,6 @@ # DLD154V4B 技术规格书 -> 单路线圈车辆检测器 | 固件版本: V4.25 | 文档版本: V2.4 +> 单路线圈车辆检测器 | 固件版本: V4.26 | 文档版本: V2.5 > 本文档面向工程开发、系统集成及故障深度分析 --- @@ -364,7 +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 内仍只冻结不污染 | +| 环境重校准(V4.24/26) | dev>+4×dlt 高位连续 10s(ENV_RESYNC_WAIT,V4.26 由 60s 提速)→ 判定永久环境变化 → 学习基线 B(空闲窗均值+判稳,黄灯常亮)→ 原子替换 Origin;铁块类瞬态 10s 内仍只冻结不污染 | #### 4.4.3 基线更新速率(两阶段) @@ -451,9 +451,12 @@ if (dev < dlt_ORG * 4) { | FREEZE_TIMEOUT | 1000 | **~10 s** | 基线冻结超时(连续稳定后更新 Origin) | | 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 | 1000 tick | **~10 s** | 环境重校准等待:dev>+4×dlt 高位连续 10s 判定永久环境变化(V4.26 拍板:60s→10s) | | ENV_LEARN_WINDOW | 200 tick | ~2 s | 环境学习窗口(基线 B 均值窗,学习期黄灯常亮) | | ENV_LEARN_MAX_TICKS | 3000 tick | **~30 s** | 学习预算:窗口不稳超时放弃,保留旧 Origin(防死锁) | +| COIL_F_OK_MIN_KHZ | 20 kHz | — | 线圈频率合理窗下界(V4.26):默认低频档口径,f<20kHz → L偏大告警 | +| COIL_F_OK_MAX_KHZ | 120 kHz | — | 线圈频率合理窗上界(V4.26):f>120kHz → L偏小告警 | +| COIL_DEF_C_NF | 66 nF | — | 默认档电容(拨码不可读,粗估 L 打印用;33nF 档结果 ×2) | > **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。 @@ -498,23 +501,28 @@ void poll_green_led(void) { ### 6.3 黄灯行为表 -| 条件 | 行为 | 参数 | 诊断含义 | -|------|------|------|---------| -| `g_disconnect_active == 1` | **快闪** | 200ms 亮 / 200ms 灭 | 线圈当前断开中 | -| `!g_loop_power_up_state` | **快闪** | 200ms 亮 / 200ms 灭 | 上电后从未连接线圈 | -| `g_disconnect_count == 1` | **1 短闪** | 80ms 亮 / 200ms 灭 → 1.2s 灭(循环) | 断开 1 次,已恢复 | -| `g_disconnect_count == 2` | **2 短闪** | 同上 | 断开 2 次 | -| `g_disconnect_count >= 3` | **3 短闪** | 同上 | 断开 3+ 次 | -| 正常 | **灭** | — | 一切正常 | +| 优先级 | 条件 | 行为 | 参数 | 诊断含义 | +|--------|------|------|------|---------| +| 1 | `g_disconnect_active == 1` 或 `!g_loop_power_up_state` | **快闪** | 200ms 亮 / 200ms 灭 | 线圈当前断开中 / 从无线圈上电 | +| 4 | `g_env_warn` 或 `g_env_resync` | **常亮** | — | 环境异常预判/重校准学习(V4.24/25;故障补偿 ~10 s 内完成) | +| 5 | `g_coil_fault`(V4.26) | **一长一短** | 长亮 200ms / 灭 100ms / 短亮 100ms / 灭 800ms 循环 | 电感量不合理:1=L偏大(f<20kHz) 2=L偏小(f>120kHz) | +| 3 | `g_disconnect_count == 1` | **1 短闪** | 80ms 亮 / 200ms 灭 → 1.2s 灭(循环) | 断开 1 次,已恢复 | +| 3 | `g_disconnect_count == 2` | **2 短闪** | 同上 | 断开 2 次 | +| 3 | `g_disconnect_count >= 3` | **3 短闪** | 同上 | 断开 3+ 次 | +| 2 | 正常 | **灭** | — | 一切正常 | ```c -void poll_yellow_led(void) { +void poll_yellow_led(void) { // 优先级 1 > 4 > 5 > 3 > 2 (V4.26) if (g_disconnect_active || !g_loop_power_up_state) - fast_blink_200ms(); // 断开中快闪 + fast_blink_200ms(); // ① 断开中快闪 + else if (g_env_warn || g_env_resync) + led_on(); // ④ 环境预判/学习中常亮 (V4.24/25) + else if (g_coil_fault) + long_short_blink(); // ⑤ 电感量不合理: 一长一短 (V4.26) else if (g_disconnect_count > 0) - n_short_flash(g_disconnect_count); // N 短闪编码 + n_short_flash(g_disconnect_count); // ③ N 短闪编码 else - led_off(); // 正常 + led_off(); // ② 正常 } ``` @@ -617,14 +625,15 @@ if (!g_loop_stable) { |--------|---------|---------| | 线圈断开 | `loop1_RF_FLAG == 0` 持续 | 黄灯快闪 / 继电器释放 | | 线圈短路 | Xn 持续为 0 或极小 | 黄灯快闪 / 继电器释放 | -| 频率过低 | Value 持续 < MIN_FREQ | 黄灯编码闪烁 | -| 频率过高 | Value 持续 > MAX_FREQ | 黄灯编码闪烁 | +| 电感量不合理(L偏大) | 空场 f 持续 < 20 kHz(默认低频档,V4.26) | 黄灯**一长一短**(g_coil_fault=1)/ 持续监视 1s 防抖 | +| 电感量不合理(L偏小) | 空场 f 持续 > 120 kHz | 黄灯**一长一短**(g_coil_fault=2)/ 持续监视 1s 防抖 | ### 10.2 黄灯编码设计 断开次数编码提供了比 TLD-110 的均匀闪烁更丰富的诊断信息: - **快闪=当前故障** → 现场立即处理 - **N短闪=历史故障** → 判断是偶发还是持续问题 +- **一长一短=线圈电感量不合理**(V4.26):空场频率出 20~120kHz 窗(默认低频档);用标定线圈实测后微调阈值 --- @@ -760,3 +769,4 @@ DLD154V4B V1.4 起实现该专利的整数化版本,用于大车通行时的 | 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.4 | 2026-09-02 | 更新至 V4.25:环境异常黄灯预判锁存(ENV_WARN_WAIT=5s 触发;回摆保持,ENV_RECOVER_WAIT 空闲 5s 才解除);预判≠学习 | +| V2.5 | 2026-09-03 | 更新至 V4.26:线圈电感量告警(频率窗 20~120kHz 单窗,黄灯一长一短);故障补偿 ENV_RESYNC_WAIT 60s→10s;黄灯优先级 1>4>5>3>2 | diff --git a/tests/test_coil_range.c b/tests/test_coil_range.c new file mode 100644 index 0000000..8f8acf8 --- /dev/null +++ b/tests/test_coil_range.c @@ -0,0 +1,116 @@ +/* + * test_coil_range.c — 线圈电感量合理范围监视 gcc 隔离单测 (DLD154V4B V4.26) + * + * 验证对象: TaskLoop.c coil_monitor() (V4.26 拍板规格) + * 默认低频档口径: 空场频率合理窗 20~120kHz, 出界即判定电感量不合理 + * g_coil_fault: 0=正常 1=L偏大(f<20kHz) 2=L偏小(f>120kHz) + * 防抖: 越界/恢复均连续 100tick(=1s) 才置位/清除 (调用点=稳定无车空闲) + * + * 镜像宏与固件一致 (改 COIL_* 时同步此处!) + */ + +#include +#include +#include + +#define COIL_F_OK_MIN_KHZ 20 +#define COIL_F_OK_MAX_KHZ 120 +#define COIL_DEF_C_NF 66 + +static uint8_t g_coil_fault = 0; +static uint16_t g_coil_bad_cnt = 0; + +/* coil_monitor() 纯逻辑镜像 (不含 Origin==0 守卫, 由调用点保证) */ +static void coil_step(uint32_t f_khz) +{ + uint8_t want = 0; + + if (f_khz < COIL_F_OK_MIN_KHZ) + want = 1; + else if (f_khz > COIL_F_OK_MAX_KHZ) + want = 2; + + if (want == g_coil_fault) { + g_coil_bad_cnt = 0; + } else { + g_coil_bad_cnt++; + if (g_coil_bad_cnt >= 100) { + g_coil_fault = want; + g_coil_bad_cnt = 0; + } + } +} + +static void run_ticks(uint32_t f_khz, int n) +{ + int i; + for (i = 0; i < n; i++) + coil_step(f_khz); +} + +static void test_too_large(void) +{ + g_coil_fault = 0; g_coil_bad_cnt = 0; + + /* f=16.3kHz(实测超限线圈) 持续 <1s 不置位 */ + run_ticks(16, 99); + assert(g_coil_fault == 0); + /* 满 1s → L 偏大 */ + coil_step(16); + assert(g_coil_fault == 1); + /* 保持 → 状态稳定不抖 */ + run_ticks(16, 1000); + assert(g_coil_fault == 1); + printf("1. L偏大 (f=16kHz, 1s 防抖置位, 保持稳定) OK\n"); +} + +static void test_too_small(void) +{ + g_coil_fault = 0; g_coil_bad_cnt = 0; + run_ticks(150, 100); + assert(g_coil_fault == 2); + printf("2. L偏小 (f=150kHz, 1s 防抖置位) OK\n"); +} + +static void test_recover(void) +{ + g_coil_fault = 1; g_coil_bad_cnt = 0; + run_ticks(62, 99); + assert(g_coil_fault == 1); /* 恢复防抖 <1s 不清除 */ + coil_step(62); + assert(g_coil_fault == 0); /* 满 1s 恢复正常 */ + printf("3. 恢复正常 (1s 防抖清除) OK\n"); +} + +static void test_boundaries(void) +{ + g_coil_fault = 0; g_coil_bad_cnt = 0; + run_ticks(19, 100); assert(g_coil_fault == 1); /* 19 → 偏大 */ + run_ticks(62, 100); assert(g_coil_fault == 0); /* 20~120 正常 */ + run_ticks(20, 100); assert(g_coil_fault == 0); /* 边界 20 正常 */ + run_ticks(120, 100); assert(g_coil_fault == 0); /* 边界 120 正常 */ + run_ticks(121, 100); assert(g_coil_fault == 2); /* 121 → 偏小 */ + printf("4. 边界 19/20/120/121 OK\n"); +} + +static void test_jitter_no_alarm(void) +{ + /* 抖动: 16 与 62 交替 (每 50tick) → 计数永远到不了 100 → 不误报 */ + int i; + g_coil_fault = 0; g_coil_bad_cnt = 0; + for (i = 0; i < 2000; i++) + coil_step((i & 1) ? 62 : 16); + assert(g_coil_fault == 0); + printf("5. 频率抖动不误报 OK\n"); +} + +int main(void) +{ + test_too_large(); + test_too_small(); + test_recover(); + test_boundaries(); + test_jitter_no_alarm(); + printf("\nALL PASS (窗口 20~120kHz, 防抖 1s)\n"); + return 0; +} diff --git a/tests/test_env_resync.c b/tests/test_env_resync.c index 7d3f13d..2575861 100644 --- a/tests/test_env_resync.c +++ b/tests/test_env_resync.c @@ -1,32 +1,43 @@ /* - * test_env_resync.c — 环境重校准(受控基线更新) gcc 隔离单测 (DLD154V4B V4.24) + * test_env_resync.c — 环境重校准(受控基线更新) gcc 隔离单测 (DLD154V4B V4.26) * - * 验证对象: TaskLoop.c vd1_task() 无车段 dev>0 分支 (V4.24 两段式) + * 验证对象: TaskLoop.c vd1_task() 无车段 dev>0 分支 (V4.24/25 两段式, V4.26 参数更新) * ─ 现场场景 (2026-09-02 实测): 线圈旁金属板A上电 → Origin 学低(128096) - * A 拿走 → CAPVD 弹回真空场(128856) dev=+761 → 双向保护(36321a1)永不更新 - * → B 触发需 809cnt (正常 54cnt) 灵敏度假性降低 - * ─ 修复: dev>+4dlt 高位连续 60s(ENV_RESYNC_WAIT) 未回摆 → 判定永久环境变化 - * → 学习基线 B (连续空闲窗均值 + settle 判稳) → 空闲原子替换 Origin + * A 拿走 → CAPVD 弹回 128856 dev=+761(0.59%) > +4dlt → 冻结累计 + * ─ V4.26 拍板: ENV_RESYNC_WAIT 60s→10s(故障补偿提速), 其余语义不变 * - * 运行: gcc -Wall tests/test_env_resync.c && ./a.out - * 期望: 4 场景 ALL PASS (瞬态 60s 内短停不学; 永久变化 ~66s 恢复) + * 黄灯语义 (poll_yellow_led): 5s 预判锁存(g_env_warn, 回摆保持) → + * 10s 进入学习(g_env_resync, 常亮) → 空闲原子替换 Origin → 空闲 5s 恢复确认 → 灭 + * + * 镜像宏与固件一致(改 ENV_RESYNC_WAIT 时同步此处!) */ #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_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_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃 +#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 -/*========== 与固件同名的 env 全局镜像 (TaskLoop.c :80-90) ==========*/ +#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; @@ -34,19 +45,9 @@ 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 uint8_t g_env_warn = 0; static uint16_t g_env_recover_cnt = 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; @@ -56,48 +57,45 @@ static void env_reset(void) g_env_prev = 0; g_env_settle = 0; g_env_tick = 0; - g_env_warn = 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; } -/* V4.25: 黄灯模式4 条件镜像 (TaskLoop.c poll_yellow_led) — 预判锁存 */ static int env_warn_active(void) { - return g_env_resync || g_env_warn; + return g_env_resync || g_env_warn; /* poll_yellow_led 模式4 条件镜像 */ } -/* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */ static void env_resync_abort(void) { - /* V4.25: abort 只退出学习, 不清预判锁存 g_env_warn (环境未恢复前黄灯保持) */ 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++; } -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) ==========*/ +/* vd1_task 无车段 dev 三分支的 env 相关逻辑镜像 (V4.26) */ static void sim_tick(void) { - int32_t dev = (int32_t)sim_CAPVD - (int32_t)sim_Origin; - int32_t freeze_band = sim_dlt * 4; + 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) ev_abort++; - env_resync_abort(); - /* V4.25: 恢复确认 — 空闲(dev>-dlt)连续 5s → 解除预判锁存 */ + if (g_env_resync) env_resync_abort(); + /* V4.25: 恢复确认 — 真空闲(未进进入线)连续 5s 解除预判锁存 */ if (dev > -(int32_t)sim_dlt) { if (g_env_warn) { g_env_recover_cnt++; @@ -107,229 +105,231 @@ static void sim_tick(void) } } } else { - g_env_recover_cnt = 0; /* 目标进入确认区 → 恢复计时重来 */ + g_env_recover_cnt = 0; /* 目标在进入确认区 → 恢复计时重来 */ } } else if (dev < 0) { /* 车方向 → 冻结 (env 相关) */ g_env_wait_cnt = 0; - if (g_env_resync) ev_abort++; - env_resync_abort(); - g_env_recover_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; /* 高位异常中 → 恢复确认失效 */ + /* 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; /* 预判锁存 (只增不清) */ + 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; - ev_learn_start++; + 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; - uint32_t _drift, _band; + uint32_t _m = g_env_sum / g_env_cnt; 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) { + 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 { + else g_env_settle = 0; - } if (g_env_settle >= STABLE_SETTLE_WINDOWS) { - sim_Origin = _m; /* env_resync_apply 替换 */ - env_resync_apply(_m); - return; /* 本 tick 不再走进入判定 */ + 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) { - ev_abort++; - env_resync_abort(); + env_resync_abort(); /* 预算超时放弃 (保留旧 Origin) */ } } } } -/*========== 测试工具 ==========*/ -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; -} - -/*========== 场景 ==========*/ +/* ============ 场景 1: 永久变化 (A拿走) ~21s 恢复 ============ */ static void test_permanent_change_recovers(void) { - /* ① 常量高位稳定 (A 拿走): Origin=128095, 空场 CAPVD=128856, dev=+761>192 */ - t_set(128095UL, 128856UL, 48); + int tick, warn_at = 0; - int tick = 0; - int warn_at = 0; - for (; tick < 7000 && !ev_applied; tick++) { + 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 = tick + 1; /* 首次黄灯预判条件成立 (wait>=500) */ + 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); - printf("常量高位稳定: 黄灯预判=%d 学习启动=%d 替换=%d 替换tick=%d resync=%d\n", - warn_at, ev_learn_start, ev_applied, tick, g_env_resync); - assert(warn_at == 500); /* V4.25: 高位 5s 即黄灯常亮预判 */ - assert(ev_learn_start == 1); - assert(ev_applied == 1); - assert(tick == 6600); /* 6000 等待 + 3×200 窗 */ + 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(g_env_resync == 0); /* 学习结束 */ - assert(env_warn_active() == 1);/* V4.25: 替换后黄灯保持 (恢复确认前不灭) */ + assert(env_warn_active() == 1); /* 替换后黄灯保持 (等待恢复确认) */ - /* 替换后 dev≈0 → 窗口分支; 空闲连续 5s 恢复确认 → 黄灯灭 */ - for (int i = 0; i < 500; i++) + /* 空闲 5s 恢复确认 → 灭 */ + for (tick = 0; tick < 600; tick++) sim_tick(); - assert(ev_applied == 1); /* 不再重复替换 */ - assert(env_warn_active() == 0);/* 恢复确认 5s 后黄灯灭 */ - printf(" 场景1 PASS — 永久环境变化 5s 预判锁存, ~66s 自动恢复, 恢复确认后灭\n\n"); + assert(env_warn_active() == 0); + printf(" 恢复确认后黄灯灭 OK\n"); } -static void test_transient_never_learns(void) +/* ============ 场景 2: 瞬态高位 (<10s 回摆) 不学, 锁存保持到恢复确认 ============ */ +static void test_transient_no_learn(void) { - /* ② 瞬态高位短停 (铁块模拟): 40s(4000tick) 高位后回摆 → 不学习 - * 但 5s 后黄灯预判已亮 (wait>=500) — 预判亮 ≠ 进入学习 */ - t_set(128095UL, 128856UL, 48); - for (int i = 0; i < 4000; i++) - sim_tick(); - assert(ev_learn_start == 0); - assert(env_warn_active() == 1); /* 黄灯预判已亮 (wait=4000) */ - printf("瞬态高位短停: 4000tick 时黄灯预判亮=%d (未学习)\n", env_warn_active()); + int i; - /* 回摆进窗口 (CAPVD 回 Origin 附近): wait 清零, 但预判锁存保持 (V4.25 核心语义) */ - sim_CAPVD = 128100UL; /* dev=+5 窗口内 */ + 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); /* 回摆不清黄灯, 保持常亮 */ + assert(env_warn_active() == 1); /* 回摆不清黄灯 (拍板锁存语义) */ - /* 窗口内连续 499 tick (累计 500 恢复确认) → 环境稳定, 解除预判 */ - for (int i = 0; i < 499; i++) + /* 窗口空闲 499tick (累计500恢复确认) → 灭 */ + for (i = 0; i < 499; i++) sim_tick(); - assert(env_warn_active() == 0); /* 新环境稳定 5s, 黄灯灭 */ + assert(env_warn_active() == 0); assert(g_env_recover_cnt == 0); - /* 再次高位 59s(5900tick): 5s 后预判重新锁存, 但 <60s 不学习 */ + /* 再高位 9s → 又锁存, 仍不学习 */ sim_CAPVD = 128856UL; - for (int i = 0; i < 5900; i++) + for (i = 0; i < 900; i++) sim_tick(); - assert(ev_learn_start == 0); - assert(ev_applied == 0); + assert(env_warn_active() == 1); + assert(sim_learn_start_cnt == 0); + assert(sim_applied == 0); assert(sim_Origin == 128095UL); - assert(env_warn_active() == 1); /* 高位重现 → 预判重新锁存 */ - printf("瞬态高位短停: 回摆保持常亮→稳定5s灭→高位重现再锁存 (未学习)\n"); - printf(" 场景2 PASS — 铁块类瞬态(60s内回摆)不触发重校准, 预判锁存语义正确\n\n"); + printf("2. 瞬态高位(<10s)不学, 锁存保持, 恢复确认后灭 OK\n"); } -static void test_learn_interrupted_restarts(void) +/* ============ 场景 3: 学习期打断重算 + 最终恢复 ============ */ +static void test_learn_interrupt_retry(void) { - /* ③ 学习期打断重算: 60s 后进入学习, 途中目标靠近(回窗) → abort → 重来并最终恢复 */ - t_set(128095UL, 128856UL, 48); + int i; + int learn_start_before; - /* 第一次 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); - assert(env_warn_active() == 1); /* 预判锁存保持 (环境未确认恢复) */ - assert(g_env_recover_cnt == 0); /* 目标在进入确认区, 恢复计时清零 */ + env_reset(); + sim_Origin = 128095UL; sim_CAPVD = 128856UL; - /* 目标离开, 高位重新 60s → 学习 3 窗 → 替换 */ - sim_CAPVD = 128856UL; - for (int i = 0; i < 7000 && !ev_applied; i++) + /* 10s 高位 → 学习开始 */ + for (i = 0; i < 1000; 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); - 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) -{ - /* ④ 学习期每窗均值漂移 >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(sim_learn_start_cnt == 1); 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; + /* 学习 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); - printf("学习期不稳: 预算放弃 tick=%d applied=%d resync=%d warn=%d Origin=%lu\n", - tick, ev_applied, g_env_resync, g_env_warn, (unsigned long)sim_Origin); - assert(ev_applied == 0); - assert(g_env_resync == 0); /* 超时退出学习 */ - assert(env_warn_active() == 1); /* 环境仍异常 → 黄灯保持锁存 */ - assert(ev_abort >= 1); - assert(sim_Origin == 128095UL); /* Origin 不被污染 */ - printf(" 场景4 PASS — 学习不稳定 30s 放弃保留旧 Origin, 黄灯保持异常指示\n\n"); + 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"); } -static void test_no_learning_after_replace(void) +/* ============ 场景 5: 替换后不震荡, 无重复学习 ============ */ +static void test_no_oscillation_after_apply(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 i, lsc; - int before = ev_learn_start; - for (int i = 0; i < 6000; i++) - sim_tick(); /* 新 Origin 下高位条件消失 */ - assert(ev_learn_start == before); /* 无新学习 */ - assert(env_warn_active() == 0); /* 恢复确认 5s 后黄灯已灭 */ - printf("替换后稳态: 高位条件消失, 无重复学习, 黄灯灭 (Origin=%lu)\n", - (unsigned long)sim_Origin); - printf(" 场景5 PASS — 恢复后进入正常跟踪, 不震荡\n\n"); + 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_never_learns(); - test_learn_interrupted_restarts(); - test_unstable_learn_gives_up(); - test_no_learning_after_replace(); - printf("ALL PASS (5 scenarios)\n"); + 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; } diff --git a/utilities/at32f421_freertos_demo/inc/TaskLoop.h b/utilities/at32f421_freertos_demo/inc/TaskLoop.h index a30dc14..01bddce 100644 --- a/utilities/at32f421_freertos_demo/inc/TaskLoop.h +++ b/utilities/at32f421_freertos_demo/inc/TaskLoop.h @@ -48,6 +48,9 @@ #define FREEZE_STABILITY_RATE 2 // 冻结稳定性窗口: 参考值的 ±2% (CAPVD 需在此范围内才累计超时) #define ENV_WARN_WAIT 500 // 环境预判 (V4.25): dev>+4dlt 高位连续 ~5s → 黄灯预判锁存(回摆保持) #define ENV_RECOVER_WAIT 500 // 恢复确认 (V4.25): 环境回正常(空闲)连续 ~5s 才解除预判, 黄灯灭 +#define COIL_F_OK_MIN_KHZ 20 // 线圈频率合理窗 (V4.26): 默认低频档口径, <20kHz → L偏大 +#define COIL_F_OK_MAX_KHZ 120 // 线圈频率合理窗 (V4.26): >120kHz → L偏小 +#define COIL_DEF_C_NF 66 // 默认档电容 (V4.26): 拨码不可读, 粗估 L 打印用; 33nF 档 ×2 /*=========================================================================== * 离开检测模式 diff --git a/utilities/at32f421_freertos_demo/inc/cmcng.h b/utilities/at32f421_freertos_demo/inc/cmcng.h index b4e8cda..bc4cbae 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.25" +#define FIRMWARE_VER "4.26" #define HARDWARE_VER "3.00" #define FIRMWARE_VER_MAIN 4 -#define FIRMWARE_VER_SUB 25 +#define FIRMWARE_VER_SUB 26 #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 c19b0c3..6cedfbc 100644 --- a/utilities/at32f421_freertos_demo/src/TaskLoop.c +++ b/utilities/at32f421_freertos_demo/src/TaskLoop.c @@ -139,6 +139,8 @@ uint8_t g_disconnect_count = 0; uint8_t g_disconnect_active = 0; uint8_t g_fault_phase = 0; uint16_t g_fault_tick = 0; +uint8_t g_coil_fault = 0; // V4.26: 线圈电感量异常 0=正常 1=L偏大(f<20kHz) 2=L偏小(f>120kHz) +uint16_t g_coil_bad_cnt = 0; // V4.26: 电感异常防抖计数 (100tick=1s 置位/清除) /*=========================================================================== * 调试计数器 @@ -300,12 +302,16 @@ void poll_green_led(void) * * 由 TMR15 ISR 每 5ms 驱动一次。 * - * 模式判定: - * 当前线圈断开 或 上电后从未接线圈: + * 模式判定 (V4.26 优先级: 1>4>5>3>2): + * 1 当前线圈断开 或 上电后从未接线圈: * → 快闪 (200ms 亮 / 200ms 灭) - * 线圈已恢复连接, 且上电后有断开记录 (g_disconnect_count >= 1): + * 4 环境异常预判/重校准学习 (g_env_warn/g_env_resync): + * → 常亮 (V4.24/25, 故障补偿 10s 内完成) + * 5 线圈电感量不合理 (g_coil_fault, 频率 20~120kHz 窗之外): + * → 一长一短 (长亮200ms/灭100ms/短亮100ms/灭800ms 循环) + * 3 线圈已恢复连接, 且上电后有断开记录 (g_disconnect_count >= 1): * → N 短闪 (80ms亮/200ms灭 × N, 1.2s间隔, N=断开次数≤3) - * 线圈正常连接, 无断开记录: + * 2 线圈正常连接, 无断开记录: * → 灭 *===========================================================================*/ void poll_yellow_led(void) @@ -338,6 +344,24 @@ void poll_yellow_led(void) return; } + /*--- V4.26 模式5: 线圈电感量不合理 → 一长一短 + * 长亮200ms(40tick) → 灭100ms(20) → 短亮100ms(20) → 灭800ms(160), 循环 + * g_coil_fault: 1=L偏大(f<20kHz) 2=L偏小(f>120kHz) + * 优先级: 断开(1) > 环境(4) > 电感量(5) > N短闪(3) > 灭 ---*/ + if (g_coil_fault) { + static const uint16_t dur[4] = { 40, 20, 20, 160 }; + if (g_fault_phase > 3) g_fault_phase = 0; + if (g_fault_phase & 1) + LED_YELLOW_OFF; + else + LED_YELLOW_ON; + if (g_fault_tick >= dur[g_fault_phase]) { + g_fault_tick = 0; + g_fault_phase++; + } + return; + } + uint8_t N = g_disconnect_count; if (N == 0) { @@ -776,6 +800,43 @@ static void env_resync_apply(uint32_t new_origin) #endif env_resync_abort(); } +/*=========================================================================== + * V4.26: 线圈电感量合理范围监视 + * 拍板规格: 默认低频档口径, 空场频率合理窗 20~120kHz, 出界即判定电感量不合理 + * (黄灯"一长一短"告警, 见 poll_yellow_led 模式5) + * 调用点保证: 仅稳定 + 无车空闲 + 未断开时执行 (车压/自检/断开不判) + * g_coil_fault: 0=正常 1=L偏大(f<20kHz) 2=L偏小(f>120kHz) + * 防抖: 越界/恢复均连续 100tick(=1s) 才置位/清除 + *===========================================================================*/ +static void coil_monitor(void) +{ + uint32_t f_khz = calc_freq_khz(loop1_Origin, loop1_LPCNT); + uint8_t want = 0; + + if (loop1_Origin == 0 || f_khz == 0) + return; /* 无有效基准 → 维持原状 */ + + if (f_khz < COIL_F_OK_MIN_KHZ) + want = 1; /* L 偏大 (>合理上限) */ + else if (f_khz > COIL_F_OK_MAX_KHZ) + want = 2; /* L 偏小 (<合理下限) */ + + if (want == g_coil_fault) { + g_coil_bad_cnt = 0; /* 状态一致 → 计时复位 */ + } else { + g_coil_bad_cnt++; + if (g_coil_bad_cnt >= 100) { + if (g_coil_fault == 0) + PRINT("EVT|coil_fault f:%d kHz L~%lu uH(C66/×2C33) %s\n", + f_khz, 25330000UL / (uint32_t)(f_khz * f_khz * COIL_DEF_C_NF), + want == 1 ? "too_large" : "too_small"); + else + PRINT("EVT|coil_ok f:%d kHz\n", f_khz); + g_coil_fault = want; + g_coil_bad_cnt = 0; + } + } +} /*=========================================================================== * vd1_task — 核心检测算法(对齐 M1H VD1_TASK) @@ -792,7 +853,7 @@ 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_RESYNC_WAIT 1000 // V4.26: dev>+4dlt 高位连续 10s → 判定永久环境变化 (拍板: 故障补偿 60s→10s) #define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s (200×10ms) #define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃 (防黄灯常亮死锁) @@ -935,7 +996,7 @@ void vd1_task(void) } else { /* 负variation (CAPVD高于Origin, 特殊铁块磁导率主导/异常高) * V4.24 两段式: - * ① 前 60s(ENV_RESYNC_WAIT): 只冻结, 不更新 Origin + * ① 前 10s(ENV_RESYNC_WAIT): 只冻结, 不更新 Origin * (36321a1 双向保护原语义: 防铁块污染 Origin → 离开假进入锁死) * ② 高位连续 ≥60s 未回摆 → 判定"永久环境变化"(如金属板A拿走、基准被学低) * → 进入受控重校准: 连续空闲窗口均值 B + settle 判稳 → 空闲原子替换 Origin @@ -1006,6 +1067,9 @@ void vd1_task(void) } } + /*--- V4.26: 线圈电感量监视 (调用点=稳定无车空闲, 见 coil_monitor) ---*/ + coil_monitor(); + /*--- 进入确认 — 连续 ENTRY_CONFIRM 次低于阈值才判有车 ---*/ if (loop1_CAPVD < (loop1_Origin - loop1_dlt_ORG)) { if (loop1_entry_cnt == 0) {