news 2026/9/13 11:12:18

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 并行输出 ADC,12 位数据总线 D11~D0,双通道共享控制引脚,输出数据在编码时钟(ENC)的驱动下更新。手册里最重要的一个参数是输出数据延迟 tOD,它表示 ENC 边沿到数据总线稳定之间的时间,通常在几纳秒到十几纳秒。如果把 FPGA 采样时钟和 ADC 的 ENC 直接连在一起,然后在同一个时钟上升沿去采数据,很容易采到总线切换过程中的中间态。

常见的做法不是把 ADC 时钟原封不动接进逻辑,而是通过 PLL 生成一个相位偏移后的采样时钟。相位偏移量可以参考 tOD 和 PCB 走线延迟来定,工程上常用 -2 ns 到 -3 ns 的延时,或者直接用时钟下降沿采样,让数据在上升沿附近处于稳定区。下面这张表是接口设计时需要对照确认的几个参数。

参数含义对 FPGA 设计的影响
分辨率12 bit数据总线位宽,存储和显示时需要做位宽裁剪
通道数2双通道可并行采集,本例程先做单通道
最高采样率65 MSPS时序裕量按采样时钟周期计算,65 MHz 下周期约 15.4 ns
tOD输出数据延迟决定 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 <= 12'd0; data_sync2 <= 12'd0; adc_data <= 12'd0; adc_valid <= 1'b0; end else begin data_sync1 <= adc_d; data_sync2 <= data_sync1; adc_data <= data_sync2; adc_valid <= 1'b1; 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({1'b0, adc_data}) - 12'sd2048; 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×480@60 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 <= 10'd0; wr_addr <= 10'd0; write_buf_sel <= 1'b0; end else if (wr_en) begin if (sample_cnt == 10'd640) begin sample_cnt <= 10'd0; write_buf_sel <= ~write_buf_sel; wr_addr <= 10'd0; end else begin sample_cnt <= sample_cnt + 1'b1; wr_addr <= wr_addr + 1'b1; 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×480@60 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 <= 10'd0; v_cnt <= 10'd0; end else begin if (h_cnt == H_ACTIVE + H_FP + H_SYNC + H_BP - 1) begin h_cnt <= 10'd0; if (v_cnt == V_ACTIVE + V_FP + V_SYNC + V_BP - 1) v_cnt <= 10'd0; else v_cnt <= v_cnt + 1'b1; end else begin h_cnt <= h_cnt + 1'b1; 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 endmodule

pixel_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 = 12'h000; // 消隐区输出黑色 end else if (wave_hit) begin rgb = 12'h0F0; // 波形亮绿色 end else begin rgb = 12'h00F; // 背景蓝色 end end

pixel_x 作为读地址在 VGA 有效区逐列递增,等价于把 640 个采样点映射到屏幕横轴。wave_y 是缓存的 12 位数据裁剪后的高 8 位,取值范围 0~255,而屏幕纵向有 480 行,因此直接比较会把波形压缩在屏幕上半部分。要占满屏幕,可以把 wave_y 乘以一个缩放系数,或者把 480 行分成上下两半,让波形围绕中心线摆动。常用做法是做一个带偏移的映射:pixel_y + wave_y - 128后再比较。

4.3 显示算法的两个常见误用

第一个误用是在消隐区做地址递增。pixel_x 在行前肩、行同步、行后肩区间仍然在变化,但这些区域 video_active 为低,不应该读取缓存或输出颜色。设计 RAM 读地址时必须用 video_active 门控,否则波形会出现在屏幕可见区域之外,或导致行显示错位。

第二个误用是对 RGB 位宽不匹配不管不顾。640×480@60 的 VGA 常用 12 位色深,RGB 各 4 位;有些开发板是 16 位或 24 位色深。代码里的 12'h0F0 是按 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 <= 1'b0; buf_sel_sync2 <= 1'b0; end else begin buf_sel_sync1 <= write_buf_sel; buf_sel_sync2 <= buf_sel_sync1; end end

这个两拍同步器把写侧的写缓冲区选择信号同步到 VGA 像素时钟域,避免读侧用异步信号直接作为 RAM 选择时出现竞争冒险。加上这个逻辑后,剩下要调的就是 VGA 显示的刷新率和波形纵向缩放系数了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 11:11:17

Lithe-IDEA:专为Spring Boot打造的轻量级开源IDE基座

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 11:11:00

Android Framework车载系统开发实战指南

1. 车载系统开发全景图&#xff1a;为什么选择Android Framework&#xff1f;十年前的车载信息娱乐系统还停留在CD播放器和FM收音机的阶段&#xff0c;而今天我们已经进入了智能座舱时代。作为这个变革的核心技术&#xff0c;Android Framework在车载领域的应用正在重塑人车交互…

作者头像 李华
网站建设 2026/9/13 11:08:57

六轴传感器姿态解算:四元数原理与嵌入式实现

简介&#xff1a;本资源是一套面向嵌入式开发者与姿态解算初学者的轻量级六轴传感器姿态估计算法实现&#xff0c;聚焦四元数在陀螺仪数据处理中的核心应用&#xff0c;解决姿态角计算中常见的万向节死锁、积分漂移与噪声干扰问题。压缩包共2个文件&#xff08;1个C源码1个头文…

作者头像 李华
网站建设 2026/9/13 11:08:23

Competitor Ad Intelligence Report — [DATE]

Competitor Ad Intelligence Report — [DATE] 【免费下载链接】awesome-copilot Community-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot. 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copil…

作者头像 李华
网站建设 2026/9/13 11:08:23

802.11a基带仿真:OFDM星座图解调与BER分析

简介&#xff1a;本资源是一份面向通信工程专业学生及无线通信初学者的MATLAB仿真实验包&#xff0c;聚焦IEEE 802.11a标准下6Mbps与24Mbps两种速率的链路性能对比分析&#xff0c;重点解决调制解调、星座图解映射&#xff08;DeConstellationMap&#xff09;及误码率&#xff…

作者头像 李华