AD转换器与DA转换器仿真:量化、FFT指标与闭环验证
发布时间:2026/9/20 19:14:58来源:尧图网络
简介这份文档面向通信原理、电子信息工程等专业的课程设计学习者围绕A/D与D/A转换器的Matlab/Simulink仿真展开可用于课程设计参考、实验报告撰写与仿真建模入门练习。内容先从原理入手讲清模拟信号的采样、幅度离散化与编码以及数字序列经低通滤波器还原模拟信号的正反过程随后给出基于Simulink通信模块库的建模方案逐条说明直流源、零阶保持器采样、量化器、整数转比特、缓冲串并转换等模块的作用与参数设置并以输入常数17.5、量化后输出18为例分析波形与结果其中对采样定理与量化误差的讨论便于读者对照理解。资源包共1个docx文档约78KB涵盖设计原理、仿真电路、模块说明、结果分析与总结结构完整便于复现。已有338人学习下载适合希望快速搭建A/D、D/A仿真链路并整理报告的中初级读者。1. AD转换器和DA转换器仿真到底在仿什么做一块 16 通道的数据采集板采样率定 100 kSPS 还是 1 MSPSADC 选 12 位还是 16 位前端抗混叠滤波器的截止频率压到多少重建端又该用几阶滤波器——这几个数字一旦拍错板子回来发现噪声底比预算高十几 dB改版就是几周起步。AD转换器和DA转换器的仿真就是把这一串判断从硬件阶段前移到模型阶段用一组可执行的数学模型把连续模拟量和离散码字之间的那座桥完整走一遍看清量化、采样抖动、重建保持、单元失配各自往总误差里贡献了多少。它解决的从来不是「画一条正弦」这么简单的事而是在给定信号带宽、目标信噪比和器件约束之后把链路预算算清楚。做音频接口、电机电流环、射频收发前端、传感器调理电路的工程师都会在这件事上反复花时间。后面按 AD 采样链路、DA 重建链路、指标计算、闭环联调的顺序往下走代码全部用 Python Numpy 就能跑起来。2. AD转换器仿真从连续电压到量化码字把 AD 转换过程拆开无非是两个动作串起来先在时间轴上采样再在幅度轴上量化。这两个动作各自引入一类误差而且它们的统计特性完全不同——混叠误差是确定性的取决于滤波器抑制能力量化误差在理想情况下是均匀分布的随机量可以精确算出来。分清这两者后面才知道该优化哪一头。2.1 采样保持与量化器的数学描述采样环节的输出可以写成x_s[n] x(n·Ts τ)其中Ts是采样周期τ是实际孔径时刻相对理想时刻的偏移。当τ存在随机波动时它就从单纯的相位偏移变成了误差源这就是后面要讲的时钟抖动。量化环节则是把连续幅度切成有限个台阶每个台阶宽度为 1 个 LSBLSB 2 · Vref / 2^N理想量化器的输出是输入信号减去量化误差而量化误差在[-LSB/2, LSB/2]上近似均匀分布其功率为LSB²/12。把这个结果代进满量程正弦波幅度A 2^(N-1)·LSB信号功率A²/2就能直接推出那条约等于6.02N 1.76的经典公式。推导过程本身比结论有价值因为它暴露了一个前提输入必须是满量程正弦且量化误差必须是白的。位数 N理论 SNR (dB)LSB/Vref 占比849.90.781%1061.960.195%1274.00.0488%1486.040.0122%1698.080.00305%2.2 用 Numpy 搭一个 N 位理想 ADC 并复现 SNR 公式先写一个参数化的理想 ADC把它跑起来再用仿真结果去对公式。import numpy as np def adc_ideal(x, nbits, vref1.0, vcm0.0): 理想 N 位 ADC。x 为输入电压数组范围 [-vref, vref] lsb 2.0 * vref / (2 ** nbits) # 输入平移后按最近码字取整得到整数码 code np.round((x - vcm vref) / lsb) code np.clip(code, 0, 2 ** nbits - 1).astype(np.int64) # 反量化回电压中码字对齐 0V xq (code - 2 ** (nbits - 1)) * lsb - vcm return code, xq nbits, fs, N 12, 100e6, 8192 M 245 # 与 N 互质满足相干采样 fin M / N * fs # ≈ 2.99 MHz t np.arange(N) / fs x 0.999 * np.sin(2 * np.pi * fin * t) code, xq adc_ideal(x, nbits) q x - xq # 量化误差 snr_sim 10 * np.log10(np.sum(x ** 2) / np.sum(q ** 2)) print(f仿真 SNR {snr_sim:.2f} dB, 理论 {6.02 * nbits 1.76:.2f} dB)这段代码里三个参数必须交代清楚。nbits决定 LSB 大小改它就直接改 SNRM取质数且保证M/N不可约是为了让信号频率正好落在 FFT 的某一个 bin 上避免频谱泄漏把噪声底抬高这一步在行业里叫相干采样输入幅度取0.999而不是 1.0是为了避开削波同时只损失约 0.01 dB 的信噪比。跑出来的结果应该在 73.9 dB 附近和理论值差不到 0.2 dB。如果差得远先检查M是否和N互质。2.3 时钟抖动与非线性的建模方法理想模型对不上真实器件主要差在两处采样时刻不是均匀的量化台阶也不是等宽的。时钟抖动的处理是把采样时刻变成随机变量。抖动引入的误差近似等于信号导数乘以时间偏差对正弦输入可以直接给出信噪比上限rng np.random.default_rng(1) sigma_t 1e-12 # 1 ps RMS 时钟抖动 x_j 0.999 * np.sin(2 * np.pi * fin * (t rng.normal(0, sigma_t, N))) _, xq_j adc_ideal(x_j, nbits) snr_j 10 * np.log10(np.sum(x_j ** 2) / np.sum((x_j - xq_j) ** 2)) # 解析上界SNR -20*log10(2*pi*fin*sigma_t) snr_limit -20 * np.log10(2 * np.pi * fin * sigma_t) print(f含抖动仿真 {snr_j:.2f} dB, 抖动上界 {snr_limit:.2f} dB)在 2.99 MHz 输入、1 ps 抖动的条件下这个上界大约 94.5 dB折算成位数约 15.4 位。结论很直接12 位 ADC 配 1 ps 抖动完全够用但如果你打算上 16 位抖动就得压到 0.2 ps 以下否则抖动会接管噪声预算。这个换算在选时钟源的时候比任何仿真都省事。非线性则要改量化阈值而不是改输出码字。给每个跳变电平加一个以 LSB 为单位的偏差就得到带 DNL 的模型def adc_with_dnl(x, nbits, dnl, vref1.0): dnl 长度应为 2**nbits - 1单位 LSB lsb 2.0 * vref / 2 ** nbits k np.arange(2 ** nbits - 1) th (-2 ** (nbits - 1) 0.5 k) * lsb dnl * lsb th np.sort(th) # 防止非单调 code np.searchsorted(th, x).astype(np.int64) xq (code - 2 ** (nbits - 1)) * lsb return code, xq dnl np.zeros(2 ** nbits - 1) dnl[[100, 101, 102]] [0.4, -0.5, 0.6] # 局部台阶不齐 _, xq_dnl adc_with_dnl(x, nbits, dnl)np.sort那一行别省。当 DNL 超过 ±1 LSB 时阈值会交叉转换曲线出现非单调searchsorted会给出错误的码字序列。真实器件说明书里的「无丢码」保证本质上就是在约束这件事。3. DA转换器仿真从码字重建模拟波形AD 是把模拟世界压成数字DA 是反过来把数字重新铺回模拟世界。方向反了误差的形态也换了DA 的主要敌人不是量化噪声而是零阶保持带来的频响凹陷、重建滤波器的相位失真以及电流舵单元之间的失配。3.1 零阶保持的频响与 sinc 衰减定量关系DAC 每一拍输出一个码字对应的电平并保持整整一个更新周期这就是零阶保持。它在频域上的响应是一个 sinc 函数def zoh_gain_db(f, fs): 零阶保持的幅度响应单位 dB return 20 * np.log10(np.abs(np.sinc(f / fs))) fs_dac 100e6 for f in [1e6, 10e6, 25e6, 40e6, 50e6]: print(f{f/1e6:4.1f} MHz : {zoh_gain_db(f, fs_dac):.2f} dB)跑出来在 50 MHz 处大约是 -3.92 dB在 25 MHz 处约 -0.9 dB。注意np.sinc的定义是sin(πx)/(πx)恰好就是零阶保持的归一化响应不用自己写。这个衰减意味着如果信号带宽一直用到奈奎斯特频率高频部分的幅度会被削掉将近 4 dB幅频平坦度直接崩掉。常见的应对是两种要么提高 DAC 更新率做数字过采样让信号落在远低于 fs/2 的位置要么在数字端加一个反 sinc 补偿滤波器把这段衰减提前抬回来。3.2 重建滤波器的设计参数与仿真验证在 Python 里验证重建滤波器用scipy.signal出系数就够了from scipy import signal import numpy as np fs_dac, fc, osr 100e6, 12e6, 8 n_taps 129 # 在过采样率 osr 下设计等效截止频率 fc b signal.firwin(n_taps, fc / (fs_dac * osr / 2), windowkaiser, fsfs_dac * osr) w, h signal.freqz(b, worN8192, fsfs_dac * osr) mag_db 20 * np.log10(np.abs(h) 1e-12) # 通带波纹与阻带抑制 pass_idx w fc stop_idx w 2 * fc print(f通带波动 {mag_db[pass_idx].max() - mag_db[pass_idx].min():.3f} dB) print(f阻带抑制 {mag_db[stop_idx].max():.1f} dB)三个参数值得逐个盯。fc是通带边界取信号最高频率再留 20% 余量osr是过采样倍数它把数字滤波器的过渡带拓宽了osr倍所以同样的抽头数能换来更陡的滚降n_taps直接决定阶数阶数不够时阻带抑制先崩。判断滤波器是否合格看输出波形的两个数通带波纹控制在 0.1 dB 以内阻带抑制在2·fc之后要压到 -70 dB 以下否则 DAC 的采样镜像会漏进后级放大器。另外把零阶保持跟重建滤波器串起来算总响应时别忘了一个容易忽略的点反 sinc 补偿最好放在数字滤波器内部一起设计而不是在模拟端单独加一级否则两级滤波器的相位叠加会让群时延变得很难看。3.3 DAC 失配与毛刺的建模方法真实 DAC 的每一位权重不会精确等于 2 的幂次方。给每个比特加一个相对偏差就能模拟出这种失配def dac_binary(codes, nbits, sigma0.0, seed0): rng np.random.default_rng(seed) w 2.0 ** np.arange(nbits, dtypefloat) w w * (1 rng.normal(0, sigma, nbits)) # 单元权重失配 bits (codes[:, None] np.arange(nbits)) 1 y bits w return 2 * y / w.sum() - 1 # 归一化到 [-1, 1)sigma建议从 0.001 起按 0.001 步进往上加观察 SFDR 掉到多少。这里有个反直觉的现象失配带来的不是噪声底抬升而是固定的谐波杂散所以蒙特卡洛跑出来的均值不会好看得看谱线位置。这也是为什么高精度 DAC 普遍改用温度计码或分段结构——把大权重的跳变拆成多个小单元的轮转失配被平均掉。毛刺建模稍微麻烦一点因为它是每个码字跳变处的瞬态def add_glitch(x, alpha0.05, tau0.3): y x.copy() d np.diff(x, prependx[0]) for i in np.where(np.abs(d) 1e-9)[0]: n np.arange(len(y) - i) y[i:] alpha * d[i] * np.exp(-n / tau) return yalpha是毛刺相对跳变幅度的比例tau控制衰减快慢用采样点数计。这个模型是近似的但它能告诉你一件事当输入是高频大幅信号时毛刺能量集中在跳变最密的时刻用 FFT 分析会表现为宽带底噪抬升。用 RZ归零编码或增加输出端的去毛刺采样保持电路一般都是针对这一点。4. 用 FFT 频谱算 SNR、ENOB 和 SFDR模型搭完了接下来得有统一的尺子去量。AD/DA 仿真的通用做法是对输出序列做 FFT把能量分类信号 bin、谐波 bin、噪声底然后分别算比值。这一步最容易出错因为窗函数、bin 数和直流剔除方式稍有不同同一组数据能算出差 5 dB 的结果。4.1 相干采样条件与窗函数的选择相干采样的条件很明确fin/fs M/NM为质数N为 2 的幂。满足这个条件时信号能量完全落在单个 bin 内不需要加窗频谱最干净也最适合做精确的指标计算。代价是输入频率被约束死了测真实器件的时候很难凑。非相干采样就必须加窗。窗函数的选择看两个指标窗类型主瓣宽度 (bin)旁瓣抑制 (dB)适用场景Rectangular2-13相干采样Hann4-31通用测量泄漏适中Blackman-Harris8-92小信号杂散测量需高动态范围Flat-top10-93幅度精度优先的幅频测试主瓣越宽靠近信号 bin 的杂散就越容易被「藏」进主瓣里算不到所以测小杂散时必须用旁瓣抑制好的窗型同时把信号附近的若干个 bin 从噪声统计里剔除。4.2 从 FFT 频谱算 SNR、ENOB 和 SFDR 的完整脚本把前面几节的结果接上一个完整的指标计算函数如下def spec_metrics(x, fs, winrect, sig_bins5, dc_bins4): 从时域序列计算 SNR / ENOB / SFDR N len(x) if win hann: w np.hanning(N) elif win bh: w np.blackman(N) else: w np.ones(N) # 相干采样时不加窗 X np.abs(np.fft.rfft(x * w)) ** 2 freq np.fft.rfftfreq(N, 1 / fs) k0 np.argmax(X[1:]) 1 # 跳过直流找信号 bin lo, hi max(k0 - sig_bins, 1), min(k0 sig_bins 1, len(X)) Ps X[lo:hi].sum() # 信号及邻近泄漏 Xn X.copy() Xn[lo:hi] 0 Xn[:dc_bins] 0 # 剔除直流与近零频 Pn Xn.sum() # 剩余全部算噪声杂散 snr 10 * np.log10(Ps / Pn) enob (snr - 1.76) / 6.02 sfdr 10 * np.log10(Ps / Xn.max()) # 最大杂散相对信号 spur_f freq[np.argmax(Xn)] return dict(snrsnr, enobenob, sfdrsfdr, spur_fspur_f) print(spec_metrics(xq, fs)) # 前面 12 位 ADC 的输出sig_bins取 5 是折中太小会把信号自身的泄漏算进噪声导致 SNR 偏低太大又会把真实的近端杂散吃掉导致结果虚高。用矩形窗相干采样时取 3 到 5 都合理用 Blackman-Harris 这类窗时因为主瓣宽达 8 个 bin至少要放到 10。dc_bins是剔除直流和超低频的分量实际板上往往还有 50 Hz 工频和电源纹波测 ADC 动态指标时必须排除否则算出来的全是这些。spur_f这个返回值容易被忽略但它才是定位问题的关键。SFDR 差的时候先看杂散落在哪个频率上落在信号频率的整数倍上是量化器非线性落在M/N相关的固定位置是采样时钟的杂散耦合落在随机的低频率上多半是电源或参考电压的问题。4.3 指标对照表与三个常见误读指标定义典型期望12 位主要来源SNR信号功率 / 积分噪声功率7074 dB量化噪声、热噪声SNDR信号功率 /噪声 谐波6873 dB上述两者之和SFDR信号功率 / 最大杂散80 dBc 以上DNL、失配、时钟ENOB(SNDR − 1.76) / 6.021111.8 位综合三个误读值得点出来。第一把 SNR 等同于 ENOB实际上一旦有谐波SNDR 才是有效位数的来源用 SNR 算出的 ENOB 会虚高一到两位。第二忽略 FFT 长度的影响N越大噪声底的统计越接近真实用 1024 点算 16 位 ADC 的噪声底基本没意义N至少取 8192最好 65536。第三混淆 dBFS 和 dBc前者相对满量程后者相对信号本身做器件对比时必须统一否则数据没法横向比。5. 联合链路验证与仿真发散排查单测完 AD 和 DA最后一步是把两者串起来跑闭环。这一步的价值在于暴露那些单独测发现不了的耦合问题比如 AD 的量化误差被 DA 重建后放大或者 DA 的毛刺通过反馈回路重新耦合回采样端。做法很直接把 AD 的输出码字直接喂给 DA 模型中间不加任何补偿看重建波形和原始模拟波形的差# 闭环前文 12 位 ADC 的输出码直接送进 12 位 DAC xq_rec dac_binary(code, nbits12, sigma0.002, seed42) * 1.0 err_closed x - xq_rec snr_closed 10 * np.log10(np.sum(x ** 2) / np.sum(err_closed ** 2)) print(f闭环 SNR {snr_closed:.2f} dB)闭环跑完通常会看到比单测低 3 到 6 dB这部分损失来自量化误差的二次叠加和失配。如果损失超过 8 dB说明两条链路的噪声预算分配不平衡得回去重新看是哪一级吃掉了余量。蒙特卡洛扫sigma从 0.0005 到 0.01每次跑 100 组取均值和标准差就能把失配预算反推出来直接对应到器件选型上。关于「仿真发散」在 AD/DA 这类模型里通常不是数值积分炸掉而是几个更隐蔽的表现形态。下面这张排查表是我一般会按顺序过一遍的现象典型原因处理办法FFT 本底随点数增加不断抬高非相干采样且未加窗改用互质 M/N或换 Blackman-Harris 窗并加宽 sig_bins12 位 ADC 仿真只有 62 dB输入幅度不足或直流未剔除幅度取 0.99 满量程dc_bins至少设 4DAC 波形在 fs/2 附近凹陷零阶保持 sinc 衰减未补偿提高过采样比或数字端加反 sinc 滤波器瞬态仿真跑到毫秒级不收敛码字跳变处步长被反复压缩设最大步长上限给 DAC 输出加最小转换时间换种子结果差很多失配/抖动样本数不够蒙特卡洛跑 100 次以上看均值与标准差最后这一条提醒尤其值得留意罚函数式的「换一组随机数结果就变脸」往往说明样本量不足而不是模型本身有问题。把蒙特卡洛次数从 10 提到 200如果标准差收敛到 0.3 dB 以内模型就算是可信的了。相比之下一味去调步长或者换求解器反而是在给一个统计上还没站稳的结论做无用功。本文还有配套的精品资源点击获取
网站建设高端定制企业官网