☰
基于FPGA的高速数据采集系统设计:从ADC接口到DDR写入的工程实践
2026/10/13 17:40:36 网站建设 项目流程

简介:本资源为基于FPGA的数据采集系统完整工程包,面向电子信息、自动化及集成电路相关专业学生与FPGA初学者,可用于课程设计、毕业设计或自学练手。系统以串口指令控制DAC芯片TLV5618完成数模转换,读取ROM中正弦波形数据输出模拟信号;同时通过按键触发ADC128S052连续采样100次,数据经同步FIFO缓存后由串口回传至PC显示。工程按功能划分为串口收发、按键消抖与控制、ROM、DAC/ADC驱动、同步FIFO及其控制等模块,代码均附详细备注,便于理解各模块接口与数据流。压缩包共390个文件,约11.57MB,包含Verilog源码、工程配置与综合报告、仿真波形、引脚约束及说明文档等,覆盖从设计到验证的完整流程。目前已有4985人学习下载,适合希望掌握FPGA与ADC/DAC协同采集、串口通信及FIFO缓存设计的读者参考复用。

1. 基于FPGA的数据采集系统:为什么它成了高速采集的默认答案

做高速数据采集的人迟早会撞上一个选择:ADC 出来的数据率一旦超过几百 MSPS,MCU 就顶不住了。中断响应、总线仲裁、内存带宽,每一环都在拖后腿。这时候基于 FPGA 的数据采集系统就成了默认答案——ADC 直接挂到 FPGA 的 IO 上,用硬件逻辑在时钟沿上抓数据,不经过任何操作系统调度,确定性是 ns 级的。这套方案解决的核心问题就一个:在采样率上去之后,怎么保证数据不丢、时序不乱、上位机能稳定拿到完整波形。适合谁?做雷达、超声、高速接口测试、电力监测这类需要连续或触发式采集的工程师,以及想从 MCU 采集方案迁移到 FPGA 的开发者。资料齐全、代码备注清楚,意味着这套东西不是只给你看个框图,而是能直接跑起来的工程。

2. 先想清楚架构:ADC 接口、时钟树与数据通路怎么定

2.1 从 ADC 输出格式反推 FPGA 接口类型

选 FPGA 之前先看 ADC 吐什么。常见三类:并行 LVDS、JESD204B、CMOS 并行。并行 LVDS 在 100~500 MSPS 区间最常见,走 DDR 采样,FPGA 侧用 IDDR 原语接收;JESD204B 用于更高采样率,但需要 GT 收发器和 SYSREF 同步,链路调试周期长;CMOS 并行简单,但速率上不去,一般不超过 100 MSPS。我一般会先确认 ADC 的 data sheet 里输出时序图,看建立保持窗口,再决定 FPGA 侧用哪种 IO 标准。这一步定错,后面全是返工。

2.2 时钟树:采样时钟、同步时钟、系统时钟必须分开管

时钟是采集系统的命门。采样时钟给 ADC,必须低抖动;同步时钟用于多通道对齐;系统时钟给 FPGA 内部逻辑和存储。常见做法是用一片时钟芯片产生多路同源输出,比如某款低抖动时钟发生器,采样时钟和 FPGA 参考时钟同源但不同分频。注意:采样时钟的抖动直接折算成 SNR 损失,公式是 SNR = -20log(2πf·t_jitter),1 ps 抖动在 100 MHz 输入下就吃掉约 4 dB。所以别拿普通晶振凑合。

2.3 数据通路:从 IO 到 DDR 再到上位机的三级缓冲

数据从 ADC 进来后不能直接往 DDR 写,中间要过一级 FIFO。典型路径是:IDDR 接收 → 位对齐 → 异步 FIFO 跨时钟域 → 打包成 AXI Stream → DDR 控制器写入 → 上位机通过 PCIe 或千兆网读取。FIFO 深度要算:如果 DDR 写入有仲裁延迟,FIFO 至少要能扛住最大延迟乘以数据率。比如 500 MSPS × 14 bit,DDR 延迟 200 ns,FIFO 至少 1400 bit,取整用 2 Kb 的 BRAM 做异步 FIFO 比较稳。

// 异步FIFO跨时钟域:ADC采样时钟域到系统时钟域 // 深度2048,位宽16,用双口BRAM实现 async_fifo_2048x16 u_adc_fifo ( .wr_clk (adc_clk), // ADC采样时钟,500MHz .wr_rst_n (adc_rst_n), .wr_en (adc_valid), // ADC数据有效标志 .din (adc_data), // 14bit数据左对齐到16bit .rd_clk (sys_clk), // 系统时钟,200MHz .rd_rst_n (sys_rst_n), .rd_en (fifo_rd_en), // 由DDR写入状态机控制 .dout (fifo_dout), .full (fifo_full), // 满标志,反压ADC或丢弃 .empty (fifo_empty) );

