☰
基于FFT的ADC动态性能指标计算:SNR、SNDR、THD、ENOB与SFDR
2026/10/21 14:22:06 网站建设 项目流程

简介:这份MATLAB计算程序面向信号处理、ADC/DAC测试与混合信号电路评估方向的学习者与工程师,用于从采集数据中提取SNR、SNDR、THD、ENOB、SFDR等关键动态性能指标。程序以读取txt数据文件为输入,经预处理、FFT频谱分析后分离信号、噪声与失真分量,再按功率比逐项计算各参数,可帮助读者理解指标定义与计算流程,并用于评估转换器精度与系统线性度。资源包共2个文件,包含1个m脚本与1个txt数据文件,压缩包约4KB,脚本负责计算逻辑,数据文件提供可运行的示例输入,便于直接对照调试。目前已有4557人学习下载,适合作为信号质量分析的入门实践与结果验证参考,也可在此基础上扩展窗函数、相干采样与杂散峰值搜索等处理环节。

1. 为什么一个 FFT 就能把 ADC 的底裤扒干净

做数据采集或者混合信号电路的人,迟早会撞上同一堵墙:芯片手册上写着 12 位、采样率 100 MSPS,可实际跑起来有效位数只有 9 位多。问题出在哪?是时钟抖动、量化噪声,还是谐波失真?光看时域波形是看不出来的,波形上那点毛刺在示波器上根本分不清是噪声还是信号。

真正能把这些指标拆开的工具,是频域分析。把一段采样数据做 FFT,噪声、谐波、杂散会各自落到不同的频点上,SNR、SNDR、THD、ENOB、SFDR 这几个指标本质上就是从同一张频谱图上量出来的不同"距离"。这份 MATLAB 计算程序解决的正是这件事:输入一段 ADC 采样数据,自动加窗、做 FFT、识别信号 bin 和諧波 bin、算出五个核心动态指标。搞懂它背后的机制,你才知道为什么同一个 ADC 换一种窗函数,ENOB 能差出 0.5 位——这不是玄学,是频谱泄漏和相干采样的数学必然。

2. 五个指标不是五个公式,而是同一张频谱的五种读法

2.1 从时域到频域:FFT 到底在算什么

ADC 的动态性能测试,核心操作是把时域采样序列 x[n] 变换到频域。MATLAB 里一行fft(x)就完事了,但这一行背后藏着几个必须搞清楚的前提。

FFT 计算的是离散傅里叶变换,输出 N 个复数点,每个点的模值代表该频点的幅度,相位代表该频点的初相。对于实数输入序列,频谱关于 N/2 对称,所以实际有用的只有前 N/2 个点(单边谱)。程序里通常取X = fft(x .* window),然后X_mag = abs(X(1:N/2+1)),再做归一化。

归一化这一步是很多人翻车的地方。如果不除以窗函数的相干增益(coherent gain),算出来的幅度和实际信号幅度对不上,后续所有指标都会偏。相干增益的定义是窗函数所有样本的平均值:

% 加窗与归一化 w = hanning(N); % 选择窗函数 xw = x .* w; % 加窗 X = fft(xw); % FFT X_mag = abs(X(1:N/2+1)); % 单边幅度谱 cg = sum(w) / N; % 相干增益 X_mag = X_mag / (N * cg); % 归一化到实际幅度

这里N是采样点数,cg是窗函数的相干增益。汉宁窗的相干增益约为 0.5,也就是说加窗后信号幅度被压了一半,不补回来就会白白损失 6 dB。这段代码之后,X_mag里每个 bin 的值才对应真实的信号幅度。

2.2 相干采样:为什么你的频谱总在"漏"

FFT 有一个隐含假设:被分析的信号在采样窗口内是周期性的。如果信号频率不是频率分辨率(fs/N)的整数倍,窗口首尾对不上,频谱就会泄漏——一个单音信号的能量会散布到相邻好几个 bin 里,看起来像噪声抬高了。

这就是相干采样的意义。相干采样要求输入信号频率满足:

f_in = (M / N) × fs

其中 M 是与 N 互质的整数,N 是采样点数,fs 是采样率。M 取质数(比如 3、5、7、11)可以保证谐波不会重叠到同一个 bin 上。程序里如果检测到非相干采样,通常会用窗函数来抑制泄漏,但窗函数本身会展宽主瓣,降低频率分辨率。

常见的实现思路是:如果用户能控制信号源,优先用相干采样,窗函数选矩形窗(相当于不加窗),此时频谱最干净;如果信号源和采样时钟不同步,那就必须加窗,汉宁窗是默认选择,主瓣宽度适中,旁瓣衰减够用。

2.3 信号 bin 与谐波 bin 的自动识别

