资讯详情

基于FPGA的数字示波器设计:从ADC选型到触发与缓存实现

📅 2026/10/14 14:34:49 | 华诺云谱 👁 阅读
基于FPGA的数字示波器设计:从ADC选型到触发与缓存实现
简介这份资源面向FPGA初学者与数字系统设计爱好者提供一套基于Verilog实现的数字示波器完整工程帮助理解从信号采集到波形显示的硬件设计全流程。包内共3726个文件以cdb、hdb等Quartus II工程数据库文件为主辅以tdf原理图描述、36个v格式Verilog源码、qip与qsf工程配置、mif存储器初始化文件及pof、sof等编程下载文件另含少量jpg、html说明文档压缩包约22.55MB。已有836人学习下载说明该案例在FPGA入门与课程设计场景中具备一定参考价值。工程覆盖采样控制、FIFO缓冲、触发逻辑、VGA显示与按键控制等模块读者可借助Quartus II完成编译、仿真、综合与时序分析对照源码理解时钟管理与数据流调度并在此基础上修改采样率、时基或触发条件快速搭建自定义示波器原型适合作为课程设计、毕业设计或自学练手项目。1. 基于FPGA的数字示波器为什么它成了硬件工程师的“必修项目”如果你手头只有一块入门级FPGA开发板、一个简易ADC模块和一台老旧的模拟示波器却想抓取并分析几十兆赫兹的SPI波形基于FPGA的数字示波器就是最值得动手的方向。它解决的核心问题是用可编程逻辑实现高速数据采集、触发判断和缓存控制把传统示波器里专用芯片干的活用FPGA的并行能力重新做一遍。适合谁适合已经掌握Verilog基础、想从“点灯流水线”跨入“真实信号链”的嵌入式开发者也适合需要自定义触发逻辑、不想被商用示波器功能限制的测试工程师。这个方向不是复刻一台泰克而是让你理解采样率、带宽、触发深度和存储架构之间怎么互相拉扯——这些参数一旦吃透你再看任何示波器规格书都不会被玄学参数唬住。2. 从模拟前端到FPGA信号链的四个关键选型2.1 为什么ADC选型决定了整个系统的天花板数字示波器的性能上限在模拟前端和ADC这一层就被锁死了。FPGA再快也救不回一个采样率不足或输入带宽不够的ADC。常见做法是选并行输出LVDS或CMOS接口的ADC比如8位、最高100 MSPS左右的型号这样FPGA的IO可以直接吞数据不需要额外解串。注意两个参数采样率必须大于信号最高频率分量的2倍但实际工程中为了看到像样的方波边沿通常要5倍以上。另一个是输入带宽ADC的模拟带宽要覆盖你关心的谐波否则测出来的上升沿是假的。我一般会按这个顺序倒推先确定要测的最大信号频率乘以5得到目标采样率再选ADC然后看ADC输出接口类型决定FPGA的IO标准和接收逻辑。如果ADC是串行LVDSFPGA端就得做ISERDES解串代码复杂度会上去但引脚少、抗干扰好。如果是并行CMOS直接用IDDR原语在时钟双边沿采样就行适合新手起步。2.2 FPGA内部数据通路从采样到缓存的流水线设计数据进FPGA之后不能直接往RAM里塞必须经过一条流水线采样对齐、触发判断、写地址生成、缓存写入。下面是一个最小可用的采集通路代码骨架假设ADC是8位并行输出采样时钟50 MHz。module acq_pipeline ( input wire adc_clk, // ADC随路时钟50MHz input wire sys_rst_n, // 低电平复位 input wire [7:0] adc_data, // ADC并行数据 input wire trig_en, // 触发使能 input wire [7:0] trig_level, // 触发电平 output reg [11:0] wr_addr, // 缓存写地址 output reg wr_en, // 缓存写使能 output reg [7:0] wr_data // 写入缓存的数据 ); reg [7:0] adc_data_r1, adc_data_r2; reg trig_hit; reg [1:0] trig_state; // 两级寄存器对齐ADC数据降低亚稳态风险 always (posedge adc_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin adc_data_r1 8d0; adc_data_r2 8d0; end else begin adc_data_r1 adc_data; adc_data_r2 adc_data_r1; end end // 上升沿触发判断前一拍低于电平当前拍高于电平 always (posedge adc_clk or negedge sys_rst_n) begin if (!sys_rst_n) trig_hit 1b0; else if (trig_en (adc_data_r1 trig_level) (adc_data_r2 trig_level)) trig_hit 1b1; else trig_hit 1b0; end // 写地址生成触发后从0开始写写满4096深度后停止 always (posedge adc_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin wr_addr 12d0; wr_en 1b0; wr_data 8d0; end else if (trig_hit) begin wr_addr 12d0; wr_en 1b1; wr_data adc_data_r2; end else if (wr_en) begin if (wr_addr 12d4095) begin wr_en 1b0; wr_addr wr_addr; end else begin wr_addr wr_addr 1b1; wr_data adc_data_r2; end end end endmodule这段代码的逻辑说明adc_data_r1和adc_data_r2构成两级同步避免跨时钟域或异步输入带来的亚稳态。触发判断用的是“前一拍低于触发电平、当前拍高于触发电平”这是最基础的上升沿检测实际产品里还会加迟滞比较和数字滤波。写地址在触发命中时清零然后每个采样时钟递增写满4096个点后自动停写等待上位机读取。参数方面trig_level是8位无符号数对应ADC的量化范围wr_addr位宽12位对应4096深度这个深度直接决定你能看多长的波形窗口。2.3 触发模块边沿触发只是起点迟滞和脉宽触发才是实战刚需边沿触发在实验室里够用但一到现场就翻车。信号上但凡叠一点噪声触发电平附近就会反复穿越波形根本稳不住。血泪经验是必须加迟滞。做法很简单设两个电平——高阈值和低阈值只有信号从低于低阈值上升到高于高阈值才算一次上升沿触发。这样噪声幅度只要小于迟滞窗口就不会误触发。再进一步是脉宽触发用来抓取窄毛刺或特定宽度的脉冲。实现思路是用一个计数器在信号高于触发电平时计数当计数超过设定值时输出触发。这个功能在调试电源纹波或通信总线异常时特别有用商用示波器里叫“脉宽触发”你自己在FPGA里实现也就几十行代码的事。2.4 存储架构为什么块RAM深度和位宽要一起算FPGA内部的块RAM是采集缓存的首选但深度和位宽不能拍脑袋定。假设你要存4096个采样点每个点8位那就是4096×832768位。一块9Kb的块RAM不够需要4块拼起来。如果ADC是12位那就要6块。常见做法是用双口RAM一口写采集数据另一口给上位机读读写时钟可以不同这样采集和上传互不干扰。注意一个坑块RAM的写使能如果一直拉高地址回绕时会覆盖旧数据。如果你想要“触发后保留触发点前后的波形”就得用预触发机制——在触发到来之前就一直在写但地址循环触发后继续写够预触发深度再停。这个逻辑比简单停写复杂但它是示波器“能看到触发前信号”的关键。3. 用Verilog把采集、触发和缓存串起来可复现的工程步骤3.1 工程目录与模块划分一个可综合的工程不需要花哨的目录但模块边界要清晰。我一般这样分adc_if.vADC接口负责采样对齐和跨时钟域处理trigger.v触发判断支持边沿和脉宽两种模式capture_ctrl.v采集控制管理写地址、预触发深度和停写条件ram_dp.v双口块RAM例化写口接采集读口接上位机uart_tx.v串口上传把缓存数据打包发出去top.v顶层把上面几个模块连起来这样分的好处是触发逻辑改的时候不用动采集控制上位机协议改的时候不用碰ADC接口。每个模块单独仿真问题定位快。3.2 预触发与后触发深度的参数化实现预触发深度是示波器好不好用的分水岭。下面这段代码展示了怎么用参数控制预触发比例。module capture_ctrl #( parameter MEM_DEPTH 4096, // 总缓存深度 parameter PRE_TRIG_PCT 50 // 预触发占50% )( input wire clk, input wire rst_n, input wire trig_hit, input wire [7:0] adc_data, output reg [11:0] ram_waddr, output reg ram_we, output reg [7:0] ram_wdata, output reg cap_done ); localparam PRE_DEPTH MEM_DEPTH * PRE_TRIG_PCT / 100; localparam POST_DEPTH MEM_DEPTH - PRE_DEPTH; reg [11:0] pre_cnt; reg [11:0] post_cnt; reg armed; always (posedge clk or negedge rst_n) begin if (!rst_n) begin ram_waddr 12d0; ram_we 1b0; ram_wdata 8d0; pre_cnt 12d0; post_cnt 12d0; armed 1b1; cap_done 1b0; end else if (armed) begin // 触发前循环写地址回绕 ram_we 1b1; ram_wdata adc_data; ram_waddr (ram_waddr MEM_DEPTH-1) ? 12d0 : ram_waddr 1b1; if (trig_hit) begin armed 1b0; post_cnt 12d0; end end else begin // 触发后继续写够后触发深度 if (post_cnt POST_DEPTH) begin ram_we 1b1; ram_wdata adc_data; ram_waddr (ram_waddr MEM_DEPTH-1) ? 12d0 : ram_waddr 1b1; post_cnt post_cnt 1b1; end else begin ram_we 1b0; cap_done 1b1; end end end endmodule逻辑说明armed为1时采集一直在跑地址循环回绕这就是预触发阶段。trig_hit到来后armed拉低进入后触发阶段继续写POST_DEPTH个点后停写cap_done拉高通知上位机可以读了。参数PRE_TRIG_PCT设50就是触发点前后各存一半调试时想多看触发前的信号就把它调大比如80。3.3 上位机读取串口打包格式与Python解析脚本FPGA采集完数据得传到电脑上。串口是最简单的虽然慢但够用。打包格式我一般用2字节帧头0xAA55 2字节数据长度 N字节采样数据 1字节校验和。下面是对应的Python解析脚本。import serial import struct import matplotlib.pyplot as plt ser serial.Serial(COM3, 115200, timeout2) def read_frame(): # 找帧头 while True: b ser.read(1) if b b\xaa: b2 ser.read(1) if b2 b\x55: break # 读长度 len_bytes ser.read(2) data_len struct.unpack(H, len_bytes)[0] # 读数据 payload ser.read(data_len) # 读校验 checksum ser.read(1)[0] calc sum(payload) 0xFF if calc ! checksum: print(checksum error) return None return list(payload) frame read_frame() if frame: plt.plot(frame) plt.xlabel(sample index) plt.ylabel(ADC code) plt.title(FPGA oscilloscope capture) plt.grid(True) plt.show()参数说明波特率115200对应每秒约11.5KB数据4096个点要传约0.36秒能接受。如果嫌慢可以升到921600但FPGA端UART发送模块的时钟分频要跟着改。校验和用累加和简单但够用工业环境建议换CRC16。3.4 从仿真到板级ILA和真实信号的双重验证代码综合通过不代表板子上能跑。我一般分两步先用Vivado或Quartus自带的ILA抓内部信号确认触发命中、写地址递增、数据对齐都正常再用信号发生器给一个已知频率和幅度的正弦波看上位机画出来的波形频率和幅度对不对。如果频率对但幅度不对查ADC参考电压和量化公式如果波形有毛刺查电源和地平面。这一步没有捷径示波器测示波器听起来绕但必须做。4. 避坑与排查基于FPGA的数字示波器最容易翻车的五个地方4.1 采样数据错位现象是波形周期性抖动原因是ADC时钟和FPGA采样时钟相位不对现象抓一个稳定正弦波画出来每个周期都在左右抖像被调制了一样。原因ADC随路时钟和FPGA内部逻辑时钟之间存在相位差直接用同一个时钟沿采样建立保持时间不够。解决用IDDR原语在时钟双边沿采样或者把ADC时钟反相后再采。更稳妥的做法是用MMCM生成一个与ADC时钟同频但相位可调的时钟用相位偏移对准数据窗口中心。4.2 触发一直不命中现象是cap_done永远不拉高原因是触发电平设在了信号范围之外现象信号明明有但触发指示灯不亮缓存里全是旧数据。原因trig_level是8位无符号数如果信号只在0到50之间摆动而你把触发电平设成了200那永远等不到上升沿。解决先用ILA看ADC原始数据的最大值和最小值把触发电平设在中间偏下一点。另外检查触发条件里的比较方向上升沿触发是“前低后高”写反了就成了下降沿。4.3 块RAM写冲突现象是波形中间有一段恒定值原因是读写同时访问同一地址现象采集回来的波形中间有一段平直的线像被削顶了。原因双口RAM的读写地址在某一时刻相同读优先或写优先配置不对导致写入的数据被读操作打断。解决在RAM例化时明确设置写优先模式或者用独立的双口RAM写口和读口完全分开。如果用的是简单双口RAM确保读时钟和写时钟不同源避免同相竞争。4.4 串口丢数据现象是上位机解析出的波形长度不对原因是波特率误差累积现象Python脚本读到的数据长度偶尔少几个字节波形末尾被截断。原因FPGA端UART发送模块的分频计数有累积误差或者上位机串口缓冲区溢出。解决FPGA端用精确的波特率分频比如50MHz时钟下115200波特率的分频值是434上位机端把超时设长一点读数据用循环直到读满长度。如果还丢加硬件流控或者降低波特率。4.5 电源噪声串入采集通道现象是小信号测量时底噪很大原因是数字地和模拟地没有分开现象测一个10mV的小信号波形上叠了几十毫伏的毛刺。原因FPGA的开关噪声通过地平面耦合到了ADC的模拟输入端。解决ADC的模拟地和FPGA的数字地单点连接中间用磁珠或0欧电阻隔离ADC电源加LC滤波信号输入端加RC低通截止频率设在你需要的带宽略高一点的位置。这个坑最隐蔽因为代码仿真全对一上板就翻车。5. 进阶技巧把死区时间变成等效采样率5.1 等效采样用低速ADC看高速信号的唯一合法手段如果你的ADC只有50 MSPS但想测100 MHz以上的周期信号等效采样是唯一出路。原理很简单每次触发后延迟一个微小的时间增量再采样多次采集后把点拼起来等效采样率可以做到GHz级别。实现上用FPGA内部的MMCM或PLL做精细相位偏移每次触发后相位步进1/256个时钟周期采集256次后拼出一整周期波形。// 相位步进控制示意每次触发后更新相位偏移 reg [7:0] phase_step; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_step 8d0; else if (cap_done) phase_step phase_step 1b1; // 每次采集完步进一档 end // MMCM动态相位偏移接口以Xilinx为例 // 实际工程中通过DRP端口修改MMCM的相位偏移寄存器这个技巧的代价是采集时间变长256次触发才能拼出一个周期所以只适合稳定周期信号。但它能把一块50 MSPS的ADC用出1 GSPS的效果性价比极高。5.2 用Python做后端分析FFT和上升沿测量采集回来的数据别只画个时域波形就完事。用numpy做FFT能看到信号的谐波分布判断ADC的量化噪声和前端滤波效果。上升沿测量更实用找到波形从10%到90%的采样点索引乘以采样周期就是上升时间。下面这段代码可以直接抄。import numpy as np def measure_rise_time(waveform, sample_period): vmin, vmax min(waveform), max(waveform) v10 vmin 0.1 * (vmax - vmin) v90 vmin 0.9 * (vmax - vmin) idx10 next(i for i, v in enumerate(waveform) if v v10) idx90 next(i for i, v in enumerate(waveform) if v v90) return (idx90 - idx10) * sample_period # 假设采样周期20ns即50MSPS rt measure_rise_time(frame, 20e-9) print(frise time: {rt*1e9:.2f} ns)参数说明sample_period是采样间隔50 MSPS对应20 ns。这个测量精度受限于采样率如果上升时间只有几纳秒50 MSPS根本抓不到必须用等效采样或者换高速ADC。5.3 一个我踩过的坑别在触发逻辑里做除法最后说一个血泪教训。早期版本我在触发模块里用除法算预触发深度综合出来时序根本跑不到50 MHz逻辑级数太深。后来改成参数化常量在综合前就算好时序立刻干净。FPGA里除法、取模、浮点都是奢侈品能用移位和查表就别用除法。这个习惯我保持到现在每次写Verilog之前先问自己这个运算能不能在综合前算完希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