feat(V4B): V4.36.44 — 铁板负偏差误释放防护(P1) + 有车最小保持定档 100ms

现场 154log260911d(22µH 铁板): 进入后 CAPVD 被磁导率抬到 dev +201/+198,
两次在 dev=+33/+96(≈2×进表dlt, 落在既有 ±4×dlt 正常跟踪窗内) 误判"离开" ⇒ 继电器掉。
修复: 有车期间 dev > +1×进表dlt ⇒ 冻结离开判定(含 band_timeout); 持续 5s ⇒ 重锚基线后放行(防锁死)。
同时 REL_MIN_HOLD_TICKS 30→10(100ms, 现场验证后正式化)。
单测场景8: 误释放 @1.04s → 冻结后 @6.04s, 正常撤板不受影响; 全套 8/8 全绿。

- P1: g_neg_rel_hold_cnt + REL_NEG_VAR_HOLD_TICKS=500; 门控普通释放与 band_timeout;
      Car_In/Car_Off 双向清零; 新增 EVT|neg_rel_hold / EVT|neg_rel_reanchor
- 边界: 真断开仍走 RELAY_HOLD_MS=1000; dev>=+4×dlt 强负 variation 仍归 V4.24 两段式环境逻辑
- 文档: devlog V2.81(+条目) / spec 头 V2.63 / manual 表 V2.20 / cmcng.h 4.36.43→4.36.44 REV 44
- 归档: docs/patches/v4.36.44-P1-negvar-hold-and-minhold-100ms.diff
This commit is contained in:
wangfq
2026-09-11 17:53:15 +08:00
parent 6c82bf61b8
commit d5e6a3d55b
8 changed files with 277 additions and 19 deletions
@@ -0,0 +1,137 @@
diff --git a/utilities/at32f421_freertos_demo/inc/TaskLoop.h b/utilities/at32f421_freertos_demo/inc/TaskLoop.h
index f37c3ef..f5dedf4 100644
--- a/utilities/at32f421_freertos_demo/inc/TaskLoop.h
+++ b/utilities/at32f421_freertos_demo/inc/TaskLoop.h
@@ -66,7 +66,8 @@
#define ENTRY_CONFIRM_MID 4 // 中Q(80~100k)
#define ENTRY_CONFIRM_LO 5 // 低Q(f>100k): 抖动大, 多样本防假进入
#define REL_ORIGIN_FREEZE_TICKS 100 // V4.36.26/32: 释放后 Origin 全冻结 1000ms — 残余缓离窗口不入均值窗
-#define REL_MIN_HOLD_TICKS 30 // V4.36.42(G3): 有车后最小保持 300ms 禁释放 (掐断进/出 100ms 级自激; 不延迟检出)
+#define REL_MIN_HOLD_TICKS 10 // V4.36.44: 有车后最小保持 100ms 禁释放 (V4.36.42 首发 300ms; 2026-09-11 现场以 10=100ms 验证通过后正式化; 0.2s 内必释放)
+#define REL_NEG_VAR_HOLD_TICKS 500 // V4.36.44(P1): 有车期间负偏差(铁块/异常高)持续上限 5s, 到顶重锚基线后放行(防永久锁死)
#define ORIGIN_LOWLEARN_TICKS 200 // V4.36.32: 冻结后降权低速补偿期 2000ms: 残余低值样本权重降 ×1/MULT
// 仿 PD132T 慢采样等效(10ms 快采样让 5s 窗装得快, 残余易拉低 Origin; 慢学更稳)
#define ORIGIN_LOWLEARN_MULT 5 // V4.36.32: 降权期均值窗放大 500→2500 样本(25s 窗), 学习速率 1/5
diff --git a/utilities/at32f421_freertos_demo/src/TaskLoop.c b/utilities/at32f421_freertos_demo/src/TaskLoop.c
index 2e0f53b..370fbd6 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 → 强制释放)
uint16_t g_rel_hold_tick = 0; // V4.36.42(G3): 有车后最小保持计数 (禁释放, 掐断进/出自激)
+uint16_t g_neg_rel_hold_cnt = 0; // V4.36.44(P1): 有车期间负偏差(>+1×dlt)持续计数
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)
@@ -289,16 +290,16 @@ uint32_t get_flt_value_rel(uint32_t new_value, uint32_t last_value)
/* V4.36.31: B3 判定参考 — min(Origin, 真空 EMA)
* Origin 可能被 env 学习锚到高位(板搁置态/过冲, J1: 130636 vs 真空 130506) → 释放线偏高
* → 撤板真空也够不着; 取 min 使参考锚向实时真空(低者=保守方向), 双侧自动正确 */
-/* V4.36.42(G1): 真空门控阈值 — 必须"状态无关"
- * 现场 20260911 日志: loop1_dlt_ORG 在进车瞬间被切成释放表值(49→23) ⇒ 同一 EMA 偏差(34)
- * 原本不触发切换、进车后却触发 ⇒ vac_ref() 从 Origin 阶跃到 EMA ⇒ 释放线瞬间浅 34 counts
- * ⇒ 深度 50~58 counts 的静止凹脉冲"进门即满足释放" ⇒ 100ms 周期自激(约 30 次/3s)
- * 修法: 门控一律用进表 dlt(与 :band_timeout 的写法统一) ⇒ 进/出车用同一参考 */
-static uint32_t vac_gate_dlt(void)
-{
- return ((uint32_t)loop1_Origin * SensTable[loop1_SensLevel]) >> 16;
-}
-
+/* V4.36.42(G1): 真空门控阈值 — 必须"状态无关"
+ * 现场 20260911 日志: loop1_dlt_ORG 在进车瞬间被切成释放表值(49→23) ⇒ 同一 EMA 偏差(34)
+ * 原本不触发切换、进车后却触发 ⇒ vac_ref() 从 Origin 阶跃到 EMA ⇒ 释放线瞬间浅 34 counts
+ * ⇒ 深度 50~58 counts 的静止凹脉冲"进门即满足释放" ⇒ 100ms 周期自激(约 30 次/3s)
+ * 修法: 门控一律用进表 dlt(与 :band_timeout 的写法统一) ⇒ 进/出车用同一参考 */
+static uint32_t vac_gate_dlt(void)
+{
+ return ((uint32_t)loop1_Origin * SensTable[loop1_SensLevel]) >> 16;
+}
+
static uint32_t vac_ref(void)
{
if (g_vacuum_ema == 0) return loop1_Origin;
@@ -597,6 +598,7 @@ void INIT_VD(void)
g_origin_freeze_tick = 0; /* V4.36.26: 释放冻结计数清零 */
g_rel_band_cnt = 0; /* V4.36.30: 环带兜底计数清零 */
g_rel_hold_tick = 0; /* V4.36.42(G3): 最小保持清零 */
+ g_neg_rel_hold_cnt = 0; /* V4.36.44(P1): 负偏差离开冻结计数清零 */
g_vacuum_ema = 0; /* V4.36.31: 真空 EMA 清零 (判稳后重建) */
g_vac_frac = 0; /* V4.37(D): 余数累加器同步清零 */
g_ready_hold_tick = 0; /* V4.36.36: 就绪巩固期清零 (重建判稳时会再置) */
@@ -1502,6 +1504,7 @@ void vd1_task(void)
g_rel_smooth = loop1_CAPVD; /* V4.36.19: 低Q 释放平滑 snap 到进入点 */
g_rel_band_cnt = 0; /* V4.36.30: 环带兜底计数进入时清零 */
g_rel_hold_tick = REL_MIN_HOLD_TICKS; /* V4.36.42(G3): 进入后 300ms 禁释放 */
+ g_neg_rel_hold_cnt = 0; /* V4.36.44(P1): 进入即重置 */
if (!SET_SAFE) {
loop1_LC_HOLD = 1;
@@ -1533,6 +1536,30 @@ void vd1_task(void)
* 有车期间永不递减 ⇒ 首次进入后 g_rel_hold_tick 常驻 ⇒ 正常释放被永久挡住, 只能靠 band_timeout
* 兜底 (现场日志 2026-09-11 17:02: 4 次 Car_OFF 全是 band_timeout, 离开延迟 3.5~4.7s) */
if (g_rel_hold_tick > 0) g_rel_hold_tick--;
+ /* V4.36.44(P1): 有车期间 CAPVD 进负偏差侧(dev > +1×进表dlt) = 铁块/环境异常高, 不是"离开"证据
+ * ⇒ 冻结离开判定(含 band_timeout 兜底); 持续 REL_NEG_VAR_HOLD_TICKS(5s) ⇒ 判为真空位移,
+ * 重锚基线后放行(防永久锁死)。真实"车走了"只让 CAPVD 回到 Origin 附近(dev≈0) ⇒ 不受影响。
+ * 现场依据: 2026-09-11 铁板日志(22µH)两次误释放发生在 dev=+96(≈2×进表dlt), 落在"-4dlt..+4dlt 正常跟踪窗"内 */
+ {
+ int32_t _neg_dev = (int32_t)loop1_CAPVD - (int32_t)loop1_Origin;
+ if (_neg_dev > (int32_t)vac_gate_dlt()) {
+ if (g_neg_rel_hold_cnt == 0) {
+ PRINT("EVT|neg_rel_hold CAPVD:%d Origin:%d dev:%d dlt:%d (离开判定冻结)\n",
+ loop1_CAPVD, loop1_Origin, (int)_neg_dev, (int)vac_gate_dlt());
+ }
+ if (g_neg_rel_hold_cnt < REL_NEG_VAR_HOLD_TICKS) {
+ g_neg_rel_hold_cnt++;
+ } else {
+ origin_set_atomic(loop1_CAPVD); /* 异常持续 5s ⇒ 判真空位移, 重锚基线 */
+ g_origin_freeze_tick = REL_ORIGIN_FREEZE_TICKS;
+ g_neg_rel_hold_cnt = 0; /* 放行: 基线已跟上, dev 归零 */
+ PRINT("EVT|neg_rel_reanchor Origin->%d (异常持续 %dms, 重锚后放行)\n",
+ loop1_Origin, REL_NEG_VAR_HOLD_TICKS * 10);
+ }
+ } else {
+ g_neg_rel_hold_cnt = 0;
+ }
+ }
#if USE_FLATNESS_EXIT
/*================================================================
* 有车状态 — 平坦性判定离开(专利 CN200910309382
@@ -1601,9 +1628,9 @@ void vd1_task(void)
loop1_dlt_ORG = ((uint32_t)loop1_Origin * SensTable_1[loop1_SensLevel]) >> 16; // 方案B(V4.06): 调浅释放表已并入 SensTable_1
#if USE_SLOW_RELEASE
- if (((loop1_Origin - loop1_dlt_ORG) < loop1_CAPVD_slow) && (g_rel_hold_tick == 0)) { // 方案A + V4.36.42(G3) 最小保持
+ if (((loop1_Origin - loop1_dlt_ORG) < loop1_CAPVD_slow) && (g_rel_hold_tick == 0) && (g_neg_rel_hold_cnt == 0)) { // 方案A + V4.36.42(G3) 最小保持
#else
- if ((g_rel_hold_tick == 0) && rel_release_hit()) { // V4.36.42(G3) 最小保持 + V4.36.19 低Q 平滑/带回加宽
+ if ((g_rel_hold_tick == 0) && (g_neg_rel_hold_cnt == 0) && rel_release_hit()) { // V4.36.42(G3) 最小保持 + V4.36.19 低Q 平滑/带回加宽
#endif
if (loop1_cnt_release == 0) {
/* 过渡事件: 第一次命中离开线 (定位"为什么开始释放") */
@@ -1630,6 +1657,7 @@ void vd1_task(void)
Hold_CNT = 0;
loop1_cnt_release = 0;
g_rel_hold_tick = 0; /* V4.36.42(G3): 释放即清保持 */
+ g_neg_rel_hold_cnt = 0; /* V4.36.44(P1): 负偏差离开冻结计数清零 */
g_origin_freeze_tick = REL_ORIGIN_FREEZE_TICKS; /* V4.36.26: 释放后 Origin 均值窗冻结 */
g_origin_lowlearn_tick = ORIGIN_LOWLEARN_TICKS; /* V4.36.32: 冻结后降权期预置 */
}
@@ -1643,7 +1671,7 @@ void vd1_task(void)
if (g_q_lo) {
uint32_t _entry_ln = vac_ref() -
(((uint32_t)loop1_Origin * SensTable[loop1_SensLevel]) >> 16); /* V4.36.31: 锚真空参考 */
- if (g_rel_smooth > _entry_ln) {
+ if ((g_rel_smooth > _entry_ln) && (g_neg_rel_hold_cnt == 0)) { /* V4.36.44(P1) */
g_rel_band_cnt++;
if (g_rel_band_cnt >= REL_BAND_TIMEOUT_TICKS) {
g_rel_band_cnt = 0;
@@ -1657,6 +1685,7 @@ void vd1_task(void)
loop1_ORG_SUM = 0;
Hold_CNT = 0;
loop1_cnt_release = 0;
+ g_neg_rel_hold_cnt = 0; /* V4.36.44(P1) */
g_origin_freeze_tick = REL_ORIGIN_FREEZE_TICKS;
g_origin_lowlearn_tick = ORIGIN_LOWLEARN_TICKS; /* V4.36.32: 冻结后降权期预置 */
}