Files
wangfq e9a19b40d3 docs(vd960Loop): 9级制灵敏度表设计 (0.01%~5.00% ΔL/L, 等比公比2.198)
- variation-analysis.md §9.3.1: 9级 sens_in/sens_out 表 (0=最低, 8=最高)
  - sens_in {1638,745,339,154,70,32,15,7,3} 端点精确 0.01%/5.00%
  - sens_out 滞回渐变 50%→65%→90%/100% (高档防不释放)
  - 4级→9级迁移映射 0→3,1→4,2→5,3→7
  - 工程警示: SENS=8 贴噪声底, SENS=0 只认重型车
- tools/variation_calc.py: --sens-table 9 支持 9级显示 (4级默认兼容)
- devlog 2026-08-25 条目追加 9级制设计小节
代码落地 (set_factory_param/sens_level_from_config) 待 9级立项
2026-08-25 15:02:15 +08:00

229 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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_TABLE_9 = { # 9级制设计稿 (0=最低, 8=最高, 0.01%~5.00% ΔL/L)
0: (1638, 819), 1: (745, 373), 2: (339, 170), 3: (154, 100), 4: (70, 46),
5: (32, 21), 6: (15, 14), 7: (7, 6), 8: (3, 3),
}
SENS_NAMES = {0: "0(低)", 1: "1(中)", 2: "2(高)", 3: "3(最高)"}
SENS_NAMES_9 = {0: "0(最低)", 1: "1", 2: "2", 3: "3", 4: "4",
5: "5", 6: "6", 7: "7", 8: "8(最高)"}
# ---------------------------------------------------------------- 核心计算
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(sens_count=4):
"""打印灵敏度档位对照表 (4级或9级)"""
table = SENS_TABLE if sens_count == 4 else SENS_TABLE_9
names = SENS_NAMES if sens_count == 4 else SENS_NAMES_9
lines = [f"灵敏度档位 → ΔL/L 触发阈值 ({sens_count}级制, 一阶 ΔL/L≈−2×vr):"]
lines.append(f"{'SENS':<10}{'进入表':<8}{'离开表':<8}{'Δf/f进入':<12}{'ΔL/L进入':<12}{'ΔL/L离开'}")
for sens in sorted(table):
sin, sout = table[sens]
vr_in = sin / 65536.0
vr_out = sout / 65536.0
lines.append(f"{names[sens]:<10}{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", nargs="?", const="4", type=int, choices=[4, 9],
help="打印灵敏度档位对照表 (4=4级制默认, 9=9级制设计稿)")
p.add_argument("--interactive", action="store_true", help="交互模式")
args = p.parse_args()
if args.sens_table:
print(fmt_sens_table(args.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())