
简介面向信号处理、ADC/数模混合测试与电子工程学习者的MATLAB参数评估脚本围绕SNR、SNDR、THD、ENOB与SFDR五项指标展开可用于读取采样数据、进行FFT频谱分析并计算信噪比、谐波失真、有效位数与无杂散动态范围适合课程实验、系统性能验证和结果复核。压缩包仅4KB共2个文件包含1个m脚本与1个txt数据文件脚本体量轻便便于直接放入MATLAB路径测试文本文件可作为示例采样输入。已有4558人学习下载说明其在相关指标计算场景中具有一定参考价值。读者可据此理解从时域数据到频域指标的完整计算流程并借助脚本核对各参数定义、频谱峰值提取与功率比计算思路减少自行搭建分析程序时的重复劳动。1. 从一段 8192 点码流开始五个动态指标在一份 MATLAB 程序里怎么落地一段 8192 点的 ADC 输出码流摆在面前很多人的第一反应是 fft 一画、频谱一瞅凭肉眼估个 SNR 就交差。真到要出测试报告这套目测法立刻露馅同一段数据两个人能差出 6 dBENOB 更是各说各话。这套 SNR / SNDR / THD / ENOB / SFDR 的 MATLAB 计算程序干的就是把这件事从「看图说话」变成「公式可追溯」——去直流、加窗、归一化、归并基波与谐波 bin、扣掉 ENBW 偏差最后吐出五个能直接写进表格的 dB 值。它适合做数据转换器评估的工程师、做 ADC 行为级仿真的同学以及要给流片结果出动态性能报表的人。下面按「定义怎么对 → 代码怎么写 → 参数怎么定 → 哪里会翻车」的顺序走一遍改几行就能跑自己的数据。2. 指标定义与频谱前处理为什么相干采样比窗函数更值得先做对2.1 五个指标的定义式与相互换算先把符号统一。设基波功率为 Ps除基波外的噪声功率为 Pn不含谐波谐波总功率为 Pd带内最大杂散功率为 Pspur。五个指标全部由这四个量派生没有第五个独立变量。指标定义式单位关键约定SNR10·log10(Ps / Pn)dB分母不含谐波SNDR / SINAD10·log10(Ps / (Pn Pd))dB分母含谐波也叫 SINADTHD10·log10(Pd / Ps)dB结果是负值报告里常写成 -THDENOB(SNDR − 1.76) / 6.02bit必须用 SNDR不能用 SNRSFDR10·log10(Ps / Pspur)dBc分母是带内最大单根杂散四个量之间有一条硬约束SNDR 一定不高于 SNR且满足1/10^(SNDR/10) 1/10^(SNR/10) 1/10^(THD/10)这条式子是后面所有排查的地基。任何一次计算只要把 SNR 和 THD 代进去反算出的 SNDR 与实际输出的 SNDR 对不上说明功率归并那一步就有 bin 被重复计入或者漏计了不用再往下看。理想量化器的理论值是另一把尺子。满量程正弦输入下N 位理想 ADC 的 SNR 6.02N 1.76 dB输入幅度回退到满量程的 A 倍A1时再减 20·log10(A)。这条式子决定了你测出来的数「该是多少」也就决定了程序有没有写错。2.2 相干采样、频谱泄漏与频率字 JFFT 只能分辨 N 个离散频点频率分辨率 Δf fs/N。如果输入频率 fin 恰好落在某个 bin 上即 fin J·fs/N 且 J 为整数能量就集中在这一根谱线里用矩形窗即可这叫相干采样。反之fin 落在两根谱线之间能量会向两侧扩散形成频谱泄漏旁瓣能抬到 -13 dBc 量级这时候你根本分不清哪根是谐波、哪根是泄漏的裙边。只要求 J 是整数还不够。若 J 与 N 有公因子采样点在相位上会周期性重复等效于只采了 N/gcd(J,N) 个不同的相位点频谱上会出现成对的镜像谱线看起来像杂散。所以相干采样的完整条件是N 取 2 的幂J 取奇数且与 N 互质。取 J 为质数是最省事的做法质数与 2^M 自动互质。现实里信号源和采样时钟往往不同源很难严格相干。这时有两条路一是加窗Hann、Blackman-Harris把泄漏压下去代价是主瓣变宽、噪声被抬高等效 1.5 到 2 倍二是做频率估计后重采样。工程上九成场景走第一条路但必须记住加窗之后基波能量分布在多根 bin 上只取峰值那一根会系统性低估信号功率。提示判断数据是否相干最直接的办法是把峰值 bin 号打印出来和设定的 J 比对。偏差 0 到 1 个 bin 说明相干偏差好几根 bin先别怀疑程序去查信号源和时钟是不是共参考。2.3 上手第一步先验证采样是不是相干的拿到数据别急着算什么 SNR先花十秒钟确认频谱形态。% check_coherent.m —— 判断一段采集数据是否满足相干采样 code load(adc_capture.txt); % 每行一个码值 x double(code(:)) - mean(code); % 去直流均值比首点稳 N numel(x); Jexp 787; % 设计时设定的频率字 w hann(N, periodic); % 周期型 Hann适合 FFT X fft(x .* w); P 20*log10(abs(X(1:N/21)) eps); % dB 幅度谱 [~, i0] max(P(2:end)); % 跳过直流 bin 找峰 k0 i0 1; fprintf(峰值 bin %d理论 J %d偏差 %d bin\n, k0, Jexp, k0 - Jexp); fprintf(实测 fin %.6f MHz理论 %.6f MHz\n, ... k0/N*1e-6*100e6, Jexp/N*100e6);逻辑上这段代码只做三件事去均值消掉直流、加周期 Hann 压低泄漏、在排除直流 bin 之后找最大值位置。参数上hann(N,periodic)的周期型比对称型更适合 FFT因为它保证 w(1) 与 w(N) 首尾相接不会在边界引入额外跳变找峰时从索引 2 开始是为了避开 ADC 失调造成的直流大峰那根谱线幅度往往比基波还高不排除必然找错。偏差落在 0 到 1 个 bin 内说明数据可用接着往下算。偏差是三根以上 bin通常有三类原因信号发生器与采样时钟没共参考、频率字 J 在配置文件里被改过、采集时丢点导致等效采样率变了。这三种情况算出来的 SNR 都不可信别硬着头皮往下走。3. 计算链路拆解从 fft 输出到五个 dB 数值的函数怎么写3.1 函数骨架与输入输出约定整个程序收敛成一个函数输入是码流和几个配置项输出是一个结构体。这样做的好处是扫频、批量跑不同频点时不用重复写代码也方便把结果直接堆成 table。函数签名设计成adc_metrics(code, fs, fmax, Nh, winName, L)后四个参数都给默认值只有code必填。fs只影响频率轴的换算不影响 dB 结果fmax决定统计带宽这个参数最容易被忽略后面单独讲。输入码型必须先统一。ADC 输出常见三种偏移二进制、二进制补码、无符号整数。偏移二进制要减掉中点2^(B-1)补码要按位宽做符号扩展无符号整数在去均值之后其实也等价处理了。这一步做错整个频谱会多出一堆本不存在的谐波属于典型的「程序没错、数据喂错」。3.2 幅度归一化、单边谱与 ENBW 噪声修正这一段是整份程序最核心、也最容易写错的地方值得单独拆开说。对加窗后的序列做 FFT得到 X(k)。要把它还原成物理功率需要除以窗函数的相干增益 S1 Σw(n)。单边谱还要对除直流和奈奎斯特 bin 之外的所有 bin 乘 2因为实信号的负频率半边是镜像。归一化之后的第 k 根谱线功率为P(k) |X(k)|^2 / S1^2 1 k N/2 时再乘 2对一根幅度为 A 的正弦这样算出来 P A²/2正好是它的平均功率信号功率这一路就对了。噪声这一路不能照搬。窗函数把噪声能量在频域上抹开等效带宽被展宽了 ENBW 倍ENBW N · Σw²(n) / (Σw(n))² 单位是 bin矩形窗 ENBW 1Hann 窗 ENBW 1.5Blackman-Harris 窗 ENBW ≈ 2。所以噪声功率求和之后必须除以 ENBW 才能还原。这一步漏掉Hann 窗下 SNR 会凭空好 10·log10(1.5) 1.76 dB而且这个偏差很隐蔽——结果看起来依然「合理」只是系统性偏乐观。这是我在对比两家实验室数据时踩过的坑两边差 1.8 dB查了两天才定位到窗函数修正。3.3 基波、谐波与杂散的 bin 归并有了归一化功率谱接下来是「哪些 bin 算信号、哪些算谐波、哪些算噪声」的划分。逻辑是建三张掩码。基波掩码以峰值 bin k0 为中心左右各扩 L 根L 是主瓣半宽矩形窗取 0Hann 取 2Blackman-Harris 取 4。谐波掩码对 h 2 到 Nh计算 kh mod(h·k0, N)再折叠到 0 到 N/2 区间kh N/2 时取 N - kh。折叠这一步不能省因为高次谐波超过奈奎斯特频率后会折回带内它依然是真实的失真能量必须计入 THD。折叠会带来一个新问题两根不同次谐波可能折到同一根 bin 上或者某次谐波恰好折回基波附近。前者重复计数会让 THD 虚高后者会把基波能量算进谐波里。处理办法是维护一个已占用 bin 的集合每次扩掩码前先检查交集有冲突就跳过。噪声就是「带内、且不在直流掩码、基波掩码、谐波掩码里」的所有 bin 之和再除以 ENBW。杂散则在「带内、排除直流与基波」的 bin 里取最大值。SFDR 按标准定义是把谐波也算作杂散的但很多数据手册会额外给一个「不含谐波的最大杂散」所以函数里两个都输出用的时候按报告口径选。3.4 用一段合成码流跑通全流程把上面的逻辑写成一个完整函数。function m adc_metrics(code, fs, fmax, Nh, winName, L) %ADC_METRICS 计算 ADC 的 SNR / SNDR / THD / ENOB / SFDR % code : N×1 输出码double 或整型均可 % fs : 采样率 Hz缺省 1 % fmax : 统计带宽上界 Hz缺省 fs/2 % Nh : 计入的谐波个数不含基波缺省 5 % winName : rect | hann | blackmanharris缺省 hann % L : 主瓣半宽bin缺省按窗型自动取 code double(code(:)); N numel(code); if nargin 2 || isempty(fs), fs 1; end if nargin 3 || isempty(fmax), fmax fs/2; end if nargin 4 || isempty(Nh), Nh 5; end if nargin 5 || isempty(winName), winName hann; end switch lower(winName) case rect, w ones(N,1); Ldef 0; case hann, w hann(N,periodic); Ldef 2; case blackmanharris, w blackmanharris(N,periodic); Ldef 4; otherwise, error(未知窗型: %s, winName); end if nargin 6 || isempty(L), L Ldef; end S1 sum(w); S2 sum(w.^2); ENBW N * S2 / S1^2; % 等效噪声带宽单位 bin x code - mean(code); % 去直流 X fft(x .* w); P abs(X(1:N/21)).^2 / S1^2; P(2:end-1) 2 * P(2:end-1); % 单边谱直流与奈奎斯特 bin 不乘 2 kmax max(3, min(N/21, floor(fmax/fs*N) 1)); band false(1, N/21); band(1:kmax) true; [~, i0] max(P(2:kmax)); % 跳过直流找基波 k0 i0 1; idx_dc 1 : min(1L, kmax); idx_sig max(1,k0-L) : min(kmax, k0L); mask_dc false(1,N/21); mask_dc(idx_dc) true; mask_sig false(1,N/21); mask_sig(idx_sig) true; mask_h false(1,N/21); Psig sum(P(idx_sig)); Pdist 0; for h 2:Nh kh mod(h*k0, N); kh min(kh, N - kh); % 折叠到 0..N/2 if kh 1L, continue; end % 折进直流区无法分辨 if abs(kh - k0) L, continue; end % 折回基波跳过 if kh kmax, continue; end % 落在统计带宽外 idx max(1,kh-L) : min(kmax, khL); mask_h(idx) true; Pdist Pdist sum(P(idx)); end Pnoise sum(P(band ~(mask_dc | mask_sig | mask_h))) / ENBW; Ptmp P; Ptmp(~band) 0; Ptmp(mask_dc | mask_sig) 0; m.SFDR 10*log10(Psig / max(Ptmp)); % 含谐波标准 SFDR Ptmp2 Ptmp; Ptmp2(mask_h) 0; m.SFDR_nh 10*log10(Psig / max(Ptmp2)); % 不含谐波的最大杂散 m.SNR 10*log10(Psig / Pnoise); m.SNDR 10*log10(Psig / (Pnoise Pdist)); m.THD 10*log10(Pdist / Psig); m.ENOB (m.SNDR - 1.76) / 6.02; m.fin k0 / N * fs; m.k0 k0; m.ENBW ENBW; m.Psig Psig; m.Pnoise Pnoise; m.Pdist Pdist; end逻辑上分五步走窗与增益预计算、功率谱归一化、掩码划分、功率归并、指标换算。每一步的中间量都塞进结构体输出是为了后面排查时能直接看Psig和Pnoise的比值而不用重新跑一遍 FFT。参数上有三处需要按数据改winName在相干采样下改成rect并配L 0结果最干净fmax在带通采样或过采样场景下不要给fs/2否则带外噪声会全部算进来SNR 无谓地变差Nh一般取 5ENOB 超过 14 bit 时取 7 到 9因为高次谐波在那时已经浮出噪底。跑一遍合成数据验算% demo_adc_metrics.m —— 12 bit 合成码流验证 clear; clc; rng(7); fs 100e6; N 2^13; J 787; B 12; assert(gcd(J,N) 1, J 与 N 不互质不是相干采样); fin J/N*fs; % 约 9.607 MHz t (0:N-1)/fs; A 0.999; % 贴近满量程 x A*sin(2*pi*fin*t) 2e-5*randn(N,1); LSB 1/2^(B-1); code round(x / LSB); % 有符号码约 ±2046 m adc_metrics(code, fs, fs/2, 5, hann, 2); fprintf(SNR%.2f SNDR%.2f THD%.2f ENOB%.2f SFDR%.2f\n, ... m.SNR, m.SNDR, m.THD, m.ENOB, m.SFDR);逻辑上先按相干条件构造正弦加一点点白噪模拟前端再量化成 12 bit 码。参数上A 0.999是为了留一点满量程余量又不太影响理论值2e-5的噪声远小于量化噪声LSB/√12 ≈ 1.4e-4对结果的贡献不到 0.01 dB。跑出来 SNR 应该在 73.9 到 74.0 之间ENOB 接近 12.0。如果差得远九成是归一化或者 ENBW 那一步写错了回去对一遍S1、S2的定义。4. 参数怎么定N、J、fmax、谐波数这四组值的取值边界4.1 采样点数 N 与频率字 J 的搭配N 决定频率分辨率和单 bin 噪底。噪声在频域上被摊到 N/2 根 bin 里单 bin 噪底相对基波大约是噪底(dBc) ≈ -(SNR 10·log10(N/2))12 bit、N 8192 时本文还有配套的精品资源点击获取