feat(vd960Loop): V1.03 — 上电自检基准判稳同步 V4B V4.23

绿灯上电自检快闪: 原固定 128 样本(≈1.3s)即判稳, 与基准是否真稳无关;
stable_cnt 仅 init_vd_single 清零, 有限存在超时/安全复位(LC_Reset=1)
重学阶段残留计数 → 二次稳定期 1 样本瞬间判稳 (同 V4B 旧缺陷)。

修复 (四路 unit 独立):
- TaskLoop.h: +STABLE_ORIGIN_PPT 1 / STABLE_SETTLE_WINDOWS 2 /
  STABLE_MAX_SAMPLES 500(≈5s兜底); Loop154_Unit +settle_cnt
- TaskLoop.c vd1_task_per_channel 稳定期: 每窗(100样本≈1s)完成比较
  Origin 均值漂移 ≤0.1% → settle++, 否则清零; 判稳=最少128样本+
  连续2窗稳定 或 500 样本兜底; 判稳退出清零
- init_vd_single / 有限存在超时 / 安全复位超时重学路径统一清零自检计数
- poll_green_led 条件不变: 绿灯闪至本路基准稳定才停 (四路独立)

验证: tests/test_stable_settle.c 5 项 PASS (常量200/首窗尖峰400/
      持续漂移500兜底/重学复位200防呆/Origin=0守卫);
      gcc -fsyntax-only TaskLoop.c 0 error
文档: vd960Loop/docs/devlog V1.03 置顶; DLD960_技术规格书/产品手册
      V1.04 (上电稳定期/绿灯自检语义 + 修订记录)
cmcng.h: FIRMWARE_VER "1.02"→"1.03" (MAIN=1/SUB=3, SSUB=1保持)

