From 1ec4a39d92dc1251d7e9978b0cb887753b9d2a82 Mon Sep 17 00:00:00 2001 From: wangfq Date: Tue, 25 Aug 2026 08:39:37 +0800 Subject: [PATCH] =?UTF-8?q?docs(vd960Loop):=20variation=20=E2=86=94=20?= =?UTF-8?q?=CE=94L/=CE=94L-L=20=E7=89=A9=E7=90=86=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E5=88=86=E6=9E=90=20+=20=E6=8D=A2=E7=AE=97=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - variation-analysis.md 新增 §9: CAPVD ∝ √L 推导, 精确/一阶/频域三口径公式 - 灵敏度档位 → ΔL/L 触发阈值表 (SensTable/65536 ×2, DLD154Pro 交叉验证) - 电容档无关性: 33/43/66/76nF 共用同一换算表 (variation/Origin 中 C 消掉) - tools/variation_calc.py: 正向/反推/3B LE 补码解析/灵敏度对照/交互 5 模式 - devlog 置顶 2026-08-25 条目 --- vd960Loop/docs/devlog.md | 44 ++++++ vd960Loop/docs/variation-analysis.md | 109 ++++++++++++++ vd960Loop/tools/variation_calc.py | 218 +++++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 vd960Loop/tools/variation_calc.py diff --git a/vd960Loop/docs/devlog.md b/vd960Loop/docs/devlog.md index 1cc0ac5..a15457f 100644 --- a/vd960Loop/docs/devlog.md +++ b/vd960Loop/docs/devlog.md @@ -4,6 +4,50 @@ --- +## 2026-08-25 — variation ↔ ΔL/ΔL-L 物理对应分析与工具 + +### 背景 + +后台需要把上报的 `variation`(CAPVD 计数域)换算成物理电感变化量,用于标定灵敏度、分析车辆信号强度。 + +### 核心推导 + +CAPVD ∝ 周期 T = 2π√(LC),即 **CAPVD ∝ √L 而非 L**: + +``` +variation/Origin = 1 − √(1 + ΔL/L₀) 精确 +ΔL/L₀ ≈ −2 × variation/Origin 一阶(残差 <0.2%) +Δf/f ≈ variation/Origin 频域对应 +``` + +- 系数 2 来源于 √(1+x) ≈ 1+x/2 展开 +- 符号:车辆进入 ΔL<0 → variation 恒为正,与 V1.05 协议语义一致 +- **电容档无关性**:Origin 与 CAPVD 均 ∝ √C,variation/Origin 中 C 消掉 → 33/43/66/76nF 四档共用同一换算表,现场换档不换标定 + +### 灵敏度档位 → ΔL/L 触发阈值(SensTable {216,108,36,10}) + +| SENS | Δf/f 进入 (=SensTable/65536) | ΔL/L 进入 ≈ −2× | +|------|------------------------------|-----------------| +| 0 低 | 0.330% | −0.659% | +| 1 中 | 0.165% | −0.330% | +| 2 高 | 0.055% | −0.110% | +| 3 最高 | 0.015% | −0.031% | + +交叉验证:DLD154Pro 技术文档 sens_in {108,54,28,14} 标注 ΔL/L = 0.330%/0.165%/0.085%/0.043%,恰为 SensTable/65536 的 2 倍——产品线口径一致。 + +### 改动 + +| 文件 | 内容 | +|------|------| +| `docs/variation-analysis.md` | 新增 §9(物理链路/公式/档位表/电容无关性/示例/注意事项) | +| `tools/variation_calc.py` | 新增换算工具:正向(freq+cap+variation+origin→L₀,ΔL,ΔL/L)、反推、3B LE 补码解析(V1.05)、灵敏度对照、交互模式 | + +### 验证 + +工具四模式实测通过,正反算自洽(ΔL/L=0.33% ↔ variation=216 @ Origin=131072)。 + +--- + ## 2026-07-14 — variation 上报量 2B→3B 有符号 (协议 V1.05) ### 背景 diff --git a/vd960Loop/docs/variation-analysis.md b/vd960Loop/docs/variation-analysis.md index 3cfbcaf..04dacb3 100644 --- a/vd960Loop/docs/variation-analysis.md +++ b/vd960Loop/docs/variation-analysis.md @@ -218,3 +218,112 @@ if (v & 0x800000) v |= 0xFF000000; // bit23 = 1 → 负数,符号扩展 - 将 variation 改为上报 **斜率 / 趋势** 而非瞬时差,削掉锯齿混叠 - ~~补 variation 饱和保护~~ → V1.05 已完成 - **随包上报当前基线状态(跟踪 / 冻结 / 有车)**,让后台区分锯齿谷与真空闲、冻结大值与真车 + +--- + +## 9. variation ↔ ΔL / ΔL-L 物理对应(2026-08-25 补充) + +> 配套工具:`tools/variation_calc.py`(正向/反推/3B 补码解析/灵敏度对照,纯 stdlib) + +### 9.1 物理链路:CAPVD 正比于 √L,不是 L + +由 `main.c` 频率转换公式反推: + +```c +freq = sclk_freq × input_div × LPCNT / CAPVD → CAPVD ∝ 1/freq = T(周期) +``` + +LC 振荡器周期 `T = 2π√(LC)`,因此: + +``` +CAPVD = A·√(LC) (A = 系统归一化常数,含时钟/分频/MEASUREMENT_BASE=131072) +``` + +**CAPVD 与周期成正比,与 √L 成正比**——这是 variation 换算 ΔL 的关键前提(平方根关系,不是线性)。 + +### 9.2 换算公式 + +设基线电感 L₀,有车后电感 L₀+ΔL: + +``` +Origin = A·√(L₀·C) +CAPVD = Origin·√(1 + ΔL/L₀) +variation = Origin − CAPVD = Origin·[1 − √(1 + ΔL/L₀)] +``` + +| 口径 | 公式 | 适用 | +|------|------|------| +| **精确** | `ΔL/L₀ = (1 − variation/Origin)² − 1 = (variation/Origin)² − 2(variation/Origin)` | 大信号(如大车全覆盖) | +| **一阶近似** | `ΔL/L₀ ≈ −2 × variation/Origin` | 检测阈值区间,残差 <0.2% | +| 频域对应 | `Δf/f ≈ variation/Origin`(CAPVD ∝ 1/f) | 与验收标准 §3 频偏口径衔接 | + +符号语义:车辆进入 → 涡流 → ΔL<0 → CAPVD 交叉验证:DLD154Pro 技术文档 sens_in {108,54,28,14} 标注 ΔL/L = 0.330%/0.165%/0.085%/0.043%,恰为 SensTable/65536 的 2 倍——产品线已统一使用 `ΔL/L ≈ 2×SensTable/65536` 口径。 + +### 9.4 电容档位无关性(33/43/66/76nF) + +四档电容只改绝对工作频率 f₀ = 1/(2π√(L₀C)): + +| C 档 | 频率 @ L₀≈59μH | +|------|----------------| +| 33nF | ~114 kHz | +| 43nF | ~100 kHz | +| 66nF | ~81 kHz | +| 76nF | ~75 kHz | + +但 **Origin 与 CAPVD 均 ∝ √C,比值 variation/Origin 中 C 被消掉**: + +``` +variation/Origin = 1 − √(1 + ΔL/L₀) ← 与 C 无关 +``` + +**结论:四档电容共用同一套 ΔL/L 换算表,现场换档不换标定。** 这是 LPCNT = MEASUREMENT_BASE/Xn 自适应归一化的直接收益。 + +### 9.5 绝对电感与 ΔL 完整换算 + +``` +① 绝对电感: L₀ = 1/(4π²·f₀²·C) ← f₀ 用上报频率(3B),C 用当前档位电容 +② 相对变化: ΔL/L₀ ≈ −2 × variation/Origin +③ 绝对变化: ΔL = L₀ × ΔL/L₀ +``` + +示例(C=43nF, f₀=100kHz → L₀≈58.9μH;SENS=1 触发,variation=216, Origin=131072): + +``` +variation/Origin = 0.1648% +ΔL/L₀ ≈ −0.330% +ΔL ≈ −58.9μH × 0.330% ≈ −0.194 μH +``` + +### 9.6 使用注意事项(后台/上位机口径) + +1. **必须归一化**:variation 是 CAPVD 计数域(~131072 量级),直接拿绝对值当 ΔL 会差 4~5 个数量级。所有换算先除 Origin。 +2. **只有有车冻结期的 variation 干净**:有车时 Origin 冻结,variation 忠实反映信号强度,此刻换算 ΔL/L 最可信;无车时 Origin 5s 阶跃 → variation 呈锯齿,锯齿峰 = 5s 漂移量,不能当车辆信号。 +3. **基线重锁识别**:冻结超时(10s 稳定)后 Origin 强制 = CAPVD,variation 突降归零——后台应识别为"基线重锁"事件,勿当车辆离开。 +4. **文档口径 2 倍陷阱**:SensTable/65536 直接读是 Δf/f(0.33%),标成 ΔL/L 是 0.66%。验收/规格文档必须注明口径(推荐统一用 ΔL/L)。 +5. **绝对 ΔL 依赖 C 档信息**:协议帧未带 C 档位,后台需按现场配置查表,或由 f₀ 与档位映射反推(f₀ 落在哪档频率区间即哪档)。 + +### 9.7 工具用法速查 + +```bash +cd tools +python3 variation_calc.py --freq 100 --cap 43 --variation 216 --origin 131072 # 正向 +python3 variation_calc.py --freq 100 --cap 43 --dl-rel 0.33 # 反推 +python3 variation_calc.py --parse-var F6FFFF # 3B LE 补码解析 +python3 variation_calc.py --sens-table # 四档对照 +python3 variation_calc.py --interactive # 交互 +``` diff --git a/vd960Loop/tools/variation_calc.py b/vd960Loop/tools/variation_calc.py new file mode 100644 index 0000000..b70ff02 --- /dev/null +++ b/vd960Loop/tools/variation_calc.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +variation_calc.py — vd960Loop variation ↔ ΔL / ΔL-L 换算工具 + +物理关系(推导见 docs/variation-analysis.md §9): + CAPVD ∝ T = 2π·√(LC) (周期域, 归一化到 MEASUREMENT_BASE≈131072) + variation = Origin − CAPVD (V1.05 起 3B 有符号补码, 正=金属进入方向) + variation/Origin = 1 − √(1 + ΔL/L0) (精确) + ΔL/L0 ≈ −2 × variation/Origin (一阶, |ΔL/L| ≪ 1 时, 残差 <0.2%) + Δf/f ≈ variation/Origin (频域对应: CAPVD ∝ 1/f) + L0 = 1/(4π²·f0²·C) (绝对电感, f0=上报频率, C=档位电容) + +电容档位无关性: Origin 与 CAPVD 均 ∝ √C, variation/Origin 中 C 消掉, +四档电容(33/43/66/76nF)共用同一套 ΔL/L 换算表。 + +用法示例: + # 正向: 频率+电容+variation+origin → L0, ΔL, ΔL/L + python3 variation_calc.py --freq 100 --cap 43 --variation 216 --origin 131072 + # 反推: 给定 ΔL/L 看需要多大 variation + python3 variation_calc.py --freq 100 --cap 43 --dl-rel 0.33 + # 解析协议 3B LE 有符号补码 (V1.05 variation 字段) + python3 variation_calc.py --parse-var F6FFFF + # 灵敏度档位对照表 + python3 variation_calc.py --sens-table + # 交互模式 + python3 variation_calc.py --interactive +""" + +import argparse +import math +import sys + +# ---------------------------------------------------------------- 常量 +CAP_OPTIONS = (33, 43, 66, 76) # 线圈内部电容四档 (nF) +ORIGIN_DEFAULT = 131072 # MEASUREMENT_BASE = 2^17 +SENS_TABLE = {0: (216, 108), 1: (108, 72), 2: (36, 18), 3: (10, 9)} +SENS_NAMES = {0: "0(低)", 1: "1(中)", 2: "2(高)", 3: "3(最高)"} + + +# ---------------------------------------------------------------- 核心计算 +def inductance(freq_hz, cap_nf): + """绝对电感 L0 = 1/(4π²·f²·C), 返回 μH""" + c = cap_nf * 1e-9 + return 1.0 / (4.0 * math.pi * math.pi * freq_hz * freq_hz * c) * 1e6 + + +def variation_ratio(variation, origin): + """variation/Origin (有符号)""" + return variation / origin + + +def delta_ll(vr): + """ΔL/L: (精确, 一阶), 返回相对值(负=电感减小)""" + exact = vr * vr - 2.0 * vr + approx = -2.0 * vr + return exact, approx + + +def variation_from_dl_rel(dl_rel_pct, origin): + """反推: 给定 |ΔL/L|(%) → 需要的 variation (计数域)""" + r = dl_rel_pct / 100.0 # ΔL/L = −r + vr = 1.0 - math.sqrt(1.0 - r) # 精确 + return vr * origin, vr + + +def sens_hit(vr): + """当前 variation/Origin 落在哪档进入阈值 (SensTable/65536)""" + for sens in (0, 1, 2, 3): + thr = SENS_TABLE[sens][0] / 65536.0 + if abs(vr) >= thr: + return sens, thr + return None, None + + +def parse_var_3byte(hex_str): + """V1.05 协议 variation 字段: 3B LE 有符号补码 → int32 + F6FF FF → −10; 000001 → 65536 + """ + h = hex_str.strip() + if len(h) != 6: + raise ValueError("需要 6 位 hex (3 字节 LE), 如 F6FFFF") + b0 = int(h[0:2], 16) + b1 = int(h[2:4], 16) + b2 = int(h[4:6], 16) + v = b0 | (b1 << 8) | (b2 << 16) + if v & 0x800000: + v |= 0xFF000000 + if v >= 0x80000000: + v -= 0x100000000 + return v + + +def fmt_sens_table(): + lines = ["灵敏度档位 → ΔL/L 触发阈值 (vd960Loop SensTable, 一阶 ΔL/L≈−2×vr):"] + lines.append(f"{'SENS':<8}{'进入表':<8}{'离开表':<8}{'Δf/f进入':<12}{'ΔL/L进入':<12}{'ΔL/L离开'}") + for sens in (0, 1, 2, 3): + sin, sout = SENS_TABLE[sens] + vr_in = sin / 65536.0 + vr_out = sout / 65536.0 + lines.append(f"{SENS_NAMES[sens]:<8}{sin:<8}{sout:<8}" + f"{vr_in * 100:>7.4f}%{'':<4}{-2 * vr_in * 100:>8.4f}%{'':<4}{-2 * vr_out * 100:>8.4f}%") + return "\n".join(lines) + + +def forward(freq, cap, variation, origin): + """正向计算并打印""" + f_hz = freq * 1000.0 if freq < 1e6 else freq + l0 = inductance(f_hz, cap) + vr = variation_ratio(variation, origin) + exact, approx = delta_ll(vr) + dl = l0 * exact + + sens, thr = sens_hit(vr) + + out = [] + out.append("=" * 62) + out.append("正向换算: 频率+电容+variation+Origin") + out.append("=" * 62) + out.append(f" 上报频率 f0 : {f_hz / 1000.0:>10.3f} kHz") + out.append(f" 电容档 C : {cap:>10d} nF") + out.append(f" 绝对电感 L0 : {l0:>10.3f} μH (L=1/(4π²f²C))") + out.append(f" Origin : {origin:>10d} (CAPVD 基线)") + out.append(f" variation : {variation:>10d} (3B 有符号)") + out.append(f" variation/Origin : {vr * 100:>9.4f} % (≈ Δf/f)") + out.append("-" * 62) + out.append(f" ΔL/L 一阶 : {-2 * vr * 100:>9.4f} % (≈ −2×vr)") + out.append(f" ΔL/L 精确 : {exact * 100:>9.4f} % (vr²−2vr)") + out.append(f" ΔL (绝对) : {dl:>9.4f} μH") + out.append("-" * 62) + if sens is not None: + out.append(f" ≥ SENS={SENS_NAMES[sens]} 进入阈值 (vr_thr={thr * 100:.4f}%)") + else: + out.append(f" 低于全部进入阈值 (最小档 SENS=3: {10 / 65536 * 100:.4f}%)") + out.append("=" * 62) + return "\n".join(out) + + +def reverse(freq, cap, dl_rel_pct, origin): + """反推: 给定 |ΔL/L|% → variation""" + f_hz = freq * 1000.0 if freq < 1e6 else freq + l0 = inductance(f_hz, cap) + variation, vr = variation_from_dl_rel(dl_rel_pct, origin) + + out = [] + out.append("=" * 62) + out.append(f"反推: ΔL/L = −{dl_rel_pct}% 需要的 variation") + out.append("=" * 62) + out.append(f" L0 = {l0:.3f} μH (f0={f_hz / 1000:.3f}kHz, C={cap}nF)") + out.append(f" ΔL = {l0 * (-dl_rel_pct / 100.0):.4f} μH") + out.append(f" variation/Origin = {vr * 100:.4f} %") + out.append(f" variation = {variation:.0f} (Origin={origin})") + out.append(f" 一阶估算 = {origin * dl_rel_pct / 200.0:.0f} (vr≈r/2)") + out.append("=" * 62) + return "\n".join(out) + + +def interactive(): + print("vd960Loop variation 换算工具 — 交互模式 (Ctrl+C 退出)") + cap = int(input("电容档 (33/43/66/76 nF): ")) + if cap not in CAP_OPTIONS: + print(f"警告: {cap}nF 不在标准档位 {CAP_OPTIONS} 中") + freq = float(input("上报频率 (kHz, 如 100): ")) + origin = int(input(f"Origin (默认 {ORIGIN_DEFAULT}): ") or ORIGIN_DEFAULT) + while True: + try: + variation = int(input("variation (有符号, q 退出): ")) + except ValueError: + break + print() + print(forward(freq, cap, variation, origin)) + + +def main(): + p = argparse.ArgumentParser( + description="vd960Loop variation ↔ ΔL/ΔL-L 换算工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__) + p.add_argument("--freq", type=float, help="上报频率 (默认 kHz, <1e6 视为 kHz)") + p.add_argument("--cap", type=int, choices=CAP_OPTIONS, + help="电容档位 nF: 33/43/66/76") + p.add_argument("--variation", type=int, help="variation (3B 有符号, 计数域)") + p.add_argument("--origin", type=int, default=ORIGIN_DEFAULT, + help=f"Origin 基线 (默认 {ORIGIN_DEFAULT})") + p.add_argument("--dl-rel", type=float, + help="反推模式: 给定 |ΔL/L|%% → 所需 variation") + p.add_argument("--parse-var", metavar="HEX6", + help="解析 V1.05 协议 3B LE 有符号补码 (如 F6FFFF = −10)") + p.add_argument("--sens-table", action="store_true", help="打印灵敏度档位对照表") + p.add_argument("--interactive", action="store_true", help="交互模式") + args = p.parse_args() + + if args.sens_table: + print(fmt_sens_table()) + return + + if args.parse_var: + v = parse_var_3byte(args.parse_var) + print(f"3B LE 补码 {args.parse_var.upper()} → {v}") + return + + if args.interactive: + interactive() + return + + if args.dl_rel is not None: + if not args.freq or not args.cap: + p.error("反推模式需要 --freq 和 --cap") + print(reverse(args.freq, args.cap, args.dl_rel, args.origin)) + return + + if None in (args.freq, args.cap, args.variation): + p.error("需要 --freq --cap --variation (或 --dl-rel / --sens-table / --interactive)") + print(forward(args.freq, args.cap, args.variation, args.origin)) + + +if __name__ == "__main__": + sys.exit(main())