docs(vd960Loop): variation-analysis.md 对齐协议 V1.05 (变化量 2B无符号→3B有符号)

- 全文口径切换: variation = Origin - CAPVD, 3B LE 补码, ±2^23 饱和
- 新增 §5 变更分析: 回绕污染/方向丢失缺陷、符号语义、bit23 符号扩展、
  步长 11→12 死绑定的静默错位风险及 Len 字节兼容方案
- 波形场景补充方向语义: 锯齿极性=漂移方向, 持续负值=基线污染诊断
- 旧问题清单更新: 2B 截断标记已解决; 待验证新增新旧固件混跑错位复现
This commit is contained in:
wangfq
2026-07-15 08:38:22 +08:00
parent 82e01cd337
commit 7c5920805d
+90 -17
View File
@@ -2,13 +2,14 @@
> 分析对象:`uart_report_packet_loop_acs()` 中上报的 **变化量 variation** > 分析对象:`uart_report_packet_loop_acs()` 中上报的 **变化量 variation**
> 涉及文件:`src/main.c`(组包)、`src/TaskLoop.c`(基线/IIR 更新)、`inc/TaskLoop.h`(参数宏) > 涉及文件:`src/main.c`(组包)、`src/TaskLoop.c`(基线/IIR 更新)、`inc/TaskLoop.h`(参数宏)
> 协议版本:与《DLD960Loop_串口通信协议.md》**V1.05** 同步(2026-07-14variation 无符号 2B → **有符号 3B**,见 §5
> 结论层次:物理层 → 信号层 → 数据层 → 上报调度层 > 结论层次:物理层 → 信号层 → 数据层 → 上报调度层
--- ---
## 0. 一句话结论 ## 0. 一句话结论
`variation = |loop_Origin loop_CAPVD|`,是 **"慢阶跃基线"** 与 **"快速跟踪当前值"** 两个不同时间尺度量的瞬时差。 `variation = loop_Origin loop_CAPVD`V1.05 起为 **3 字节有符号补码**:正 = 车辆/金属进入方向,负 = 反向漂移),是 **"慢阶跃基线"** 与 **"快速跟踪当前值"** 两个不同时间尺度量的瞬时差。
它在 **每次组包时实时计算**,本身无独立状态;真正决定其波形形态的是三个错位的时钟: 它在 **每次组包时实时计算**,本身无独立状态;真正决定其波形形态的是三个错位的时钟:
- `loop_CAPVD`**≈10ms** 采样 - `loop_CAPVD`**≈10ms** 采样
@@ -21,19 +22,24 @@
## 1. 定义位置 ## 1. 定义位置
`src/main.c``uart_report_packet_loop_acs()` 组包段: `src/main.c``uart_report_packet_loop_acs()` 组包段V1.05 现行实现)
```c ```c
// 变化量 (2B, LE) // 变化量 (3B, LE, 有符号补码) — V1.05
uint32_t variation = (unit->loop_Origin > unit->loop_CAPVD) // 定义: variation = Origin - CAPVD
? (unit->loop_Origin - unit->loop_CAPVD) // 正值 = 当前值低于基线(车辆/金属进入, 检测裕量方向)
: (unit->loop_CAPVD - unit->loop_Origin); // 负值 = 当前值高于基线(反向漂移/异常抬升)
int32_t variation = (int32_t)unit->loop_Origin - (int32_t)unit->loop_CAPVD;
if (variation > 8388607) variation = 8388607; // +2^23-1 饱和, 防回绕
if (variation < -8388608) variation = -8388608; // -2^23 饱和
g_pkg_uart_report.pkg[i++] = (uint8_t)(variation & 0xFF); g_pkg_uart_report.pkg[i++] = (uint8_t)(variation & 0xFF);
g_pkg_uart_report.pkg[i++] = (uint8_t)((variation >> 8) & 0xFF); g_pkg_uart_report.pkg[i++] = (uint8_t)((variation >> 8) & 0xFF);
g_pkg_uart_report.pkg[i++] = (uint8_t)((variation >> 16) & 0xFF);
``` ```
- `|Origin CAPVD|`,无符号,无方向信息 - **带符号带方向**:正 = 检测裕量方向(与旧版有车时的数值语义连续),负 = 反向漂移
- 仅上报低 **2 字节**(≤ 65535),高位被截断 - 3 字节小端补码,值域 ±2²³−1,发送端 **饱和限幅,不会回绕**
- 旧版(≤V1.04)为 2B 无符号绝对值,缺陷与变更动机见 **§5**
--- ---
@@ -43,7 +49,7 @@ g_pkg_uart_report.pkg[i++] = (uint8_t)((variation >> 8) & 0xFF);
|---|---|---|---|---| |---|---|---|---|---|
| `loop_CAPVD` | IIR 后当前线圈值 | 每次 `loop_CAP_OK``vd1_task_per_channel()` | **≈10ms**(固定测量窗 `LPCNT×Xn≈131072` timer counts | 快,τ≈32ms,斜率限幅 5% | | `loop_CAPVD` | IIR 后当前线圈值 | 每次 `loop_CAP_OK``vd1_task_per_channel()` | **≈10ms**(固定测量窗 `LPCNT×Xn≈131072` timer counts | 快,τ≈32ms,斜率限幅 5% |
| `loop_Origin` | 基线 | `update_moving_average()` 窗口满才写 | **阶跃式**(见 §3) | 慢,平台期恒定不动 | | `loop_Origin` | 基线 | `update_moving_average()` 窗口满才写 | **阶跃式**(见 §3) | 慢,平台期恒定不动 |
| `variation` | 上报的变化量 | 每次组包现算 | 被 **上报窗采样**:事件 150ms / 空闲 600ms | 瞬时差,2 字节 | | `variation` | 上报的变化量 | 每次组包现算 | 被 **上报窗采样**:事件 150ms / 空闲 600ms | 瞬时差,3 字节有符号(V1.05 |
> 关键点:`variation` 没有独立状态,它只是组包那一刻两值之差。 > 关键点:`variation` 没有独立状态,它只是组包那一刻两值之差。
> 它的"更新周期"实际是 **上报采样周期**150/600ms);它的"波形形态"由 Origin 的阶跃周期决定。 > 它的"更新周期"实际是 **上报采样周期**150/600ms);它的"波形形态"由 Origin 的阶跃周期决定。
@@ -107,41 +113,108 @@ variation: | | | | ← 锯齿波!
- 5s 窗口内 Origin 恒定,CAPVD 漂 → variation 线性爬升 - 5s 窗口内 Origin 恒定,CAPVD 漂 → variation 线性爬升
- 窗口边界 Origin 阶跃到近 5s 均值 → variation **突然跌回 ~0** - 窗口边界 Origin 阶跃到近 5s 均值 → variation **突然跌回 ~0**
- **锯齿幅度 ≈ 漂移速率 × 5s**;变频器干扰下漂移快,齿更高 - **锯齿幅度 ≈ 漂移速率 × 5s**;变频器干扰下漂移快,齿更高
- **V1.05 起锯齿带方向**:CAPVD 向下漂(裕量方向)锯齿为正,向上漂为负——漂移方向后台直接可见。旧版绝对值把负向锯齿翻成正向,与正向漂移完全不可区分
### 场景 2 — 车辆进入 / 驻留(设计正确 ✅) ### 场景 2 — 车辆进入 / 驻留(设计正确 ✅)
- 车进入瞬间 `ORG_CNT/SUM` 清零 + `VD_FLAG=1`Origin **完全冻结** - 车进入瞬间 `ORG_CNT/SUM` 清零 + `VD_FLAG=1`Origin **完全冻结**
- variation 快速跟随 CAPVD 拉大 → 真实反映车辆信号强度,干净无锯齿 - variation 快速跟随 CAPVD 拉大**恒为正值**→ 真实反映车辆信号强度,干净无锯齿
### 场景 3 — 大干扰但非车(半截车 / 邻道串扰 / 金属堆放) ### 场景 3 — 大干扰但非车(半截车 / 邻道串扰 / 金属堆放)
- `dev ≥ dlt_ORG×4` 触发冻结,variation 维持大值不被基线"吃掉" - `dev ≥ dlt_ORG×4` 触发冻结,variation 维持大值不被基线"吃掉"
- 偏差稳住 ±2% 达 10s 后,Origin 一次性重锁,variation 突降 → 防污染 + 慢速重学基线 - 偏差稳住 ±2% 达 10s 后,Origin 一次性重锁,variation 突降 → 防污染 + 慢速重学基线
- V1.05 起,若干扰使 CAPVD **高于**基线(如基线被污染后干扰源撤走),variation 呈 **持续负值**——这是旧版完全观测不到的形态,可作为"基线被污染"的诊断特征
--- ---
## 5. 需警惕的问题(直击要害 ## 5. V1.05 变更分析:无符号 2B → 有符号 3B2026-07-14
### 5.1 改动对照
| 项目 | 旧(≤V1.04 | 新(V1.05 |
|---|---|---|
| 计算式 | `\|Origin CAPVD\|` 取绝对值 | `Origin CAPVD` 带符号 |
| 编码 | 2 字节无符号,小端 | **3 字节有符号补码**,小端 |
| 值域 | 0~65535**溢出回绕** | ±8388607**发送端饱和限幅** |
| 方向信息 | 无 | 正 = 车/金属进入,负 = 反向漂移 |
| 每通道单元 | 11 字节 | **12 字节** |
| 帧 Len / 总长(4 路无分包) | 0x2F=47 / 52B | **0x33=51 / 56B**(仍 < `BUFF_STACK_SIZE=64`,缓冲安全) |
### 5.2 为什么必须改(旧版两个致命缺陷)
1. **回绕污染**variation 是 **CAPVD 计数域**的量(量级 ~131072 timer counts,不是 Hz)。大车 / 大金属全覆盖时偏差可超 65535,2B 无符号直接回绕成小值——单测实例:真实偏差 100000,旧版上报 **34464**。后台看到"大信号突然变小"的假象,判决裕量分析整段作废。
2. **方向丢失**:绝对值抹掉符号,分不清"车辆/金属进入(CAPVD 掉到基线下)"与"反向漂移/异常抬升(CAPVD 升到基线上)"——两者的判决意义**完全相反**。雨天漂移方向、邻道串扰极性、基线污染诊断都依赖符号。
### 5.3 符号语义(方案 B
```
variation = Origin CAPVD
> 0 : CAPVD 低于基线 → 金属进入 / 车辆压线(检测裕量方向)
与旧版语义连续:旧版有车时的数值 ≈ 新版正值,后台阈值可平移沿用
= 0 : 当前值与基线重合(基线刚追上 / 真空闲)
< 0 : CAPVD 高于基线 → 反向漂移 / 基线污染残留 / 线圈异常抬升
```
新版正值方向与检测判决方向一致,**有车时 variation 恒为正**;负值只出现在无车漂移或异常场景——这本身就是一个免费的诊断维度。
### 5.4 接收端解析:必须做 bit23 符号扩展
```c
int32_t v = b0 | (b1 << 8) | ((int32_t)b2 << 16);
if (v & 0x800000) v |= 0xFF000000; // bit23 = 1 → 负数,符号扩展
```
- 典型陷阱:按无符号 3B 拼接不扩展,**−10(F6 FF FF)会被读成 16777206**,后台曲线直接飞天
- 存储 / 传输链路上该字段类型必须是 **有符号整型**(int32 及以上),数据库建 UNSIGNED 列会写入失败或截断
### 5.5 影响范围(联动清单)
| 端 | 变更 | 风险点 |
|---|---|---|
| Loop 固件 `main.c` | `uint32_t``int32_t`,3B LE + 双向饱和 | 已完成,gcc 隔离单测通过 |
| 协议文档 | V1.05:字段表、Len 47→51、示例报文重算 | 已发布 |
| **vd960DBN** | 解析步长 **11→12**、misc 偏移右移 1B、3B 解码 + bit23 符号扩展、fast_mode 改用 `abs(variation)` | **步长死绑定**(见 5.6 |
| 后台 / 上位机 | 字段改有符号 int;阈值逻辑沿用正值方向;负值单独归类为漂移诊断量 | 无符号存储 / 不做符号扩展 |
### 5.6 兼容性风险(重点盯防)
1. **Loop 与 DBN 固件必须同版本发布**。单元步长 11↔12 死绑定,任一端用旧固件,4 通道数据整体错位。**且帧级 XOR/SUM 校验依然通过**(校验的是整帧,不感知单元边界)——这是**静默数据损坏**,不会报错,只会得到一堆"看起来合理"的错值。
2. 上位机若想兼容新旧固件,可用 **Len 字节区分**4 路无分包时 Len=0x2F(47) 为旧格式(2B 无符号),Len=0x33(51) 为新格式(3B 有符号),按帧动态选解析器。
3. 口径小注:协议文字写值域 ±8388607,代码负向饱和到 8388608(0x800000)。解析端符号扩展后无歧义,且实际物理量远到不了该量级(CAPVD 全量程才 ~1310723B 有 **~64 倍余量**),不构成问题,仅作记录。
### 5.7 对分析工作的直接增益
- **冻结场景幅度可信**:大偏差不再回绕,判决裕量、灵敏度档位标定可以直接拿 variation 定量
- **漂移方向可见**:锯齿极性 = 漂移方向,雨天 / 温漂 / 变频器干扰的方向特征可离线统计
- **基线污染可诊断**:持续负值 = 基线学高了,此前只能靠人工比对频率曲线猜
---
## 6. 需警惕的问题(直击要害)
1. **采样混叠**:用 600ms 上报窗去采一个 5s 阶跃量。后台若按单次 variation 做实时判断,会看到同一个齿被多次采到不同高度,叠加 600ms/5s 相位漂移 → 看似"忽大忽小"。 1. **采样混叠**:用 600ms 上报窗去采一个 5s 阶跃量。后台若按单次 variation 做实时判断,会看到同一个齿被多次采到不同高度,叠加 600ms/5s 相位漂移 → 看似"忽大忽小"。
**后台不能拿单次 variation 当绝对阈值**,应看趋势或直接用 MCU 已判好的 `car_state` **后台不能拿单次 variation 当绝对阈值**,应看趋势或直接用 MCU 已判好的 `car_state`
2. **2 字节截断**`variation` 仅发 2B(≤65535)。CAPVD≈131072 量级,正常 Δf/f 才百分之几,一般到不了 65535;但 **冻结场景下偏差若超 65535 会回绕** 2. ~~**2 字节截断 / 回绕**~~**已在 V1.05 解决**(3B 有符号 + 发送端饱和限幅,见 §5)。遗留动作:确认所有接收端(DBN / 后台)都已按 12B 步长 + 符号扩展升级
→ 建议加饱和:`if (variation > 0xFFFF) variation = 0xFFFF;`
3. **锯齿谷值≈0 误导"无车确认"**:若有逻辑用 `variation < 小值` 判"绝对空闲",会在每个 5s 边界被误触发。 3. **锯齿谷值≈0 误导"无车确认"**:若有逻辑用 `|variation| < 小值` 判"绝对空闲",会在每个 5s 边界被误触发。
→ 判空闲以 `car_state` / `loop_VD_FLAG` 为准,variation 仅作辅助幅度参考。 → 判空闲以 `car_state` / `loop_VD_FLAG` 为准,variation 仅作辅助幅度参考。
4. **稳定期 window=100 → 正常 500 的切换**:上电 1s 尺度基线,128 样本后切 5s 尺度。过渡期 variation 锯齿周期从 1s 突变到 5s,后台做周期特征分析要注意此拐点。 4. **稳定期 window=100 → 正常 500 的切换**:上电 1s 尺度基线,128 样本后切 5s 尺度。过渡期 variation 锯齿周期从 1s 突变到 5s,后台做周期特征分析要注意此拐点。
--- ---
## 6. 待现场验证 ## 7. 待现场验证
- `loop_CAP_OK` 的 ~10ms 为 devlog 设计值。**实际周期取决于 TMR3 时钟与线圈频率**,建议现场用示波器量相邻两次 `CAP_OK` 间隔确认。 - `loop_CAP_OK` 的 ~10ms 为 devlog 设计值。**实际周期取决于 TMR3 时钟与线圈频率**,建议现场用示波器量相邻两次 `CAP_OK` 间隔确认。
- 新旧固件混跑的错位形态(Loop 新 + DBN 旧)建议在工装上**故意复现一次**,留存错位数据样本,方便日后现场快速识别。
--- ---
## 7. 后续可选改进方向 ## 8. 后续可选改进方向
- 将 variation 改为上报 **斜率 / 趋势** 而非瞬时差,削掉锯齿混叠 - 将 variation 改为上报 **斜率 / 趋势** 而非瞬时差,削掉锯齿混叠
- 补 variation **饱和保护 + 随包上报当前状态(跟踪 / 冻结 / 有车)**,让后台区分锯齿谷与真空闲 - ~~补 variation 饱和保护~~ → V1.05 已完成
- **随包上报当前基线状态(跟踪 / 冻结 / 有车)**,让后台区分锯齿谷与真空闲、冻结大值与真车