基于 FPGA 与 USB 2.0 的线阵 CCD 光谱数据采集系统设计
关键词:FPGA、线阵 CCD、光谱采集、AD9240、FIFO、USB 2.0、FTDI、Vivado、ILA、数据传输
摘要
本文介绍一套基于 FPGA 的线阵光谱数据采集系统。系统从线阵探测器输出的模拟信号开始,经模拟前端和 AD9240 转换为 14 位数字样本;FPGA 负责产生采集时序、识别有效样本、组织帧边界、利用双 FIFO 完成跨时钟缓存,并经 FTDI USB 2.0 接口将原始数据送至 PC。本文重点分析 FPGA 的模块划分、关键数据格式、FIFO/USB 数据通路和 ILA 波形验证,不涉及光学设计、扫描控制、OCT 重建或图像识别。
1. 引言
线阵探测器输出的数据具有两个特点:一是样本随时序连续到来,二是 PC 端 USB 读取并不总是连续。若没有缓存,USB 传输的等待会反过来干扰采集;若没有帧边界,主机也无法判断一段字节流是否是一帧完整光谱。
因此,数据采集系统的核心并不是“把数据传到电脑”,而是保证下面四件事同时成立:
- FPGA 在正确时刻驱动探测器和 ADC;
- 只有有效窗口内的 ADC 样本进入数据通路;
- 每帧数据都具有可识别边界;
- 采集时钟域和 USB 时钟域被缓存隔离,数据按顺序送往 PC。
2. 系统总体设计
系统的硬件数据路径很明确:
线阵 CCD / 探测器 ↓ 模拟光谱信号 模拟前端(滤波、幅度匹配) ↓ AD9240(14 位 ADC) ↓ adc_data[13:0] FPGA(时序、有效标志、帧封装、双 FIFO、USB 调度) ↓ FTDI 245 FIFO 型 USB 2.0 接口 ↓ PC(原始帧接收、显示或保存)FPGA 同时生成ccd_clk、st、data_en等时序信号;PC 可以通过 USB 发送启动、停止和采集参数。由此形成“数据上送、控制下发”的双向接口,但正文始终只围绕数据采集展开。
3. FPGA 设计方案
3.1 代码模块如何划分
顶层ccd_usb_top没有把所有功能塞在同一段逻辑中,而是按数据路径拆分:
tra_ccd → ad_dr → data_12_16 → fifo_temp × 2 → fifo_drive → usb_top_ft232h其中:
| 模块 | 作用 |
|---|---|
tra_ccd | 产生探测器时钟、启动和采集使能 |
ad_dr | 接收 AD9240 的 14 位数据并输出有效标志 |
data_12_16 | 将有效样本转换为带帧边界的 16 位传输字 |
fifo_temp × 2 | 在采集时钟域和 USB 60 MHz 时钟域之间缓存数据 |
fifo_drive | 根据 A/B FIFO 空满状态和 USB 状态调度读写 |
usb_top_ft232h | 对接 FTDI 245 FIFO 型 USB 通道并解析控制帧 |
这样写的好处是:数据不对时看ad_dr,帧不对时看data_12_16,USB 卡顿时看 FIFO 与握手,而不是面对一个无法拆分的大状态机。
3.2 启动和停止,为什么要先清 FIFO
采集系统最容易被忽略的不是“如何启动”,而是“停止后留下了什么”。如果一次采集停止时 FIFO 内还有残留字节,而下一次采集直接启动,PC 端可能会先读到上一轮的帧尾或半帧数据。
代码中的star模块专门处理这一点。它用IDLE、RUNNING、CLEAR_FIFO、WAIT_STOP四个状态控制pll_flag和fifo_clean:启动时放行采集时钟;停止时关闭时钟放行、置位 FIFO 清空;下一次启动前一直保持等待状态。这样,采集会话的第一帧从干净的缓存开始。
启动命令 → 放行采集时钟 → 接收一组组新帧 停止命令 → 停止写入 → 清空 FIFO → 等待下一次启动3.3 时序模块和采样模块为什么分开
tra_ccd不直接处理 ADC 数据,它只产生探测器相关的ccd_clk、st和data_en。当前实现中,st_cnt负责周期计数,trig_cnt在指定计数窗口后短暂置位data_en。这样“什么时候让探测器工作”和“什么时候允许数据写入”被分开表达。
ad_dr再把trig输出为ad_clk,并以IDLE → HIGH → LOW状态机产生ad_flag。其中data_en表示采集窗口已经打开,eos用于结束当前有效段。后级并不是见到时钟就写数据,而是依据ad_flag识别有效样本,这一点对排除无效区和避免帧错位非常关键。
3.2 从 14 位 ADC 样本到 USB 帧
AD9240 数据经ccd_data[13:0]进入 FPGA。data_12_16利用ad_flag的边沿确定帧的开始与结束,并采用如下组织方式:
0xAAAA | {2'b00, ccd_data[13:0]} × N | 0x55550xAAAA是帧头,0x5555是帧尾,中间为扩展到 16 位的 ADC 样本。这使 PC 接收端可以在连续字节流中寻找边界、检查数据是否截断,并在需要时重新同步。它解决的是数据对齐问题,不是完整的差错校验协议。
data_12_16的状态机逻辑可以理解为:检测到ad_flag上升沿时先写帧头;下一拍开始输出{2'b00, ccd_data};在ad_flag保持期间持续输出样本;检测到下降沿后输出最后一个样本,再单独写帧尾。帧头、首个样本和帧尾因此不会挤在同一个数据字里。
3.3 FIFO 为什么要分成两个
采集数据写入 FIFO 的节奏由采集时钟决定,USB 读数据的节奏由usb_clk_60m和 PC 握手状态决定。两端节奏不同,所以需要把“持续采样”和“按 USB 条件发送”隔开。
当前实现中:
- FIFO 写侧接收 16 位帧数据;
- FIFO 读侧输出 8 位字节流;
- 两个 FIFO 由
fifo_drive统一调度; - A/B FIFO 的空满状态决定读写是否允许。
这给系统提供了跨时钟域和位宽适配的缓冲位置。后续 FIFO 专题会专门讨论切换条件、缓存深度和连续帧验证。
3.4 A/B FIFO 如何交替工作
fifo_drive实际上有写、读两套状态机。写端运行在采集相关时钟下:一帧开始后选择 FIFO A 或 B 写入,发现eos时记录这一帧已经结束,再切换到另一组 FIFO。读端运行在 USB 60 MHz 时钟下:只有确认某一组 FIFO 已经完成一帧写入、并且尚未读空时,才选择它送往 USB。
采集第 n 帧:写 FIFO A USB:读上一帧的 FIFO B 采集第 n+1 帧:写 FIFO B USB:读上一帧的 FIFO A这个过程把“采样不能停”和“USB 可能等待”分离开。它不是抽象概念上的乒乓缓存,而是代码中fifo_wrreq_select、fifo_rd_select、eos_a/eos_b、fifo_empty_a/b共同完成的帧级调度。
4. USB 2.0 数据传输
数据从 FIFO 读出后进入usb_top_ft232h,再经ftdi_245fifo_top与 FTDI 245 FIFO 风格接口握手。USB 可写时,FPGA 按顺序发送缓存字节;USB 接收方向则用于传入启动、停止和参数控制帧。
对数据采集来说,USB 模块的判断标准不是“接口能枚举”,而是:
- 有数据时能从正确的 FIFO 读出;
- FIFO 为空时不发生无效读;
- USB 不可写时不会覆盖未发送的数据;
- 一段连续采集后,PC 仍能找到完整帧头和帧尾。
从代码接口可见,上传侧是 8 位字节流,控制接收侧则以 128 位帧进入tx_specified_len。启动标志为16'h0024,全1的 128 位帧用于停止。对数据采集而言,这套控制帧的作用只有一个:把“何时开始写新数据、何时停止并清空缓存”明确地交给 FPGA,而不是依赖 PC 端随意断开读取。
5. 数据通路的时钟域和位宽变化
把一帧数据从 ADC 送往 USB,需要跨过三个边界:
| 阶段 | 数据宽度 | 主要时钟/标志 | 关键任务 |
|---|---|---|---|
| ADC 输入 | 14 位 | trig/ad_clk、data_en、ad_flag | 只保留有效样本 |
| FPGA 帧数据 | 16 位 | 采集相关时钟、帧首尾状态机 | 扩展数据位宽并加入帧边界 |
| FIFO 写侧 | 16 位 | 采集相关时钟 | 连续写入当前帧 |
| FIFO 读侧/USB | 8 位 | usb_clk_60m、USB 握手 | 字节化输出并等待接口可写 |
因此,FIFO 的 16 位写、8 位读不是为了压缩数据,而是把 FPGA 内部的帧字适配为 USB 的字节流。PC 接收端只要按相同字节序重新拼回 16 位字,就可以寻找0xAAAA和0x5555来恢复帧边界。
6. ILA 波形如何验证这条链路
这张 ILA 波形将data_en、FIFO A/B 的读写请求与空满状态、USB 相关状态、data_out[13:0]和data_16[15:0]放在同一时间轴上。它的价值在于把采样、封帧、缓存和传输放到一次观察中:
| 观察信号 | 用于确认 |
|---|---|
data_out[13:0] | ADC 样本确实进入 FPGA |
data_16[15:0] | 样本已被组织为传输用数据字 |
fifo_wrreq_a/b | 有效帧数据正在进入缓存 |
fifo_rdreq_a/b | USB 侧正在从缓存取数据 |
fifo_empty/full_a/b | 缓存切换没有越过空/满边界 |
| USB 状态 | 读写请求受接口握手条件约束 |
看这张波形时,建议不要只盯一个信号,而是按顺序检查:先确认data_en出现时data_out[13:0]有连续样本;再确认data_16[15:0]产生对应传输字;接着确认fifo_wrreq_a/b只在当前 FIFO 未满时写入;最后看fifo_rdreq_a/b是否只在 FIFO 非空、USB 条件允许时产生。这样就能把“没有数据”“没有写入缓存”“缓存没有读出”“USB 没有发送”四类问题区分开。
波形中的fifo_full_a/b在当前观察窗口保持低电平,说明这一次捕获窗口内没有写满;fifo_empty_a/b则配合读请求判断缓存是否已经被读空。对外展示时应把它表述为“本次波形观察结果”,不把单个窗口写成长期稳定性的结论。
工程保存的实现报告还显示,该 FPGA 快照的用户时序约束满足,WNS 为 9.900 ns、TNS 为 0。它是 RTL 已完成实现和时序检查的证据,不用于外推整机性能。
7. 总结
这套数据采集系统只做一件事:以 FPGA 为核心,稳定地把线阵探测器经 AD9240 得到的数字光谱样本组织、缓存并传输到 PC。其关键不是单一模块,而是探测器时序 → ADC 有效样本 → 帧边界 → FIFO 跨时钟缓存 → USB 握手的连续关系。
下一篇将从 线阵光谱探测器驱动与采集 开始,继续拆解tra_ccd与ad_dr的时序和样本有效窗口;之后依次展开 AD9240、FIFO 和 USB 专题。
本文仅分享公开架构、局部波形和设计思路;完整 RTL、约束、IP 配置、原理图、PCB 和原始数据后续分享。