ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FPGA驱动AD9238采集并在VGA上实时显示波形的方法

FPGA驱动AD9238采集并在VGA上实时显示波形的方法 简介面向FPGA学习者的AD9238数据采集与VGA波形显示例程包基于Cyclone IV E系列EP4CE6F17C8器件与Quartus 17.1环境适合正在钻研FPGA与高速ADC接口、视频显示驱动的开发者。压缩包共167个文件约4.91MB核心为Verilog源码v/tdf、Quartus工程配置qsf/qpf/qip、时序约束sdc、编译报告rpt/logdb及PDF/readme说明文档目录结构完整清晰。已有1125人学习下载。程序含顶层模块、AD9238双通道采样、地址缓冲、VGA时序生成与波形绘制等模块并具备网格背景及双通道叠加显示能力。读者可直接打开Quartus工程运行与仿真也可参照源码梳理ADC采样时钟、数据对齐和VGA同步信号的设计思路对数字示波器、信号采集显示类项目具有直接参考价值。1. 一张 640×480 的屏幕怎么把 AD9238 的采样点画成波形做 FPGA 数据采集的人迟早会碰到这类需求ADC 以几十 MHz 采样FPGA 把数据接住然后直接在 VGA 显示器上画出实时波形不经过上位机。标题里的 AD9238 是一片 12 位、双通道、最高 65 MSPS 的并行 ADC而 VGA 显示的核心难点不在「画点」在于两个时钟域之间的数据搬运和时序对齐。这个例程正好把 FPGA 入门到进阶的几个关键点串在了一起并行 ADC 接口时序、跨时钟域存储、VGA 时序生成和 Quartus 工程的引脚约束。适合刚学完 Verilog 语法、想用一块开发板做出一个能看得见摸得着的采集系统的读者也适合正在做 FPGA 图像处理或仪器类项目、需要快速搭一个「采样-缓存-显示」验证链路的人。下文所有代码和参数都按可在 Quartus Prime 里直接建工程复现的粒度来写。2. AD9238 数据采集并行接口时序与 Verilog 采样代码2.1 AD9238 并行输出接口的时序特征AD9238 是典型的 CMOS 并行输出 ADC12 位数据总线 D11D0双通道共享控制引脚输出数据在编码时钟ENC的驱动下更新。手册里最重要的一个参数是输出数据延迟 tOD它表示 ENC 边沿到数据总线稳定之间的时间通常在几纳秒到十几纳秒。如果把 FPGA 采样时钟和 ADC 的 ENC 直接连在一起然后在同一个时钟上升沿去采数据很容易采到总线切换过程中的中间态。常见的做法不是把 ADC 时钟原封不动接进逻辑而是通过 PLL 生成一个相位偏移后的采样时钟。相位偏移量可以参考 tOD 和 PCB 走线延迟来定工程上常用 -2 ns 到 -3 ns 的延时或者直接用时钟下降沿采样让数据在上升沿附近处于稳定区。下面这张表是接口设计时需要对照确认的几个参数。参数含义对 FPGA 设计的影响分辨率12 bit数据总线位宽存储和显示时需要做位宽裁剪通道数2双通道可并行采集本例程先做单通道最高采样率65 MSPS时序裕量按采样时钟周期计算65 MHz 下周期约 15.4 nstOD输出数据延迟决定 FPGA 采样时钟的相位偏移量输出格式偏移二进制波形显示前要转成有符号数或做中心偏移2.2 采样模块的 Verilog 实现先写最核心的 ADC 数据采集模块接口包含采样时钟、复位、12 位数据输入和输出给后级缓存的数据与写使能。这里用打两拍的方式降低亚稳态风险数据在采样时钟域内同步。module ad9238_capture #( parameter DATA_WIDTH 12 )( input wire rst_n, input wire adc_clk, // PLL 输出相对 ENC 做相位偏移 input wire [DATA_WIDTH-1:0] adc_d, // AD9238 D11..D0 output reg [DATA_WIDTH-1:0] adc_data, // 同步后数据 output reg adc_valid // 数据有效标志 ); reg [DATA_WIDTH-1:0] data_sync1; reg [DATA_WIDTH-1:0] data_sync2; always (posedge adc_clk or negedge rst_n) begin if (!rst_n) begin data_sync1 12d0; data_sync2 12d0; adc_data 12d0; adc_valid 1b0; end else begin data_sync1 adc_d; data_sync2 data_sync1; adc_data data_sync2; adc_valid 1b1; end end endmodule代码逻辑分两层第一级寄存器直接采 ADC 总线第二级寄存器再次同步最后输出的 adc_data 是与 adc_clk 同域的信号。adc_valid 在每个时钟周期拉高表示数据有效如果不希望每个周期都采集可以在这个模块外部用采样使能信号门控它。需要调整的地方是 adc_clk 的来源。在 Quartus 里用 ALTPLL 生成采样时钟时把 PLL 输出相移设置为 -2 ns 左右具体值要结合 ModelSim 仿真观察 adc_d 与 adc_clk 的建立关系。2.3 偏移二进制转有符号数与位宽裁剪AD9238 默认输出偏移二进制格式即输入为 0 V 时输出接近中间码 2048满量程时接近 4095。直接把这 12 位数据拿去显示波形会整体偏上看起来像一条贴在屏幕上沿的平线。显示前先做一个转换减掉 2048 得到有符号数再根据屏幕纵向分辨率决定取哪些位。wire signed [11:0] adc_signed; assign adc_signed $signed({1b0, adc_data}) - 12sd2048; wire [7:0] display_data; assign display_data adc_signed[11:4]; // 取高 8 位压缩到 256 级转换逻辑放在数据进入缓存之前还是之后取决于后级显示算法的位数要求。这里取了高 8 位因为 640×480 的 VGA 屏幕纵向只有 480 行12 位原始分辨率在像素比较算法里用不到反而会增加存储宽度。如果想要更高纵向精度可以保留 10 位在比较时做乘法缩放代价是缓存位宽和比较器面积变大。3. 跨时钟域存储双口 RAM 乒乓缓存与读写速率匹配3.1 为什么 ADC 采集时钟和 VGA 像素时钟必须解耦VGA 显示需要独立的像素时钟比如 640×48060 Hz 约 25.175 MHz而 ADC 采样时钟可能是 25 MHz、40 MHz 或 50 MHz。两个时钟即使频率标称相同也来自不同 PLL 输出存在相位差和微小频差不能直接共用一个时钟域。如果把采集数据直接接到 VGA 显示逻辑会出现两种情况显示端读到正在更新的数据导致波形中间出现撕裂或者采样端写入过快缓存溢出丢点。解耦手段只有两个方向异步 FIFO或者双口 RAM 加乒乓切换。异步 FIFO 适合连续不断的数据流但需要处理空满标志的格雷码同步逻辑复杂。对单帧 640 个采样点的波形显示双口 RAM 乒乓是更直观的方案一块 RAM 被 ADC 写入时另一块被 VGA 读取写满后交换角色读写互不干扰。3.2 乒乓控制逻辑与双口 RAM 实例化双口 RAM 的写口由采样模块控制读口由 VGA 显示模块控制。关键在「什么时候采满 640 个点」和「什么时候切换缓冲区」。下面用一个简化版乒乓控制器说明开关逻辑。module waveform_pingpong #( parameter ADDR_WIDTH 10, // 1024 深度实际使用 640 parameter DATA_WIDTH 8 )( input wire rst_n, input wire wr_clk, input wire wr_en, input wire [DATA_WIDTH-1:0] wr_data, input wire rd_clk, input wire [ADDR_WIDTH-1:0] rd_addr, output wire [DATA_WIDTH-1:0] rd_data, output reg buf_ready // 当前缓冲区可读 ); wire [DATA_WIDTH-1:0] ram0_dout; wire [DATA_WIDTH-1:0] ram1_dout; // 写地址计数器采满 640 点后停 reg [ADDR_WIDTH-1:0] wr_addr; reg [9:0] sample_cnt; reg write_buf_sel; always (posedge wr_clk or negedge rst_n) begin if (!rst_n) begin sample_cnt 10d0; wr_addr 10d0; write_buf_sel 1b0; end else if (wr_en) begin if (sample_cnt 10d640) begin sample_cnt 10d0; write_buf_sel ~write_buf_sel; wr_addr 10d0; end else begin sample_cnt sample_cnt 1b1; wr_addr wr_addr 1b1; end end end // 读端根据 write_buf_sel 的取反选择另一块 RAM wire read_buf_sel ~write_buf_sel; assign rd_data read_buf_sel ? ram1_dout : ram0_dout; // 双口 RAM 实例化 dual_port_ram u_ram0 ( .addr_a (wr_addr), .clk_a (wr_clk), .din_a (wr_data), .we_a (wr_en !write_buf_sel), .addr_b (rd_addr), .clk_b (rd_clk), .dout_b (ram0_dout) ); dual_port_ram u_ram1 ( .addr_a (wr_addr), .clk_a (wr_clk), .din_a (wr_data), .we_a (wr_en write_buf_sel), .addr_b (rd_addr), .clk_b (rd_clk), .dout_b (ram1_dout) ); endmodule这个例子用 write_buf_sel 区分写侧正在使用哪块 RAM读侧固定读另一块。sample_cnt 到 640 时完成一次波形采集write_buf_sel 翻转。读侧不需要等「整帧写完」才切换因为读侧在 VGA 消隐期才会产生新的读地址天然有时间窗口。实际工程中建议把 buf_ready 拉高放在切换完成之后VGA 显示逻辑只在 buf_ready 为高时开始读。表格对比三种缓存方案便于按项目选型方案适用场景优点缺点单口 RAM 共享采集率远低于显示率资源最省读写不能同时容易闪烁双口 RAM 乒乓波形刷新显示读写完全并行无撕裂双倍存储资源异步 FIFO连续高速数据流天然处理速率抖动空满标志设计复杂调试成本高3.3 读写速率不匹配时的节流策略VGA 一帧只有约 16.6 ms而 ADC 在这个时间内可能采了 40 万个点。乒乓缓存不会消除这种数量级差异必须在采样入口做节流。最直接的方法在每个 VGA 帧同步信号到来后只采集 640 个点采满后关闭 wr_en等待下一帧。这样显示的波形是上一帧时刻的实时快照帧率等于 VGA 刷新率逻辑上也最简单。另一种做法是「滑动窗口」ADC 持续写入VGA 读侧每次从某个偏移地址开始读实现波形水平滚动。这需要把双口 RAM 的写地址设计成循环回绕并在读地址上叠加偏移量。对理解 AD9238 连续采样场景有帮助但调试难度比快照方式高一个级别建议先把乒乓快照跑通再扩展。4. VGA 时序与波形绘制行场同步参数与像素比较逻辑4.1 VGA 行场同步参数与状态机VGA 显示的核心是严格按照时序产生行同步 HSYNC、场同步 VSYNC 和有效显示区间信号。以最常用的 640×48060 Hz 为例像素时钟约 25.175 MHz实际工程中如果 FPGA 开发板没有这个频率的晶振可以用 25 MHz 近似显示几何上略有偏差但可接受。整个行周期 800 个像素时钟其中有效显示 640 个整个帧周期 525 行其中有效显示 480 行。参数数值说明像素时钟25.175 MHz工程可用 25 MHz 替代行同步脉冲96 像素HSYNC 低电平宽度行前肩16 像素同步脉冲后到有效数据行后肩48 像素有效数据后到同步脉冲行周期800 像素总计场同步脉冲2 行VSYNC 低电平宽度场前肩10 行场后肩33 行帧周期525 行状态机可以用计数器实现不需要写成复杂状态跳转。module vga_timing #( parameter H_ACTIVE 640, parameter H_FP 16, parameter H_SYNC 96, parameter H_BP 48, parameter V_ACTIVE 480, parameter V_FP 10, parameter V_SYNC 2, parameter V_BP 33 )( input wire clk, input wire rst_n, output reg hsync, output reg vsync, output reg video_active, output reg [9:0] pixel_x, output reg [9:0] pixel_y ); reg [9:0] h_cnt; reg [9:0] v_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin h_cnt 10d0; v_cnt 10d0; end else begin if (h_cnt H_ACTIVE H_FP H_SYNC H_BP - 1) begin h_cnt 10d0; if (v_cnt V_ACTIVE V_FP V_SYNC V_BP - 1) v_cnt 10d0; else v_cnt v_cnt 1b1; end else begin h_cnt h_cnt 1b1; end end end always (*) begin hsync ~(h_cnt H_ACTIVE H_FP h_cnt H_ACTIVE H_FP H_SYNC); vsync ~(v_cnt V_ACTIVE V_FP v_cnt V_ACTIVE V_FP V_SYNC); video_active (h_cnt H_ACTIVE) (v_cnt V_ACTIVE); pixel_x h_cnt; pixel_y v_cnt; end endmodulepixel_x 和 pixel_y 在有效区内从 0 开始递增可直接作为波形缓存的读地址来源。VGA 接口针脚定义中 HSYNC、VSYNC 通常为 TTL 电平RGB 各通道按颜色位宽输出。FPGA 开发板上往往通过排针或 VGA 母座引出接线时注意对照原理图核对同步信号极性。4.2 像素比较法绘制波形波形绘制的核心算法可以用一个比较器完成从双口 RAM 读出一个采样点它的值代表这条波形在屏幕纵向上应该出现的位置当前扫描行 pixel_y 恰好等于这个值时输出波形像素颜色否则输出背景色。wire [9:0] wave_y; wire [7:0] wave_data; ram_rd #( .DATA_WIDTH (10) ) u_ram_rd ( .rd_clk (clk), .rd_addr (pixel_x), .rd_data (wave_y) ); reg [11:0] rgb; wire wave_hit (pixel_y wave_y); always (*) begin if (!video_active) begin rgb 12h000; // 消隐区输出黑色 end else if (wave_hit) begin rgb 12h0F0; // 波形亮绿色 end else begin rgb 12h00F; // 背景蓝色 end endpixel_x 作为读地址在 VGA 有效区逐列递增等价于把 640 个采样点映射到屏幕横轴。wave_y 是缓存的 12 位数据裁剪后的高 8 位取值范围 0255而屏幕纵向有 480 行因此直接比较会把波形压缩在屏幕上半部分。要占满屏幕可以把 wave_y 乘以一个缩放系数或者把 480 行分成上下两半让波形围绕中心线摆动。常用做法是做一个带偏移的映射pixel_y wave_y - 128后再比较。4.3 显示算法的两个常见误用第一个误用是在消隐区做地址递增。pixel_x 在行前肩、行同步、行后肩区间仍然在变化但这些区域 video_active 为低不应该读取缓存或输出颜色。设计 RAM 读地址时必须用 video_active 门控否则波形会出现在屏幕可见区域之外或导致行显示错位。第二个误用是对 RGB 位宽不匹配不管不顾。640×48060 的 VGA 常用 12 位色深RGB 各 4 位有些开发板是 16 位或 24 位色深。代码里的 12h0F0 是按 4:4:4 写的如果板子是 5:6:5 或 8:8:8需要按实际位宽展开颜色值否则显示颜色会发暗或偏色。5. Quartus 工程调试Signaltap 验证数据对齐与常见花屏修复5.1 SignalTap II 抓取 ADC 输入与写地址Quartus Prime 里调试这类跨时钟域问题最有效的工具是 SignalTap II Logic Analyzer。在 .qsf 工程里添加 SignalTap 文件把 adc_clk 设为采样时钟添加观察信号adc_d、adc_data、adc_valid、wr_addr、write_buf_sel、h_cnt、v_cnt。触发条件推荐用写地址等于 0这样能抓到一次完整波形采集过程确认 ADC 数据在写入缓存第一个点时的数值是否符合预期。采样深度不用太大2048 足够覆盖 640 个采样点加前后上下文。触发位置设置在中间可以看到写地址翻转前的完整过程。如果在 SignalTap 里看到 adc_valid 连续拉高、wr_addr 按顺序递增说明采集链路工作正常。如果 adc_data 在低几位出现跳动优先检查 PLL 相位偏移量。5.2 VGA 花屏、闪烁与数据错位的排查顺序花屏先分两类如果是整个屏幕颜色错乱但不闪通常是 VGA 行场参数或引脚约束错误如果波形区域闪烁而背景稳定问题在乒乓缓存切换。现象常见原因检查点屏幕整体偏移hsync/vsync 极性反检查 VGA 驱动对同步信号的有效电平波形向上或向下跳变缓存切换时读到半帧数据buf_ready 判断时序切换后同步一个周期波形有毛刺ADC 采样建立时间不足PLL 相移加大或改下降沿采样画面闪动读写块切换过快确认 sample_cnt 640 计数完整检查 wr_en 节流数据错位还有一个隐蔽来源AD9238 输出数据总线如果布线较长各 bit 之间延迟不一致高位和低位可能出现在不同时钟周期。这时需要检查 PCB 走线等长或者用 IDDR 原语把 ADC 时钟的双沿都用上在数据眼图中间采样。5.3 复位释放与显示锁定的一个小技巧复位释放和缓存切换同时发生时可能出现第一帧读出全零的情况。常见做法是让 VGA 显示逻辑等待垂直同步信号到来后再开始扫描同时给乒乓切换做一个两拍同步确保写侧切换和读侧选择不在同一时刻竞争。reg buf_sel_sync1, buf_sel_sync2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin buf_sel_sync1 1b0; buf_sel_sync2 1b0; end else begin buf_sel_sync1 write_buf_sel; buf_sel_sync2 buf_sel_sync1; end end这个两拍同步器把写侧的写缓冲区选择信号同步到 VGA 像素时钟域避免读侧用异步信号直接作为 RAM 选择时出现竞争冒险。加上这个逻辑后剩下要调的就是 VGA 显示的刷新率和波形纵向缩放系数了。本文还有配套的精品资源点击获取
返回列表