#!/usr/bin/env python3 # -*- coding: utf-8 -*- """测量窗量化计算器(周期累积法) 背景:检测器不是"累加固定 tick 数",而是"累加固定个数的周期": LPCNT = floor(BASE / Xn0) # 整数除法,学习时算一次后【冻结】 窗长 = LPCNT * Xn1 # 单位:捕获时钟 tick ← 不是恒定 BASE! 窗时长 = 窗长 / f_cap 其中 Xn0 = 学习时的周期计数、Xn1 = 当前周期计数(Xn 与线圈频率成反比)。 结论(详见 docs/measurement-window-quantization.md): * 表中 BASE/f_cap(如 10.92ms / 21.85ms)是【标称上限】,实际窗 = BASE - (BASE mod Xn0) tick * 站点偏差 = r0/BASE(r0 = BASE mod Xn0),带内 0 ~ -6.2% * 深占压(线圈频偏 -d%)时窗时长再放大 1/(1-d) * 【比值口径不受影响】:Value/Origin = Xn1/Xn0 精确(余数完全约掉) 用法: python3 window_calc.py # 打印内置型号对照表 python3 window_calc.py --model PD132T --f-coil 100k python3 window_calc.py --f-cap 3M --base 32768 --edge 2 --divider 32 --f-coil 60k 参数:--f-coil 线圈频率(Hz 或 100k/60k)、--f-cap 捕获时钟、--base 归一化基数、 --edge 捕获沿系数(双沿=2 / 单沿=1)、--divider 前端分频比(CD4060 ÷32 或直连=1) """ import argparse import sys # 内置型号参数: (BASE, f_cap, edge_factor, front_divider, note) MODELS = { "M1H": (32768, 4.0e6, 2, 32, "STC12C5202, PCA 双沿, ÷32(推断), 4MHz"), "PD132L": (32768, 1.2e6, 1, 32, "STM8S003, TIM1_CC3 下降沿, ÷32, 1.2MHz"), "PD132T": (32768, 3.0e6, 2, 32, "MA801, PCA 双沿, ÷32(Q5), 3MHz"), "PD136HA": (32768, 1.5e6, 1, 32, "HC32F003C4, ADTIM4 单沿, ÷32, 1.5MHz"), "DLD154V4B": (131072, 120e6, 1, 1, "AT32F421, TIM3 直连振荡(1 Xn = 1 原始周期), 120MHz"), "DLD154Pro": (524288, 60e6, 1, 1, "CH591R, TMR0 下降沿直连, 60MHz 实测"), } def parse_freq(text): """'100k' / '3M' / '1.5e6' / '480' -> float Hz""" t = str(text).strip() mult = 1.0 if t[-1] in "kK": mult, t = 1e3, t[:-1] elif t[-1] in "mM": mult, t = 1e6, t[:-1] return float(t) * mult def window(base, f_cap, edge, divider, f_coil, f_now=None): """返回 dict:Xn、LPCNT、窗长(tick)、窗时长(s)、偏差、口径验证""" f_now = f_coil if f_now is None else f_now C = divider * f_cap / float(edge) # 判据常数: Xn = C / f_coil xn0 = C / f_coil lpcnt = int(base // xn0) if lpcnt == 0: # 固件兜底: Xn > BASE 时 LPCNT=1 lpcnt = 1 xn_float0 = xn0 xn0 = round(xn0) # 固件里 Xn 是整数计数 xn1 = round(C / f_now) window_ticks = lpcnt * xn0 # 学习时/稳态 window_ticks_now = lpcnt * xn1 # 当前频率下 r0 = base % xn0 if xn0 <= base else base return { "C": C, "Xn0": xn0, "LPCNT": lpcnt, "r0": r0, "window_ticks": window_ticks, "nominal_ticks": base, "window_s": window_ticks / f_cap, "nominal_s": base / f_cap, "dev_pct": (window_ticks / base - 1.0) * 100.0, "window_s_now": window_ticks_now / f_cap, "dev_now_pct": (window_ticks_now / base - 1.0) * 100.0, # vs 标称上限 "dev_now_vs_learned_pct": (window_ticks_now / float(window_ticks) - 1.0) * 100.0, # vs 学习时窗 "ratio_exact": xn1 / float(xn0), # Value/Origin 应为该值 "ratio_formula": f_now and (f_coil / f_now), } def main(): ap = argparse.ArgumentParser(description="周期累积法测量窗量化计算器") ap.add_argument("--model", choices=sorted(MODELS), help="内置型号") ap.add_argument("--base", type=int, default=32768) ap.add_argument("--f-cap", default="3M") ap.add_argument("--edge", type=int, choices=(1, 2), default=2, help="捕获沿系数: 双沿=2 单沿=1") ap.add_argument("--divider", type=int, default=32, help="前端分频比 (CD4060 ÷32=32, 直连=1)") ap.add_argument("--f-coil", default="100k", help="线圈频率 (100k / 3M / 480)") ap.add_argument("--f-now", default=None, help="当前频率(默认=f-coil),用于看频率跟踪分量") a = ap.parse_args() if a.model: base, fcap, edge, div, note = MODELS[a.model] print("型号 %s: %s" % (a.model, note)) else: base, fcap, edge, div = a.base, parse_freq(a.f_cap), a.edge, a.divider f_coil = parse_freq(a.f_coil) f_now = parse_freq(a.f_now) if a.f_now else None w = window(base, fcap, edge, div, f_coil, f_now) print("参数: BASE=%d f_cap=%.6g Hz 边沿系数=%d 分频=%d 线圈=%.6g Hz" % (base, fcap, edge, div, f_coil)) print("判据常数 C = 分频·f_cap/边沿 = %.4g (Xn = C/f_coil)" % w["C"]) print("Xn0 = %.2f -> 取整 %d ; LPCNT = floor(%d/%d) = %d ; r0 = BASE mod Xn0 = %d" % (w["C"] / f_coil, w["Xn0"], base, w["Xn0"], w["LPCNT"], w["r0"])) print("窗长 = LPCNT·Xn0 = %d tick (标称上限 %d tick)" % (w["window_ticks"], w["nominal_ticks"])) print("窗时长 = %.4f ms (标称上限 %.4f ms) 偏差 %+.2f%%" % (w["window_s"] * 1e3, w["nominal_s"] * 1e3, w["dev_pct"])) if f_now and abs(f_now - f_coil) > 1e-9: print("频率跟踪: f_now=%.6g Hz -> 窗 %.4f ms(vs 标称 %+.2f%% / vs 学习时窗 %+.2f%%)" % (f_now, w["window_s_now"] * 1e3, w["dev_now_pct"], w["dev_now_vs_learned_pct"])) print(" 比值口径验证: Value/Origin = %.6f ← 应等于 f0/f_now = %.6f" % (w["ratio_exact"], f_coil / f_now)) print("口径提示: Δf/f = 表值/65536 不受窗长/ BASE 影响(余数约掉);" "量化误差上限 = Xn0/BASE = %.2f%%" % (w["Xn0"] / base * 100.0)) # 无参时打印族内对照表 if not any([a.model, a.f_now]) and a.f_coil == "100k": print("\n附:内置型号对照(同为 100kHz 线圈,前端均按各自 divider)") print("%-11s %8s %6s %5s %8s %11s %8s" % ("型号", "f_cap", "Xn0", "LPCNT", "窗tick", "窗时长", "偏差")) for name, (b, fc, e, dv, _n) in MODELS.items(): C = dv * fc / float(e) if not (1 <= C / 1e5 <= b): # 该频率不在其频段内则跳过 continue ww = window(b, fc, e, dv, 1e5) print("%-11s %7.3gM %6d %5d %8d %9.4fms %+7.2f%%" % (name, fc / 1e6, ww["Xn0"], ww["LPCNT"], ww["window_ticks"], ww["window_s"] * 1e3, ww["dev_pct"])) return 0 if __name__ == "__main__": sys.exit(main())