这段代码的关键在wr_clk和rd_clk完全异步,FIFO 内部用格雷码指针做跨时钟域同步。参数上,深度 2048 是经验值,对应 500 MSPS 下约 4 μs 的缓冲,足够覆盖 DDR 仲裁的最坏延迟。adc_valid必须由 ADC 的随路时钟或数据有效信号驱动,不能自己造。如果fifo_full拉高,说明 DDR 侧吞吐不够,要么降采样率,要么加 FIFO 深度,要么检查 DDR 带宽分配。

3. 动手搭最小系统:从 ADC 配置到 DDR 写入的完整链路

3.1 ADC 配置:SPI 寄存器写入与回读校验

大多数高速 ADC 上电后需要配置内部寄存器,比如增益、偏置、测试模式、输出格式。用 FPGA 写一个 SPI 主控,按 data sheet 的时序要求逐条写。注意:写完后必须回读校验,否则 ADC 没配好,采回来的全是噪声。常见做法是写一个状态机,先软复位,再写配置表,最后逐条回读比对。

// ADC SPI配置状态机:写-回读-比对 localparam S_IDLE=0, S_WR=1, S_RD=2, S_CMP=3, S_ERR=4; reg [2:0] state; reg [7:0] cfg_addr, cfg_data, rd_data; reg [7:0] cfg_idx; always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin state <= S_IDLE; cfg_idx <= 0; end else begin case(state) S_IDLE: if(cfg_start) state <= S_WR; S_WR: begin spi_write(cfg_addr, cfg_data); // 写寄存器 state <= S_RD; end S_RD: begin spi_read(cfg_addr, rd_data); // 回读 state <= S_CMP; end S_CMP: begin if(rd_data != cfg_data) state <= S_ERR; // 比对失败 else if(cfg_idx == CFG_NUM-1) state <= S_IDLE; else begin cfg_idx <= cfg_idx + 1; state <= S_WR; end end S_ERR: begin error_flag <= 1'b1; // 配置错误,点亮LED或上报 state <= S_IDLE; end endcase end end

逻辑说明:状态机按「写→读→比对」循环,CFG_NUM是配置表条目数。参数上,SPI 时钟一般不超过 10 MHz,具体看 ADC 手册。error_flag拉高后不要自动重试,先停下来查硬件,因为回读失败通常是 SPI 时序或电源问题,重试没用。

3.2 DDR 写入:AXI Stream 打包与突发长度设置

ADC 数据进 DDR 不能零散写,要打包成 AXI Stream 突发。常见做法是攒够 256 个采样点再发起一次 AXI Burst,突发长度设 64 或 128,位宽 128 bit。这样 DDR 效率最高。注意:AXI 的tvalid和tready握手不能死等,要加超时计数器,否则 DDR 控制器一忙,整个采集就卡死。

// AXI Stream打包:256点一组,触发DDR突发写 reg [15:0] pkt_cnt; reg [127:0] axis_tdata; reg axis_tvalid; always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin pkt_cnt <= 0; axis_tvalid <= 0; end else begin if(!fifo_empty && pkt_cnt < 256) begin axis_tdata <= {axis_tdata[111:0], fifo_dout}; // 移位拼接 pkt_cnt <= pkt_cnt + 1; axis_tvalid <= 1'b1; end else if(pkt_cnt == 256) begin axis_tvalid <= 1'b0; // 一包结束,等DDR写完 pkt_cnt <= 0; end end end

参数说明:pkt_cnt到 256 清零,对应 256×16 bit = 4 Kb 一包。AXI 突发长度在 DDR 控制器 IP 里设成 64,位宽 128 bit,这样一包正好 4 个突发。axis_tvalid拉低后要等axis_tready再拉高,否则数据丢失。如果 DDR 带宽不够,优先加大突发长度,而不是加 FIFO 深度。

3.3 上位机读取:PCIe DMA 与千兆网 UDP 的取舍

上位机拿数据两条路:PCIe DMA 和千兆网 UDP。PCIe 带宽高,适合连续采集,但需要驱动和 DMA 引擎,调试周期长;千兆网 UDP 简单,适合触发式采集,但带宽上限约 110 MB/s,500 MSPS×14 bit 肯定不够。我一般会先算数据率:500 MSPS×14 bit = 7 Gbps,PCIe Gen2×4 够用,千兆网只能降采样或触发模式。选型时别只看峰值,要看持续写入能力。

