fix(V4B): V4.36.42 — 低Q 临界悬停自激根治 (进/出参考统一 + 释放带夹紧 + 最小保持)

用户反馈: 金属板缓慢接近时判据乱跳; 35µH(128kHz)/46µH(112kHz) 线圈同样现象(46µH 在设计窗内且无告警)。

根因(三段链条, 现场日志与代码逐值吻合):
 ① 无车态 dlt_ORG=进表 49 ⇒ vac_ref() 门控 1×49; Origin−EMA=32~34 ≯49 ⇒ 参考=Origin ⇒ 进入线=Origin−49
 ② 进车瞬间 dlt_ORG 切成释表 23 ⇒ 同一 gap > 1×23 ⇒ vac_ref() 改用 EMA(低 32~34 counts)
 ③ 释放线 = EMA−1.8×23 ⇒ 比进入线还浅 ⇒ 深度 50~58 的静止凹脉冲"进门即满足释放"
   ⇒ 进入确认 5 + 释放确认 5 = 10 样本 = 100ms 周期自激(日志实测 ~30 组/3s)

实现:
 - G1 新增 vac_gate_dlt()(状态无关进表 dlt)供 vac_ref() 门控 (TaskLoop.c:297/307) ⇒ 进/出同一参考
 - G2 低Q 释放带夹紧 _band ≤ vac_gate_dlt()-1 (TaskLoop.c:339) ⇒ 只作未来兜底; 180(=41@SENS3, V4.36.28 现场值)一字未动
 - G3 REL_MIN_HOLD_TICKS=30 有车后 300ms 禁释放 (TaskLoop.h:69 + TaskLoop.c 调用点) ⇒ 只挡释放不延迟检出

