通俗的解读一下:当给 ADC 输入一个干净的正弦波,而且这个频率刚好是采样频率的整数倍时,会出现奇怪的频谱伪像——明明没有失真、没有噪声,却在 FFT 里看到很多“鬼影”(假的频率成分)。为了打破这个“相关性陷阱”,我们尝试往输入信号里加入一点点扰动(也叫“加抖”、“抖码”、“打散”),让每次采样点不会总是重复。这也是今天文章要解决的问题。
这个问题
其实这个问题问的不准,不如这样说:为了打破输入频率与采样频率相关性而引入的噪声(或扰动),其大小需要达到什么程度才“足以有效打散量化伪像”。
MT-001
在这里,初看不知他在叽里咕噜什么,但是仔细分析感觉是一个非常有趣的话题,我觉得可以计算一下。
说啥呢?
当 ADC 输入是一个频率与采样率成整数比的纯正弦波,如:
在这种情况下:
采样点总是落在信号的固定相位点,导致输出“周期性重复码字”,FFT 显示“完美的谐波结构”而非真实量化噪声(我们的FFT会看到很多的尖刺,其实就是谐波),这不是 ADC 真正的 SNR,而是被误导的高伪像分布。
目标是:
“打破 ADC 采样点与输入波形的固定相位关系”,从而使量化误差“变得随机”
MT-001 的经验公式如下:
扰动噪声的峰值幅度至少要达到 ±0.5 LSB,理想值为 ±1 LSB
也可以直观的看:
量化误差
1 LSB 的最小码间隔定义了量化台阶,若扰动小于 0.5 LSB,则采样值仍会集中在固定码;达到 ±1 LSB,才能有效“跨越码间界限”,使量化行为去相关。
但是这样说就太没有说服力了!接下来就使用数学的方法来研究一下。问题涉及量化误差的统计扰动模型,可以从量化误差的均值、方差、相关性分析出发,推导扰动最小要求。
假如我们有一个理想 N 位 ADC,满量程为 FS
,量化步长为:
目标:引入一个扰动噪声 δ[n],使得量化误差变成非周期性、非相关(即白噪声样态)。
当输入是:;且:
→ 输入在 M 点内重复 → 量化码重复 → 误差 e[n] 周期性,非白噪声!
引入扰动后的输入为:
量化误差为:
我们希望 e[n]
近似为 白噪声过程(均值为 0,方差为 q²/12,且无自相关)。
若扰动太小,x[n] + δ[n]
仍落在原来的同一个量化区间 → 输出不变
所以我们希望:
即:扰动足够大,使得大多数样本跨越量化边界
假设扰动 δ[n] 为均值 0、标准差 σ 的高斯白噪声:
我们希望扰动使得:
比如设 p_thresh = 0.5
,即希望有 50% 的概率扰动超过半个 LSB
查标准正态分布函数 的反函数:
当扰动噪声 RMS(标准差) σ > 0.74 × LSB 时,有超过 50% 的概率打断原有码字重复。
我以上的模型是假设:每个点平均扰动 ≈ 跨区边界,也可以使用使扰动近似平均分布(均匀扰动),数学表达是,计算出RMS = q/√3 ≈ 0.577q
分析模型 | 数学条件 |
---|---|
高斯扰动模型 | |
均匀分布扰动 | |
实际设计 | 峰值扰动 ≥ ±LSB,RMS ≥ 0.5~1 LSB |
我这里也计算了不同位数ADC的LSB:
ADC 分辨率 | 1 LSB 幅度(FS=2Vpp) | 建议扰动 RMS |
---|---|---|
8-bit | ≈ 7.8 mV | ≥ 4 mV RMS |
12-bit | ≈ 0.5 mV | ≥ 0.3 mV RMS |
16-bit | ≈ 31 µV | ≥ 20 µV RMS |
建议扰动噪声采用均匀分布或白噪声,不要使用高频干扰或谐波信号作为扰动源。
展示了量化伪像被打散的实际效果
Without Dither:量化误差在频域呈现出周期性峰值(假谐波),即采样相关性伪像
With ±1 LSB Dither:这些峰值消失,频谱噪声分布更加均匀平滑,呈现真实 ADC 量化噪声特性
明显看到主频率信号(7Hz)之外,有周期性、等距的谐波峰值。
这些不是真实谐波,而是由于:输入频率 = 采样率的整数比,从而导致 采样点重复,进而量化误差重复 ,形成假谐波,就是典型的相关性伪像现象。
主频率仍清晰存在(7Hz),原本的伪像“谐波”被打散为连续的背景噪声,频谱变得更接近理想 SNR 白噪声分布,说明扰动噪声起到了“去相关 + 去伪谐波”的作用。
扰动幅度达到 ±1 LSB(RMS ≈ 0.577 LSB)时,基本可打破相关性造成的频谱伪像
我想了一个办法,如果将这个扰动幅度逐级扫描,能不能定量评估 SNR 提升,就好像是扫频的原理。
**扰动幅度(以 LSB 为单位)** 与 **SNR(信噪比)** 之间的关系曲线
扰动幅度(LSB) | 典型现象 | SNR 变化趋势 |
---|---|---|
0 | 无扰动 → 严重相关性伪像 | SNR 被虚高(假 SFDR、假高峰) |
0.1 ~ 0.5 | 扰动不足 → 伪像仍部分存在 | SNR 波动,不稳定 |
0.7 ~ 1.0 | 打破相关性伪像、量化近似白噪声 | SNR 最接近理论值 |
>1.2 | 扰动过大,开始破坏信号本身 | SNR 下降,信号被噪声淹没 |
扰动 RMS ≈ 0.5~0.7 LSB(峰值 ±1 LSB)最合适:有效打散伪像又不会破坏信号质量
小扰动:无效打散,频谱仍伪造整洁
大扰动:信号失真,SNR 下降
加噪幅度建议在 ±1 LSB 峰值,或 ≥ 0.5 LSB RMS,这是最少可有效破除相关性的水平。
再看看扰动幅度(以 LSB 计)与 SNR、THD、SFDR、ENOB 四项关键 ADC 性能指标之间的关系:
扰动幅度(以 LSB 计)与 SNR、THD、SFDR、ENOB的关系
指标 | 随扰动幅度变化的趋势 |
---|---|
SNR | - 在 0.5~1.0 LSB 时达到最优(有效打散伪像) - 太小扰动无效,太大扰动反而引入额外噪声 |
THD | - 原始量化造成严重谐波 - 扰动提升后 THD 明显改善 |
SFDR | - 未扰动时有高伪峰值 - ±1 LSB 附近扰动可极大提升 SFDR |
ENOB | - 0~0.3 LSB 下偏离理论值(受相关性影响) - 达到 1 LSB 扰动时趋近理论 ENOB(约 7.8bit) |
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft
# 模拟参数
fs = 1000 # 采样频率 Hz
N = 1024 # FFT 点数
f_in = fs * 7 / N # 输入频率为 fs 的整数倍:造成相关性
t = np.arange(N) / fs
# ADC 模拟参数
n_bits = 8
q = 1 / (2 ** n_bits) # LSB
v_fullscale = 1.0 # 满量程 ±1V
v_in = 0.9 * np.sin(2 * np.pi * f_in * t) # 输入信号
# 加扰动前
x_no_dither = np.round(v_in / q) * q
# 加扰动后(添加 ±1 LSB 峰值均匀分布扰动)
dither = np.random.uniform(-q, q, size=N)
x_with_dither = np.round((v_in + dither) / q) * q
# FFT
def compute_fft(signal):
fft_vals = fft(signal * np.hanning(N))
fft_db = 20 * np.log10(np.abs(fft_vals[:N // 2]) / N + 1e-12)
freqs = np.linspace(0, fs / 2, N // 2)
return freqs, fft_db
freqs, fft_no_dither = compute_fft(x_no_dither)
_, fft_with_dither = compute_fft(x_with_dither)
# 绘图(图例改为英文)
plt.figure(figsize=(10, 5))
plt.plot(freqs, fft_no_dither, label="Without Dither", linewidth=1.5)
plt.plot(freqs, fft_with_dither, label="With ±1 LSB Dither", linewidth=1.5)
plt.title("FFT Comparison: Correlation Artifacts vs Dithered Signal")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude (dB)")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft
# 模拟参数
fs = 1000
N = 1024
f_in = fs * 7 / N
t = np.arange(N) / fs
# ADC 参数
n_bits = 8
q = 1 / (2 ** n_bits)
v_in = 0.9 * np.sin(2 * np.pi * f_in * t)
# 定义扰动幅度列表(单位:LSB)
dither_levels = np.linspace(0, 1.5, 16) # 0 到 1.5 LSB
snr_results = []
def calc_snr(signal, N):
fft_vals = fft(signal * np.hanning(N))
fft_mags = np.abs(fft_vals[:N // 2]) / N
signal_bin = np.argmax(fft_mags)
signal_power = fft_mags[signal_bin] ** 2
noise_power = np.sum(fft_mags ** 2) - signal_power
snr = 10 * np.log10(signal_power / noise_power + 1e-12)
return snr
for dither_amp in dither_levels:
dither = np.random.uniform(-dither_amp * q, dither_amp * q, size=N)
x_dithered = np.round((v_in + dither) / q) * q
snr = calc_snr(x_dithered, N)
snr_results.append(snr)
# 绘图
plt.figure(figsize=(8, 5))
plt.plot(dither_levels, snr_results, marker='o')
plt.title("SNR vs Dither Amplitude")
plt.xlabel("Dither Amplitude (LSB Peak)")
plt.ylabel("SNR (dB)")
plt.grid(True)
plt.tight_layout()
plt.show()
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft
# 模拟参数
fs = 1000
N = 1024
f_in = fs * 7 / N
t = np.arange(N) / fs
n_bits = 8
q = 1 / (2 ** n_bits)
v_in = 0.9 * np.sin(2 * np.pi * f_in * t)
# 扰动幅度列表
dither_levels = np.linspace(0, 1.5, 16)
results = {"Dither": [], "SNR": [], "THD": [], "SFDR": [], "ENOB": []}
def spectral_metrics(signal, N):
win = np.hanning(N)
signal_win = signal * win
fft_vals = fft(signal_win)
fft_mags = np.abs(fft_vals[:N // 2]) / (np.sum(win) / 2)
fft_db = 20 * np.log10(fft_mags + 1e-20)
signal_bin = np.argmax(fft_mags)
signal_power = fft_mags[signal_bin] ** 2
# 排除主频后计算 THD
harmonics = []
for h in range(2, 6): # 2nd~5th harmonic
bin_idx = h * signal_bin
if bin_idx < len(fft_mags):
harmonics.append(fft_mags[bin_idx] ** 2)
thd = 10 * np.log10(sum(harmonics) / signal_power + 1e-20)
# SFDR
spurs = np.copy(fft_mags)
spurs[signal_bin] = 0
sfdr = 20 * np.log10(fft_mags[signal_bin] / (np.max(spurs) + 1e-20))
# SNR(不含谐波,只含背景)
total_power = np.sum(fft_mags ** 2)
noise_power = total_power - signal_power - sum(harmonics)
snr = 10 * np.log10(signal_power / noise_power + 1e-20)
# ENOB
enob = (snr - 1.76) / 6.02
return snr, thd, sfdr, enob
# 模拟各扰动强度下的性能指标
for d_amp in dither_levels:
dither = np.random.uniform(-d_amp * q, d_amp * q, size=N)
x_dithered = np.round((v_in + dither) / q) * q
snr, thd, sfdr, enob = spectral_metrics(x_dithered, N)
results["Dither"].append(d_amp)
results["SNR"].append(snr)
results["THD"].append(thd)
results["SFDR"].append(sfdr)
results["ENOB"].append(enob)
# 绘图
plt.figure(figsize=(12, 8))
plt.subplot(2, 2, 1)
plt.plot(results["Dither"], results["SNR"], marker='o')
plt.title("SNR vs Dither Amplitude")
plt.xlabel("Dither Amplitude (LSB)")
plt.ylabel("SNR (dB)")
plt.grid(True)
plt.subplot(2, 2, 2)
plt.plot(results["Dither"], results["THD"], marker='o')
plt.title("THD vs Dither Amplitude")
plt.xlabel("Dither Amplitude (LSB)")
plt.ylabel("THD (dB)")
plt.grid(True)
plt.subplot(2, 2, 3)
plt.plot(results["Dither"], results["SFDR"], marker='o')
plt.title("SFDR vs Dither Amplitude")
plt.xlabel("Dither Amplitude (LSB)")
plt.ylabel("SFDR (dB)")
plt.grid(True)
plt.subplot(2, 2, 4)
plt.plot(results["Dither"], results["ENOB"], marker='o')
plt.title("ENOB vs Dither Amplitude")
plt.xlabel("Dither Amplitude (LSB)")
plt.ylabel("ENOB (bits)")
plt.grid(True)
plt.tight_layout()
plt.show()