diff --git a/docs/variation-analysis.md b/docs/variation-analysis.md new file mode 100644 index 0000000..6c5887c --- /dev/null +++ b/docs/variation-analysis.md @@ -0,0 +1,329 @@ +# variation 上报量分析报告 + +> 分析对象:`uart_report_packet_loop_acs()` 中上报的 **变化量 variation** +> 涉及文件:`src/main.c`(组包)、`src/TaskLoop.c`(基线/IIR 更新)、`inc/TaskLoop.h`(参数宏) +> 协议版本:与《DLD960Loop_串口通信协议.md》**V1.05** 同步(2026-07-14:variation 无符号 2B → **有符号 3B**,见 §5) +> 结论层次:物理层 → 信号层 → 数据层 → 上报调度层 + +--- + +## 0. 一句话结论 + +`variation = loop_Origin − loop_CAPVD`(V1.05 起为 **3 字节有符号补码**:正 = 车辆/金属进入方向,负 = 反向漂移),是 **"慢阶跃基线"** 与 **"快速跟踪当前值"** 两个不同时间尺度量的瞬时差。 +它在 **每次组包时实时计算**,本身无独立状态;真正决定其波形形态的是三个错位的时钟: + +- `loop_CAPVD` 的 **≈10ms** 采样 +- `loop_Origin` 的 **5s 阶跃** 更新 +- 上报的 **150ms / 600ms** 采样窗 + +三者不同步 → 无车时 variation 天然呈 **锯齿波**。 + +--- + +## 1. 定义位置 + +`src/main.c` — `uart_report_packet_loop_acs()` 组包段(V1.05 现行实现): + +```c +// 变化量 (3B, LE, 有符号补码) — V1.05 +// 定义: variation = Origin - CAPVD +// 正值 = 当前值低于基线(车辆/金属进入, 检测裕量方向) +// 负值 = 当前值高于基线(反向漂移/异常抬升) +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 >> 8) & 0xFF); +g_pkg_uart_report.pkg[i++] = (uint8_t)((variation >> 16) & 0xFF); +``` + +- **带符号带方向**:正 = 检测裕量方向(与旧版有车时的数值语义连续),负 = 反向漂移 +- 3 字节小端补码,值域 ±2²³−1,发送端 **饱和限幅,不会回绕** +- 旧版(≤V1.04)为 2B 无符号绝对值,缺陷与变更动机见 **§5** + +--- + +## 2. 三个操作数的更新节奏 + +| 量 | 物理含义 | 更新触发 | 真实周期 | 特性 | +|---|---|---|---|---| +| `loop_CAPVD` | IIR 后当前线圈值 | 每次 `loop_CAP_OK` → `vd1_task_per_channel()` | **≈10ms**(固定测量窗 `LPCNT×Xn≈131072` timer counts) | 快,τ≈32ms,斜率限幅 5% | +| `loop_Origin` | 基线 | `update_moving_average()` 窗口满才写 | **阶跃式**(见 §3) | 慢,平台期恒定不动 | +| `variation` | 上报的变化量 | 每次组包现算 | 被 **上报窗采样**:事件 150ms / 空闲 600ms | 瞬时差,3 字节有符号(V1.05) | + +> 关键点:`variation` 没有独立状态,它只是组包那一刻两值之差。 +> 它的"更新周期"实际是 **上报采样周期**(150/600ms);它的"波形形态"由 Origin 的阶跃周期决定。 + +上报间隔宏(`inc/TaskLoop.h`,50ms tick): + +```c +#define REPORT_IDLE_TICKS 12 // 空闲稳定: 12×50ms = 600ms +#define REPORT_EVENT_TICKS 3 // 事件/变化: 3×50ms = 150ms +``` + +上报调度还叠加了 **上电 3s 抑制**(`misc_counter < 60` 跳过,等基线稳定)。 + +--- + +## 3. loop_Origin 基线更新的状态机(核心) + +`update_moving_average()` 是 **累加到窗口满才写一次**,中途 Origin 岿然不动: + +```c +*p_sum += new_value; (*p_cnt)++; +if (*p_cnt >= window) { // 只有这里改写 Origin + *p_origin = *p_sum / window; + *p_cnt = 0; *p_sum = 0; +} +``` + +| 状态 | 判据 | window | 阶跃周期 @10ms | 对 variation 的影响 | +|---|---|---|---|---| +| **稳定期** | `!loop_stable`,`stable_cnt < 128` | 100 | **~1s** | 上电快速收敛,`CAPVD = Value` 直通 | +| **正常跟踪** | 无车 & `dev < dlt_ORG×4` | **500** | **~5s** | Origin 每 5s 阶跃一次,锯齿主因 | +| **冻结** | 无车 & `dev ≥ dlt_ORG×4` | — | `ORG_CNT/SUM` 清零,**不更新** | Origin 锁死,variation 保持大值 | +| **冻结超时** | 冻结持续 & 漂移 <±2% 达 1000 次 | — | **~10s 一次性抓取** | Origin 直接 = CAPVD,variation **突然归零** | +| **有车** | `loop_VD_FLAG == 1` | — | 不进基线分支 | Origin 全程冻结,variation 忠实跟随 CAPVD | + +相关参数宏(`inc/TaskLoop.h`): + +```c +#define STABLE_SAMPLES 128 // 稳定期样本数 +#define WINDOW_ORIGIN 500 // 基线跟踪窗口 (500×10ms = 5s) +#define FREEZE_TIMEOUT 1000 // 冻结超时 ~10s @10ms +#define FREEZE_STABILITY_RATE 2 // 稳定性窗口 ±2% +#define MAX_SLOPE_RATE 5 // 斜率限幅 5% +#define ENTRY_CONFIRM 3 // 进入确认次数 +``` + +--- + +## 4. variation 波形特征 + +### 场景 1 — 无车缓慢漂移(温漂 / 湿漂),最典型 + +``` +CAPVD ─╱╱╱╱╱ 持续缓慢漂移(每10ms微动) +Origin ───┐ ┌───┐ ┌── 每5s阶跃追一次 + └────────┘ └────────┘ +variation: ╱| ╱| ╱| ╱| ← 锯齿波! + 峰=5s漂移量, 谷≈0(基线刚追上) +``` + +- 5s 窗口内 Origin 恒定,CAPVD 漂 → variation 线性爬升 +- 窗口边界 Origin 阶跃到近 5s 均值 → variation **突然跌回 ~0** +- **锯齿幅度 ≈ 漂移速率 × 5s**;变频器干扰下漂移快,齿更高 +- **V1.05 起锯齿带方向**:CAPVD 向下漂(裕量方向)锯齿为正,向上漂为负——漂移方向后台直接可见。旧版绝对值把负向锯齿翻成正向,与正向漂移完全不可区分 + +### 场景 2 — 车辆进入 / 驻留(设计正确 ✅) + +- 车进入瞬间 `ORG_CNT/SUM` 清零 + `VD_FLAG=1`,Origin **完全冻结** +- variation 快速跟随 CAPVD 拉大(**恒为正值**)→ 真实反映车辆信号强度,干净无锯齿 + +### 场景 3 — 大干扰但非车(半截车 / 邻道串扰 / 金属堆放) + +- `dev ≥ dlt_ORG×4` 触发冻结,variation 维持大值不被基线"吃掉" +- 偏差稳住 ±2% 达 10s 后,Origin 一次性重锁,variation 突降 → 防污染 + 慢速重学基线 +- V1.05 起,若干扰使 CAPVD **高于**基线(如基线被污染后干扰源撤走),variation 呈 **持续负值**——这是旧版完全观测不到的形态,可作为"基线被污染"的诊断特征 + +--- + +## 5. V1.05 变更分析:无符号 2B → 有符号 3B(2026-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 全量程才 ~131072,3B 有 **~64 倍余量**),不构成问题,仅作记录。 + +### 5.7 对分析工作的直接增益 + +- **冻结场景幅度可信**:大偏差不再回绕,判决裕量、灵敏度档位标定可以直接拿 variation 定量 +- **漂移方向可见**:锯齿极性 = 漂移方向,雨天 / 温漂 / 变频器干扰的方向特征可离线统计 +- **基线污染可诊断**:持续负值 = 基线学高了,此前只能靠人工比对频率曲线猜 + +--- + +## 6. 需警惕的问题(直击要害) + +1. **采样混叠**:用 600ms 上报窗去采一个 5s 阶跃量。后台若按单次 variation 做实时判断,会看到同一个齿被多次采到不同高度,叠加 600ms/5s 相位漂移 → 看似"忽大忽小"。 + → **后台不能拿单次 variation 当绝对阈值**,应看趋势或直接用 MCU 已判好的 `car_state`。 + +2. ~~**2 字节截断 / 回绕**~~ → **已在 V1.05 解决**(3B 有符号 + 发送端饱和限幅,见 §5)。**DBN 解析端已核实升级完成**(2026-07-15 代码核对:`loop_uart_proto.c` 步长 12B + bit23 符号扩展、misc 偏移 c[8..11]、fast_mode 双向绝对值;详见 vd960DBN devlog 2026-07-14)。后台仅消费 JSON(带符号十进制),无二进制解析负担。 + +3. **锯齿谷值≈0 误导"无车确认"**:若有逻辑用 `|variation| < 小值` 判"绝对空闲",会在每个 5s 边界被误触发。 + → 判空闲以 `car_state` / `loop_VD_FLAG` 为准,variation 仅作辅助幅度参考。 + +4. **稳定期 window=100 → 正常 500 的切换**:上电 1s 尺度基线,128 样本后切 5s 尺度。过渡期 variation 锯齿周期从 1s 突变到 5s,后台做周期特征分析要注意此拐点。 + +--- + +## 7. 待现场验证 + +- `loop_CAP_OK` 的 ~10ms 为 devlog 设计值。**实际周期取决于 TMR3 时钟与线圈频率**,建议现场用示波器量相邻两次 `CAP_OK` 间隔确认。 +- 新旧固件混跑的错位形态(Loop 新 + DBN 旧)建议在工装上**故意复现一次**,留存错位数据样本,方便日后现场快速识别。 + +--- + +## 8. 后续可选改进方向 + +- 将 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/memory/2026-08-25.md b/memory/2026-08-25.md new file mode 100644 index 0000000..6ce1ee1 --- /dev/null +++ b/memory/2026-08-25.md @@ -0,0 +1,23 @@ +# 2026-08-25 日志 + +## variation 上报量分析报告(归档 + 工具落位) + +- 老大上传《variation 上报量分析报告》(vd960Loop `variation = Origin − CAPVD`,V1.05 起 3B 有符号补码) +- 全文关键数字我全部独立验算通过: + - 灵敏度表 216/108/36/10 → Δf/f 0.3296%/0.1648%/0.0549%/0.0153% ✓ + - DLD154Pro 交叉验证 {108,54,28,14} → 2× 口径 0.330/0.165/0.085/0.043% 全命中,产品线 ΔL/L≈2×SensTable/65536 实锤 + - 一阶近似误差上界 <0.2%(最低档 0.165%)✓ + - 电容档 33/43/66/76nF → 114.1/99.9/80.7/75.2 kHz,比值 C 无关,换档不换标定 ✓ + - 2B 回绕案例 100000→34464 ✓;3B 补码 F6FFFF→−10(无符号读 16777206)✓ +- 分析要点(反馈给老大): + - 锯齿峰 = 漂移速率 × 5s,可白嫖做漂移速率在线估计器(不用改协议) + - 判决裕量取值要等 IIR 稳态(进车后 ≥250ms),别取进入瞬态 + - 协议文字勘误建议:±8388607 → −8388608~+8388607 + - 静默损坏加固:解析端加"单元数×步长+帧头尾==Len"校验,fail-loud + - ENTRY_CONFIRM=3 + 150ms 上报窗 → 后台见"有车"最坏延迟 ~180ms,防砸走 GPIO +- 配套工具 `variation_calc.py` 落位 `tools/`(上传文本缩进丢失,已恢复;全部示例+边界验证通过) +- 归档:`docs/variation-analysis.md`,已 git commit + push(SSH 222) +- 待现场验证:CAP_OK 实际周期(50ms tick 累加法)、新旧固件混跑错位复现、串扰 variation 极性统计 + +## 备忘 +- 工具字节序坑:3B LE 补码,+8388607 输入 FFFF7F,7FFFFF 是 −129(工具解析正确) diff --git a/tools/variation_calc.py b/tools/variation_calc.py new file mode 100755 index 0000000..e2a820f --- /dev/null +++ b/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 + F6FFFF → −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())