ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

FPGA实现AD9238双通道采集至VGA波形显示完整链路解析

FPGA实现AD9238双通道采集至VGA波形显示完整链路解析 简介一套基于Quartus 17.1的FPGA读取AD9238并VGA波形显示例程适配Cyclone IV E系列EP4CE6F17C8面向数字信号采集与显示方向的硬件工程师和FPGA学习者解决AD双通道数据采集及VGA实时波形展示的工程落地问题。压缩包共167个文件、约4.91MB以Verilog源码.v/.tdf、Quartus工程配置与编译报告.qpf/.qsf/.rpt为主并包含SDC时序约束、PDF说明文档及部分中间文件目录结构清晰便于直接打开工程进行编译、仿真和上板验证。已有1125人学习下载。从顶层module可见例程设计adc_clk驱动AD9238双通道采样数据经缓冲后分路输出至VGA网格与波形叠加显示代码覆盖采样时钟生成、写地址控制、双口RAM/异步缓冲、VGA行场同步及时序生成等关键模块是一份可读性强、可直接复用的ADC采集与VGA显示完整工程模板。1. 从 AD9238 并行数据到 VGA 波形的完整链路一套例程是否值得拆先看它把哪几段硬骨头串了起来。EP4CE6F17C8 是 Cyclone IV E 里逻辑规模很小的器件要在它上面同时处理 AD9238 的双通道 12 位并行数据、VGA 640×480 行场时序和波形绘制考验的不是某一个模块有多复杂而是三个模块之间的数据流怎么衔接。这套工程恰好给出了完整答案AD9238 的两路采样数据先进双端口 RAMVGA 控制器按像素节拍读 RAM 并叠加网格最终在屏幕上形成随模拟输入上下变化的波形。对于正在搭数据采集前端或者第一次把 ADC 和显示引擎连起来的人这是一个能直接对照管脚和波形的可运行样例。2. AD9238 双通道采样下降沿锁存与数据截位2.1 并行 ADC 的数据窗口与参考时钟AD9238 是双通道 12 位并行输出 ADCFPGA 不需要通过指令寄存器做启动握手只需要持续供给采样时钟并且在数据线上按固定的时序关系取数。这种结构在示波器前端里很常见因为两个通道可以做到完全同步采样相位差只取决于 PCB 走线长度和 ADC 内部的转换延迟。真正容易出问题的地方在数据有效窗口。AD9238 的输出数据在采样时钟的上升沿附近会发生变化如果 FPGA 也在上升沿打拍读取就可能把正在翻转的总线电平采进来。PCB 走线越长数据相对时钟的延迟越大建立时间越紧张。实际工程里最常见的做法是把锁存沿放到下降沿上升沿过后数据已稳定等半个时钟周期再读整个时序裕量会大很多。这个工程把 adc_clk 通过 ad9238_clk_ch0 和 ad9238_clk_ch1 两个引脚单独引出而不是直接复用内部时钟线目的就是让两路 ADC 时钟到芯片引脚的驱动路径可以独立调整避免共用一条时钟线带来的扇出和反射问题。2.2 PLL 分频与双通道数据锁存代码板载晶振一般是 50MHzVGA 640×48060 需要 25.175MHz工程里直接用 25MHz 也可以正常显示。使用 PLL 同时生成 VGA 像素时钟和 ADC 采样时钟能保证两个时钟同源、相位关系确定RAM 读写两侧频率一致这是整个显示链路不发散的前提。// 顶层 PLL 例化c0 用于 VGA 时序c1 用于 AD9238 采样 pll u_pll( .inclk0(clk), // 板载 50MHz 晶振 .c0(video_clk), // 25MHzVGA 像素时钟 .c1(adc_clk) // 25MHzAD9238 采样时钟 ); // 下降沿锁存双通道数据避开数据翻转窗口 reg [11:0] adc0_data_r; reg [11:0] adc1_data_r; always (negedge adc_clk or negedge rst_n) begin if (!rst_n) begin adc0_data_r 12d0; adc1_data_r 12d0; end else begin adc0_data_r ad9238_data_ch0; adc1_data_r ad9238_data_ch1; end end这段代码里 rst_n 低电平复位把两个锁存寄存器清零正常工作时每个 adc_clk 下降沿对 AD9238 数据总线采一拍。之所以不用上升沿是因为 AD9238 在采样时钟上升沿内部更新输出寄存器数据线上的电平翻转会持续一段时间下降沿采样等于把采样点放到数据最稳定的区间。对 25MHz 采样率来说半个周期是 20ns足够覆盖常见的十几纳秒走线延迟。2.3 12 位到 8 位的存储选择AD9238 输出 12 位数据但 VGA 波形显示并不需要 4096 级灰度。例程的做法是把 12 位右移四位保留高 8 位这样每个采样点只占一个字节RAM 深度减半。从信噪比角度看12 位 ADC 的有效位数通常不到 10 位最低几位基本是量化噪声截掉低 4 位对波形轮廓几乎没有影响。信号位宽例程取值说明ad9238_data_ch012ADC 引脚直接输入通道 0 并行数据ad9238_data_ch112ADC 引脚直接输入通道 1 并行数据adc0_buf_addr11写地址发生器输出单通道波形缓存深度 2048adc0_buf_data812 位数据取高 8 位送入 RAM 写端口adc0_buf_wr1采样节拍控制每采样一次拉高一个周期wire [7:0] adc0_data_8b adc0_data_r[11:4]; wire [7:0] adc1_data_8b adc1_data_r[11:4];截位放在 RAM 写入之前后续的 VGA 层、网格层和波形层都基于 8 位数据计算。这样做的另一个好处是 BRAM 资源占用小EP4CE6F17C8 只有 30 个 M9K 块每个 9Kbit单通道 2K×8 的缓存只需要两到三个 M9K两个通道加显示辅助逻辑也能控制在十个块以内剩下的资源留给 SignalTap 调试。3. VGA 640×480 时序行场计数与采样帧同步3.1 行场参数为什么不能省钱VGA 时序的行场参数虽然可以在一定范围内伸缩但 640×48060 是兼容性最好的模式几乎所有显示器都能识别。水平方向每个像素都需要一个时钟周期所以 25MHz 像素时钟下一行 640 像素只占 25.6us剩余时间要填充前沿、同步脉冲和后沿凑满 800 个时钟周期。垂直方向同理一帧 480 行之外还要加 45 行空白总计 525 行。时序项数值用途水平有效区 H_ACTIVE640实际显示像素数水平前沿 H_FP16行同步前消隐水平同步 H_SYNC96行同步脉冲宽度水平后沿 H_BP48行同步后消隐水平总数 H_TOTAL800一行完整周期垂直有效区 V_ACTIVE480实际显示行数垂直前沿 V_FP10场同步前消隐垂直同步 V_SYNC2场同步脉冲宽度垂直后沿 V_BP33场同步后消隐垂直总数 V_TOTAL525一帧完整周期如果参数算错了最常见的现象是画面偏左或偏上、顶部有滚动条。调试时不要凭感觉改同步脉冲宽度先用计数器把 H_TOTAL 和 V_TOTAL 算准再看同步信号相对有效区的位置。3.2 计数器、同步信号和 data enable 的 Verilog 写法VGA 控制器的核心就是两个计数器行计数器统计每个像素时钟场计数器统计每一行。行计数器计到 799 后归零同时场计数器加一场计数器计到 524 后归零表示一帧结束。localparam H_ACTIVE 640, H_FP 16, H_SYNC 96, H_BP 48, H_TOTAL 800; localparam V_ACTIVE 480, V_FP 10, V_SYNC 2, V_BP 33, V_TOTAL 525; reg [9:0] h_cnt; reg [9:0] v_cnt; always (posedge video_clk or negedge rst_n) begin if (!rst_n) h_cnt 10d0; else if (h_cnt H_TOTAL - 1) h_cnt 10d0; else h_cnt h_cnt 1b1; end always (posedge video_clk or negedge rst_n) begin if (!rst_n) begin v_cnt 10d0; end else if (h_cnt H_TOTAL - 1) begin if (v_cnt V_TOTAL - 1) v_cnt 10d0; else v_cnt v_cnt 1b1; end end assign video_hs (h_cnt H_ACTIVE H_FP) (h_cnt H_ACTIVE H_FP H_SYNC); assign video_vs (v_cnt V_ACTIVE V_FP) (v_cnt V_ACTIVE V_FP V_SYNC); assign video_de (h_cnt H_ACTIVE) (v_cnt V_ACTIVE);video_hs 有效区间是第 656 到第 751 个时钟这段是高电平、同步脉冲低电平显示器靠这个下降沿对齐行起点。video_vs 同理在第 490 到 491 行之间拉低两个行周期。video_de 表示当前扫描位置处于有效显示区域只有 video_de 为高时才允许把波形数据写到 RGB 输出上。3.3 像素时钟和采样时钟同源的意义这个工程把所有时钟都挂在同一个 PLL 上VGA 读取侧的 video_clk 和 ADC 写入侧的 adc_clk 都是 25MHz。写 RAM 的地址由采样计数器提供读 RAM 的地址由 h_cnt 提供两边频率一致地址增长速度一致显示出来的波形就是等间距的。如果把 ADC 采样时钟改成其他频率比如 50MHz那么每两个采样点才能对应一个水平像素位置或者需要在写入时做抽取。更麻烦的是如果 AD9238 由独立晶振提供时钟RAM 两端就成了异步时钟域写满一帧后立即换帧依然有可能读到半新半旧的数据必须引入异步 FIFO 或者乒乓缓存做跨时钟隔离。例程把采样率和像素时钟设为同值是刻意降低复杂度让调试者把注意力放在波形本身而不是同步问题上。4. 波形绘制与网格叠加采样值到屏幕坐标的映射4.1 纵向映射与防止无符号数求差溢出拿到 8 位采样值之后第一步是把它换算成屏幕上的一条扫描线。VGA 有效区有 480 行如果直接把采样值映射到 0 到 255波形只会出现在画面上方四分之一区域看起来非常奇怪。更合理的方式是让波形居中显示垂直方向留出上下边距中间用 240 行表示满幅信号。localparam V_OFFSET 9d120; // 垂直方向偏移让波形居中 wire [8:0] v_cnt9 {1b0, v_cnt[8:0]}; wire [8:0] wave_y0; wire [8:0] wave_y1; assign wave_y0 V_OFFSET (9d239 - {1b0, adc0_data_8b}); assign wave_y1 V_OFFSET (9d239 - {1b0, adc1_data_8b});adc0_data_8b 为 0 时wave_y0 等于 359波形落在屏幕中下方adc0_data_8b 为 255 时wave_y0 等于 104波形靠近屏幕上方。V_OFFSET 取 120是因为上下各留 120 行中间 240 行放波形视觉比例比较接近真实示波器。写代码时特别要注意 v_cnt 本身是 10 位直接和 9 位 wave_y0 比较会在高位产生符号扩展问题所以先把 v_cnt 扩展成 9 位再参与运算。4.2 波形线宽、网格亮度和两层合并单个采样点只对应一个像素直接把 wave_y0 和 v_cnt 判等波形会出现断裂。显示波形时一般让当前扫描行落在波形点附近两三行范围内都输出前景色相当于把波形画成一条有宽度的线。wire [9:0] diff0 (wave_y0 v_cnt9) ? (wave_y0 - v_cnt9) : (v_cnt9 - wave_y0); assign wave0_draw video_de (diff0 10d2); wire grid_draw video_de ((h_cnt[4:0] 5d0) || (v_cnt[4:0] 5d0)); // 颜色优先级波形 网格 背景 assign video_r wave0_draw ? 8hFF : (grid_draw ? 8h2F : 8h00); assign video_g wave0_draw ? 8hC0 : (grid_draw ? 8h2F : 8h00); assign video_b wave0_draw ? 8h00 : (grid_draw ? 8h2F : 8h00);diff0 用条件运算符做了绝对值计算避免无符号数相减出现下溢。wave0_draw 为高时RGB 输出黄色系颜色网格线输出暗灰色背景为黑色。网格每隔 32 个像素画一条水平和垂直方向同时生效看起来是标准的方格纸效果。4.3 双通道分屏与叠加显示的取舍两个通道都画在同一个位置会互相覆盖工程里常见两种处理方式。一是分屏上半屏显示通道 0下半屏显示通道 1通道之间互不干扰二是颜色叠加通道 0 用黄色、通道 1 用青色两个波形重叠在一起方便看相位差。wire ch0_en (v_cnt V_ACTIVE / 2); wire ch1_en (v_cnt V_ACTIVE / 2) (v_cnt V_ACTIVE); wire wave1_draw video_de ch1_en (diff1 10d2);分屏模式下ch0_en 控制上半屏ch1_en 控制下半屏每个通道独立计算一次 diff各自判断波形是否落在当前扫描行附近。颜色叠加模式则把两个通道的 draw 信号分别接到 R 和 B 通道上视觉上一眼就能区分两路信号。例程里的 wave0 和 wave1 两层本来就是独立计算的最终怎么合并完全由顶层模块决定建议在实际使用时先跑通分屏模式确认两路 ADC 数据都正常后再切换到叠加模式。5. Quartus 调试与跨时钟稳定性从 SignalTap 到乒乓缓存5.1 SignalTap II 采样深度与触发条件Quartus 17.1 里调试这个工程直接用 Tools 菜单下的 SignalTap II Logic Analyzer 即可。采样时钟选 video_clk添加 adc0_data_r、adc1_data_r、adc0_buf_addr、video_hs、video_vs 和 wave0_draw 这些信号。触发条件设置为 adc0_buf_wr 为高采样深度控制在 1024这样既能捕捉到完整的写地址变化又不会让综合器为了插桩耗费太多 M9K 块。EP4CE6 资源有限SignalTap 采样深度不要开到 4096。如果报资源不足可以先缩小采样深度再把 adc1 相关信号移除单独验证通道 0。触发后重点观察两个地方adc0_buf_addr 是否在 0 到 2047 之间循环adc0_data_r 是否随着信号源幅度变化而跳动。如果数据一直在变化但波形不动问题几乎都在 VGA 读地址和写地址没有对齐。5.2 双端口 RAM 的读优先与 A/B 缓存交换RAM 读操作与写操作同时进行时如果读写地址指向同一个单元就会产生读出新数据还是旧数据的不确定行为。这个工程可以接受轻微的画面撕裂但要做到无撕裂显示常见做法是加一个 A/B 缓存利用 VGA 场同步信号换帧。reg vs_dly; wire vs_rise video_vs ~vs_dly; always (posedge video_clk or negedge rst_n) begin if (!rst_n) vs_dly 1b0; else vs_dly video_vs; end reg frame_sel; always (posedge video_clk or negedge rst_n) begin if (!rst_n) frame_sel 1b0; else if (vs_rise) frame_sel ~frame_sel; end // frame_sel0 时 A 写、B 读frame_sel1 时交换 assign ram_a_we adc0_buf_wr ~frame_sel; assign ram_b_we adc0_buf_wr frame_sel; assign wave0_data frame_sel ? ram_a_q : ram_b_q;video_vs 是低有效脉冲所以用打拍的方式检测上升沿每次上升沿到来切换一次 frame_sel。这一帧 ADC 写入 A 块 RAMVGA 从 B 块读数据下一帧两边交换。由于读写永远落在不同物理 RAM 上彻底避免了读写地址冲突代价只是多占一块 BRAM。5.3 RGB 位宽映射和波形滚动显示的细节VGA 接口的 RGB 通道位宽并不对称例程中 vga_out_r 是 5 位、vga_out_g 是 6 位、vga_out_b 是 5 位而内部 video_r、video_g、video_b 都是 8 位输出前必须做一次截位映射。assign vga_out_r video_r[7:3]; assign vga_out_g video_g[7:2]; assign vga_out_b video_b[7:3];绿色通道多保留一位是因为人眼对绿色亮度变化最敏感6 位绿色能减少波形显示时的色阶断层。如果画面偏色先查这三条映射是否接对不要急着改显示蓝底比例。最后提一个波形滚动显示的小技巧adc0_buf_addr 是 11 位、深度 2048而水平有效区只有 640 像素读地址不需要从 0 开始。把读地址的起点写成一个可配置寄存器每帧加一个固定偏移就能让波形在屏幕上缓慢左移这个改动只动读地址发生器不碰 ADC 采样逻辑。本文还有配套的精品资源点击获取
返回列表