频谱算出来之后,下一步是找到信号落在哪个 bin 上。最直接的方法是找最大峰值:

[~, sig_bin] = max(X_mag(2:end-1)); % 排除 DC 和 Nyquist bin sig_bin = sig_bin + 1; % 修正索引偏移

找到信号 bin 后,谐波 bin 就是信号 bin 的整数倍。二次谐波在2*sig_bin,三次在3*sig_bin,以此类推。但这里有个坑:如果谐波频率超过 Nyquist 频率(fs/2),它会折叠回来,实际位置是N - mod(k*sig_bin, N)。程序里必须处理这种折叠,否则会把噪声误判成谐波,或者漏掉真正的高次谐波。

num_harm = 5; % 通常取前 5 次谐波 harm_bins = zeros(1, num_harm); for k = 2:num_harm+1 hb = mod(k * sig_bin, N); if hb > N/2 hb = N - hb; % 折叠修正 end harm_bins(k-1) = hb; end

这段逻辑看起来简单,但mod和折叠修正的顺序不能反。先取模再判断是否超过 N/2,才能正确映射到单边谱的索引范围。

3. SNR、SNDR、THD、ENOB、SFDR:从频谱能量到性能数字

3.1 SNR 与 SNDR:一字之差,差在谐波算不算噪声

SNR(信噪比)的定义是信号功率与噪声功率之比,这里的"噪声"不包括谐波。SNDR(信纳比)则是信号功率与所有非信号成分(噪声 + 谐波)之比。两者的差别就在谐波那几根谱线算不算进去。

计算时,先把信号 bin 及其左右各一个 bin(共 3 个 bin)的能量归为信号功率,DC 附近的 bin 归为直流分量,谐波 bin 及其左右各一个 bin 归为谐波功率,剩下的所有 bin 归为噪声功率。这种"每个成分占 3 个 bin"的做法是为了覆盖窗函数主瓣的展宽。

% 信号功率(含主瓣展宽) sig_power = sum(X_mag(sig_bin-1:sig_bin+1).^2); % 谐波功率 harm_power = 0; for k = 1:num_harm hb = harm_bins(k); harm_power = harm_power + sum(X_mag(hb-1:hb+1).^2); end % 噪声功率 = 总功率 - 信号 - 谐波 - DC total_power = sum(X_mag(2:end-1).^2); noise_power = total_power - sig_power - harm_power; % SNR 与 SNDR SNR_dB = 10*log10(sig_power / noise_power); SNDR_dB = 10*log10(sig_power / (noise_power + harm_power));

注意total_power从索引 2 开始,跳过了 DC bin。如果不跳,直流失调会被算进噪声,SNR 会莫名其妙地差好几个 dB。这是实测数据里最常见的坑之一。

3.2 THD:谐波失真到底怎么加

THD(总谐波失真)是谐波总功率与信号功率之比,通常用百分比或 dB 表示。关键在于取几次谐波。工程上一般取前 5 次,因为更高次谐波的能量通常已经淹没在噪声底之下,再算进去只会让 THD 看起来更好,没有实际意义。

THD_percent = sqrt(harm_power / sig_power) * 100; THD_dB = 10*log10(harm_power / sig_power);

这里harm_power是前面累加的所有谐波功率之和。如果某次谐波恰好落在信号 bin 附近(比如信号频率很低,二次谐波还在低频区),需要做重叠判断,避免把信号主瓣的能量重复算进谐波。

3.3 ENOB 与 SFDR:一个看整体,一个看最差的那根刺

ENOB(有效位数)是从 SNDR 反推出来的:

ENOB = (SNDR - 1.76) / 6.02

这个公式的来源是理想 ADC 的量化噪声模型:一个 N 位 ADC 的 SNR 理论值是 6.02N + 1.76 dB。反过来,已知 SNDR 就能算出"等效"有多少位。1.76 这个常数来自量化噪声的均匀分布假设,如果实际噪声不满足这个假设,ENOB 只是一个参考值。

SFDR(无杂散动态范围)则是信号幅度与最大杂散(不包括谐波,有时也包括)的比值。它不关心噪声底有多低,只关心最差的那根谱线离信号有多远。在通信系统里,SFDR 往往比 SNR 更重要,因为一根强杂散可能直接淹没邻近信道的弱信号。

% SFDR:找最大杂散(排除信号和谐波) spur_spectrum = X_mag; spur_spectrum(sig_bin-1:sig_bin+1) = 0; for k = 1:num_harm hb = harm_bins(k); spur_spectrum(hb-1:hb+1) = 0; end max_spur = max(spur_spectrum(2:end-1)); SFDR_dB = 20*log10(max(X_mag(sig_bin-1:sig_bin+1)) / max_spur);