单测: 新增场景 7 现场参数逐位复现 —— 22µH 悬停 300/300 → 1/0; 35µH 300/300 → 1/0; 撤板 1/1; V4.36.28 回摆仍释放; 非低Q 同深度不自激; 全套 8/8 全绿
文档: devlog V2.79(+条目) / spec 文档头 V2.61 / manual 表 V2.18 / cmcng.h 4.36.41→4.36.42 REV 42
待现场: 上板复测 + 锁死侧三线圈实验 (devlog 条目内 [ ] 占位)
This commit is contained in:
wangfq
2026-09-11 16:59:26 +08:00
parent 5fd58e035a
commit 0f8a0d6fbd
10 changed files with 411 additions and 10 deletions
+144 -2
View File
@@ -24,6 +24,7 @@ static const uint16_t SensTable[4] = { 337, 71, 38, 25 }; /* SENS3 = 25 */
#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) */
#define REL_BAND_LO_MULT 180 /* 镜像 TaskLoop.h (V4.36.28 现场验证; G2 只夹紧不改它) */
/* ===== 镜像全局 ===== */
static uint32_t loop1_Origin = 0, loop1_CAPVD = 0, loop1_dlt_ORG = 0;
@@ -104,9 +105,144 @@ static void reset_all(uint32_t origin)
g_vac_frac = 0;
}
/* ================= 场景 6: vac_ref 硬开关抖动(缓慢接近/悬停)=================
* V4.36.41 现场发现: 板缓慢接近时判据"乱跳"。机理 = EMA 跟随近场金属向下时,
* vac_ref 的 "min + 1×dlt" 硬开关被反复扫过 ⇒ 进入线跳变(±40~150 counts) ⇒ 抖动。
* mode: 0=旧截断(死区) 1=D余数累加 2=E单向(只上不下); profile: 0=悬停 1=手抖 2=极慢接近
*/
static int sim_switch_cnt(int mode, int profile)
{
uint32_t Origin = VAC_ANCHOR, EMA = VAC_ANCHOR, capvd;
int32_t frac = 0, d;
int i, cnt500 = 0, prev_ref = -1, sw = 0;
for (i = 0; i < 3000; i++) { /* 30s @10ms */
int x;
if (profile == 0) x = (i < 400) ? i * 3 / 10 : 120; /* 4s 接近到 x=120 后悬停 */
else if (profile == 1) x = ((i % 300) < 150) ? 20 + (i % 300) * 100 / 150 /* 手抖 20~120, 周期 3s */
: 120 - ((i % 300) - 150) * 100 / 150;
else x = ((i < 1000) ? i * 200 / 1000 : 200) + ((i % 200 < 100) ? 8 : -8);
capvd = VAC_ANCHOR - (uint32_t)x;
d = (int32_t)capvd - (int32_t)EMA;
if (d > 3 * (int32_t)loop1_dlt_ORG) d = 3 * (int32_t)loop1_dlt_ORG;
else if (d < -3 * (int32_t)loop1_dlt_ORG) d = -3 * (int32_t)loop1_dlt_ORG;
++cnt500; if (cnt500 >= 500) { Origin = capvd; cnt500 = 0; } /* Origin 块平均 5s */
if (d != 0) {
if (mode == 0) EMA += (uint32_t)(d / 128);
else if (mode == 1) { frac += d;
int32_t st = frac / 128; frac -= st * 128; EMA += (uint32_t)st; }
else if (d > 0) { frac += d; /* E: 只上不下 */
int32_t st = frac / 128; frac -= st * 128; EMA += (uint32_t)st; }
}
{
int ref = ((int32_t)Origin - (int32_t)EMA > (int32_t)loop1_dlt_ORG) ? (int)EMA : (int)Origin;
if (prev_ref >= 0 && ref != prev_ref) ++sw;
prev_ref = ref;
}
}
return sw;
}
static void test_chatter(void)
{
int p, s_old, s_frac, s_one;
for (p = 0; p < 3; p++) {
s_old = sim_switch_cnt(0, p);
s_frac = sim_switch_cnt(1, p);
s_one = sim_switch_cnt(2, p);
printf(" [抖动] profile%d vac_ref 切换/30s: 旧死区=%d D余数=%d E单向=%d\n", p, s_old, s_frac, s_one);
assert(s_one <= 8); /* E: 抖动回到旧水平 */
assert(s_frac > s_one); /* D 单独: 显著放大 = 本次现场"乱跳" */
}
printf("场景 6: vac_ref 抖动 ✓ 通过 (E 单向 ≤8 次/30s; D 余数显著放大, 保持旧死区水平)\n");
}
/* ================= 场景 7: 低Q 临界悬停自激(现场日志 154log260911a 复现)=================
* 现场: Origin=129254 进表 dlt=49 释表 dlt=23 TEMA≈129220 CAPVD≈129204(深 50)
* 现状(V4.36.41): 进门瞬间 dlt_ORG 切成 23 ⇒ vac_ref() 门控由 49 变 23 ⇒ 同意偏差(34)下参考从
* Origin 阶跃到 EMA ⇒ 释放线 EMA-41=129179 比进入线 Origin-49=129205 还浅
* ⇒ 深度 50~58 的静止凹脉冲"进门即满足释放" ⇒ 100ms 周期自激
* G1: 门控用状态无关进表 dlt; G2: 带回夹紧 ≤ 进表 dlt(不动 180=B4.36.28 的 41); G3: 进入后 300ms 禁释放
*/
static uint32_t gate_dlt(uint32_t origin, int sens) { return (uint32_t)(((uint64_t)origin * SensTable[sens]) >> 16); }
static void sim_flap_x(int mode, int n, uint32_t (*capvd_at)(int), uint32_t org, int32_t ema0, int *p_in, int *p_off)
{
uint32_t Origin = org; int32_t EMA = ema0, frac = 0;
uint32_t dlt_ent = gate_dlt(Origin, SENS_LEVEL), dlt_rel = (uint32_t)(((uint64_t)Origin * 12) >> 16);
uint8_t vd = 0; int i, cnt_in = 0, cnt_off = 0, ent = 0, rel = 0, hold = 0, freeze = 0;
for (i = 0; i < n; i++) {
uint32_t capvd = capvd_at(i), ref, d = dlt_ent;
if (mode == 0 && vd) d = dlt_rel; /* 现状: 门控用"当前状态"dlt */
ref = ((int32_t)Origin - EMA > (int32_t)(d * VAC_REF_DELTA_DLT)) ? (uint32_t)EMA : Origin;
if (!vd) {
if (capvd < ref - dlt_ent) { if (++ent >= 5) { vd = 1; ent = 0; cnt_in++; hold = (mode >= 3) ? 30 : 0; } }
else ent = 0;
if (freeze > 0) freeze--; /* 释放后 1s 全冻结(REL_ORIGIN_FREEZE_TICKS) */
else if (capvd > Origin - 4*dlt_ent && capvd < Origin + 4*dlt_ent) { /* 对称窗内 EMA 跟踪(D: 余数累加) */
int32_t dd = (int32_t)capvd - EMA, lim = (int32_t)(3*dlt_ent);
if (dd > lim) dd = lim;
if (dd < -lim) dd = -lim;
frac += dd; { int32_t st = frac / VACUUM_EMA_ALFA; frac -= st * VACUUM_EMA_ALFA; EMA += st; }
}
} else {
if (hold > 0) { hold--; rel = 0; }
else {
uint32_t band = (uint32_t)(((uint64_t)dlt_rel * REL_BAND_LO_MULT) / 100); /* 41 */
if (mode >= 2 && band > dlt_ent) band = dlt_ent - 1; /* G2 兜底夹紧 */
/* mode 4 = 非低Q(QLO=0): 释放线 = Origin dlt_rel, 不经 vac_ref ⇒ 结构上无重叠 */
uint32_t line = (mode == 4) ? (Origin - dlt_rel) : (ref - band);
if (line < capvd) { if (++rel >= 5) { vd = 0; rel = 0; cnt_off++; freeze = 100; } }
else rel = 0;
}
}
}
*p_in = cnt_in; *p_off = cnt_off;
}
static void sim_flap(int mode, int n, uint32_t (*capvd_at)(int), int *p_in, int *p_off)
{ sim_flap_x(mode, n, capvd_at, 129254, 129220, p_in, p_off); } /* 22µH/163kHz 现场参数 */
static uint32_t tl_hold(int i) { (void)i; return 129204; } /* 静止悬停 深 50 */
static uint32_t tl_rebound(int i){ return i < 50 ? 129194 : 129218; } /* 深 60 → 回摆到 Origin-36 */
static uint32_t tl_leave(int i) { return i < 50 ? 129194 : 129254; } /* 深 60 → 撤板 */
static uint32_t tl_hold35(int i) { (void)i; return 129088; } /* 35µH/128kHz: Origin 129141 深 53 */
static uint32_t tl_leave35(int i){ return i < 50 ? 129070 : 129141; } /* 35µH 撤板 */
static void test_lowq_limit_cycle(void)
{
int i, ci, co, ci22 = 0, ci35 = 0;
printf("\n--- 场景 7: 低Q 临界悬停自激(现场参数逐位复现)---\n");
struct { int mode; const char *name; } M[4] = {
{0, "现状 V4.36.41"}, {1, "G1"}, {2, "G1+G2"}, {3, "G1+G2+G3"} };
printf(" 静止悬停(深 50) 3s: ");
for (i = 0; i < 4; i++) { sim_flap(M[i].mode, 3000, tl_hold, &ci, &co);
printf("[%s: Car_In %d / OFF %d] ", M[i].name, ci, co); }
printf("\n");
sim_flap(0, 3000, tl_hold, &ci, &co);
assert(ci >= 10); /* 现状必然自激(现场日志 ~30 次/3s) */
sim_flap(3, 3000, tl_hold, &ci, &co);
assert(ci == 1 && co == 0); /* 修后: 进入一次, 悬停期间不释放 */
sim_flap(3, 3000, tl_leave, &ci, &co);
assert(ci == 1 && co == 1); /* 撤板仍能释放 */
sim_flap(2, 3000, tl_rebound, &ci, &co);
assert(co >= 1); /* V4.36.28 回摆释放(VACVD Origin-36)不被打破 */
sim_flap(0, 3000, tl_hold, &ci, &co); { ci22 = ci; }
printf(" 35µH/128kHz 悬停(深 53) 3s: ");
sim_flap_x(0, 3000, tl_hold35, 129141, 129109, &ci, &co); printf("[现状: In %d / OFF %d] ", ci, co);
sim_flap_x(3, 3000, tl_hold35, 129141, 129109, &ci, &co); printf("[G1+G2+G3: In %d / OFF %d]\n", ci, co);
sim_flap_x(0, 3000, tl_hold35, 129141, 129109, &ci, &co); assert(ci >= 10); /* 35µH 同样自激 */
ci35 = ci;
sim_flap_x(3, 3000, tl_hold35, 129141, 129109, &ci, &co); assert(ci == 1 && co == 0);
sim_flap_x(3, 3000, tl_leave35, 129141, 129109, &ci, &co); assert(ci == 1 && co == 1);
printf(" 现状自激: 22µH Car_In=%d 次/3s, 35µH Car_In=%d 次/3s (≥10 OK, 现场日志同为 ~100ms 周期)\n", ci22, ci35);
sim_flap_x(4, 3000, tl_hold, 129254, 129220, &ci, &co);
assert(ci == 1 && co == 0); /* 判别预测: 非低Q(f<100kHz) 结构上不自激 */
printf(" 判别预测 OK: 非低Q(f<100kHz) 同深度悬停不自激(1/0) ⇒ 现场换 L≳68µH 应完全不跳\n");
printf(" 修后: 悬停 1/0 OK | 撤板 1/1 OK | V4.36.28 回摆仍释放 OK\n");
}
int main(void)
{
printf("test_vac_ref_reanchor (V4.36.41 草案: A+B 同步 + D 死区)\n");
printf("test_vac_ref_reanchor (V4.36.42: A+B 同步 / D 死区 / E 单向 / G1+G2+G3 低Q自激)\n");
/* ================= 场景 1: 复现 + 补丁效果 ================= */
reset_all(VAC_ANCHOR);
@@ -200,6 +336,12 @@ int main(void)
}
printf("5. 死区实测 OK(现状 127 counts 常驻偏差 → 修复 D 归零)\n");
printf("ALL PASS (A+B 同步 / 护栏 / 门控回归 / 上电语义 / 死区)\n");
/* ================= 场景 6: vac_ref 硬开关抖动 ================= */
test_chatter();
/* ================= 场景 7: 低Q 临界悬停自激 ================= */
test_lowq_limit_cycle();
printf("ALL PASS (A+B 同步 / 护栏 / 门控回归 / 上电语义 / 死区 / 抖动 / 低Q自激)\n");
return 0;
}