4. 避坑与排查:采集系统最容易翻车的五个地方

4.1 现象:采回来的波形有周期性毛刺,幅度不大但一直存在

原因:采样时钟和系统时钟同源但相位没对齐,导致 IDDR 采样窗口偏移。解决:用 MMCM 把采样时钟和系统时钟做相位扫描,找到眼图最开的位置,或者直接用 ADC 的随路时钟做接收时钟。

4.2 现象:DDR 写入一段时间后 FIFO 满,数据丢失

原因:AXI 突发长度太短,DDR 仲裁频繁切换,有效带宽下降。解决:把突发长度从 16 加到 64 或 128,同时检查 DDR 控制器的读写优先级,采集期间给写通道更高优先级。

4.3 现象:ADC 配置回读失败,但 SPI 波形看起来正常

原因:ADC 的 SPI 时序要求建立保持时间比 FPGA 默认输出更严格,或者 ADC 电源还没稳就发了配置。解决:SPI 时钟降到 1 MHz 试,配置前加 10 ms 延时等电源稳,回读失败时用示波器抓 SPI 三线时序。

4.4 现象:上位机收到的数据包序号错乱,偶尔丢包

原因:UDP 发送没有流控,FPGA 发太快,上位机网卡缓冲区溢出。解决:加发送间隔或改用带确认的协议,FPGA 侧加发送 FIFO 水位控制,水位低于阈值才发下一包。

4.5 现象:多通道采集时通道间相位不一致,差几个采样点

原因:各通道 ADC 的同步信号走线长度不一致,或者 JESD204B 的 SYSREF 没对齐。解决:用同源同步信号,走线等长,JESD204B 模式下检查 SYSREF 的建立保持窗口,必要时用 FPGA 内部延迟单元做微调。

5. 进阶技巧:用触发状态机和在线校准把采集系统做稳

5.1 触发状态机:从自由采集到条件触发的平滑切换

自由采集和触发采集不能靠上位机切换,要在 FPGA 里做状态机。常见做法是:自由模式下数据持续写 DDR 环形缓冲区,触发条件满足后,状态机把触发点前后的数据打上标记,上位机只读标记段。这样既不丢触发前数据,也不浪费 DDR 带宽。

// 触发状态机:自由采集→触发→标记→回读 localparam F_FREE=0, F_ARM=1, F_TRIG=2, F_HOLD=3; reg [1:0] trig_state; reg [31:0] pre_cnt, post_cnt; always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) trig_state <= F_FREE; else case(trig_state) F_FREE: if(arm_en) trig_state <= F_ARM; F_ARM: if(trig_in) begin trig_state <= F_TRIG; pre_cnt <= 0; // 开始记录触发前点数 end F_TRIG: begin pre_cnt <= pre_cnt + 1; if(pre_cnt == PRE_NUM) trig_state <= F_HOLD; end F_HOLD: if(read_done) trig_state <= F_FREE; endcase end

参数说明:PRE_NUM是触发前保留点数,一般设 1024 或 2048,对应环形缓冲区深度。trig_in要过两级同步器再进状态机,否则亚稳态会导致误触发。read_done由上位机回读完成信号驱动。

5.2 在线校准:用已知测试信号修正增益和偏置

ADC 的增益和偏置会随温度漂移,高精度采集必须在线校准。做法是:在采集间隙切到内部测试信号,采一组数据算均值和方差,和理想值比对,把修正系数写回 FPGA 的乘法器。注意:校准期间不要采真实信号,否则修正系数会被污染。

校准项测试信号计算方式更新时机
偏置零输入均值每次上电或温度变化>5℃
增益满量程正弦峰值比对每 10 分钟或触发校准
相位同源方波过零点计数多通道同步时

5.3 验证方法:用已知频率和幅度的信号源做端到端校验

系统搭完后,别急着上真实信号。先用信号源给一个已知频率和幅度的正弦波,采回来做 FFT,看 SNR、SFDR、ENOB 是否和 ADC 手册标称值接近。如果差太多,先查时钟抖动,再查电源噪声,最后查 DDR 是否丢点。我一般会存一组「黄金数据」在 Flash 里,每次上电自检时比对,偏差超过阈值就报警。

这套东西做下来,最深的教训是:别在时钟和电源上省钱。我见过太多采集系统,逻辑代码写得漂亮,最后败在一颗普通晶振上。还有,FIFO 深度和 DDR 突发长度要一起调,单独调一个永远调不通。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询