基于FPGA的AD9238高速数据采集与VGA波形显示系统 简介FPGA采样AD9238数据并通过VGA波形显示例程是一套完整的Verilog逻辑源码工程面向FPGA学习者与数字示波器入门开发者基于Cyclone IV E系列EP4CE6F17C8芯片和Quartus 17.1环境演示AN9238型ADC65MHz采样率、12位精度双通道数据采集与VGA波形显示是数字示波器的雏形设计。资源共168个文件压缩包约5.03MB以Quartus工程文件qpf、qsf、sdc、sof、Verilog源码.v、.tdf、综合与时序报告.rpt、.map及PDF/readme说明文档为主目录结构清晰便于按模块检索。已有1857人学习浏览。通过该工程可掌握ADC采样时序控制、双通道数据缓存、VGA时序生成、网格绘制与波形叠加等关键设计方法工程内还包含模块化RTL代码和详细文档说明适合在真实板卡上验证显示效果是理解FPGA数据采集与视频显示链路的高质量参考。1. 用FPGA做AD9238高速采集和VGA波形显示AD9238是一颗12位双通道ADC配上FPGA和VGA接口可以搭出能看波形的采集系统。这比用单片机加TFT屏的方案更有意思因为FPGA能在同一个系统里同时处理采样、缓存和显示扫描MCU很难在微秒级响应ADC并同步刷新一屏。做这套东西最常遇到的不是代码写不出而是采样数据在哪个时钟沿有效、显示端怎么从RAM读回、两个时钟域怎么握手。这里只讨论AD9238采集到VGA波形显示这条主线先解决数据进得来再解决数据出得去最后用Quartus工程里常见的调试手段验证。适合刚学完Verilog语法、想动手做完整FPGA系统的读者。2. AD9238采样接口从数据手册到Verilog采集模块2.1 先看AD9238的引脚和输出数据格式写采集代码前不能直接写posedge dco先看数据手册里DCO与数据的关系。AD9238的典型接法是FPGA用PLL产生编码时钟给ADCADC在时钟控制下采样同时把12位数据以及DCO回送到FPGA。DCO是数据同步时钟数据手册会给出DCO到数据有效的最小/最大时间。通道A和通道B同时输出12根线分别接FPGA引脚。信号方向说明ADC_CLK输入到ADC编码时钟由FPGA PLL输出CH_A / CH_B模拟输入两路采样通道DCO输出到FPGA数据时钟和输出数据同步DA0~DA11输出到FPGA通道A的数据DB0~DB11输出到FPGA通道B的数据MODE配置脚控制输出编码格式MODE引脚决定输出是二进制偏移码还是二进制补码。偏移码下中间值2048对应0V0对应负满幅4095对应正满幅补码下则需要把最高位取反。很多设计者看到波形上下颠倒其实是编码格式没核对。用SignalTap抓数时如果波形中线在2048附近说明偏移码正常如果看到0和4095跳变排除硬件短路后就该怀疑MODE接反了。2.2 用DCO边沿锁存AD9238的12位数据DCO和AD9238数据一样来自ADC内部用它做采样时钟可以保证数据到达FPGA后满足建立时间。下面是最小采集模块只完成锁存和有效标志。// adc_capture.v module adc_capture ( input wire rst_n, input wire dco, // ADC回传的数据时钟 input wire [11:0] adc_data, // 12位并行数据 output reg [11:0] sample_data, output reg sample_valid ); always (posedge dco or negedge rst_n) begin if (!rst_n) begin sample_data 12d0; sample_valid 1b0; end else begin sample_data adc_data; sample_valid 1b1; end end endmodule代码逻辑很直接DCO上升沿把AD9238的数据锁进sample_data并把sample_valid拉高。sample_valid在后续写RAM时作为写使能。这里没有把DCO再做一次打拍因为DCO本身是时钟信号直接接在敏感列表上最稳定如果需要捕获下降沿把posedge dco改成negedge dco但前提是数据手册说明数据在下降沿稳定。不要用组合逻辑生成脉冲再去触发采样那样会产生毛刺在Quartus中容易看到时序违规。通道A和通道B只需例化两次数据总线不同即可。两个通道可以同时采样因此不需要ch_sel信号。若你想让两个通道分时显示那是在VGA侧做通道选择而不是在采集侧。2.3 PLL产生采样时钟和Quartus input delay约束ADC时钟一般由ALTPLL产生。比如系统时钟50MHzAD9238工作在25MSPS就生成25MHz时钟接到ADC_CLK引脚。如果VGA也用25MHz像素时钟和采样时钟频率一致对跨时钟域处理会方便很多但仍然存在相位差。在Quartus中需要让时序分析知道DCO是采样时钟。SDC中常见写法create_clock -name adc_dco -period 40.000 [get_ports {dco}] set_input_delay -clock adc_dco -max 4.000 [get_ports {adc_data[*]}] set_input_delay -clock adc_dco -min 0.000 [get_ports {adc_data[*]}]这里set_input_delay的max和min来自AD9238数据手册中DCO到数据输出的延迟。max设为数据稳定相对DCO沿的时间min设为数据保持的相对时间单位ns。不约束时Quartus会把adc_data当作某个内部时钟域的异步数据综合结果可能没问题但布线之后偶尔抓错位。一个容易踩的坑是DCO没有接到FPGA的专用时钟引脚。普通IO也能驱动always (posedge dco)但布线延迟大25MHz以下尚可采样率高时建议把DCO引到全局时钟引脚或用PLL的外置时钟输入。3. 跨时钟域缓冲与VGA波形绘制3.1 为什么采样数据不能直接接VGA把AD9238的输出直接接到VGA显示逻辑是不行的。采样时钟和VGA像素时钟虽然可能都是25MHz但相位和抖动不同直接读数据会看到画面闪烁或花屏。另外VGA一秒钟刷新60帧采样数据却在持续更新必须有一个存储区域保存最近一段时间的采样值。常见做法是用双口RAM。写端口以sample_clk为时钟读端口以vga_clk为时钟。使用Quartus的RAM IP时参数大致如下参数建议值存储深度2048数据宽度12写时钟adc_dco读时钟vga_clk读地址时钟使能始终使能深度不用太大波形显示需要的是最近几个毫秒的数据。2048点能覆盖一场图像的六七行足够观察周期性信号。若板载SRAM富余可以加深到8192。3.2 ADC码值到屏幕坐标的移位换算640x480分辨率下显示区域一般只放中间的一部分。把波形区域设为宽640、高25612位ADC值直接截高8位再反相就得到y坐标。查表或乘法器在这里都没必要。wire [7:0] y_adc; assign y_adc ~adc_data_q[11:4]; // 取高8位并反相 wire [9:0] y_out 10d64 {2b00, y_adc};adc_data_q是从RAM读出的数据已经延迟了一个读时钟周期。y_out落在64到319之间屏幕上方留了64行给文字或网格。若想观察低幅信号可以先做减法再截位把ADC码值中段放大但移位方法在这种基础工程里最不容易出错。3.3 VGA行场时序和波形点亮信号VGA 640x48060的时序参数比较固定记住行和场两部分分隔即可参数像素周期数行同步96行后沿48行有效数据640行前沿16行总计800场同步2场后沿33场有效数据480场前沿10场总计525用两个计数器从0循环累加就得到h_cnt和v_cnt。有效区内的x坐标由h_cnt减去行同步加后沿得到RAM读地址来自该x。波形显示逻辑要比较当前扫描行的v_cnt是否等于这一列上的y值。wire [9:0] x_pos h_cnt - 10d144; wire [9:0] x_addr x_pos; // y_wave是x_addr读RAM后延迟一拍的输出 reg [9:0] v_cnt_d; always (posedge vga_clk) v_cnt_d v_cnt; wire wave_match (v_cnt_d y_wave) h_active_region v_active_region; assign vga_red wave_match ? 3b111 : 3b000; assign vga_green wave_match ? 3b111 : 3b000; assign vga_blue wave_match ? 3b111 : 3b000;v_cnt_d用来补偿RAM读出的延迟。如果不用它每列都会错一个像素波形整体右移。比较时v_cnt是当前扫描行y_wave是该x坐标下波形点的y两者相等时让该像素显示白色。这就是VGA波形显示的本体在每个列地址上当前行的y坐标命中波形y值就画一个点。3.4 用竖线填充让波形不出现断点单点显示在低频波形上会有明显断线。显示高频方波时陡峭边沿的y差值超过一行屏幕上只看到离散点。比较常见的做法是把点变粗wire wave_match (v_cnt_d (y_wave - 10d1)) (v_cnt_d (y_wave 10d1));这样每个列位置画3个像素高度的点视觉连续感好很多。资源占用几乎没有增加只是比较逻辑多了一个加法。若要求更严格的连线可以在RAM里同时存上一列的y值在VGA时钟下计算两点之间哪些行需要填充。不过对基础例程来说3像素高度足够且不会像垂直直线那样把波形区域涂满。4. Quartus工程组织、仿真与板上调试4.1 顶层模块划分和引脚分配一个能维护的Quartus工程至少包含这几部分top.v做例化adc_capture.v处理AD9238vga_timing.v生成VGA时序vga_waveform.v做波形比较再包装一个双口RAM。这样拆分方便仿真和排查问题。顶层里链接ADC和RAM的例子adc_capture u_adc_a ( .rst_n (rst_n), .dco (dco_pin), .adc_data (adc_a_data), .sample_data (sample_a), .sample_valid (sample_valid_a) ); wave_buffer u_wavebuf ( .wrclk (sample_clk), .wraddr (wr_addr), .wrdata (sample_a), .wren (sample_valid_a), .rdclk (vga_clk), .rdaddr (rd_addr), .q (buffer_out) );引脚分配在Pin Planner中完成。AD9238的12根数据线建议放在同一个Bank并统一I/O standard一般是3.3V LVCMOS。DCO引脚优先选标着CLK的专用引脚。VGA的RGB可以用普通IOHS和VS也是普通IO但要注意板卡上是否接了电阻网络。4.2 SDC约束里最容易漏掉的两个地方Quartus编译能过但波形乱跳多半是SDC里少写了输入延迟。除了2.3节的DCO约束VGA像素时钟如果来自PLL需要让Quartus自动生成时钟derive_pll_clocks derive_clock_uncertaintyPLL输出到VGA控制器的时钟如果没被约束时序分析会把VGA逻辑归到一个default时钟域里导致无法准确检查建立时间。derive_pll_clocks可以自动识别PLL输出并建立时钟但create_clock定义在dco端口不能被自动识别所以必须手动写。cant launch modelsim这类报错经常出现。在Assignments/EDA Tool Settings里选对ModelSim版本然后重新指定路径。AD9238采样模块仿真时testbench里要模拟DCO并给adc_data一组变化量。用两个always块分别产生时钟和数据即可重点不是仿真速度而是确认sample_valid的时序。4.3 SignalTap II抓AD9238数据的三个步骤板上调试时SignalTap比逻辑分析仪更直接。添加采样时钟为sample_clk即DCO经过BUFG后的时钟。抓取sample_a、sample_valid_a、wr_ptr、rd_addr触发条件设为sample_a[11:0] 12hE00这样当输入信号为正峰值时会停下方便检查波形。如果抓到的sample_a全是0先看DCO有没有翻转DCO正常但数据不变就需要确认AD9238的片选或休眠引脚。如果抓到的波形像是右边被裁掉一半说明RAM的写使能不连续检查sample_valid_a是否一直被拉高。还有一个实际经验如果把SignalTap的采样时钟设成vga_clk抓出来的adc_data会有周期性稳定值看起来像波形但那是跨时钟域采样的结果容易误导。必须用采样时钟去抓ADC那侧信号。5. 进阶一下滚动显示与双通道叠加5.1 用写指针实现滚动波形示波器上最常见的是滚动显示新数据从右边进入旧数据从左边消失。如果每次来一个点都把RAM里所有数据搬一次FPGA资源浪费很大。更好的方法是让VGA读地址跟着写指针一起走wire [9:0] rd_addr (h_cnt[9:0] wr_ptr[9:0]) 10h3FF;wr_ptr是当前写入RAM的位置。当wr_ptr从0向1023递增时屏幕左端读到的地址也跟着增加表现就是波形向左滚动。h_cnt[9:0]在有效区从0到639加上写指针后和RAM深度取模刚好覆盖最近1024个采样点。滚动速度由采样率决定。如果AD9238以25MHz采样写指针每40ns加一屏幕一帧里写指针变化太大波形会快得看不清。要给wr_ptr加分频让每次有效数据到达时只让写指针加1/10或1/100等效降采样率。5.2 双通道波形同屏叠加AD9238本身是双通道例化两个adc_capture再例化两个RAM就能同时画两条曲线。VGA侧比较两路y值分别用不同颜色点亮。wire match_a (v_cnt_d y_a); wire match_b (v_cnt_d y_b); assign vga_red 3b000; assign vga_green match_a ? 3b111 : 3b000; assign vga_blue match_b ? 3b111 : 3b000;两路完全重叠时只看到蓝色这是叠显示的正常现象。为了区分可以让通道A错开半格或者把显示区域分为上下两半通道A在上半区通道B在下半区。下半区的y坐标要做偏移wire [9:0] y_b_display 10d320 y_b[9:0];这样两条波形的零点不在同一水平线视觉上更清晰。5.3 用一个白色边框验证VGA时序调试VGA时先别急着看波形。在屏幕四角的固定位置画四条白色边线如果四条线闭合说明行场同步和有效区判断正确如果左右错位检查h_active_region的起始值是不是144如果上下错位检查v_active_region的起始值是不是35。把边线的判断条件写成单独一个信号叠加到波形显示里确认边框完整后再注掉这个信号。这个方法能快速把存储问题和时序问题分开比盯着波形猜测效率高。本文还有配套的精品资源点击获取