发布日期:2026-02-05 13:22 点击次数:138
之前写了比较多的多位表的内容,但是相关的建模内容很少,今天就写一些。
图片
K2000什么叫“位”和“半位”(核心定义)“位(digit)”不是小数位,而是“显示计数位”在万用表里:
1 位 = 1 个完整的十进制数字(0–9)
能完整显示 0~9,称为 1 位
能显示 0 或 1(有时到 2),称为 半位(½ digit)
六位半 = 6 个完整数字 + 1 个“最高位受限数字”6½ 位显示能力 = 1999999 counts(≈ 2,000,000 counts)
类型最大显示对应 counts5½ 位199999200k6½ 位19999992M7½ 位1999999920M重点:
“位数”描述的是 ADC 输出+显示系统的动态范围,不是精度本身。
6½ 位到底“能分辨多细”?(分辨率)我们以最典型的 10 V 档 为例:
分辨率公式也就是说:
显示:10.000000 V
最小显示变化 = 5 µV
实际常标称为 1 µV 级分辨率
注意:
这是“分辨率”,不是“误差”
分辨率 ≠ 精度 ≠ 准确度很多人第一次都会混淆这三件事。
三个概念对比名称看到的决定因素分辨率最小跳变位数 / counts精度(Accuracy)离真值多远校准、漂移、线性重复性抖不抖噪声、积分时间一个典型 6½ 位表的现实指标以 Keithley 2000 为例:
分辨率:1 µV
一年 DCV 精度:≈ 30–50 ppm
噪声(NPLC=1):~3–5 µV RMS
噪声(NPLC=10):~0.7–1 µV RMS
所以:
能“显示”1 µV,不代表“测得准”1 µV
6½ 位是怎么“测出来”的?(ADC 架构)几乎所有台式 6½ 位表都用:积分型 ADC(双积分,多斜率积分(MSI))
核心思想用时间换精度,用积分抑制噪声
把输入电压 → 转换成 积分时间,里面的参考源、电容、运放参数误差会被抵消,这样就对 50 / 60 Hz 干扰天然免疫
这也是为什么会看到一个参数叫:
NPLC(Number of Power Line Cycles)NPLC积分时间特性0.12 ms快,噪声大120 ms标准10200 ms低噪声1002 s极限稳定结论:
6½ 位不是“瞬时精度”,而是“积分后的精度”
6½ 位 vs ADC ENOB很多做 ADC 的人会问:
“6½ 位 ≈ 几位 ADC?”
粗略等效:
但注意:这是 直流 + 积分后的等效和高速 ADC 的 ENOB 不可直接类比,因为它追求的是 低频、直流、长期稳定。
用数学模型推导 6½ 位积分 ADC 的噪声极限用数学模型把“6½ 位积分 ADC(以双积分/多斜率积分为代表)能做到的噪声极限”推出来。前置假设是:
把积分型 ADC 看成一个线性滤波器 + 采样/比值测量器用滤波器的 ENBW(等效噪声带宽)把各种噪声源折算到输出把输出噪声换算成 counts,看它能不能支撑 6½ 位(2,000,000 counts)建模对象与符号以最典型的 双积分(dual-slope)为主(多斜率只是把“回零阶段”做得更精密,噪声推法类似):
输入电压:
参考电压:
积分时间(run-up):
回零时间(run-down):
积分器输出:
积分器输入等效噪声:
理想双积分的测量式(忽略误差项):
所以:输出读数本质上是“时间比值”。噪声会让这个比值抖动。
关键一步:双积分对输入噪声的“滤波器等效”把积分过程写成线性算子。积分器(电容
在 run-up 结束时(
run-down 阶段用
整理得到:
因此估计的输入电压:
这给了一个非常重要的结论:
双积分对“输入等效电压噪声”就是做了两段矩形窗的积分,再除以
为了得到噪声 RMS,我们把上式看成线性滤波器输出:
其中权函数(简单近似
也就是“长度
设输入等效噪声是白噪声,单边谱密度(单位 V/√Hz)为
定理: 对白噪声,通过线性滤波器,输出方差
其中 ENBW 是滤波器等效噪声带宽。
矩形平均器的 ENBW长度
其 ENBW 为:
这个结果非常经典:平均越久,带宽越窄,噪声越低。
这里双积分近似等效平均长度
所以输出 RMS 噪声极限(白噪声):
这就是我们要的“噪声极限公式”之一:
积分时间
台式 DMM 常设:
例如 50 Hz 下:1 NPLC = 20 ms;60 Hz 下:1 NPLC ≈ 16.67 ms。
代入上式:
这条特别好用:
NPLC 越大,噪声按
6½ 位约等于 2,000,000 counts。对某个量程
要让显示“稳定到最后几位”,经验上希望:
换成对噪声密度的要求:
如果以“1 LSB”为目标:
这给了一个非常清晰的工程含义:
想撑住 6½ 位,输入等效噪声密度
上面白噪声很好推,但真实 DMM 在长积分时,常被 1/f 噪声、漂移、温漂、热电势限制。
典型 1/f 噪声模型:
输出方差:
对“平均器”
白噪声随着
因为结果是比值
工程上常用一个近似:参考噪声在 run-down 的“有效平均”后,以比例形式进入:
其中
所以在大信号测量时,参考噪声会决定“有效位数”的上限;在小信号时,前端噪声/热电势更关键。
一个可直接用的“噪声预算”总公式把主要噪声项近似平方和(RMS):
然后换算到 counts:
判据(经验性的看):
在“白噪声主导、双积分、
这就是“积分 ADC 的噪声极限标尺”。
继续推导: 6½ 位的门槛线先用一组“典型台式 DMM 前端噪声密度”的假设值,做一张从 0.1 到 100 NPLC 的极限曲线,把 6½ 位的门槛线标出来。
的用一组“典型台式 DMM 前端输入等效白噪声密度”的假设值,画出了 0.1–100 NPLC 的双积分(dual-slope)白噪声极限曲线,并把 6½ 位门槛线标出来了(以 10 V 量程、2,000,000 counts 为例:1 LSB = 5 µV)。
你在下面看到两张图:
σV(伏特 RMS) vs NPLC:并标出 1 LSB = 5 µV、0.3 LSB ≈ 1.5 µV 两条水平线
σ(counts RMS) vs NPLC:更直观地看“最后一位稳不稳”(1 count、0.3 count 门槛)
用的数学模型对双积分在“白噪声主导、
并用 50 Hz 作为电网频率(60 Hz 时整体会略变差一点点,因为
6½ 位在 10 V 档:
图片
图片
image-20260102130747411Assumptions: 50 Hz line, 10 V range, 6½-digit => 1 LSB = 5.00 µVNPLC points: [ 0.1 1. 10. 100. ]10 nV/√Hz (very good) NPLC= 0.1: σ_V= 0.112 µV, σ= 0.022 counts NPLC= 1.0: σ_V= 0.035 µV, σ= 0.007 counts NPLC= 10.0: σ_V= 0.011 µV, σ= 0.002 counts NPLC=100.0: σ_V= 0.004 µV, σ= 0.001 counts30 nV/√Hz (good) NPLC= 0.1: σ_V= 0.335 µV, σ= 0.067 counts NPLC= 1.0: σ_V= 0.106 µV, σ= 0.021 counts NPLC= 10.0: σ_V= 0.034 µV, σ= 0.007 counts NPLC=100.0: σ_V= 0.011 µV, σ= 0.002 counts100 nV/√Hz (okay) NPLC= 0.1: σ_V= 1.118 µV, σ= 0.224 counts NPLC= 1.0: σ_V= 0.354 µV, σ= 0.071 counts NPLC= 10.0: σ_V= 0.112 µV, σ= 0.022 counts NPLC=100.0: σ_V= 0.035 µV, σ= 0.007 counts仅从“白噪声极限”看:6½ 位门槛其实很宽松
会发现,即便假设输入等效白噪声密度到 100 nV/√Hz(我标成“okay”),在 50 Hz 下:
NPLC=0.1:σ ≈ 0.224 counts RMS
NPLC=1:σ ≈ 0.071 counts RMS
NPLC=10:σ ≈ 0.022 counts RMS
也就是说:白噪声并不会卡在 6½ 位,它远低于 1 count 的门槛。
这对应一个现实经验:
真实 6½ 位表的“最后几位跳动”,更多来自 1/f 噪声、漂移、热电势、开关注入、参考源低频噪声、温漂与机械/接触噪声,而不是白噪声。
6½ 位“能不能钉死最后一位”的主导不是 NPLC,而是低频误差机制所以真实机器上常见到:
NPLC 从 1 拉到 10:明显更稳
从 10 拉到 100:改善变小,甚至出现慢漂、慢跳(因为 1/f 与热漂移开始主导)
继续完善加入:
1/f 噪声项:
热电势/漂移项:可用随机游走或线性漂移 + 温度噪声模型叠加
图片
image-20260102131101936图片
image-20260102131107344Assumed parameters: Line freq: 50.0 Hz, Ti = NPLC/f_line 10 V range, 6½-digit => 1 LSB = 5.00 µV 1/f low cutoff via auto-zero timescale T_az = 5.0 s (f_L=0.200 Hz)Component breakdown for '30 nV/√Hz (good)': en=30.0 nV/√Hz, f_c=1.0 Hz, k_rw=0.15 µV/√s NPLC= 0.1: white= 0.335 µV, 1/f= 0.080 µV, drift= 0.200 µV => total= 0.399 µV (0.080 counts) NPLC= 1.0: white= 0.106 µV, 1/f= 0.066 µV, drift= 0.201 µV => total= 0.237 µV (0.047 counts) NPLC= 10.0: white= 0.034 µV, 1/f= 0.048 µV, drift= 0.211 µV => total= 0.219 µV (0.044 counts) NPLC=100.0: white= 0.011 µV, 1/f= 0.014 µV, drift= 0.292 µV => total= 0.292 µV (0.058 counts)
把 1/f 噪声 和 热电势/漂移(低频游走) 两个机制都加到模型里,并重新画了两张“极限曲线”:
总 RMS 噪声 σV vs NPLC(0.1–100),并标出 6½ 位门槛线(10 V 档:1 LSB = 5 µV、0.3 LSB = 1.5 µV)
同一张图换算成 counts(更直观判断最后一位抖不抖)
会明显看到:曲线不再是一直往下掉,而是出现了“U 型 / 拐点”——这正是现实 DMM 的典型现象: 短积分时白噪声主导,积分变长后 1/f 与漂移开始主导,继续变慢收益变小甚至变差。
加的两个项:数学形式白噪声项(之前那条)设 1/f 噪声 PSD:
上限
下限
得到:
我假设:
用随机游走系数
其中
以我给的“good 级别”假设(30 nV/√Hz,
NPLC=0.1:总噪声 ≈ 0.399 µV(0.080 counts)
NPLC=1:总噪声 ≈ 0.237 µV(0.047 counts)
NPLC=10:总噪声 ≈ 0.219 µV(0.044 counts)
NPLC=100:总噪声 ≈ 0.292 µV(0.058 counts)
能看到:从 1 到 10 提升不大;到 100 反而变差一点点(漂移项开始抬头)。
同时也能看到另一个事实:即便加入 1/f + 漂移,在 10 V 档“1 LSB=5 µV”的 6½ 位门槛仍然很宽松(远低于 1 count RMS)。
这也符合现实:6½ 位表在 10 V 档通常“最后几位”更多是显示滤波策略、短时抖动、以及环境热电势导致的慢漂,而不是白噪声限制。
后记我虽然不做这么高级的表,但是我会算,,,
import numpy as npimport matplotlib.pyplot as plt# -----------------------------# Model: Dual-slope DMM noise vs NPLC# Add: white noise + 1/f noise + thermal EMF / drift (random walk)# -----------------------------f_line = 50.0 # Hznplc = np.logspace(-1, 2, 500) # 0.1..100T = nplc / f_line # integration time (seconds), using Ti = NPLC / f_line# Range and counts for "6½-digit @10V"V_FS = 10.0counts = 2_000_000V_LSB = V_FS / counts # 5 µV# --- White noise (input-referred) assumptions (V/sqrt(Hz)) ---en_list = [ ("10 nV/√Hz (very good)", 10e-9), ("30 nV/√Hz (good)", 30e-9), ("100 nV/√Hz (okay)", 100e-9),]# Dual-slope white-noise limit (approx): sigma_w = e_n /(2*sqrt(Ti))def sigma_white(en, Ti): return en / (2.0 * np.sqrt(Ti))# --- 1/f noise model ---# Define 1/f corner where 1/f equals white at f_c:# sqrt(S_1f(f)) = en * sqrt(f_c/f) (V/sqrt(Hz))# so PSD: S_1f(f) = en^2 * f_c / f (V^2/Hz)## For an averaging-like transfer function, low-f |H(f)|~1 up to ~f_H ~ 1/(2Ti) (order),# and the effective lower bound f_L set by auto-zero / reversal / calibration cadence.# We'll model f_L as 1 / T_az, with a plausible T_az = 1..10 s for DMM-style zero/offset tracking.T_az = 5.0 # seconds (assumed effective low-frequency "reset" / autozero timescale)f_L = 1.0 / T_az# Use f_H ~ 1/(2Ti) (captures that longer integration narrows passband)def sigma_1f(en, f_c, Ti): f_H = 1.0 / (2.0 * Ti) # ensure f_H >= f_L to avoid negative log; if not, clamp -> log=0 meaning 1/f largely averaged out below f_L ratio = np.maximum(f_H / f_L, 1.0) var = (en**2 * f_c) * np.log(ratio) return np.sqrt(var)# Pick plausible 1/f corners for each "quality" level (order-of-magnitude)# Better front-end has lower corner (less 1/f) and/or uses chopper/AZ.f_c_map = { "10 nV/√Hz (very good)": 0.2, # Hz "30 nV/√Hz (good)": 1.0, # Hz "100 nV/√Hz (okay)": 5.0, # Hz}# --- Thermal EMF / drift model ---# Thermal EMF + contact noise often behaves like low-frequency wander.# Simple model: random walk of offset with coefficient k_rw (V/sqrt(s)),# and the "measurement" averages over Ti, so RMS contribution scales ~ k_rw * sqrt(T_obs) / sqrt(N_eff).# A pragmatic way: treat it as sigma_drift = k_rw * sqrt(Ti) (grows with time)# plus a small "floor" (e.g., due to micro-thermals that don't average).k_rw_map = { "10 nV/√Hz (very good)": 0.05e-6, # 0.05 µV/sqrt(s) "30 nV/√Hz (good)": 0.15e-6, # 0.15 µV/sqrt(s) "100 nV/√Hz (okay)": 0.40e-6, # 0.40 µV/sqrt(s)}sigma_floor = 0.2e-6 # 0.2 µV RMS "micro-thermal" floordef sigma_drift(k_rw, Ti): return np.sqrt((k_rw**2) * Ti + sigma_floor**2)# --- Compute and plot ---plt.figure(figsize=(8.4, 5.4))for label, en in en_list: f_c = f_c_map[label] k_rw = k_rw_map[label] s_w = sigma_white(en, T) s_1f = sigma_1f(en, f_c, T) s_d = sigma_drift(k_rw, T) s_tot = np.sqrt(s_w**2 + s_1f**2 + s_d**2) plt.loglog(nplc, s_tot, label=f"{label} (fc={f_c} Hz, k_rw={k_rw*1e6:.2f} µV/√s)")# 6½-digit thresholds on 10V rangeplt.axhline(V_LSB, linestyle="--", linewidth=1, label="6½-digit @10V: 1 LSB = 5 µV")plt.axhline(0.3*V_LSB, linestyle=":", linewidth=1, label="0.3 LSB ≈ 1.5 µV")plt.xlabel("NPLC")plt.ylabel("Total RMS noise σ_V (V)")plt.title("Dual-slope DMM noise vs NPLC: white + 1/f + thermal EMF/drift (assumptions)")plt.grid(True, which="both")plt.legend(fontsize=8)plt.tight_layout()plt.show()# counts plotplt.figure(figsize=(8.4, 5.4))for label, en in en_list: f_c = f_c_map[label] k_rw = k_rw_map[label] s_w = sigma_white(en, T) s_1f = sigma_1f(en, f_c, T) s_d = sigma_drift(k_rw, T) s_tot = np.sqrt(s_w**2 + s_1f**2 + s_d**2) plt.loglog(nplc, s_tot / V_LSB, label=f"{label}")plt.axhline(1.0, linestyle="--", linewidth=1, label="1 count RMS")plt.axhline(0.3, linestyle=":", linewidth=1, label="0.3 count RMS")plt.xlabel("NPLC")plt.ylabel("RMS noise σ (counts)")plt.title("Same in counts (10 V range, 6½-digit => 1 count = 5 µV)")plt.grid(True, which="both")plt.legend()plt.tight_layout()plt.show()# Show component breakdown for "good" case at representative NPLClabel_sel = "30 nV/√Hz (good)"en_sel = dict(en_list)[label_sel]f_c_sel = f_c_map[label_sel]k_rw_sel = k_rw_map[label_sel]points = np.array([0.1, 1, 10, 100.0])Ti_p = points / f_lines_w = sigma_white(en_sel, Ti_p)s_1f = sigma_1f(en_sel, f_c_sel, Ti_p)s_d = sigma_drift(k_rw_sel, Ti_p)s_tot = np.sqrt(s_w**2 + s_1f**2 + s_d**2)print("Assumed parameters:")print(f" Line freq: {f_line} Hz, Ti = NPLC/f_line")print(f" 10 V range, 6½-digit => 1 LSB = {V_LSB*1e6:.2f} µV")print(f" 1/f low cutoff via auto-zero timescale T_az = {T_az} s (f_L={f_L:.3f} Hz)")print(f"\nComponent breakdown for '{label_sel}': en={en_sel*1e9:.1f} nV/√Hz, f_c={f_c_sel} Hz, k_rw={k_rw_sel*1e6:.2f} µV/√s")for n, w, onef, d, tot in zip(points, s_w, s_1f, s_d, s_tot): print(f" NPLC={n:>5}: white={w*1e6:>6.3f} µV, 1/f={onef*1e6:>6.3f} µV, drift={d*1e6:>6.3f} µV => total={tot*1e6:>6.3f} µV ({tot/V_LSB:>5.3f} counts)")import numpy as npimport matplotlib.pyplot as plt# 支持中文 + 正确显示负号# import matplotlib.pyplot as plt# 设置支持更多符号的字体plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'SimHei'] # fallback 到 SimHei 显示中文plt.rcParams['axes.unicode_minus'] = False # 避免 U+2212 减号问题# Assumptions (typical bench DMM front-end, order-of-magnitude)f_line_50 = 50.0 # Hzf_line_60 = 60.0 # Hz# NPLC sweepnplc = np.logspace(-1, 2, 400) # 0.1 ... 100# Typical input-referred white-noise density assumptions (V/sqrt(Hz))en_list = [ ("10 nV/√Hz (very good)", 10e-9), ("30 nV/√Hz (good)", 30e-9), ("100 nV/√Hz (okay)", 100e-9),]# Dual-slope white-noise limit: sigma_V ≈ (e_n/2)*sqrt(f_line/NPLC)def sigma_v(en, f_line, nplc): return (en / 2.0) * np.sqrt(f_line / nplc)# 6½-digit threshold on 10 V range: 2,000,000 counts -> 1 LSB = 10V/2e6 = 5 µVV_FS = 10.0counts = 2_000_000V_LSB = V_FS / counts # voltsV_1LSB = V_LSBV_0p3LSB = 0.3 * V_LSB# --- Plot 1: sigma_V vs NPLC (50 Hz) with 6½-digit thresholds ---plt.figure(figsize=(8, 5))for label, en in en_list: plt.loglog(nplc, sigma_v(en, f_line_50, nplc), label=label)plt.axhline(V_1LSB, linestyle="--", linewidth=1, label="6½-digit @10V: 1 LSB = 5 µV")plt.axhline(V_0p3LSB, linestyle=":", linewidth=1, label="0.3 LSB ≈ 1.5 µV")plt.xlabel("NPLC (integration time in line cycles)")plt.ylabel("RMS output noise floor σ_V (V)")plt.title("Dual-slope white-noise limit vs NPLC (assumed input-referred white noise, 50 Hz line)")plt.grid(True, which="both")plt.legend()plt.tight_layout()plt.show()# --- Plot 2: same but in counts (50 Hz) ---plt.figure(figsize=(8, 5))for label, en in en_list: sig_counts = sigma_v(en, f_line_50, nplc) / V_LSB plt.loglog(nplc, sig_counts, label=label)plt.axhline(1.0, linestyle="--", linewidth=1, label="1 count RMS (≈ 1 LSB)")plt.axhline(0.3, linestyle=":", linewidth=1, label="0.3 count RMS")plt.xlabel("NPLC (integration time in line cycles)")plt.ylabel("RMS noise σ (counts)")plt.title("Same plot in counts (6½-digit on 10 V range means 1 count = 5 µV), 50 Hz line")plt.grid(True, which="both")plt.legend()plt.tight_layout()plt.show()# Quick numeric table at a few NPLC pointstest_nplc = np.array([0.1, 1, 10, 100.0])rows = []for label, en in en_list: sig_uV = sigma_v(en, f_line_50, test_nplc) * 1e6 sig_counts = sigma_v(en, f_line_50, test_nplc) / V_LSB rows.append((label, sig_uV, sig_counts))print(f"Assumptions: 50 Hz line, 10 V range, 6½-digit => 1 LSB = {V_LSB*1e6:.2f} µV")print("NPLC points:", test_nplc)for label, sig_uV, sig_counts in rows: print(f"\n{label}") for n, u, c in zip(test_nplc, sig_uV, sig_counts): print(f" NPLC={n:>5}: σ_V={u:>8.3f} µV, σ={c:>8.3f} counts")
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报。
上一篇:三桶油,不姓「油」
下一篇:没有了