docs(measurement): 测量节拍是区间 —— 周期累积法的窗口量化(用户提出)+ 全仓回灌

- 新文件 docs/measurement-window-quantization.md:窗长 = ⌊BASE/Xn₀⌋·Xn₁ tick(LPCNT 整数除法 + 学习后冻结)
  · 标称 BASE/f_cap(10.92/21.85ms)是【上限】,实际 = 标称 ×(BASE−r₀)/BASE × f₀/f_now
  · 站点偏差:100kHz −0.39%(PD132T 10.88ms / PD136HA 21.76ms)、60kHz −2.34%、带内最差 −6.20%(Xn₀=2049)
  · 深占压(频偏 −5%)窗再 +5.3%;"检测阈值"量级(≤0.46%)可忽略
  ·  Δf/f 口径不受影响:Value/Origin = Xn₁/Xn₀ 精确(余数完全约掉)—— 灵敏度/交付结论全部保持
  · ⚠ 新发现:loop1_LPCNT 为 INT8U(≤255),同族靠 406<Xn<2050 保证 ≤80;
    PD136HA 该窗口已注释 ⇒ Xn<129 时 8 位截断 ⇒ 异常线圈可能报"堵车"而非"故障"(建议恢复或加钳位)
- 新增 tools/window_calc.py(内置六型号参数,实跑验证文档全部数字)
- 回灌:cross-model 总表(新增脚注 + 测频路线说明 + 待补证1条)、PD132T↔PD136HA 对比报告(§0/§3.1/§3.2/§4.3)、
  两份交付对齐报告("测量窗列 = 标称上限" + 区间脚注)、filter-coefficient 专题(M1H 8.19ms 勘误 + τ 复算 + 脚注)、
  README(新增"专题文档"段 + 目录树)
- 表格校验 0 异常
This commit is contained in:
wangfq
2026-09-11 09:25:49 +08:00
parent 0c05726699
commit fc243c010c
8 changed files with 345 additions and 15 deletions
+131
View File
@@ -0,0 +1,131 @@
#!/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/BASEr0 = 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):
"""返回 dictXn、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 msvs 标称 %+.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())