在需要实现高采样率模数转换器(ADC)数据向主机PC高速、稳定传输的嵌入式系统中,一种高效的架构是采用EZ-USB FX3 (CYUSB3014) 芯片工作于同步 Slave FIFO 模式。
在这种架构中,USB芯片的角色发生了转变。它不再是一个需要复杂命令控制的“智能设备”,而是退化为一个“傻瓜式”的高速数据管道:
- Master (主控):FPGA。FPGA负责提供时钟,并决定何时向USB芯片读写数据。
- Slave (从机):USB 3.0芯片(FX3)。它只负责提供FIFO的状态标志(如满/空),并被动地接收来自FPGA的数据。
USB 3.0 芯片的固件 (Firmware)
对于FX3这类芯片,其内部集成了一个ARM核,需要加载特定的固件来配置其内部硬件连接。固件的核心任务之一是创建一条直接的DMA(直接内存访问) 通道,将芯片的外部引脚(GPIF II接口)与内部的USB 3.0端点FIFO直接连通。
我们需要使用Cypress官方提供的开发工具(EZ-USB Suite)来生成此固件,核心配置包括以下几项:
-
GPIF II 接口配置:
- 设置为 32-bit 宽度。
- 设置为 Synchronous Slave FIFO 模式。
- 时钟源:由FPGA提供(通常为100MHz)。
-
DMA 通道配置:
- 创建一个 ”Auto DMA Channel”。
- 源端(Producer):GPIF II接口(连接FPGA)。
- 目的端(Consumer):USB Bulk IN端点(连接主机PC)。
- “Auto”意味着数据从FPGA流入后,ARM核不进行任何干预,直接由硬件打包并通过USB发出,这种方式效率最高。
-
标志位 (Flags) 配置:
- 配置一个引脚为 “Watermark Flag (水位标志)”。例如,当USB芯片内部buffer即将填满时(例如仅剩2KB空间),该引脚会被拉低,以此通知FPGA暂停发送数据,实现流控。
固件编译后将得到一个 .img 文件。系统上电时,主控SoC需要通过某种方式(例如SPI或USB控制传输)将这个固件先行下载到FX3芯片的RAM中运行。
FPGA 端的数据传输逻辑 (Verilog)
在此架构中,FPGA是数据发送的主动方。为了匹配前端ADC(例如1MHz)的连续采样特性与后端USB的突发传输需求,必须在FPGA内部使用大容量FIFO和精心设计的状态机。
1. 跨时钟域 FIFO
通常,前端的DSP处理逻辑(如CIC滤波器)可能运行在一个时钟域(例如50MHz),而USB接口逻辑则需要运行在另一个时钟域(例如100MHz)。为了解决时钟域差异并缓冲数据,我们需要:
- 实例化一个异步 FIFO (Async FIFO)。
- 写端口:连接CIC滤波器等处理模块的输出(使用DSP处理时钟,如50MHz)。
- 读端口:连接后续的USB接口逻辑(使用USB接口时钟,如100MHz)。
- 深度:建议至少 32KB (8192 x 32bit),甚至更大,以防止因主机PC端暂时繁忙而导致数据溢出。
2. 核心逻辑:USB 接口状态机 (FSM)
这个状态机负责监测内部FIFO的状态以及USB芯片的标志位,并据此控制数据总线的传输时序。
接口信号假设 (基于FX3定义):
usb_clk:输入,100MHz,可由USB芯片提供或由FPGA提供。
usb_data[31:0]:输出,32位数据总线。
slwr_n:输出,低电平有效的写使能信号。
flag_full_n:输入,低电平时表示USB芯片内部缓冲区快满了(背压信号,Back Pressure)。
下面是一个简化的Verilog示例,展示了核心状态机的控制逻辑:
module usb3_master_interface (
input wire usb_clk,
input wire rst_n,
// 连接 FPGA 内部大 FIFO 的读端口
input wire [31:0] fifo_rdata,
input wire fifo_empty,
output reg fifo_ren, // 读使能
// 连接 USB 3.0 芯片的物理引脚
output reg [31:0] usb_data_out,
output reg slwr_n, // 写选通 (低有效)
input wire flag_full_n // USB芯片满标志 (低表示满)
);
// 状态定义
localparam S_IDLE = 2'd0;
localparam S_TRANSFER = 2'd1;
reg [1:0] state;
// 为了时序更好,通常将 FIFO 输出打一拍
reg [31:0] fifo_rdata_d1;
always @(posedge usb_clk) fifo_rdata_d1 <= fifo_rdata;
always @(posedge usb_clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
fifo_ren <= 1'b0;
slwr_n <= 1'b1; // 无效
usb_data_out <= 32'd0;
end else begin
case (state)
S_IDLE: begin
slwr_n <= 1'b1; // 停止写
// 条件:FPGA FIFO 不空,且 USB 芯片不满
if (!fifo_empty && flag_full_n) begin
fifo_ren <= 1'b1; // 预读一个数据
state <= S_TRANSFER;
end else begin
fifo_ren <= 1'b0;
end
end
S_TRANSFER: begin
// 持续突发传输
if (!fifo_empty && flag_full_n) begin
fifo_ren <= 1'b1; // 继续读下一个
slwr_n <= 1'b0; // 写使能有效!
usb_data_out <= fifo_rdata; // 发送数据 (注意流水线延迟)
// 实际应用中可能需要处理 FIFO 读延迟,这里简化了
end else begin
// 一旦条件不满足,立即停止
fifo_ren <= 1'b0;
slwr_n <= 1'b1; // 停止写
state <= S_IDLE;
end
end
endcase
end
end
// 注意:这是一个极简模型,实际设计要处理好 FIFO 读潜伏期(Latency)
// 确保 slwr_n 拉低的那一拍,usb_data_out 上是非常正确的数据。
endmodule
主机端软件的关键考量
当数据通过USB 3.0管道稳定送达主机后,PC端软件的角色至关重要。对于持续流量大于 24MB/s 的高速应用,绝对不能使用简单的同步读取API。为了确保不掉包并能及时响应USB的背压机制,必须使用 libusb-1.0的异步API 进行数据传输。异步API允许应用程序提交传输请求后立即返回,当传输完成或出现状态时通过回调函数通知,这种方式能极大地提高吞吐量和系统的响应能力。
在云栈社区的硬件开发板块,你可以找到更多关于FPGA与高速接口整合的实战讨论与经验分享。
|