Files
wangfq fc243c010c 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 异常
2026-09-11 09:25:49 +08:00

132 lines
6.6 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 -*-
"""测量窗量化计算器(周期累积法)
背景:检测器不是"累加固定 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())