fix(V4B): V4.36.41 — 环境重校准后触发高度变低根治 (基准/派生参考同步 + EMA 余数累加)

用户反馈: 黄灯常亮故障补偿后, 金属板进入触发的高度比断电上电后低很多, 补偿结束也恢复不了正常灵敏度。

根因(两处, 均可复核): V4B 进入判定用 vac_ref()=min(Origin, 真空EMA) 而非 Origin (TaskLoop.c:1432)
 ① env_resync_apply() 只原子换 loop1_Origin, 没同步 g_vacuum_ema。补偿前提是 dev>+4×dlt 连续 7s,
    学到的 B≈当前 CAPVD ⇒ Δ≥4×dlt 必然 ⇒ Origin−EMA=Δ>1×dlt ⇒ vac_ref 必然切旧 EMA ⇒ 进入线整整低 Δ
 ② 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 不变式; 小步不动 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 场景; gcc -O0 -Wall -Wextra 0 warning; 全套 8/8 全绿):
 补偿后进入线 130457(低 130 counts = 2.65×dlt = 0.100% Δf/f) → 130587(低 0); EMA 追赶偏差 127→0 counts

文档: devlog V2.78(+条目) / spec 文档头 V2.60 / manual 版本表 V2.17 / cmcng.h 4.36.40→4.36.41 (REV 41)
归档: docs/env-resync-trigger-line-analysis.md + docs/patches/v4.36.41-{A-B,D}*.diff

待现场(编译烧录后回填 devlog 条目): 4 项 [ ] + 2 处日志待填 (EVT|env_resync / DBG Origin·VEM·RLN)
This commit is contained in:
wangfq
2026-09-11 14:08:25 +08:00
parent 1de4b4c8f3
commit 5fd58e035a
10 changed files with 551 additions and 9 deletions
+205
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
/* ===== 镜像固件宏 (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(补偿后进入线由'低 Δ'恢复为 Origindlt\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. OriginEMA=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(" OriginEMA=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;
}