待现场验证: 绿灯自检时长(干净≈2s/扰动3~4s/兜底5s)与四路独立闪烁
This commit is contained in:
wangfq
2026-09-02 17:02:15 +08:00
parent 77b667f1a0
commit b90842f89d
7 changed files with 264 additions and 13 deletions
+4 -3
View File
@@ -1,7 +1,7 @@
# DLD960 四通道车辆检测器 产品手册
> Product Manual · 产品型号:**DLD960GA**
> 文档版本 V1.03 · 2026-08-21 · 适用固件:整机 V1.02.05Loop 1.0 + DBN 1.02.05
> 文档版本 V1.04 · 2026-09-02 · 适用固件:整机 V1.02.05Loop V1.03 + DBN 1.02.05
---
@@ -50,7 +50,7 @@ DLD960 是一款四通道环形线圈车辆检测器,一台设备即可覆盖
| LED_LP1~LP4 | 绿色 | 对应线圈 1~4:有车亮 / 无车灭 |
| ETH_LED_G / ETH_LED_Y | 绿/黄 | 网口连接/活动指示 |
> 上电后 LED_PWR 呼吸、四路车检灯全灭(无车),约 1.3 秒完成基线建立进入检测状态。若某路车检灯常亮且现场无车,参见 §7 故障排查。
> 上电后 LED_PWR 呼吸;各通道车检灯自检慢闪,该路绿灯闪至**本路基准稳定**才停(约 2~5 s,最长 5 s 兜底),随后无车全灭进入检测状态。若某路车检灯常亮且现场无车,参见 §7 故障排查。
### 2.3 线圈施工要求(关键!)
@@ -69,7 +69,7 @@ DLD960 是一款四通道环形线圈车辆检测器,一台设备即可覆盖
## 3. 快速上手
1. **接线**:接入线圈、继电器输出至道闸/控制器,需要联网则接网线。
2. **上电**LED_PWR 呼吸闪烁,约 1.3 秒后进入检测状态(上电 3 秒内不主动上报数据)。
2. **上电**LED_PWR 呼吸闪烁;各通道绿灯自检慢闪至基准稳定(约 2~5 s,最长 5 s 兜底)后进入检测状态(上电 3 秒内不主动上报数据)。
3. **配置**:手机小程序经蓝牙连接设备 → 验证密码 → 设置各路灵敏度/频率/输出模式。
4. **验证**:车辆压线,对应绿色车检灯亮、继电器吸合;离开后灯灭、继电器释放。
5. **联网(可选)**:小程序或 TCP 命令配置 IP/MQTT 参数,接入平台。
@@ -180,6 +180,7 @@ DLD960 是一款四通道环形线圈车辆检测器,一台设备即可覆盖
| 版本 | 修订时间 | 修订说明 | 修订人 |
|------|----------|----------|--------|
| V1.04 | 2026-09-02 | Loop V1.03 单侧更新:绿灯上电自检说明改为闪至基准稳定才停(约 2~5 s,最长 5 s 兜底),上电流程/故障排查同步 | wangfq |
| V1.03 | 2026-08-21 | 配套整机发布 V1.02.04→V1.02.05:核心特性补网络上报携带地感版本 + 联网稳定性加固;相关文档表 MQTT 协议 V1.07→V1.10(修正滞后);故障排查配置保存建议升至 V1.02.05 及以上 | wangfq |
| V1.02 | 2026-08-19 | 配套整机发布 V1.02.03→V1.02.04BLE OTA 升级链路加固说明(0x9F 响应帧透传,停等 ACK 完整);故障排查新增 OTA 失败 + 配置无法保存条目;固件升级补断电重启提示;存储兼容适配(去厂商代码 + 恢复 factory 写入) | wangfq |
| V1.01 | 2026-08-18 | 配套整机发布 V1.02.01:通信稳定性修复说明(UART2 DMA 防丢帧、异常重启修复);新增 BLE 脱机日志/传感快照读写说明(§5.1) | wangfq |
+3 -2
View File
@@ -1,7 +1,7 @@
# DLD960 四通道车辆检测器 技术规格书
> Technical Specification · 产品型号:**DLD960GA**
> 文档版本 V1.03 · 2026-08-21 · 配套整机发布 V1.02.05
> 文档版本 V1.04 · 2026-09-02 · 配套整机发布 V1.02.05Loop V1.03 单侧更新)
---
@@ -62,7 +62,7 @@ DLD960 是一款基于环形线圈(LC 振荡)检测原理的四通道车辆
| 基线跟踪 | 滑动平均窗口 500 样本(5s 更新周期,噪声抑制 22×) | 跟随温漂/环境慢变 |
| 基线冻结保护 | 偏差超限即冻结基线更新 | 防止过车信号污染基线 |
| 冻结超时自适应 | 10 s 超时 + ±2% 稳定性窗校验 | 温漂/换线圈后自动重建基线;慢速进车不误吸收 |
| 上电稳定期 | 128 样本(≈1.3 s)快速收敛,上电 3 s 内抑制主动上报 | 快速可用且不上报无效数据 |
| 上电稳定期 | 基准稳定判定(Loop V1.03 起):连续 2 窗(100 样本/窗≈1 sOrigin 均值漂移 ≤0.1% + 最少 128 样本(≈1.3 s),硬兜底 500 样本(≈5 s);上电 3 s 内抑制主动上报 | 基准真正稳定才进入检测,避免上电误判;绿灯自检闪至本路基准稳定才停 |
| 线圈安全模式 | 可配置超时(单位 10 s,0=关闭) | 异常长时间占用强制复位重建基线 |
### 3.4 检测性能指标(设计目标,依据《环路车辆检测器验收标准》v1.0)
@@ -187,6 +187,7 @@ DLD960 是一款基于环形线圈(LC 振荡)检测原理的四通道车辆
| 版本 | 修订时间 | 修订说明 | 修订人 |
|------|----------|----------|--------|
| V1.04 | 2026-09-02 | Loop V1.03 单侧更新:上电稳定期由固定 128 样本改为基准判稳(连续 2 窗 Origin 漂移 ≤0.1% + 5 s 硬兜底),绿灯自检闪至基准稳定才停止 | wangfq |
| V1.03 | 2026-08-21 | 配套整机发布 V1.02.04→V1.02.05:协议矩阵标题/版本配套矩阵/固件三段式版本同步;新增已知约束 UART2 0x4A 版本查询总线时序;MQTT V1.10 地感版本上报与 loop_version_query 说明 | wangfq |
| V1.02 | 2026-08-19 | 配套整机发布 V1.02.03→V1.02.04UART2 RX 0x9F OTA 透传机制说明(§2/§5/§7)、协议矩阵与版本配套矩阵更新、已知约束补充 OTA 无自动退出;存储适配(SPI 识别去厂商代码 + factory 写入恢复);2026-08-20 协议矩阵 MQTT 同步 V1.08Loop 远程 OTA 协议设计先行,固件待实现,Slot A/B 100KB | wangfq |
| V1.01 | 2026-08-18 | 配套整机发布 V1.02.01:版本矩阵更新、UART2 RX DMA 通信可靠性说明、已知约束补充 | wangfq |
+32
View File
@@ -4,6 +4,38 @@
---
## 2026-09-02 — V1.03:上电自检基准判稳同步 V4B V4.23(版本号 V1.03
> **固件版本:V1.03**cmcng.hFIRMWARE_VER="1.03" + MAIN=1/SUB=3SSUB=1 保持)。devlog 修订。
### 背景(源自 DLD154V4B 现场反馈,V4B V4.23 已实测通过)
绿灯上电自检快闪:原逻辑固定 128 样本(≈1.3 s)即判稳,**与基准值是否真稳定无关**;
`stable_cnt` 只在 `init_vd_single` 清零,**有限存在超时 / 安全复位(LC_Reset=1)重学阶段不清零**
残留计数会让二次稳定期几乎瞬间判稳(绿灯自检形同虚设)。V4B V4.23 修复后本仓四路同步。
### 改动
- `TaskLoop.h`:新增 `STABLE_ORIGIN_PPT 1`(窗口 Origin 均值漂移 ≤ Origin×0.1%/
`STABLE_SETTLE_WINDOWS 2`(连续 2 窗判稳)/ `STABLE_MAX_SAMPLES 500`(≈5 s 硬兜底);
`Loop154_Unit` 新增 `settle_cnt`
- `TaskLoop.c`
- 稳定期:每窗(100 样本≈1 s)完成时比较 Origin 均值漂移,≤0.1% → `settle_cnt++` 否则清零;
判稳 = 最少 128 样本 + 连续 2 窗稳定,或 500 样本硬兜底;判稳退出时 `stable_cnt/settle_cnt` 清零
- `init_vd_single` / 有限存在超时 / 安全复位超时(重学路径)统一清零自检计数 → 二次稳定期从零开始
- 绿灯 `poll_green_led` 条件不变:该路绿灯闪至**本路基准稳定**才停(四路独立)
### 验证
- `tests/test_stable_settle.c`:常量 → 200 样本判稳;首窗尖峰 → 400(扰动后需 2 个干净窗口);
持续漂移 → 500 硬兜底;重学复位后 → 200(防呆:非瞬间判稳)ALL PASS
### 待现场验证
上电绿灯自检时长(干净启动 ≈2 s / 有扰动 3~4 s / 干扰大 5 s 兜底)与四路独立闪烁观感。
---
## 2026-09-02 — V1.02:基线跟踪双向对称保护同步 V4B V4.20(版本号 V1.02
> **固件版本:V1.02**cmcng.hFIRMWARE_VER="1.02" + MAIN=1/SUB=2SSUB=1 保持)。devlog 修订。
+183
View File
@@ -0,0 +1,183 @@
/*
* test_stable_settle.c — 上电自检基准判稳 gcc 隔离单测 (vd960Loop V1.03)
*
* 验证对象: TaskLoop.c vd1_task_per_channel() 稳定期判稳逻辑
* (对齐 DLD154V4B V4.23, 2026-09-02 实测通过后同步)
*
* 语义:
* 绿灯上电自检快闪 → 仅当"基准值真正稳定"才停止:
* - 每窗(100样本≈1s)完成时比较窗口平均 Origin 漂移, ≤ Origin×0.1% → settle++
* - 判稳 = 最少 128 样本 + 连续 2 窗漂移达标; 硬兜底 500 样本(≈5s)
* - 判稳退出/init_vd_single/有限存在超时/安全复位(LC_Reset=1) 均清零计数
* → 二次稳定期从零开始 (修掉原"残留计数 1 样本瞬间判稳")
*
* 编译运行:
* gcc tests/test_stable_settle.c -o /tmp/test_stable_settle && /tmp/test_stable_settle
*/
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
/* ---- 与 TaskLoop.h / TaskLoop.c 一致的宏与结构 (隔离副本) ---- */
#define STABLE_SAMPLES 128 // 稳定期最少样本数 (128×10ms≈1.3s)
#define STABLE_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×0.1%
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗(100样本/窗≈1s)漂移达标 → 判稳
#define STABLE_MAX_SAMPLES 500 // 硬兜底 ≈5s
typedef struct {
uint32_t loop_Origin;
uint32_t loop_Value;
uint32_t loop_CAPVD;
uint32_t loop_ORG_SUM;
uint16_t loop_ORG_CNT;
uint16_t stable_cnt;
uint16_t settle_cnt;
uint8_t loop_stable;
} Unit;
/* ---- 与 TaskLoop.c update_moving_average 同逻辑副本 ---- */
static uint8_t update_moving_average(uint32_t *p_sum, uint16_t *p_cnt,
uint32_t *p_origin, uint32_t new_value,
uint16_t window)
{
*p_sum += new_value;
(*p_cnt)++;
if (*p_cnt >= window) {
*p_origin = *p_sum / window;
*p_cnt = 0;
*p_sum = 0;
return 1;
}
return 0;
}
/* init_vd_single 等价 (含 V1.03 清零) */
static void unit_init(Unit *u, uint32_t origin)
{
u->loop_Origin = origin;
u->loop_Value = 0;
u->loop_CAPVD = 0;
u->loop_ORG_SUM = 0;
u->loop_ORG_CNT = 0;
u->stable_cnt = 0;
u->settle_cnt = 0;
u->loop_stable = 0;
}
/* vd1_task_per_channel() 稳定期分支的精确副本 (V1.03)
* 含函数入口守卫: Origin==0 直接 return (首个测量窗确立 Origin 前不推进) */
static void stable_step(Unit *u, uint32_t value)
{
uint32_t _prev_origin = u->loop_Origin;
uint8_t _win_done;
if (u->loop_Origin == 0) return;
u->loop_CAPVD = value;
_win_done = update_moving_average(&u->loop_ORG_SUM, &u->loop_ORG_CNT,
&u->loop_Origin, u->loop_CAPVD, 100);
u->stable_cnt++;
if (_win_done) {
uint32_t _drift = (u->loop_Origin > _prev_origin)
? (u->loop_Origin - _prev_origin)
: (_prev_origin - u->loop_Origin);
uint32_t _band = (uint32_t)(u->loop_Origin * STABLE_ORIGIN_PPT / 1000);
if (_drift <= _band) {
if (u->settle_cnt < 0xFFFF) u->settle_cnt++;
} else {
u->settle_cnt = 0;
}
}
if ((u->stable_cnt >= STABLE_SAMPLES &&
u->settle_cnt >= STABLE_SETTLE_WINDOWS) ||
u->stable_cnt >= STABLE_MAX_SAMPLES) {
u->loop_stable = 1;
u->stable_cnt = 0;
u->settle_cnt = 0;
}
}
/* 打样本直至判稳, 返回判稳时的累计样本数; 上限 5000 防死循环 */
static int run_until_stable(Unit *u, uint32_t (*gen)(Unit *u, int i))
{
int i;
for (i = 1; i <= 5000; i++) {
stable_step(u, gen(u, i));
if (u->loop_stable) return i;
}
return -1;
}
static uint32_t gen_const(Unit *u, int i) { (void)u; (void)i; return 100000UL; }
static uint32_t gen_spike(Unit *u, int i) { (void)u; return (i == 50) ? 200000UL : 100000UL; }
static uint32_t gen_drift(Unit *u, int i) { (void)u; return 100000UL + 2UL * (uint32_t)i; }
static int expect_stable(int n, int exp, const char *tag)
{
printf("%-24s 判稳样本=%d (期望 %d)%s\n", tag, n, exp,
(n == exp) ? "" : " <-- MISMATCH");
return n == exp;
}
int main(void)
{
Unit u;
int n, fail = 0;
/* 1. 常量基准: 窗1@100 settle=1, 窗2@200 settle=2 → 200 判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_const);
fail += !expect_stable(n, 200, "1. 常量值");
/* 2. 首窗尖峰(第50样本 200000): 窗1均值101000 漂移>0.1% 清零;
* 窗2均值100000 与101000差1000 仍>band → 清零; 窗3/窗4 漂移0
* settle=1@300, settle=2@400 → 400 判稳 (扰动后需2个干净窗口) */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_spike);
fail += !expect_stable(n, 400, "2. 首窗尖峰");
/* 3. 持续漂移(每样本+2): 每窗漂移200 > band(≈100) → 永远不达标,
* 500 样本硬兜底强制判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_drift);
fail += !expect_stable(n, 500, "3. 持续漂移(硬兜底)");
/* 4. 二次稳定期防呆: 判稳后模拟 有限存在/安全复位 重学路径
* (loop_stable=0 + 计数清零, 即 C2/C3 新增的两处清零),
* 常量基准须重新满 200 样本判稳, 绝非 1 样本瞬间判稳 */
unit_init(&u, 100000UL);
n = run_until_stable(&u, gen_const);
if (n == 200) {
u.loop_stable = 0; /* 重学复位 (同 LC_Reset=1 路径) */
u.stable_cnt = 0;
u.settle_cnt = 0;
u.loop_ORG_CNT = 0;
u.loop_ORG_SUM = 0;
n = run_until_stable(&u, gen_const);
fail += !expect_stable(n, 200, "4. 二次稳定期(重学复位)");
} else {
printf("4. 二次稳定期 前置失败 (首轮 n=%d)\n", n);
fail++;
}
/* 5. Origin==0 边界 (vd1 入口 return, 稳定期不应推进) */
unit_init(&u, 0UL);
stable_step(&u, 100000UL);
if (u.stable_cnt != 0 || u.settle_cnt != 0) {
printf("5. Origin=0 守卫 FAIL (stable_cnt=%u settle_cnt=%u)\n",
u.stable_cnt, u.settle_cnt);
fail++;
} else {
printf("%-24s OK (不推进自检计数)\n", "5. Origin=0 守卫");
}
if (fail == 0) {
printf("ALL PASS (test_stable_settle)\n");
return 0;
}
printf("FAILED (%d)\n", fail);
return 1;
}
@@ -33,7 +33,10 @@
#define ALFA_CAP1 79 // IIR α = 79/256 ≈ 0.31 (@10ms → τ≈32ms)
#define MAX_SLOPE_RATE 5 // 斜率限幅: 单次最大变化 5%
#define ENTRY_CONFIRM 3 // 进入确认: 连续 N 次低于阈值
#define STABLE_SAMPLES 128 // 稳定期样本数
#define STABLE_SAMPLES 128 // 稳定期最少样本数 (128×10ms≈1.3s)
#define STABLE_ORIGIN_PPT 1 // 基准稳定判据: 窗口均值漂移 ≤ Origin×0.1%
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗(100样本/窗≈1s)漂移达标 → 判稳
#define STABLE_MAX_SAMPLES 500 // 硬兜底 ≈5s (防环境长期不稳绿灯无限自检)
/*===========================================================================
* 线 (V2.3~V2.5)
@@ -182,7 +185,8 @@ typedef struct {
uint16_t fault_tick;
/*--- 稳定期 ---*/
uint16_t stable_cnt;
uint16_t stable_cnt; // 稳定期累计样本 (每次 CAP_OK +1)
uint16_t settle_cnt; // 基准稳定连续窗口计数 (窗=100样本≈1s, 漂移≤0.1%则+1)
/*--- 调试 ---*/
uint32_t xn_counter;
@@ -16,10 +16,10 @@
#include <stdint.h>
#define PRODUCT_MODEL "DLD960"
#define FIRMWARE_VER "1.02"
#define FIRMWARE_VER "1.03"
#define HARDWARE_VER "1.00"
#define FIRMWARE_VER_MAIN 1
#define FIRMWARE_VER_SUB 2
#define FIRMWARE_VER_SUB 3
#define FIRMWARE_VER_SSUB 1
#define HARDWARE_VER_MAIN 1
#define HARDWARE_VER_SUB 0
@@ -154,6 +154,7 @@ void init_vd_single(Loop154_Unit *unit)
unit->SET_SAFE = 1;
unit->stable_cnt = 0;
unit->settle_cnt = 0; // V1.03: 自检计数从零开始
unit->xn_counter = 0;
}
@@ -318,6 +319,8 @@ void TMR15_GLOBAL_IRQHandler(void)
unit->loop_INI_LOOP = 1;
unit->loop_LOOP_OK0 = 0;
unit->loop_stable = 0;
unit->stable_cnt = 0; // V1.03: 重学自检计数从零开始
unit->settle_cnt = 0;
unit->loop_ORG_CNT = 0;
unit->loop_ORG_SUM = 0;
unit->last_exit_tick = g_loop_states.misc_counter;
@@ -337,6 +340,8 @@ void TMR15_GLOBAL_IRQHandler(void)
unit->loop_INI_LOOP = 1;
unit->loop_LOOP_OK0 = 0;
unit->loop_stable = 0;
unit->stable_cnt = 0; // V1.03: 二次稳定期从零开始
unit->settle_cnt = 0;
#if USE_FLATNESS_EXIT
unit->exit_state = 0;
unit->max_slope = 0;
@@ -459,15 +464,40 @@ void vd1_task_per_channel(Loop154_Unit *unit)
unit->loop_CAPVD = get_flt_value(clamped_value, unit->loop_CAPVD);
}
/*--- 2. 稳定期:绕过 IIR 和斜率限幅,小窗口快速收敛 ---*/
/*--- 2. 稳定期:绕过 IIR 和斜率限幅,小窗口快速收敛;
* ( Origin ) (V1.03, V4B V4.23) ---*/
if (!unit->loop_stable) {
uint32_t _prev_origin = unit->loop_Origin;
uint8_t _win_done;
unit->loop_CAPVD = unit->loop_Value;
update_moving_average(&unit->loop_ORG_SUM, &unit->loop_ORG_CNT,
&unit->loop_Origin, unit->loop_CAPVD, 100);
_win_done = update_moving_average(&unit->loop_ORG_SUM, &unit->loop_ORG_CNT,
&unit->loop_Origin, unit->loop_CAPVD, 100);
unit->stable_cnt++;
if (unit->stable_cnt >= STABLE_SAMPLES) {
/* 每窗(100样本≈1s)完成时比较 Origin 均值漂移;
* Origin×STABLE_ORIGIN_PPT/1000 settle +1,
* ( Origin , ) */
if (_win_done) {
uint32_t _drift = (unit->loop_Origin > _prev_origin)
? (unit->loop_Origin - _prev_origin)
: (_prev_origin - unit->loop_Origin);
uint32_t _band = (uint32_t)(unit->loop_Origin * STABLE_ORIGIN_PPT / 1000);
if (_drift <= _band) {
if (unit->settle_cnt < 0xFFFF) unit->settle_cnt++;
} else {
unit->settle_cnt = 0;
}
}
/* 判稳条件: 最少样本 + 连续 2 窗基准稳定; 硬兜底 STABLE_MAX_SAMPLES */
if ((unit->stable_cnt >= STABLE_SAMPLES &&
unit->settle_cnt >= STABLE_SETTLE_WINDOWS) ||
unit->stable_cnt >= STABLE_MAX_SAMPLES) {
unit->loop_stable = 1;
unit->stable_cnt = 0; // 计数清零: 重连/安全复位二次稳定期从零开始
unit->settle_cnt = 0;
PRINT("Loop%d stable, Origin:%d\n", unit->loop_num + 1, unit->loop_Origin);
}
return;