注意 SFDR 用的是幅度比(20log10),不是功率比(10log10),因为它是电压域的定义。这一点在写程序时如果搞混,结果会差一倍。

4. 避坑与排查:那些让 ENOB 凭空少一位的细节

4.1 现象:同一组数据,两次运行结果不一样

原因通常是 FFT 点数不固定,或者窗函数选择依赖了随机状态。有些程序会根据输入长度自动补零到 2 的幂次,补零会改变频率分辨率,但不改变实际噪声功率,导致 SNR 计算出现偏差。

解决:固定 N 为 2 的幂次(如 4096、8192、16384),并且不要补零。如果数据长度不够,截断而不是补零。补零只在需要更细的频谱插值时使用,不能用于功率计算。

4.2 现象:SNR 算出来比理论值还高

原因通常是噪声功率计算时漏掉了某些 bin,或者把谐波错误地排除在噪声之外。另一个常见原因是 DC 附近的低频噪声被跳过了,而实际电路中 1/f 噪声恰恰集中在低频。

解决:检查total_power的积分范围是否覆盖了从 bin 2 到 bin N/2 的所有点。如果 ADC 有明显的 1/f 噪声,考虑在计算 SNR 时把低频段单独标注出来,而不是直接忽略。

4.3 现象:THD 为负值或无穷大

原因通常是谐波 bin 识别错误,把噪声 bin 当成了谐波,或者信号 bin 找错了导致谐波位置全偏。如果harm_power算出来是 0,THD 的 dB 值就是负无穷。

解决:在找信号 bin 之前,先确认频谱最大值不在 DC 附近。如果 DC 失调很大,最大值可能落在 bin 1,此时需要先做去均值处理:x = x - mean(x)。另外,谐波 bin 的搜索范围要限制在sig_bin的整数倍附近,不要全局搜索。

4.4 现象:ENOB 比手册标称值低 2 位以上

原因可能是采样时钟抖动、电源噪声、或者输入信号本身就不干净。但在计算程序层面,最常见的原因是窗函数相干增益没有正确补偿,导致信号功率被低估。

解决:先用一个理想正弦波(用 MATLAB 生成)跑一遍程序,看 SNR 是否接近理论值。如果理想信号都算不对,问题一定在程序里;如果理想信号没问题,那就是硬件或数据采集的问题。

4.5 现象:SFDR 结果忽高忽低

原因通常是杂散搜索时没有排除谐波,或者谐波折叠处理有误。如果某次谐波恰好折叠到信号附近,会被误判为杂散,导致 SFDR 突然变差。

解决:在计算 SFDR 之前,打印出所有谐波 bin 的位置,人工确认一遍。对于折叠谐波,确保mod和N - hb的逻辑正确。另外,SFDR 的杂散搜索范围应该排除信号主瓣和谐波主瓣,但不要排除太多 bin,否则会漏掉真正的杂散。

5. 窗函数、相干采样与指标可信度:一个没有标准答案的取舍

写到这里,五个指标的计算逻辑已经清楚了。但真正决定这些数字可不可信的,是前面那些看起来"只是预处理"的步骤:窗函数选什么、采样是否相干、FFT 点数取多少。

矩形窗的频率分辨率最高,但泄漏最严重,只适合相干采样。汉宁窗泄漏小,但主瓣宽,频率分辨率下降。平顶窗幅度精度最高,但主瓣最宽,适合做幅度校准而不适合做噪声分析。这份 MATLAB 程序通常会提供窗函数选项,但默认值的选择本身就是一种取舍。

我自己的习惯是:如果信号源和采样时钟可以同步,强制走相干采样 + 矩形窗,这时候算出来的 ENOB 最接近真实值。如果做不到相干,那就用汉宁窗,并且在报告里注明"非相干采样,指标仅供参考"。最怕的是拿非相干采样的数据去和手册标称值硬比,然后怀疑芯片有问题——很多时候问题出在测试方法上,不是芯片上。

另一个容易被忽略的点是 FFT 点数。N 越大,频率分辨率越高,噪声功率的积分越准确,但计算量也越大。对于 12 位以上的 ADC,通常需要 N ≥ 8192 才能让 ENOB 的估计稳定在 0.1 位以内。如果 N 太小,噪声功率的统计涨落会让每次运行的结果都不一样。

这份程序的价值不在于它用了多复杂的算法,而在于它把这些取舍都固化成了可复现的代码。你可以看到每一步在做什么,也可以改参数看结果怎么变。从那以后我每次拿到一组 ADC 数据,都会先用理想正弦波验证一遍程序,再跑实测数据,最后把窗函数和采样条件一起写进报告里。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询