diff --git a/docs/devlog.md b/docs/devlog.md index 8339d1b..11682c8 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -4,6 +4,44 @@ --- +## 2026-09-11 — 环境重校准后触发高度变低根治:基准/派生参考同步 + EMA 余数累加(固件 V4.36.41) + +用户反馈:黄灯常亮故障补偿后,金属板进入触发的高度比断电上电后低很多,感觉补偿结束后恢复不了正常灵敏度的触发高度。 + +**根因(两处,均可复核)**:V4B 的**进入判定用的是 `vac_ref() = min(Origin, 真空EMA)`,不是 `Origin`**(`TaskLoop.c:1432`)。而 V4.36.31 引入真空 EMA 后,**补偿路径没有同步它**: + +- **机制1(一次性,量级大)**:`env_resync_apply()` 只原子替换 `loop1_Origin`,EMA 停在旧真空。补偿的前提是 `dev > +4×dlt` 连续 7s,学到的 B ≈ 当前 CAPVD ⇒ **Δ ≥ 4×dlt 是必然**,于是 `Origin − EMA = Δ > 1×dlt` ⇒ `vac_ref()` **必然**切到旧 EMA ⇒ 进入线整整低 Δ(SENS3@130k:Δ≥192 counts ≈ 0.15% Δf/f 的额外触发门槛)。断电上电时 EMA 由判稳重建 = Origin,故立刻恢复。 +- **机制2(常驻,单测实测)**:`g_vacuum_ema += _ema_d/128` 是**整数除法**,`|d| < 128` 时步进恒为 0 ⇒ EMA 被"粘"在**真空下方最多 127 counts(2.59×dlt > 1×dlt 门槛)** ⇒ 进入线常驻偏低 ≈ 0.100% Δf/f 且不自愈;断电上电因 `EMA = Origin` 精确相等(偏差 0)而持久正常。 + +**实现**: +- 新增 `origin_set_atomic()`(`TaskLoop.c:308`):"一步换 Origin"时,若 `|ΔOrigin| > ORIGIN_JUMP_REANCH_DLT(=1)×dlt` 则同步重锚 `g_vacuum_ema`;与 `VAC_REF_DELTA_DLT` 同口径 ⇒ 维持 `|Origin−EMA| ≤ 1×dlt` 不变式;小步(≤1×dlt,如悬停抖动/重连对齐)不动 EMA,不干扰对称窗内的实时跟踪。 +- 3 处 Origin 写入点改经它:**`env_resync_apply`(:968,补偿路径)**、重连判稳完成(:1207)、冻结超时更新(:1372)。 +- EMA 更新改**余数累加器** `g_vac_frac`(:1344):消 ±127 counts 整数死区,τ/噪声不变;`g_vac_frac` 在 INIT(:583)/判稳(:1266)同步清零。 +- 新增镜像单测 `tests/test_vac_ref_reanchor.c`:5 场景(复现+修复对比 / 护栏大小步 / V4.36.33 门控回归 / 上电语义 / 死区实测)。 + +**单测实测数字**(`Origin 130506→130636`,Δ=130,dlt=49): + +| 场景 | 现状 | V4.36.41 | +|---|---|---| +| 补偿后进入线 | 130457(低 130 = 2.65×dlt = 0.100% Δf/f) | **130587(低 0)** | +| EMA 追赶 40s 后偏差 | 127 counts(2.59×dlt) | **0** | + +**现场验证(待填)**: +- [ ] 制造黄灯常亮补偿 → 抓 `EVT|env_learn_start` / `EVT|env_resync Origin:..->..` +- [ ] 抓 `DBG|… Origin=… VEM=… RLN=…`:确认 `Origin − VEM` 是否长期 > 1×dlt(本根因的直接判据) +- [ ] 单变量对照(同一块板同一高度序列):(a)补偿后立刻测 /(b)灭灯后静置 10s 再测 /(c)断电上电测 —— 本版应 (a)≈(b)≈(c) +- [ ] 现场日志片段粘贴区: + +``` +(待填)EVT|env_resync Origin:……->…… +(待填)DBG|…… Origin=…… VEM=…… RLN=…… dlt=…… +``` + +语法:待 MRS 编译确认(本机无 AT32 工具链);**镜像单测 8/8 全绿**(gcc -O0 -Wall -Wextra,0 warning);REV 4.36.41;spec V2.60 / devlog V2.78。 + +> 归档:根因分析稿 `docs/env-resync-trigger-line-analysis.md`;补丁 `docs/patches/v4.36.41-A-B-origin-ema-sync.diff`、`docs/patches/v4.36.41-D-ema-frac-no-deadband.diff`。 +> ⚠ 提交前:`cmcng.h` 的 `FIRMWARE_VER` 字符与 `FIRMWARE_VER_REV` 需 40→41;`technical-spec.md` 文档头(固件版本 V4.36.41 / 文档版本 V2.60)同步;本条目日期按实际 commit 日校准。 + ## 2026-09-08 — 就绪巩固期 Q 分级:低Q 上电首压防卡(固件 V4.36.36) 用户反馈:电感偏低/低Q 线圈上电自检结束(绿灯灭)马上压板,大概率绿灯常亮;10s 后再测正常。要求绿灯自检闪时长分级:高Q 5s/中Q 7s/低Q ≥10s。 @@ -2151,6 +2189,7 @@ if (dev >= dlt_ORG × 4) { | 版本 | 时间 | 说明 | |------|------|------| +| V2.78 | 2026-09-11 | V4.36.41 环境重校准后触发高度变低根治: 新增 origin_set_atomic() 同步真空 EMA(3 处写入点; 维持 Origin−EMA ≤1×dlt 不变式) + EMA 余数累加消 ±127 counts 整数死区; 新增镜像单测 test_vac_ref_reanchor 5 场景全绿 | | V2.77 | 2026-09-08 | 注释同步: TaskLoop.c/h + tests 全部 疾闪 → 一长两短 (V4.36.38 现役描述, 历史版本行保留变更记录) | | V2.76 | 2026-09-08 | V4.36.40 热切换修复: fault3 强制期 Origin/LPCNT 冻结 → recover 改 TMR3 实时 Xn 判回窗(240000/Xn); 恢复后 LOOP_OK 回位自动发起重连确认+判稳重建 Origin, 换线圈不断电 ~6s 自愈 | | V2.75 | 2026-09-08 | V4.36.39: COIL_UNDER_MIN_AS_OPEN 默认 0→1 (用户拍板) —— L<20µH 短路级出厂等同断开 | diff --git a/docs/env-resync-trigger-line-analysis.md b/docs/env-resync-trigger-line-analysis.md new file mode 100644 index 0000000..a7cafd2 --- /dev/null +++ b/docs/env-resync-trigger-line-analysis.md @@ -0,0 +1,151 @@ +# 黄灯常亮(环境重校准)后触发高度变低 — 根因分析 + +> 适用版本:**V4.36.31 及以后**(进入判定已切到 `vac_ref()`;判据 = DBG 行是否含 `VEM=` 字段) +> 现象:黄灯常亮故障补偿结束后,金属板进入触发的"高度"明显变低(要压得更近才触发);**断电上电后恢复正常**。 +> 结论(一句话):**环境重校准 `env_resync_apply()` 原子替换了 `loop1_Origin`,但没有同步真空 EMA `g_vacuum_ema`;而进入判定用的是 `vac_ref() = min(Origin, EMA)`,于是补偿后进入线被留在旧真空上、整体低了 Δ(≥4×dlt)⇒ 必须多压出 Δ 的感应量才触发。断电上电时 EMA 由判稳重建 = Origin,故立刻恢复。** + +--- + +## 1. 判定链(代码位置,全部可复核) + +| 环节 | 位置 | 内容 | +|---|---|---| +| **进入线** | `TaskLoop.c:1432` | `if (loop1_CAPVD < (vac_ref() - loop1_dlt_ORG) && ...)` ← **用 vac_ref,不是 Origin** | +| 参考函数 | `TaskLoop.c:287-299` | `vac_ref() = (Origin − EMA > VAC_REF_DELTA_DLT×dlt) ? EMA : Origin`(阈值 =1×dlt,取**低者**) | +| 进入阈值 | `TaskLoop.c:1278` | `dlt = Origin × SensTable[SENS] >> 16`(SENS3=25 ⇒ 0.038%;SENS0=337 ⇒ 0.514%) | +| **补偿替换** | `TaskLoop.c:945-958` | `env_resync_apply()`:`loop1_Origin = new_origin;` + 清 freeze/ORG/entry + snap slow;**没有 EMA** | +| EMA 写点(全仓仅 3 处) | `564` / `1246` / `1309-1321` | INIT 清零 / 判稳完成 `EMA = Origin` / 无车对称窗内跟踪(α=128 τ≈1.28s,每步 ±3×dlt,25% 自愈) | +| 黄灯常亮 | `TaskLoop.c:437-442` | 模式4 = `g_env_warn \|\| g_env_resync`(环境异常预判 / 重校准学习) | +| 补偿参数 | `TaskLoop.c:81,82,1082-1084` | 预判 2s → 补偿 7s → 学习窗 2s/窗 + 连续 2 窗判稳(0.1%) → 替换 → 恢复确认 2s 灭灯 | + +**关键事实**:`g_vacuum_ema` 的三个写点里**没有**环境重校准路径 ⇒ 补偿把 `Origin` 一次性抬到新真空电平后,EMA 仍停在补偿前的旧真空值。 + +--- + +## 2. 数值推演(Δ 必然 ≥ 4×dlt) + +1. 触发补偿的前提是 `dev = CAPVD − Origin > +4×dlt` **连续 7s**(`ENV_RESYNC_WAIT=700`)。 +2. 期间处于"负 variation 高位"分支 ⇒ **Origin 与 EMA 都被冻结**(`1327-1425`),EMA 保持旧真空值。 +3. 学习得到的 B = 当前 CAPVD 的 2s 窗均值(`1397-1416`)⇒ `Origin_new ≈ Origin_old + Δ`,其中 **Δ ≥ 4×dlt**。 +4. 替换后:`Origin − EMA = Δ ≥ 4×dlt > 1×dlt` ⇒ `vac_ref()` 返回 **EMA(旧真空)**。 +5. ⇒ **进入线 = 旧真空 − dlt**,而物理正确应为 **新真空 − dlt = 旧真空 + Δ − dlt** ⇒ **进入线整整低了 Δ(≥4×dlt)**。 + - SENS3(dlt=48@130k):Δ ≥ 192 counts ⇒ 0.15% Δf/f 起的额外感应量才触发; + - devlog J1 实测位移 130~1513 counts ⇒ 观测即"触发高度下降一半甚至更多"。 + +--- + +## 3. 为什么断电上电就正常 + +`g_vacuum_ema` 在 INIT 清 0(`564`),判稳完成时 `g_vacuum_ema = loop1_Origin`(`1246`)⇒ 两者重合 ⇒ `vac_ref() = Origin` ⇒ 进入线回到 `Origin − dlt` ✓ + +--- + +## 4. 为什么"黄灯灭"不等于灵敏度回来了 + +黄灯灭的条件只有**非高位、空闲连续 2s**(`ENV_RECOVER_WAIT=200`,`1290-1300`),**不检查 EMA 是否已追上新 Origin**。 +补偿后 dev≈0 才落入 ±4×dlt 对称窗 ⇒ EMA 这时才开始以 τ≈1.28s 向上爬: +- 黄灯灭(替换后 2s)时 EMA 仅追回 1−e^(−2/1.28) ≈ **79%** +- Δ=130 时仍差 ~27 counts(≈0.56×dlt) + +--- + +## 5. 为什么"反复试板"会自持(越试越钝) + +EMA 更新窗 = 无车 + `dev ∈ ±4×dlt`(`1302-1326`)。把板拿到感应边缘(未触发、dev −20~−40)时 **EMA 被向下拖**(V4.36.33 devlog 实测 2~3s 拉低 20~40 counts);进入线 = `min(Origin, EMA) − dlt` ⇒ **每次试板都把线再拖低一截** ⇒ 主观上"补偿结束了但灵敏度回不来",直到彻底离开真空若干秒才慢慢回正。 +> 这与 V4.36.33 已记录的老现象("金属板间隔几秒再压,触发高度下降一半甚至更多")是**同一机理**;本次新增的点是:**环境重校准会把 Δ 一次性甩到 4×dlt 以上**,比边缘悬停(0.4~0.8×dlt)严重一个量级,且黄灯灭的时刻刚好是 EMA 还差一截的时刻。 + +--- + +## 6. 第二个机制:真空 EMA 的整数步进死区(2026-09-11 单测实测) + +第 5 节解释了"补偿后一次性被压低 Δ",但**为什么反复试板后仍感觉回不来**、而断电上电就彻底好?单测 `tests/test_vac_ref_reanchor.c` 实测出第二个机制: + +``` +现状公式 (V4.36.35): g_vacuum_ema += _ema_d / VACUUM_EMA_ALFA; /* 有符号整数除法 */ + 当 |_ema_d| < 128 时, 步进恒为 0 ⇒ 死区 ±127 counts +``` + +- 实测:让 EMA 从真空下方 200 counts 追赶,**40s 后停在真空下方 127 counts**(= 2.59×dlt@SENS3)⇒ 因为 127 > 1×dlt,`vac_ref()` 持续选中这个偏低的 EMA ⇒ **进入线常驻偏低 127 counts ≈ 0.10% Δf/f**。 +- 该偏差**不会自愈**:EMA 只会在 |d| ≥ 128 的样本上移动,而板离开后 d 稳定在 127 附近 ⇒ 需要长时间 CAPVD 噪声偶然越过阈值才缓慢爬升。 +- 而**断电上电**时 `EMA = Origin`(判稳建立,精确相等,偏差 0),且此后 |d| < 128 ⇒ EMA 被"粘"在原地不动 ⇒ **永久保持正常灵敏度** ⇒ 这就是"断电上电就好了"的另一半原因。 +- ⇒ 三层叠加:① 补偿一步拉开 Δ(≥4×dlt)② 边缘悬停把 EMA 拖低(≤4×dlt)③ **整数死区把 EMA 钉在真空下方 ~127 counts**。三者都让 `vac_ref()` 选到偏低的参考。 + +--- + +## 7. 现场验证(建议顺序) + +**第 0 步先确认版本**:DBG 行含 `VEM=`/`RLN=` ⇒ V4.36.31+,本分析适用;若老版本无 VEM,则走"Origin 被学高"的旧机理,需另判。 + +1. **抓日志**(TTL):补偿会打印 + - `EVT|env_learn_start Origin:.. CAPVD:.. dev:..` + - `EVT|env_resync Origin:..->.. CAPVD:..` + - 之后每 2s 的 `DBG|... Origin=.. VEM=.. RLN=..` + **判据**:`Origin − VEM > 1×dlt` 的持续时间 ≈ "触发高度异常"的持续时间;`RLN` 与 `Origin−dlt` 的差 ≈ 触发高度损失。 +2. **单变量对照**(同一块板、同一高度序列): + (a) 补偿后立刻测 → (b) 黄灯灭后静置 10s(板远离)再测 → (c) 断电上电后测 + 预期 (b) 明显好于 (a)、(b)≈(c)。若 (b) 仍远差于 (c) ⇒ 另有固化路径(学习窗污染 Origin),需再取日志。 +3. **固件对照实验(有权限时,最直接)**:临时在 `env_resync_apply()` 内加一行 `g_vacuum_ema = new_origin;` 复测 ⇒ 触发高度应与断电上电一致 ⇒ 根因钉死。 + +--- + +## 8. 修复候选(待拍板,暂不改源码) + +| 方案 | 内容 | 风险/代价 | +|---|---|---| +| **A(最小对症)** | `env_resync_apply()` 补 `g_vacuum_ema = new_origin;`(与判稳完成建初值同语义:Origin 原子换 ⇒ 真空参考同步换) | 低。1 行;补偿后判定参考与 Origin 同时就位,黄灯灭即恢复正常灵敏度 | +| **B(通用护栏,建议叠加 A)** | "Origin 大步跳变(\|Δ\| > k×dlt)即同步重锚 EMA":一次收口 `1188`(重连判稳对齐 Origin)、`1344`(冻结超时更新 Origin)等**同源**路径 | 低。把"Origin 动、EMA 不动"这类隐患统一堵住 | +| **C(稳健性,可选)** | 黄灯灭条件追加"\|Origin − EMA\| ≤ 1×dlt" ⇒ 让"灯灭 = 判定参考已就位" | 低。可能让黄灯多亮 1~3s(现场需接受) | +| **D(建议叠加)** | EMA 更新改**余数累加器**(消 `d/128` 的 ±127 counts 整数死区) | 低。单测实测同条件偏差 **127→0 counts**;τ/噪声不变 | +| ✗ 不建议 | 进入线改回 `Origin − dlt` | 会退回 V4.36.31 之前"Origin 学高 ⇒ 假 Car_In"(devlog 20260904 已实锤) | + +--- + +## 9. 待确认(现场) + +- 测试固件的**版本号**(V4.36.31+ 才适用本文) +- 黄灯是**常亮**(=环境重校准,模式4)还是其他花样:一长一短=电感量出窗(模式5)/一长两短=156~171k(模式6)/快闪=断开·短路级(模式1) +- 线圈与 Q 档(33µH/23µH/35µH 现场差异大;V4.36.33 devlog 记 23µH 仍可复现) +- 触发补偿的动作("金属板拿走/搁置")与位移量 Δ 的量级 + +--- + +## 10. 补丁草案与验证(**已应用,未提交**) + +| 产物 | 路径 | 规模 | +|---|---|---| +| 补丁 A+B(Origin/EMA 同步不变式) | `docs/patches/v4.36.41-A-B-origin-ema-sync.diff` | TaskLoop.h +1 / TaskLoop.c +20 −3 | +| 补丁 D(EMA 余数累加,消死区) | `docs/patches/v4.36.41-D-ema-frac-no-deadband.diff` | TaskLoop.c +13 −2 | +| 新增单测 | `tests/test_vac_ref_reanchor.c` | 5 场景,`gcc -O0 -Wall -Wextra` 0 warning | + +**A+B 核心**:新增 `origin_set_atomic()`,把"一步换 Origin"的 3 处(`env_resync_apply` / 重连判稳完成 `:1188` / 冻结超时更新 `:1344`)改为经由它 ⇒ 步长 > 1×dlt 时同步重锚 `g_vacuum_ema`(与 `VAC_REF_DELTA_DLT` 同口径 ⇒ 维持 `|Origin−EMA| ≤ 1×dlt` 不变式)。上电首窗那处(`:701`)不需要改:EMA 在判稳完成建立、此前进入判定被 `g_loop_stable` 门控挡住。 + +**单测实测结论**: + +| 场景 | 现状行为 | 打补丁后 | +|---|---|---| +| 补偿(Δ=130 counts)后进入线 | 130457(低 130 = 2.65×dlt = **0.100% Δf/f**) | 130587(低 **0**) | +| 护栏:小步 20(≤1×dlt) / 大步 150 | — | 小步不动 EMA ✓ / 大步重锚 ✓ | +| V4.36.33 门控(Origin−EMA = 30 / 130) | 用 Origin ✓ / 用 EMA ✓ | 语义不变 ✓ | +| 上电(EMA=0 → 判稳=Origin) | 进入线 = Origin−dlt ✓ | 不变 ✓ | +| EMA 追赶 40s(现状公式,起点 −200) | **停在真空下方 127 counts(2.59×dlt)** | 修复 D:偏差 **0** | + +**已完成的验证**:① 两个补丁对干净树 `git apply --check` 通过,且 **A+B → D 顺序 apply** 通过;② `origin_set_atomic()` 独立 `gcc -fsyntax-only` 通过(确认在文件作用域,不在 `vac_ref()` 内);③ 大括号平衡 202/201 → **205/204**(成对增加);④ **8 个单测全绿**(原 7 个未受影响);⑤ 生成补丁过程中已修正两处结构缺陷(helper 误置于 `vac_ref()` 内、D 的 `if (_ema_d != 0)` 缺大括号)。 + +**应用/回退**:`git apply docs/patches/v4.36.41-A-B-*.diff`(可再叠 `-D-*.diff`;两者 hunk 不重叠,已顺序 apply 验证);回退 `git apply -R` 或 `git checkout -- `。**2026-09-11 更新:A+B 与 D 已按采纳决定应用到工作树(仍未提交)** —— `TaskLoop.h` +1 / `TaskLoop.c` +33 −5(合计 +34 −5);CRLF 保持(1825 行全 CR)、大括号 205/204 平衡、8 个单测全绿。回退:`git checkout -- utilities/`。 + +**待拍板**:① 采用 A+B?② 叠加 D?③ 提交前要我同步 `version`/spec 表 + devlog 条目(V4.36.41)? + +--- + +## 附:相关历史(devlog) + +| 版本 | 内容 | +|---|---| +| V4.36.29/30 | Origin 过冲 + 释放带压缩 → 低Q 卡死 75s;引入环带超时兜底 | +| V4.36.31 | 引入真空 EMA `vac_ref()=min(Origin,EMA)`,修"Origin 学高 ⇒ 释放线偏高/假触发" | +| V4.36.33 | vac_ref 加 1×dlt 门控,修"**边缘悬停拉低 EMA ⇒ 触发高度漂移**"(本文同机理,量级更大) | +| V4.36.34/35 | EMA 内存污染与 cast 爆炸源修复(`(uint32_t)(_ema_d/128)`) | +| V4.36.36/38 | 就绪巩固期 Q 分级;电感量告警模式 6 | + +--- +*分析日期:2026-09-11 | 证据:`TaskLoop.c` / `TaskLoop.h` / `tests/test_env_resync.c` / `docs/devlog.md` 逐行核对* diff --git a/docs/patches/v4.36.41-A-B-origin-ema-sync.diff b/docs/patches/v4.36.41-A-B-origin-ema-sync.diff new file mode 100644 index 0000000..e276b71 --- /dev/null +++ b/docs/patches/v4.36.41-A-B-origin-ema-sync.diff @@ -0,0 +1,67 @@ +diff --git a/utilities/at32f421_freertos_demo/inc/TaskLoop.h b/utilities/at32f421_freertos_demo/inc/TaskLoop.h +index afece45..35d3d36 100644 +--- a/utilities/at32f421_freertos_demo/inc/TaskLoop.h ++++ b/utilities/at32f421_freertos_demo/inc/TaskLoop.h +@@ -43,6 +43,7 @@ + #define VACUUM_EMA_ALFA 128 // V4.36.31: 真空 EMA 平滑因子 (τ≈1.28s@10ms): B3 判定参考解耦 + // 无车 dev 窗内跟踪实时真空; 噪声 σ_ema≈σ/16 (σ1-2 → 0.1 counts) + #define VAC_REF_DELTA_DLT 1 // V4.36.33: vac_ref 重锚阈值 (×dlt): 仅 Origin−EMA > 1×dlt(真锚错) ++#define ORIGIN_JUMP_REANCH_DLT 1 // V4.36.41: Origin 单步跳变 > 1×dlt ⇒ 同步重锚真空 EMA (B1 护栏) + // 才用 EMA 锚低 — 防金属板悬感应边缘(dev−20~−40<4dlt)快速拉低 EMA + // → 触发线漂移(触发高度下降); 小偏差仍用 Origin + // 150(→35)不够: 23µH 现场实测板停临界回摆 Origin-36, 需释放线 ORIGIN_JUMP_REANCH_DLT×dlt 才重锚 EMA; 小步(≤1×dlt)不动, ++ * 与 VAC_REF_DELTA_DLT 同口径 ⇒ 保持 |Origin−EMA| ≤ 1×dlt 不变式, ++ * 不干扰 EMA 在对称窗内的实时真空跟踪 */ ++static void origin_set_atomic(uint32_t new_origin) ++{ ++ int32_t _jump = (int32_t)new_origin - (int32_t)loop1_Origin; ++ if (_jump < 0) _jump = -_jump; ++ loop1_Origin = new_origin; ++ if (g_vacuum_ema == 0 || loop1_dlt_ORG == 0 || ++ _jump > (int32_t)(loop1_dlt_ORG * ORIGIN_JUMP_REANCH_DLT)) ++ g_vacuum_ema = new_origin; ++} ++ + static uint8_t rel_release_hit(void) + { + uint32_t _cmp = loop1_CAPVD; +@@ -946,7 +963,7 @@ 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; ++ origin_set_atomic(new_origin); /* V4.36.41: 同步真空 EMA — 补偿后进入线不再被旧真空拖低 */ + loop1_freeze_cnt = 0; + loop1_freeze_ref = 0; + loop1_ORG_CNT = 0; +@@ -1185,7 +1202,7 @@ void vd1_task(void) + if (_d < 0) _d = -_d; + if (_d <= (int32_t)_dlt) { + /* 回带内=空场确认 → Origin 对齐当前空场(消基线差) */ +- loop1_Origin = loop1_CAPVD; ++ origin_set_atomic(loop1_CAPVD); /* V4.36.41: 护栏(小步≤1×dlt 不动 EMA) */ + loop1_CAPVD_slow = loop1_CAPVD; + loop1_dlt_ORG = ((uint32_t)loop1_Origin * + SensTable[loop1_SensLevel]) >> 16; +@@ -1341,7 +1358,7 @@ void vd1_task(void) + } + loop1_freeze_cnt++; + if (loop1_freeze_cnt >= FREEZE_TIMEOUT) { +- loop1_Origin = loop1_CAPVD; ++ origin_set_atomic(loop1_CAPVD); /* V4.36.41: 护栏(大步下跳也同步重锚) */ + loop1_freeze_cnt = 0; + loop1_freeze_ref = 0; + loop1_ORG_CNT = 0; diff --git a/docs/patches/v4.36.41-D-ema-frac-no-deadband.diff b/docs/patches/v4.36.41-D-ema-frac-no-deadband.diff new file mode 100644 index 0000000..30d4c6b --- /dev/null +++ b/docs/patches/v4.36.41-D-ema-frac-no-deadband.diff @@ -0,0 +1,49 @@ +diff --git a/utilities/at32f421_freertos_demo/src/TaskLoop.c b/utilities/at32f421_freertos_demo/src/TaskLoop.c +index afbe2d1..a8bc9d6 100644 +--- a/utilities/at32f421_freertos_demo/src/TaskLoop.c ++++ b/utilities/at32f421_freertos_demo/src/TaskLoop.c +@@ -48,6 +48,7 @@ uint16_t g_origin_freeze_tick = 0; // V4.36.26/32: 释放后 Origin 全冻 + uint16_t g_origin_lowlearn_tick = 0; // V4.36.32: 冻结后降权低速补偿计数 (2s, 残余低值降权学入) + uint16_t g_rel_band_cnt = 0; // V4.36.30: 低Q 环带超时兜底计数 (离开触发深度持续 2.5s → 强制释放) + uint32_t g_vacuum_ema = 0; // V4.36.31: 真空 EMA 基线 (B3: 释放/触发参考与锚错 Origin 解耦) ++static int32_t g_vac_frac = 0; // V4.37(D): EMA 余数累加器 (消 ±127 counts 整数死区) + uint16_t g_ready_hold_tick = 0; // V4.36.36: 判稳后就绪巩固期计数 (5ms tick, poll_green_led 递减; 按 Q 档置 5s/7s/10s) + uint32_t loop1_Origin; + uint32_t loop1_ORG_SUM; +@@ -562,6 +563,7 @@ void INIT_VD(void) + g_origin_freeze_tick = 0; /* V4.36.26: 释放冻结计数清零 */ + g_rel_band_cnt = 0; /* V4.36.30: 环带兜底计数清零 */ + g_vacuum_ema = 0; /* V4.36.31: 真空 EMA 清零 (判稳后重建) */ ++ g_vac_frac = 0; /* V4.37(D): 余数累加器同步清零 */ + g_ready_hold_tick = 0; /* V4.36.36: 就绪巩固期清零 (重建判稳时会再置) */ + g_origin_lowlearn_tick = 0; /* V4.36.32: 降权期计数清零 */ + loop1_CAP_OK = 0; +@@ -1244,6 +1246,7 @@ void vd1_task(void) + g_stable_cnt = 0; // 计数退出时清零: 二次稳定期(安全复位)从零开始 + g_settle_cnt = 0; + g_vacuum_ema = loop1_Origin; /* V4.36.31: 判稳完成建立真空基线 */ ++ g_vac_frac = 0; /* V4.37(D): 余数累加器同步清零 */ + /* V4.36.36: 就绪巩固期按 Q 分档 (高Q 5s/中Q 7s/低Q 10s) — 绿灯继续慢闪期间系统收敛 */ + { + uint32_t _f0 = calc_freq_khz(loop1_Origin, loop1_LPCNT); +@@ -1315,10 +1318,18 @@ void vd1_task(void) + int32_t _ema_lim = (int32_t)(loop1_dlt_ORG * 3); + if (_ema_d > _ema_lim) _ema_d = _ema_lim; + else if (_ema_d < -_ema_lim) _ema_d = -_ema_lim; +- if (_ema_d != 0) ++ if (_ema_d != 0) { + /* V4.36.35: cast 顺序修复 — (uint32_t)负值/128 = +3355万/样本(爆炸源!) + * 必须 有符号先除: -10/128=0, -200/128=-1(wrap 减) — 20260908 实锤根因 */ +- g_vacuum_ema += (uint32_t)(_ema_d / VACUUM_EMA_ALFA); ++ /* V4.37(D): 余数累加 —— d/128 整数截断有 ±127 counts 死区 ++ * (|d|<128 步进=0 ⇒ EMA 常驻真空下方 ~127 counts ⇒ vac_ref 被拉低 ++ * ⇒ 进入线偏低 ⇒ 触发高度漂移, 只能断电上电复位; 2026-09-11 单测实测 127) */ ++ g_vac_frac += _ema_d; ++ { int32_t _st = g_vac_frac / VACUUM_EMA_ALFA; ++ g_vac_frac -= _st * VACUUM_EMA_ALFA; ++ g_vacuum_ema += (uint32_t)_st; ++ } ++ } + if (((int32_t)loop1_Origin - (int32_t)g_vacuum_ema) > (int32_t)(loop1_Origin >> 2) || + ((int32_t)g_vacuum_ema - (int32_t)loop1_Origin) > (int32_t)(loop1_Origin >> 2)) + g_vacuum_ema = loop1_Origin; diff --git a/docs/product-manual.md b/docs/product-manual.md index ea6b270..62c9e46 100644 --- a/docs/product-manual.md +++ b/docs/product-manual.md @@ -1,6 +1,6 @@ # DLD154V4B 产品手册 -> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.12 +> 单路线圈车辆检测器 | 型号: DLD154V4B | 版本: V2.17 > 适用于停车场、收费站、交通卡口等场景的车辆到达/离开检测 --- @@ -281,6 +281,7 @@ SW_5 = ON 时:车辆(金属)持续压线超过 **4 分钟**(LC_HOLD_TIME | V2.14 | 2026-09-08 | 电感分级降级(固件 V4.36.37 方案A):f>156k 拆分——156~171k 可用级(恒判车)、f>171k 短路级快闪200ms(宏=1 等同断开);真短路阈 20µH | | V2.15 | 2026-09-08 | 可用级黄灯改**一长两短**(固件 V4.36.38):900/300/100/300/100/700ms 循环,与一长一短(出窗)/快闪(断开)区分 | | V2.16 | 2026-09-08 | 短路级宏默认=1(固件 V4.36.39,用户拍板):L<20µH 出厂即等同断开;=0 可关恢复仅指示 | +| V2.17 | 2026-09-11 | **环境重校准后灵敏度恢复**(固件 V4.36.41):黄灯常亮故障补偿结束后,进入判定参考与基准同步;此前会残留"旧真空参考" ⇒ 金属板进入触发高度偏低、需断电重启才恢复 | --- diff --git a/docs/technical-spec.md b/docs/technical-spec.md index 23c0305..7e59485 100644 --- a/docs/technical-spec.md +++ b/docs/technical-spec.md @@ -1,6 +1,6 @@ # DLD154V4B 技术规格书 -> 单路线圈车辆检测器 | 固件版本: V4.36.40 | 文档版本: V2.59 +> 单路线圈车辆检测器 | 固件版本: V4.36.41 | 文档版本: V2.60 > 本文档面向工程开发、系统集成及故障深度分析 --- @@ -863,3 +863,4 @@ DLD154V4B V1.4 起实现该专利的整数化版本,用于大车通行时的 | V2.57 | 2026-09-08 | V4.36.39: COIL_UNDER_MIN_AS_OPEN 默认值 0→1 (用户拍板) —— L<20µH 短路级出厂即等同断开(判车停+继电器释放+绿灯灭) | | V2.58 | 2026-09-08 | V4.36.40 热切换修复: recover 恢复判定改 TMR3 实时 Xn(240000/Xn) —— 原 calc_freq_khz(冻结Origin,LPCNT) 永不回窗致换线圈死锁; 恢复后自然走重连+判稳重建 Origin | | V2.59 | 2026-09-08 | 注释同步: 代码/测试全部 疾闪 → 一长两短 (V2.55~58 历史版本行保留当时变更记录) | +| V2.60 | 2026-09-11 | **V4.36.41 环境重校准后触发高度变低根治**:进入判定用 `vac_ref()=min(Origin,真空EMA)`,环境重校准路径未同步该参考 ⇒ 新增 `origin_set_atomic()`(3 处 Origin 写入点;维持 Origin−EMA ≤1×dlt 不变式)+ EMA 余数累加消 ±127 counts 整数死区;新增镜像单测 test_vac_ref_reanchor(5 场景) | diff --git a/tests/test_vac_ref_reanchor.c b/tests/test_vac_ref_reanchor.c new file mode 100644 index 0000000..5ccaafa --- /dev/null +++ b/tests/test_vac_ref_reanchor.c @@ -0,0 +1,205 @@ +/* + * test_vac_ref_reanchor.c — V4.36.41 补丁草案单测 (gcc 隔离, DLD154V4B) + * + * 镜像 = TaskLoop.c V4.36.41 草案: vac_ref() + origin_set_atomic() + env_resync_apply() + * 覆盖 5 件事: + * ① 现象复现: env 重校准(黄灯常亮补偿)后判定参考未同步 ⇒ 进入线被拖低 Δ (修复前) + * ② 补丁效果: origin_set_atomic 同步 ⇒ 进入线立即回到 Origin−dlt (与断电上电同口径) + * ③ 回归: V4.36.33 的 1×dlt 门控语义不变; 小步跳变不吃掉 EMA 跟踪; 上电 EMA=0 语义 + * ④ 新发现(2026-09-11): EMA 整数步进 d/128 有 ±127 counts 死区 ⇒ 真空 EMA 长期停在 + * 真空下方 ~127 counts(≈2.6×dlt) ⇒ vac_ref 偏低 ⇒ 触发高度持续漂移(断电才复位) + * ⑤ 修复 D(余数累加器) 后 EMA 无死区收敛到 0 counts 偏差 + * + * 编译: gcc -O0 -Wall -o /tmp/t_vac tests/test_vac_ref_reanchor.c && /tmp/t_vac + * 镜像宏与固件一致(改 VAC_/ORIGIN_JUMP_/SENS 时同步此处!) + */ +#include +#include +#include +#include + +/* ===== 镜像固件宏 (TaskLoop.h / TaskLoop.c) ===== */ +static const uint16_t SensTable[4] = { 337, 71, 38, 25 }; /* SENS3 = 25 */ +#define SENS_LEVEL 3 +#define VAC_REF_DELTA_DLT 1 /* V4.36.33: vac_ref 重锚阈值 (×dlt) */ +#define ORIGIN_JUMP_REANCH_DLT 1 /* V4.36.41 草案: 原子换 Origin 重锚阈值 (×dlt) */ +#define VACUUM_EMA_ALFA 128 /* V4.36.31: EMA 平滑因子 (τ≈1.28s@10ms) */ + +/* ===== 镜像全局 ===== */ +static uint32_t loop1_Origin = 0, loop1_CAPVD = 0, loop1_dlt_ORG = 0; +static uint32_t g_vacuum_ema = 0; +static int32_t g_vac_frac = 0; /* 修复 D: EMA 余数累加器 */ + +static uint32_t calc_dlt(uint32_t origin) +{ + return (uint32_t)(((uint64_t)origin * SensTable[SENS_LEVEL]) >> 16); +} + +/* ===== 镜像: vac_ref() (TaskLoop.c:287-299) ===== */ +static uint32_t vac_ref(void) +{ + if (g_vacuum_ema == 0) return loop1_Origin; + if ((int32_t)loop1_Origin - (int32_t)g_vacuum_ema > + (int32_t)(loop1_dlt_ORG * VAC_REF_DELTA_DLT)) + return g_vacuum_ema; + return loop1_Origin; +} + +/* 进入线 (TaskLoop.c:1432 的判定阈值面) */ +static uint32_t enter_line(void) { return vac_ref() - loop1_dlt_ORG; } + +/* ===== 镜像: origin_set_atomic() (V4.36.41 草案, B1 护栏) ===== */ +static void origin_set_atomic(uint32_t new_origin) +{ + int32_t _jump = (int32_t)new_origin - (int32_t)loop1_Origin; + if (_jump < 0) _jump = -_jump; + loop1_Origin = new_origin; + if (g_vacuum_ema == 0 || loop1_dlt_ORG == 0 || + _jump > (int32_t)(loop1_dlt_ORG * ORIGIN_JUMP_REANCH_DLT)) { + g_vacuum_ema = new_origin; + g_vac_frac = 0; + } +} + +/* ===== 镜像: env_resync_apply() 修复前 / 修复后 ===== */ +static void env_resync_apply_old(uint32_t new_origin) /* V4.36.40: 只换 Origin */ +{ + loop1_Origin = new_origin; +} +static void env_resync_apply_new(uint32_t new_origin) /* V4.36.41 草案 (A+B) */ +{ + origin_set_atomic(new_origin); +} + +/* ===== 镜像: EMA 步进 —— 现状公式 vs 修复 D ===== */ +static void ema_step_cur(void) /* V4.36.35 现状: d/128 整数截断 (死区 ±127) */ +{ + int32_t d = (int32_t)loop1_CAPVD - (int32_t)g_vacuum_ema; + int32_t lim = (int32_t)(loop1_dlt_ORG * 3); + if (d > lim) d = lim; else if (d < -lim) d = -lim; + if (d != 0) g_vacuum_ema += (uint32_t)(d / VACUUM_EMA_ALFA); +} +static void ema_step_fix(void) /* 修复 D: 余数累加 (无死区) */ +{ + int32_t d = (int32_t)loop1_CAPVD - (int32_t)g_vacuum_ema; + int32_t lim = (int32_t)(loop1_dlt_ORG * 3); + if (d > lim) d = lim; else if (d < -lim) d = -lim; + if (d != 0) { + g_vac_frac += d; + int32_t st = g_vac_frac / VACUUM_EMA_ALFA; + g_vac_frac -= st * VACUUM_EMA_ALFA; + g_vacuum_ema += (uint32_t)st; + } +} + +/* ===== 夹具 ===== */ +#define VAC_TRUE 130506UL /* 真实真空 CAPVD (devlog J1 实测) */ +#define VAC_ANCHOR 130636UL /* env 学习后 Origin (锚高 130 counts = 2.65×dlt) */ +static void reset_all(uint32_t origin) +{ + loop1_Origin = origin; + loop1_CAPVD = origin; + loop1_dlt_ORG = calc_dlt(origin); + g_vacuum_ema = origin; + g_vac_frac = 0; +} + +int main(void) +{ + printf("test_vac_ref_reanchor (V4.36.41 草案: A+B 同步 + D 死区)\n"); + + /* ================= 场景 1: 复现 + 补丁效果 ================= */ + reset_all(VAC_ANCHOR); + uint32_t dlt_anchor = loop1_dlt_ORG; + uint32_t dlt_true = calc_dlt(VAC_TRUE); + printf("1. 参数: VAC_TRUE=%u(真空) VAC_ANCHOR=%u(补偿后 Origin) Δ=%u counts | dlt=%u(SENS3)\n", + (unsigned)VAC_TRUE, (unsigned)VAC_ANCHOR, (unsigned)(VAC_ANCHOR - VAC_TRUE), + (unsigned)dlt_anchor); + + reset_all(VAC_TRUE); /* 补偿前: Origin=真空, EMA=真空 */ + env_resync_apply_old(VAC_ANCHOR); /* 修复前: 只换 Origin, EMA 停旧真空 */ + uint32_t line_old = enter_line(); + printf(" ① 修复前: Origin=%u EMA=%u 进入线=%u | 正确应为 %u ⇒ 低 %u counts" + " = %.2f×dlt = %.3f%% Δf/f\n", + (unsigned)loop1_Origin, (unsigned)g_vacuum_ema, (unsigned)line_old, + (unsigned)(VAC_ANCHOR - dlt_anchor), + (unsigned)((VAC_ANCHOR - dlt_anchor) - line_old), + ((VAC_ANCHOR - dlt_anchor) - line_old) / (double)dlt_anchor, + ((VAC_ANCHOR - dlt_anchor) - line_old) * 100.0 / VAC_ANCHOR); + assert(g_vacuum_ema == VAC_TRUE); /* EMA 未同步(缺陷) */ + assert(line_old == VAC_TRUE - dlt_true); /* 进入线被留在旧真空 */ + assert((VAC_ANCHOR - dlt_anchor) - line_old == 130); /* 整整低 Δ=130 */ + + reset_all(VAC_TRUE); + env_resync_apply_new(VAC_ANCHOR); /* 修复后 (A+B) */ + printf(" ② 修复后: Origin=%u EMA=%u 进入线=%u | 与断电上电口径一致 ⇒ 低 0 counts\n", + (unsigned)loop1_Origin, (unsigned)g_vacuum_ema, (unsigned)enter_line()); + assert(g_vacuum_ema == VAC_ANCHOR); + assert(vac_ref() == VAC_ANCHOR); + assert(enter_line() == VAC_ANCHOR - dlt_anchor); + printf("1. 复现+修复 OK(补偿后进入线由'低 Δ'恢复为 Origin−dlt)\n"); + + /* ================= 场景 2: B1 护栏(大步重锚 / 小步不动) ================= */ + reset_all(VAC_TRUE); + origin_set_atomic(VAC_TRUE - 20); /* 步长 20 ≤ 1×dlt ⇒ EMA 不动 */ + printf("2. 小步 20(≤1×dlt): Origin=%u EMA=%u ⇒ EMA 未动 ✓\n", + (unsigned)loop1_Origin, (unsigned)g_vacuum_ema); + assert(g_vacuum_ema == VAC_TRUE); + origin_set_atomic(VAC_TRUE + 130); /* 步长 150 > 1×dlt ⇒ 重锚 */ + printf(" 大步 150(>1×dlt): Origin=%u EMA=%u ⇒ 已重锚 ✓\n", + (unsigned)loop1_Origin, (unsigned)g_vacuum_ema); + assert(g_vacuum_ema == VAC_TRUE + 130); + printf("2. 护栏 OK(不吃掉 EMA 的正常跟踪,也不容忍大偏差)\n"); + + /* ================= 场景 3: V4.36.33 门控语义回归 ================= */ + reset_all(VAC_ANCHOR); + g_vacuum_ema = VAC_ANCHOR - 30; /* 边缘悬停拉低 0.61×dlt */ + printf("3. Origin−EMA=30(0.61×dlt): vac_ref=%u ⇒ 用 Origin ✓(门控保留)\n", + (unsigned)vac_ref()); + assert(vac_ref() == VAC_ANCHOR); + g_vacuum_ema = VAC_ANCHOR - 130; /* 老锚错 2.65×dlt */ + printf(" Origin−EMA=130(2.65×dlt): vac_ref=%u ⇒ 用 EMA ✓(老场景保留)\n", + (unsigned)vac_ref()); + assert(vac_ref() == VAC_ANCHOR - 130); + printf("3. 门控回归 OK\n"); + + /* ================= 场景 4: 上电/判稳语义 ================= */ + reset_all(VAC_ANCHOR); + g_vacuum_ema = 0; /* INIT 清零 */ + assert(vac_ref() == VAC_ANCHOR); + g_vacuum_ema = loop1_Origin; /* 判稳完成建立初值 */ + assert(vac_ref() == VAC_ANCHOR && enter_line() == VAC_ANCHOR - loop1_dlt_ORG); + printf("4. 上电(EMA=0→Origin)语义 OK: 进入线=%u\n", (unsigned)enter_line()); + + /* ================= 场景 5: EMA 整数步进死区(新发现) ================= */ + reset_all(VAC_ANCHOR); + loop1_CAPVD = VAC_ANCHOR; /* 真空就在 Origin */ + g_vacuum_ema = VAC_ANCHOR - 200; /* 被拖低 200 counts 后追赶 */ + { int i; for (i = 0; i < 4000; i++) ema_step_cur(); } + { uint32_t gap = loop1_Origin - g_vacuum_ema; + printf("5. 现状公式 d/128 追赶 4000 样本(40s): EMA=%u 停在真空下方 %u counts" + " (%.2f×dlt) ⇒ vac_ref=%s ⇒ 进入线低 %u counts\n", + (unsigned)g_vacuum_ema, (unsigned)gap, gap / (double)loop1_dlt_ORG, + (vac_ref() == loop1_Origin) ? "Origin" : "EMA", (unsigned)gap); + assert(gap == 127); /* 死区: |d|<128 ⇒ 步进 0 */ + assert(vac_ref() == g_vacuum_ema); /* 127 > 1×dlt ⇒ 判定参考被拉低 */ + assert(enter_line() == g_vacuum_ema - loop1_dlt_ORG); + } + reset_all(VAC_ANCHOR); + loop1_CAPVD = VAC_ANCHOR; + g_vacuum_ema = VAC_ANCHOR - 200; + g_vac_frac = 0; + { int i; for (i = 0; i < 4000; i++) ema_step_fix(); } + { uint32_t gap = loop1_Origin - g_vacuum_ema; + printf(" 修复 D(余数累加)同条件: EMA=%u 偏差 %u counts ⇒ vac_ref=%s ⇒ 进入线=%u ✓\n", + (unsigned)g_vacuum_ema, (unsigned)gap, + (vac_ref() == loop1_Origin) ? "Origin" : "EMA", (unsigned)enter_line()); + assert(gap == 0); + assert(vac_ref() == loop1_Origin); + assert(enter_line() == loop1_Origin - loop1_dlt_ORG); + } + printf("5. 死区实测 OK(现状 127 counts 常驻偏差 → 修复 D 归零)\n"); + + printf("ALL PASS (A+B 同步 / 护栏 / 门控回归 / 上电语义 / 死区)\n"); + return 0; +} diff --git a/utilities/at32f421_freertos_demo/inc/TaskLoop.h b/utilities/at32f421_freertos_demo/inc/TaskLoop.h index afece45..35d3d36 100644 --- a/utilities/at32f421_freertos_demo/inc/TaskLoop.h +++ b/utilities/at32f421_freertos_demo/inc/TaskLoop.h @@ -43,6 +43,7 @@ #define VACUUM_EMA_ALFA 128 // V4.36.31: 真空 EMA 平滑因子 (τ≈1.28s@10ms): B3 判定参考解耦 // 无车 dev 窗内跟踪实时真空; 噪声 σ_ema≈σ/16 (σ1-2 → 0.1 counts) #define VAC_REF_DELTA_DLT 1 // V4.36.33: vac_ref 重锚阈值 (×dlt): 仅 Origin−EMA > 1×dlt(真锚错) +#define ORIGIN_JUMP_REANCH_DLT 1 // V4.36.41: Origin 单步跳变 > 1×dlt ⇒ 同步重锚真空 EMA (B1 护栏) // 才用 EMA 锚低 — 防金属板悬感应边缘(dev−20~−40<4dlt)快速拉低 EMA // → 触发线漂移(触发高度下降); 小偏差仍用 Origin // 150(→35)不够: 23µH 现场实测板停临界回摆 Origin-36, 需释放线 ORIGIN_JUMP_REANCH_DLT×dlt 才重锚 EMA; 小步(≤1×dlt)不动, + * 与 VAC_REF_DELTA_DLT 同口径 ⇒ 保持 |Origin−EMA| ≤ 1×dlt 不变式, + * 不干扰 EMA 在对称窗内的实时真空跟踪 */ +static void origin_set_atomic(uint32_t new_origin) +{ + int32_t _jump = (int32_t)new_origin - (int32_t)loop1_Origin; + if (_jump < 0) _jump = -_jump; + loop1_Origin = new_origin; + if (g_vacuum_ema == 0 || loop1_dlt_ORG == 0 || + _jump > (int32_t)(loop1_dlt_ORG * ORIGIN_JUMP_REANCH_DLT)) + g_vacuum_ema = new_origin; +} + static uint8_t rel_release_hit(void) { uint32_t _cmp = loop1_CAPVD; @@ -562,6 +580,7 @@ void INIT_VD(void) g_origin_freeze_tick = 0; /* V4.36.26: 释放冻结计数清零 */ g_rel_band_cnt = 0; /* V4.36.30: 环带兜底计数清零 */ g_vacuum_ema = 0; /* V4.36.31: 真空 EMA 清零 (判稳后重建) */ + g_vac_frac = 0; /* V4.37(D): 余数累加器同步清零 */ g_ready_hold_tick = 0; /* V4.36.36: 就绪巩固期清零 (重建判稳时会再置) */ g_origin_lowlearn_tick = 0; /* V4.36.32: 降权期计数清零 */ loop1_CAP_OK = 0; @@ -946,7 +965,7 @@ 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; + origin_set_atomic(new_origin); /* V4.36.41: 同步真空 EMA — 补偿后进入线不再被旧真空拖低 */ loop1_freeze_cnt = 0; loop1_freeze_ref = 0; loop1_ORG_CNT = 0; @@ -1185,7 +1204,7 @@ void vd1_task(void) if (_d < 0) _d = -_d; if (_d <= (int32_t)_dlt) { /* 回带内=空场确认 → Origin 对齐当前空场(消基线差) */ - loop1_Origin = loop1_CAPVD; + origin_set_atomic(loop1_CAPVD); /* V4.36.41: 护栏(小步≤1×dlt 不动 EMA) */ loop1_CAPVD_slow = loop1_CAPVD; loop1_dlt_ORG = ((uint32_t)loop1_Origin * SensTable[loop1_SensLevel]) >> 16; @@ -1244,6 +1263,7 @@ void vd1_task(void) g_stable_cnt = 0; // 计数退出时清零: 二次稳定期(安全复位)从零开始 g_settle_cnt = 0; g_vacuum_ema = loop1_Origin; /* V4.36.31: 判稳完成建立真空基线 */ + g_vac_frac = 0; /* V4.37(D): 余数累加器同步清零 */ /* V4.36.36: 就绪巩固期按 Q 分档 (高Q 5s/中Q 7s/低Q 10s) — 绿灯继续慢闪期间系统收敛 */ { uint32_t _f0 = calc_freq_khz(loop1_Origin, loop1_LPCNT); @@ -1315,10 +1335,18 @@ void vd1_task(void) int32_t _ema_lim = (int32_t)(loop1_dlt_ORG * 3); if (_ema_d > _ema_lim) _ema_d = _ema_lim; else if (_ema_d < -_ema_lim) _ema_d = -_ema_lim; - if (_ema_d != 0) + if (_ema_d != 0) { /* V4.36.35: cast 顺序修复 — (uint32_t)负值/128 = +3355万/样本(爆炸源!) * 必须 有符号先除: -10/128=0, -200/128=-1(wrap 减) — 20260908 实锤根因 */ - g_vacuum_ema += (uint32_t)(_ema_d / VACUUM_EMA_ALFA); + /* V4.37(D): 余数累加 —— d/128 整数截断有 ±127 counts 死区 + * (|d|<128 步进=0 ⇒ EMA 常驻真空下方 ~127 counts ⇒ vac_ref 被拉低 + * ⇒ 进入线偏低 ⇒ 触发高度漂移, 只能断电上电复位; 2026-09-11 单测实测 127) */ + g_vac_frac += _ema_d; + { int32_t _st = g_vac_frac / VACUUM_EMA_ALFA; + g_vac_frac -= _st * VACUUM_EMA_ALFA; + g_vacuum_ema += (uint32_t)_st; + } + } if (((int32_t)loop1_Origin - (int32_t)g_vacuum_ema) > (int32_t)(loop1_Origin >> 2) || ((int32_t)g_vacuum_ema - (int32_t)loop1_Origin) > (int32_t)(loop1_Origin >> 2)) g_vacuum_ema = loop1_Origin; @@ -1341,7 +1369,7 @@ void vd1_task(void) } loop1_freeze_cnt++; if (loop1_freeze_cnt >= FREEZE_TIMEOUT) { - loop1_Origin = loop1_CAPVD; + origin_set_atomic(loop1_CAPVD); /* V4.36.41: 护栏(大步下跳也同步重锚) */ loop1_freeze_cnt = 0; loop1_freeze_ref = 0; loop1_ORG_CNT = 0;