简介:这份 MATLAB 计算程序面向信号处理、ADC 测试与数字通信方向的学习者和工程师,围绕 SNR、SNDR、THD、ENOB、SFDR 五项常用指标提供可运行的评估示例,适合课程实验、器件性能对比和算法验证。压缩包共 2 个文件,含 1 个 m 脚本与 1 个 txt 数据文件,大小约 4KB;脚本负责频谱分析与指标计算,txt 提供待处理样本,便于替换实测数据后复现结果。已有 4558 人学习下载,说明其在相关指标计算场景中具有一定参考价值。读者可借此理解五项指标的定义、功率比关系及频域分析思路,并对照脚本梳理从时域数据到 FFT、谐波与杂散提取,再到结果输出的完整计算链路,为信号链性能评估和结果复核提供可直接参考的模板。
1. 从一张频谱截图说起:这套 SNR matlab 计算程序到底算什么
有人拿着一张某数据采集板的频谱截图,问"这块板子到底几 bit 有效"。截图上有底噪、有谐波、有杂散,但谁也说不清该读哪根线。这套 SNR matlab 计算程序处理的就是这件事:把一段 ADC/DAC 输出码值丢进去,它按 FFT 法算出 SNR、SNDR、THD、ENOB、SFDR 五个指标,直接给出可以写进测试报告的数值。适合三类人:做数据采集、音频、射频前端,需要验收 ADC 指标的硬件工程师;写完 RTL 要做行为级验证的数字设计;以及拿实测数据回推系统性能的算法同学。不需要装任何工具箱,核心就一个主函数加一个窗函数,纯 MATLAB 基础语法,跑起来门槛很低。
2. 指标口径先对齐:五个数字背后的功率归属
算法写错十行,不如口径错一个字。同样一段数据,SNR 差 1.5 dB、ENOB 差 0.3 bit,八成是"哪部分功率算噪声、哪部分算谐波"没谈拢。先把账算清楚,代码才站得住。
2.1 SNR、SNDR、THD、SFDR 各自把谁扣掉
把这几个指标拆开看,本质是对同一份频谱做不同的功率归集。信号功率记作 Ps,谐波总功率 Ph,其余噪声(含杂散)功率 Pn:
| 指标 | 定义式 | 通俗说法 |
|---|---|---|
| SNR | 10·log10(Ps / Pn) | 只扣谐波和 DC,剩下的都算噪声 |
| SNDR(SINAD) | 10·log10(Ps / (Pn + Ph)) | 噪声和谐波一起扣,最"悲观" |
| THD | 10·log10(Ph / Ps) | 谐波占总信号的比例,负值 |
| SFDR | 10·log10(Ps / Pmax_spur) | 最大那根杂散离信号多远,含谐波 |
| ENOB | (SNDR − 1.76) / 6.02 | 把 SNDR 折算成等效位数 |
有一处容易被忽略:SNR 的定义里,谐波是不计入噪声的,所以理想正弦输入下 SNR 永远优于 SNDR。SFDR 里的"杂散"通常包含谐波,因为最坏情况下最大的那根毛刺往往就是二次或三次谐波。如果只统计非谐波杂散,得出的 SFDR 会虚高好几 dB,这在验收场合会被质疑。
ENOB 的 1.76 和 6.02 不是拍脑袋来的:理想量化器的量化噪声均匀分布在 ±0.5 LSB 内,功率为 q²/12,正弦满量程输入功率为 (2^(N−1)·q)²/2,两者一比再取对数,就得到 6.02N + 1.76。所以 12 bit 理想 ADC 的 SNR 上限是 74.0 dB,ENOB 上限就是 12。实际芯片能到 11.2 bit 已经算做得不错了。
2.2 相干采样与窗函数:直接 fft 会翻车的原因
FFT 只对周期延拓的信号成立。如果采样长度 N 里装的不是整数个输入周期,首尾接不上,频谱就会"漏":能量从信号 bin 向两侧扩散,本来干净的底噪抬起来,SNR 直接虚低好几个 dB。这就是非相干采样的经典问题。
工程上有两条路。第一条是相干采样,让输入频率精确落在 FFT 的 bin 上:
N = 8192; % 采样点数,取 2 的整数次幂 fs = 100e6; % 采样率 M = 37; % 窗内周期数,取奇数 while gcd(M, N) ~= 1 % 必须与 N 互质,否则某些谐波会落到同一 bin M = M + 2; end fin = M * fs / N; % 相干采样下的输入频率,窗内正好 M 个整周期这里 M 与 N 互质是关键。如果 M 和 N 有公因数,比如 M = 64、N = 8192,那么第 128 次谐波会精确折回信号 bin 上,谐波和信号混在一起,THD 彻底失真。取奇数且互质,能让所有谐波都落在不同于信号的 bin 上。
第二条路是加窗。实测时信号源和被采系统往往不同源,很难做到严格相干,这时候必须加窗压制旁瓣泄漏。代价是主瓣变宽,且噪声功率被窗"放大"了,需要按等效噪声带宽(ENBW)修正回来:
| 窗类型 | 相干增益 | ENBW(bin) | 主瓣宽度(bin) | 最高旁瓣(dB) |
|---|---|---|---|---|
| 矩形 rect | 1.000 | 1.00 | 2 | −13 |
| 汉宁 hann | 0.500 | 1.50 | 4 | −31 |
| 汉明 hamming | 0.540 | 1.36 | 4 | −43 |
| 四项布莱克曼-哈里斯 | 0.359 | 2.00 | 8 | −92 |
汉宁窗的 ENBW 是 1.5,意味着加窗后噪声功率被放大了 1.5 倍,换算成 dB 就是 1.76 dB。如果不做修正,同一段 12 bit 数据算出来的 SNR 会从 74 dB 掉到 72.2 dB 左右,ENOB 少 0.3 bit。这个偏差稳定复现、非常迷惑人,属于典型的踩坑点。
窗的选择按场景定:做研发验证、追求 THD 精度,用四项布莱克曼-哈里斯,旁瓣压到 −92 dB,代价是主瓣宽、bin 分辨率下降;做产线快速验收,汉宁窗够用;相干采样且信号源质量好的时候,直接用矩形窗,省掉所有修正。
2.3 谐波折叠与 bin 定位:THD 最容易算错的一步
谐波不是想出现就出现的。二次谐波频率 2·fin 一旦超过 fs/2,就会被采样定理折叠回低半频。比如 fin = 0.4·fs,二次谐波 0.8·fs 折回 0.2·fs,三次谐波 1.2·fs 折回 0.2·fs(1.2 − 1.0),二次和三次谐波撞在同一个 bin 上。
正确的做法是在 bin 域上算,不要用频率域算:
k0 = k0_rel + 1; % 信号所在 bin(1 基) kh = mod(h * (k0 - 1), N); % 第 h 次谐波的 bin 号,按整数倍扩展 if kh > N / 2 kh = N - kh; % 超过 Nyquist 折回低半频 end这条mod再折叠的两行,是整个程序里最少写、也最容易出错的逻辑。漏掉折叠,高次谐波会被算到不存在的频点上,THD 偏低;折叠方向搞反,谐波 bin 可能落到 DC 附近被当成噪声扣掉,THD 同样失真。
还有一个边界情况:当 fin 接近 fs/2 时,二次谐波会折回并紧贴信号主瓣,甚至落进主瓣范围内。此时谐波 bin 和信号 bin 重叠,程序必须把它从谐波列表里剔除,否则会把信号能量重复扣一次,SNDR 虚低。这属于极端工况,代码里要显式判断,不能假设"谐波一定远离信号"。
3. 目录与核心函数拆解:一次 FFT 怎么把五个指标算出来
整个包结构相当扁平,没有依赖,没有工具箱,两个文件撑起全部计算,外加一个演示脚本。这种结构对二次开发很友好,也方便直接拷进别的工程里。
3.1 文件清单与调用关系
| 文件 | 职责 | 关键输入 |
|---|---|---|
adc_dynamic.m | 主入口,完成 FFT、功率归集、指标计算 | 码值向量、fs、fin |
local_window.m | 生成窗序列并返回相干增益、ENBW | 窗名、点数 N |
demo_ideal_adc.m | 用理想量化器造已知答案的数据自测 | 无 |
调用链路只有一层:演示脚本造数据,主函数调用窗函数取窗参数,然后一路算到底,返回一个结构体。之所以不做成类,是因为这类计算通常是批量脚本里的一环,函数式调用写起来更顺手,parfor并行也简单。
主函数前几行是参数声明,用inputParser做名称-值传参。这样调用方可以只写关心的参数,其余走默认值:
function res = adc_dynamic(code, fs, fin, varargin) %ADC_DYNAMIC FFT 法计算 ADC/DAC 的 SNR、SNDR、THD、SFDR、ENOB % code : 输出码值列向量(已去掉上电建立段),长度建议为 2 的整数次幂 % fs : 采样率,单位 Hz % fin : 输入正弦频率,单位 Hz(相干采样时应满足 fin = M*fs/N) % 名称-值参数: % 'Bits' 量化位数,默认 12 % 'FullScale' 满量程码值,默认 2^(Bits-1) % 'Window' 窗函数,默认 'hann' % 'Harmonics' 统计到第几次谐波,默认 5 % 'SigBinSpan' 信号主瓣单侧 bin 数,默认 1 p = inputParser; p.addParameter('Bits', 12); p.addParameter('FullScale', []); p.addParameter('Window', 'hann'); p.addParameter('Harmonics', 5); p.addParameter('SigBinSpan', 1); p.parse(varargin{:}); opt = p.Results; if isempty(opt.FullScale) opt.FullScale = 2^(opt.Bits - 1); end x = double(code(:)) / opt.FullScale; % 归一化到满量程,功率即 dBFS 口径 N = numel(x); if 2^nextpow2(N) ~= N warning('点数 %d 不是 2 的幂,存在栅栏效应,建议重采或截断', N); end % ---- 1. 去直流 + 加窗 ---- [xw, cg, enbw] = local_window(x, opt.Window); xw = (xw - mean(xw)) .* local_win_seq(opt.Window, N); % ---- 2. 单边幅度谱 ---- X = fft(xw) / N; P = abs(X(1:N/2+1)); P(2:end-1) = 2 * P(2:end-1); % 单边谱补偿,DC 与 Nyquist 不乘 2 P = P / cg; % 相干增益修正,恢复真实幅度 % ---- 3. 定位信号 bin ---- [~, k0] = max(P(2:end)); % 跳过 DC bin 找峰值 k0 = k0 + 1; sigIdx = max(2, k0-opt.SigBinSpan) : min(N/2+1, k0+opt.SigBinSpan); % ---- 4. 定位谐波 bin(含折叠)---- hIdx = []; for h = 2:opt.Harmonics kh = mod(h * (k0-1), N); if kh > N/2 kh = N - kh; % 超过 Nyquist 的谐波折回低半频 end if kh < 2 || kh > N/2+1 continue; end hIdx = [hIdx, max(2, kh-opt.SigBinSpan) : min(N/2+1, kh+opt.SigBinSpan)]; %#ok<AGROW> end hIdx = setdiff(unique(hIdx), sigIdx); % 谐波与信号重叠时剔除 % ---- 5. 功率归集 ---- P2 = P.^2; Ps = sum(P2(sigIdx)); Ph = sum(P2(hIdx)) / enbw; % 谐波是谱线,这里保持与噪声同口径 mask = true(size(P2)); mask(1:3) = false; % 跳过 DC 及其泄漏 mask(sigIdx) = false; mask(hIdx) = false; Pn = sum(P2(mask)) / enbw; % 宽带噪声要按 ENBW 修正 % ---- 6. 指标计算 ---- res.SNR = 10*log10(Ps / Pn); res.SNDR = 10*log10(Ps / (Pn + Ph)); res.THD = 10*log10(Ph / Ps); spur = P2; spur(sigIdx) = 0; spur(1:3) = 0; res.SFDR = 10*log10(Ps / max(spur)); res.ENOB = (res.SNDR - 1.76) / 6.02; res.Ps = Ps; res.Pn = Pn; res.Ph = Ph; res.f = (0:N/2)' * fs / N; res.PdB = 20*log10(P / max(P)); res.fin_meas = (k0 - 1) * fs / N; res.ENBW = enbw; end逐段说清几个关键决策。
归一化放在最前面。x / opt.FullScale之后,所有功率都是相对满量程的比例,算出来的 Ps 直接就是 dBFS 口径。如果跳过这一步,Ps 是绝对码值平方,数量级和理论值对不上,ENOB 会差好几个 bit 且没有任何报错提示,属于血泪经验。
单边谱补偿的边界处理。P(2:end-1) = 2*P(2:end-1)把负频率能量折到正频率,但 DC 和 Nyquist 这两个 bin 是自共轭的,不能乘 2。写成2*P全乘一遍,DC 会翻倍,低频杂散全被抬高,SFDR 直接崩。
SigBinSpan决定了信号主瓣吃掉几个 bin。相干采样 + 矩形窗时取 0 也行,信号能量全在单 bin;非相干采样 + 汉宁窗时主瓣有 4 个 bin 宽,取 1 到 2 才不漏能量。取少了,信号功率被低估,SNR 虚高,看起来"性能很好",实际是把信号能量算进了噪声。这个参数必须和窗类型绑死,不能一套值走天下。
谐波功率除以 ENBW 这一行,不同人的口径不一样。严格讲谐波是确定性谱线,加窗后被相干增益修正即可,不需要 ENBW 修正;噪声是宽带的,必须除。但工程实现里图和噪声保持同一量纲,很多代码统一除以 ENBW,好处是 THD 和 SNDR 的一致性更好,坏处是 THD 会系统性偏 1.76 dB。两种都能自洽,关键是同一批报告里只能用一种口径,别换人换算法。
SFDR 的spur里保留了谐波位置,因为 SFDR 定义里最大杂散通常就是谐波。如果只看非谐波杂散,那是另一个指标,不要混叫 SFDR。
3.2 窗函数模块:三个返回值一个都不能少
function [w, cg, enbw] = local_window(name, N) % 返回窗序列 w、相干增益 cg、等效噪声带宽 enbw(单位:bin) n = (0:N-1)'; switch lower(name) case 'rect' w = ones(N,1); case 'hann' w = 0.5 - 0.5*cos(2*pi*n/N); % periodic 形式,适合 FFT case 'hamming' w = 0.54 - 0.46*cos(2*pi*n/N); case 'blackmanharris' a = [0.35875 0.48829 0.14128 0.01168]; w = a(1) - a(2)*cos(2*pi*n/N) ... + a(3)*cos(4*pi*n/N) - a(4)*cos(6*pi*n/N); otherwise error('未知窗函数: %s', name); end cg = sum(w) / N; % 相干增益:信号幅度被压低的倍数 enbw = N * sum(w.^2) / sum(w)^2; % 等效噪声带宽,单位 bin end窗序列用的是2*pi*n/N的 periodic 形式,而不是2*pi*n/(N-1)的 symmetric 形式。做 FFT 必须用 periodic,否则窗在首尾不连续,泄漏压不下去,等于白加。这个细节在造窗函数时差一个分母,频谱上就能看出旁瓣抬高十几 dB。
cg和enbw是这套程序能用对的前提。信号幅度除以 cg 恢复真值,噪声功率除以 enbw 恢复真实噪底,两个修正对象不同、方向不同,少一个都会让结果偏。
4. 跑通流程:先造已知答案的数据,再换实测码值
上手最快的办法不是拿实测数据硬跑,而是先用理想量化器造一段"答案已知"的数据,把链路跑通、把偏差范围摸清,再去接真实采集文件。这样出问题时能立刻判断是算法错还是数据脏。
4.1 用理想 ADC 自测:验证算法链路
理想 12 bit 量化器在相干采样下,SNR 理论值 74.0 dB,ENOB 理论值 12。用它做基线,能一眼看出代码有没有系统性偏差:
%% demo_ideal_adc.m fs = 100e6; N = 8192; bits = 12; M = 37; % 与 N 互质,保证相干采样 fin = M * fs / N; t = (0:N-1)' / fs; amp = 0.5; % 相对满量程 -6 dBFS,留足裕量 vin = amp * sin(2*pi*fin*t); code = round(vin * 2^(bits-1)); % 量化,得到理想 ADC 输出码 code = max(min(code, 2^(bits-1)-1), -2^(bits-1)); % 饱和截断 res = adc_dynamic(code, fs, fin, 'Bits', bits, 'Window', 'hann'); fprintf('SNR=%6.2f dB SNDR=%6.2f dB THD=%7.2f dBc SFDR=%6.2f dBc ENOB=%5.2f bit\n', ... res.SNR, res.SNDR, res.THD, res.SFDR, res.ENOB);跑完对着下面这张表核一遍,落在区间内就说明链路是干净的:
| 指标 | 理论 / 参考值 | 合理区间 | 超出说明什么 |
|---|---|---|---|
| SNR | 74.0 dB | 73.0 ~ 74.0 | 低于 73 查 ENBW 修正和主瓣 bin 数 |
| SNDR | 73.8 dB | 72.8 ~ 74.0 | 明显低于 SNR 说明谐波没找准 |
| THD | −85 dBc 以下 | −90 ~ −78 | 高到 −60 查谐波折叠 |
| SFDR | 接近 SNDR | 72 ~ 78 | 掉到 60 以下查 DC 附近杂散 |
| ENOB | 12.00 bit | 11.8 ~ 12.0 | 低于 11.5 基本是先修噪声 |
幅度取 0.5 满量程而不是 0.99,是有意的。接近满量程时量化噪声和信号的相关性增强,谐波会抬高;同时也预留了实测系统增益误差的裕量,避免输入稍大就削顶。做验收时如果要测满量程附近的性能,再单独扫一遍幅度。
4.2 接实测码值:从二进制流到有符号向量
实测数据一般是逻辑分析仪或 FPGA 抓的并行码流,格式可能是十六进制文本,也可能是打包的二进制。第一步永远是转成有符号十进制码值,并做补码转换:
%% load_capture.m —— 读取十六进制码流并转有符号码值 bits = 14; fid = fopen('adc_capture.txt', 'r'); raw = textscan(fid, '%s'); fclose(fid); hexs = raw{1}; n = numel(hexs); code = zeros(n, 1); for i = 1:n v = hex2dec(hexs{i}); % 按无符号解析 if v >= 2^(bits-1) % 最高位为 1,说明是负数 v = v - 2^bits; % 补码转有符号 end code(i) = v; end code = code(1025:end); % 丢掉前 1024 点,避开上电建立段 code = code(1:2^floor(log2(numel(code)))); % 截断到 2 的整数次幂这段里有三个动作,每个都不能省。
补码转换漏了,负半周会被当成接近满量程的正数,波形看起来像被"整流"过,频谱上二次谐波暴涨,THD 直接到 −20 dBc,一看就知道数据脏了。
丢掉前 1024 点是避开建立段。ADC 上电或量程切换后,前几百个采样往往还没稳定,混进来会让低频段出现鼓包,SFDR 被一个假的近 DC 杂散决定。丢多少看具体器件,一般抓 1/8 到 1/4 的长度就够。
截断到 2 的幂是为了避免栅栏效应。点数不是 2 的幂时,FFT 的 bin 宽度不均匀,谱线落在两个 bin 之间会被劈成两半,信号功率和噪声功率都算不准,每次跑的结果还会抖。宁可贵一点重采,也别拿非 2 的幂硬算。
加载完之后,把fin换成实测信号源的输出频率,Bits和FullScale换成实际器件的参数,直接调主函数即可。如果信号源和被采系统不同源、做不了相干采样,把Window换成'blackmanharris',SigBinSpan调到 3,用宽主瓣换干净的底噪。
4.3 出问题前的自检:三个必看量
跑完先别急着信数字,扫一眼三样东西。
码值范围。min(code)和max(code)应该落在 ±2^(bits−1) 内且没有长期贴着边界。贴着边界说明削顶了,这时候算出来的 THD 是削顶失真,不是器件本身的谐波。
直方图。histogram(code, 64)看一眼,正常正弦输入应该呈现"两头高、中间低"的反正弦分布,对称且平滑。如果出现明显的双峰缺失或者单边堆积,说明码值转换错了位,或者有位线接反,这类问题在频谱上表现为莫名其妙的杂散。
频谱图。plot(res.f/1e6, res.PdB)把前几个大分量标出来,确认信号峰、二次谐波、三次谐波的位置符合预期。如果看到一系列等间距的杂散,多半是电源干扰或时钟馈通,不是量化噪声,这时候算出的 SNR 再准也没意义,得先治板子。
5. 避坑与排查:让 ENOB 凭空少两三个 bit 的细节
这套算法的坑集中在"看起来对、其实偏了"的地方。偏差往往不大,1 到 3 dB,但足以让验收结论反转。下面五条都是实际跑数据时反复遇到的,按现象、原因、解决三步记。
现象一:同一批数据,两个人算出的 ENOB 差 0.3 bit 以上。原因:窗类型和信号主瓣 bin 数不一致。甲用汉宁窗取 ±1 bin,乙用四项布莱克曼-哈里斯取 ±3 bin,两者对信号功率的归集范围不同,噪声底也差 1.76 dB 以上,指标自然对不上。 解决:把窗类型、SigBinSpan、Harmonics三个参数写进报告头,和指标一起存档。更彻底的做法是在主函数里建立窗与主瓣宽度的映射表,SigBinSpan不传时按窗自动取默认值,杜绝人工不一致。
现象二:实测 SNR 比手册低 1.5 到 2 dB,反复换数据都一样。原因:加窗后没做 ENBW 修正。汉宁窗 ENBW = 1.5,噪声功率被放大 1.5 倍,换算成 dB 正好 1.76。这个偏差稳定、可复现、不随数据变化,非常像"器件就是这个水平"。 解决:确认噪声求和那一步除以了enbw。可以把'Window'换成'rect'做对照实验:矩形窗 ENBW = 1,如果两种窗算出的 SNR 差 1.7 dB 左右,就是修正漏了。
现象三:THD 好到 −100 dBc 以下,好得不像真的。原因:谐波 bin 没做 Nyquist 折叠。mod(h*(k0-1), N)得到的是 0 到 N−1 的 bin 号,大于 N/2 的部分必须折回,否则会被当作不存在的频点直接跳过,hIdx变成空数组,Ph = 0,THD 趋近负无穷。 解决:在谐波循环后加一句断言,if isempty(hIdx), warning('未找到任何谐波 bin,检查折叠逻辑'); end。另外把hIdx对应的频点打印出来,人工核一遍是否符合mod(h*fin, fs)的预期。
现象四:SFDR 被一根贴着 DC 的杂散决定,频率只有零点几 MHz。原因:DC 泄漏没清干净。直接fft不去均值,DC bin 会溢出到邻近几个 bin;即使去了均值,加窗后 DC 的旁瓣仍会污染前几个 bin。程序里mask(1:3) = false只跳过了 3 个 bin,用四项布莱克曼-哈里斯窗时主瓣有 8 个 bin 宽,3 个不够。 解决:跳过 bin 的数量跟着窗走,矩形窗跳 2、汉宁窗跳 4、四项窗跳 8。更稳妥的做法是在 FFT 之前先做一次一阶差分或者多项式去趋势,把低频漂移压掉,再进频谱。
现象五:仿真数据算出 11.5 bit,实测数据只有 8 bit,差得离谱。原因:十有八九是数据加载环节。补码没转、满量程参数给错、前段建立点没去,这三个里必中一个。曾经遇到过把FullScale设成 2^bits 而不是 2^(bits−1) 的情况,所有功率都小了 6 dB,SNR 看起来没变但 SFDR 和绝对幅度全错。 解决:加载完先跑三行自检,fprintf('min=%d max=%d mean=%.2f\n', min(code), max(code), mean(code))。理想正弦输入下mean应该接近 0,min和max绝对值接近但不相等。这一步花十秒,能省掉半天。
提示:这套程序默认输入是干净的量化噪声场景。如果数据里混了电源干扰、时钟馈通、或者参考电压抖动,算出来的 SNR 是"系统 SNR"而不是"ADC SNR",写报告时要注明测试条件,别把板子的问题算到芯片头上。
6. 二次开发:把单次计算扩成扫频与双音测试
单点计算只是起点。真正做器件评估,要的是 SNDR 随输入频率的曲线、ENOB 随幅度的曲线、以及双音互调。这些都能在现有主函数上套一层循环,不用改动核心计算。
扫频是最常用的一层。关键是每个频点都要重新选相干频率,不能直接拿目标频率硬算:
%% sweep_fin.m —— 扫输入频率,画 SNDR 曲线 fs = 100e6; N = 8192; bits = 12; Ms = [37 101 251 509 997 2003 3001]; % 全部与 N 互质 sndr = zeros(size(Ms)); enob = sndr; for i = 1:numel(Ms) fin = Ms(i) * fs / N; % 就近取相干频点 t = (0:N-1)'/fs; code = round(0.5 * 2^(bits-1) * sin(2*pi*fin*t)); r = adc_dynamic(code, fs, fin, 'Bits', bits, ... 'Window', 'hann', 'SigBinSpan', 1); sndr(i) = r.SNDR; enob(i) = r.ENOB; end plot(Ms*fs/N/1e6, sndr, 'o-'); grid on; xlabel('fin / MHz'); ylabel('SNDR / dB');这里有一处取舍:目标频率和相干频率之间会有偏差,低频段相对偏差大,高频段小。做曲线时要在报告里注明"测试频率为就近相干频点",否则别人复现时对不上数。如果非要用精确频率,就得改用加窗 + 插值校正,把SigBinSpan放大到 3,代价是底噪抬高、动态范围压缩。
双音互调是另一个常见需求,用于评估 IMD3。做法是把两路正弦叠加后送进同一个量化器,然后修改谐波统计逻辑——不再是h*fin的整数倍,而是2*f1-f2、2*f2-f1这些互调分量。这块建议单独写一个calc_imd.m,不要塞进主函数里加if分支,否则主函数会越来越难读。互调分量的 bin 定位公式和折叠逻辑与谐波完全一致,可以直接复用那段mod加折返的代码。
再上一层是批量文件的批处理。实测往往一次抓几十个文件,对应不同频率、不同幅度、不同温度。这时候把主函数改成静默模式,去掉warning,用parfor并行跑,结果汇总成一张表导出到 Excel 或者 CSV。我在做这类批处理时,习惯把每个文件的原始参数(文件名、fs、fin、bits、窗类型)和五个指标放同一行,一旦某一行数据异常,能立刻回查到具体是哪个文件出了问题,不用重跑。
最后一个技巧和验证有关。改完代码之后,不要只拿实测数据看指标合不合理——实测数据没有标准答案,改了也看不出来对不对。每次改完,都拿理想量化器那组数据重跑一遍,确认 SNR 还在 73 到 74 dB 之间、ENOB 还在 11.8 以上。这组数据是唯一能当"后悔药"用的基线。从那以后我每次动主函数,都强制先走一遍理想数据自测,再碰真实采集文件。希望帮到你。
本文还有配套的精品资源,点击获取