ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

FPGA与CY68013实现USB2.0高速传输:Slave FIFO模式实战解析

FPGA与CY68013实现USB2.0高速传输:Slave FIFO模式实战解析 简介面向需要实现USB2.0高速批量数据传输的嵌入式开发者与FPGA工程师这份资源提供基于Cypress CY68013的完整Bulk传输实施方案采用Slave FIFO模式实现FPGA与USB主机间的高效数据交互适用于数据采集、文件备份等非实时大批量传输场景。包内共45个文件其中Verilog源码构成FPGA侧核心逻辑C语言固件与头文件负责USB控制器配置另有a51启动代码、链接脚本、hex固件、IIC文件及docx控制器说明书整体约385KB工程结构紧凑且模块划分清晰便于对照学习。目前已有721人学习下载。资源中FPGA代码覆盖握手信号管理、FIFO缓冲、时序控制、CRC错误检测与协议转换等关键模块固件源码则处理端点配置、中断维护与Bulk传输队列管理两者协同可直接验证高速通信。附带详尽的控制器说明书和测试例程能帮助开发者快速理解寄存器设置、数据传输流程及联调方法极大简化USB2.0设备端设计缩短项目开发周期。1. 给 FPGA 项目加一条 40MB/s 的高速通道CY68013 Slave FIFO 模式实战做某图像处理 Demo 时FPGA 端一帧数据就有 512×512×16bit串口根本传不动等一帧出来够喝一杯茶了。后来把这个 USB2.0 的 CY68013 芯片用起来跑高速 bulk 传输的 Slave FIFO 模式FPGA 侧只写状态机固件侧只改初始化配置整条链路稳定跑到了 30~40MB/s。这个项目等于把“FPGA 固件”两段一起打通固件把 CY68013 配成 Slave FIFOFPGA 像操作普通 FIFO 一样去读写端点PC 端再开一条 bulk 管道读数据。适合不想从头啃 USB 协议栈、但手里已经有 FPGA 逻辑想做成高速采集通道的人。读完这篇你能自己把模块复刻到板子上。2. 整体架构数据从 PC 到 FPGA 是怎么流过去的2.1 Slave FIFO 模式下 CPU 只干配置的活在 CY68013 的 Slave FIFO 模式下USB 端点 RAM 被直接映射到外部引脚8051 内核在数据搬运路径上被绕开了。你可以先把这个芯片理解成一个带状态标志的 FIFOPC 通过 USB 写下来的数据会出现在外部数据总线FD上FPGA 想往 PC 发数据只要往FD上写硬件会把数据打包成 USB IN 包送出去。8051 只在TD_Init里完成配置之后就可以一直停在轮询循环里数据不走 CPU。整条链路分三段PC 上跑测试程序通过 bulk 管道和设备通信设备这边是 CY68013 的 SIE 和端点 FIFO再往外是 FPGA 的 Slave FIFO 控制逻辑。这个项目把 EP2 配置成 IN 方向FPGA 到 PC把 EP6 配置成 OUT 方向PC 到 FPGA。后续你会发现真正需要你写逻辑的地方其实很少关键是初始化顺序和寄存器值得对不对。2.2 关键信号表FPGA 侧需要认全的十几根线FPGA 和 CY68013 之间不经过任何协议转换就是一根数据总线加几根控制线。下面这张表是我在工程里一直对着看的方向都是以 FPGA 为基准。信号方向FPGA 视角作用FD[15:0]输入/输出数据总线支持 8 位或 16 位FIFOADR[1:0]输出选择端点00EP201EP410EP611EP8SLCS输出片选低有效调试时可以直接拉低SLWR输出写选通低电平有效SLRD输出读选通低电平有效SLOE输出输出使能控制 FD 总线方向低电平有效PKTEND输出手动结束短包低电平有效FLAGB输入当前端点的状态标志FLAGC输入当前端点的状态标志常见做法是把 FLAGC 配成“当前 FIFO 非满”FPGA 上传时先查它把 FLAGB 配成“当前 FIFO 有数据”FPGA 下载时先查它。注意极性不是固定的可以在固件里通过端点 FIFO 极性寄存器改。后面所有代码我都按低有效写如果你的板子恰好是反的把if (!flagc)改成if (flagc)就行。2.3 为什么是 Slave FIFO而不是 GPIF 或 USB 转串口GPIF 模式下CY68013 自己是主控固件要维护波形描述符时序由芯片产生。这样做对复杂外设很灵活但调试成本高改一个等待周期都要重新编译。Slave FIFO 正好反过来外部主控是 FPGA芯片只是个从设备。FPGA 里本来就有采集、缓存逻辑把 USB 侧抽象成“往 FIFO 里写、从 FIFO 里读”状态机能少写一半。USB 转串口芯片也不是不能传数据但很多方案的稳定吞吐只有几 MB/s和高速 bulk 的 40MB/s 级别差太远。这个项目选择 Slave FIFO 的核心逻辑就是把 USB 协议复杂度全部留给硬件让 FPGA 只碰最原始的读写信号。3. 固件侧实现先把 CY68013 配成 Slave FIFO3.1 固件框架里必看的三个文件标准的 FX2 固件框架一般由fw.c、periph.c、dscr.c组成。拿到源码先不要急着看所有文件我一般只看这三个fw/ fw.c # 主循环、设备枚举、重枚举 periph.c # TD_Init / TD_Poll项目主要改动在这 dscr.c # USB 描述符表TD_Init()在芯片上电后调用一次所有 Slave FIFO 模式相关的寄存器都在这里配。TD_Poll()是主循环里的周期函数Slave FIFO 模式下 CPU 不搬数据所以这个函数可以留空或者只放一条SUDPTRH命令。dscr.c里改 VID/PID 和端点描述符改完以后上位机驱动才认你的设备是这个样子。3.2 TD_Init 里的初始化顺序时钟、复位、端点TD_Init()里最关键的步骤如下代码里我加了注释可以直接对着寄存器手册看void TD_Init(void) { // 1. 配置接口Slave FIFO 模式使用内部 48MHz 时钟 IFCONFIG 0xE3; SYNCDELAY; // 等待芯片内部同步完成 // 2. 复位所有端点 FIFO这一步能解决很多“FLAG 拉不低”的玄学问题 FIFORESET 0x80; SYNCDELAY; FIFORESET 0x02; // 复位 EP2 SYNCDELAY; FIFORESET 0x04; // 复位 EP4 SYNCDELAY; FIFORESET 0x06; // 复位 EP6 SYNCDELAY; FIFORESET 0x08; // 复位 EP8 SYNCDELAY; FIFORESET 0x00; SYNCDELAY; // 3. 打开端点自动模式相关控制位 REVCTL 0x03; SYNCDELAY; // 4. 配置端点方向和类型 EP2CFG 0xA0; // EP2BULK IN512 字节端点 SYNCDELAY; EP6CFG 0xE0; // EP6BULK OUT512 字节端点 SYNCDELAY; // 5. 配置端点 FIFO 数据宽度和自动模式 EP2FIFOCFG 0x18; // EP216 位数据 AUTOIN SYNCDELAY; EP6FIFOCFG 0x0C; // EP616 位数据 AUTOOUT SYNCDELAY; // 6. 设置自动提交长度为 512 字节 EP2AUTOINLENH 0x02; SYNCDELAY; EP2AUTOINLENL 0x00; SYNCDELAY; }第一行IFCONFIG 0xE3是很多例程里都会出现的值它把接口模式切到 Slave FIFO同时选择内部 48MHz 时钟。如果你的 FPGA 想自己提供接口时钟就要把这一行里的时钟源位改掉我就遇到过有人在这里漏改导致后续所有数据都串位。FIFORESET顺序很重要。我在第一次调这块芯片时跳过这一步结果FLAGB、FLAGC一直保持同一个电平FPGA 状态机卡死在等待里。后来把 0x80、0x02、0x04、0x06、0x08、0x00 按顺序执行一遍以后标志位马上正常了。EP2CFG 0xA0的二进制是1010_0000bit7 置 1 表示端点有效bit6 为 0 表示 IN 方向bit5、bit4 为10表示 BULK。EP6CFG 0xE0的 bit6 为 1表示 OUT 方向其余字段含义一致。如果你想让 EP4 或 EP8 干活就要把EPxCFG和FIFOADR编址一起对上。3.3 AUTOIN 与短包提交FPGA 不用自己封 USB 包EP2FIFOCFG 0x18打开了 AUTOIN 位。AUTOIN 的意义是FPGA 往 EP2 的 FIFO 里写满 512 字节后芯片硬件自动把这个 packet 提交给 USBCPU 完全不参与。EP2AUTOINLENH/L设置的 0x0200 就是 512 字节刚好和端点描述符里的wMaxPacketSize对应。既然有自动提交那短包怎么处理FPGA 传的数据如果不足 512 字节AUTOIN 不会自己提交必须由 FPGA 拉低PKTEND信号告诉芯片“这个包到这里结束”。这个信号在项目里特别容易漏上位机收到的数据少了一段或者干脆整个事务卡死一查是 FPGA 端PKTEND没有产生。后面 FPGA 状态机里会给一个UP_PKT状态专门处理它。EP6FIFOCFG 0x0C则是把 EP6 配成 AUTOOUT主机数据到达 512 字节后自动推给 FPGA。这个方向不需要 PKTEND主机结束包由主机侧控制。3.4 描述符端点属性和 VID/PID 改哪里描述符表不一定经常动但改端点时必然要改。我把常用的改动点放在一个表里方便照着改描述符位置常见值说明VID/PID例如0x04B4/0x1004每款板子建议独立设置驱动靠它认设备接口描述符 bInterfaceClass0xFF厂商自定义避免绑定系统自带的 HID 或 WinUSB 类驱动端点描述符 bmAttributes0x02BULK需要高速吞吐就用 BULK不是 Interrupt端点描述符 wMaxPacketSize0x0200512必须和EPxCFG里的 512 一致Slave FIFO 模式不改 USB 协议描述符里只要写着“这是个 bulk 端点”上位机就能用 bulk 管道访问。很多跑不通的例子最后都是 VID/PID 和驱动签名不一致重新插拔后设备被识别成未知设备。建议每次改完固件用厂商推荐的重枚举方式加载不要为了省事强制安装不匹配的驱动。4. FPGA 侧实现两个状态机打满 FIFO 带宽4.1 顶层模块把 FX2 接口封装成可复用 IPFPGA 侧不需要知道 USB 协议它只需要知道“什么时候能写、什么时候能读”。我习惯把 Slave FIFO 接口封装成一个独立模块这样以后换板卡只要改顶层连接。module usb_fx2_slave_fifo #( parameter DW 16 )( input wire clk, input wire rst_n, inout wire [DW-1:0] fd, output reg slrd, output reg slwr, output reg sloe, output reg [1:0] fifoaddr, output reg slcs, output reg pktend, input wire flagb, input wire flagc, // 用户侧上行FPGA 要发给 PC 的数据 input wire tx_valid, input wire [DW-1:0] tx_data, input wire tx_last, // 短包结束标志 output reg tx_ready, // 用户侧下行PC 发给 FPGA 的数据 output reg rx_valid, output reg [DW-1:0] rx_data );这里DW默认是 16因为固件那边已经把EP2FIFOCFG和EP6FIFOCFG配成了 16 位数据宽度。如果你的板子是 8 位总线把DW改成 8同时固件里把那两个寄存器的 WORDWIDE 位清掉。clk用 FPGA 全局时钟就可以不一定非要和IFCLK同源但做时序收敛时输出到 FX2 的这些控制信号要加适当的约束。4.2 上行状态机FPGA 如何把数据灌进 EP2上行方向的数据路径是用户逻辑 →tx_data→ 16 位 FD 总线 → EP2 FIFO → USB。核心状态机如下注意所有条件都按低有效标志写的reg [3:0] up_state; reg [9:0] up_cnt; reg fd_out_en; reg [15:0] fd_out_reg; localparam UP_IDLE 4d0, UP_WR 4d1, UP_HOLD 4d2, UP_PKT 4d3; assign fd fd_out_en ? fd_out_reg : {16{1bz}}; always (posedge clk or negedge rst_n) begin if (!rst_n) begin up_state UP_IDLE; slwr 1b1; pktend 1b1; sloe 1b1; fifoaddr 2b00; // 默认选 EP2 slcs 1b0; fd_out_en 1b0; up_cnt 10d0; end else begin case (up_state) UP_IDLE: begin fifoaddr 2b00; // 指定当前 FIFO 是 EP2 pktend 1b1; if (tx_valid) begin tx_ready 1b1; up_state UP_WR; end end UP_WR: begin fd_out_en 1b1; fd_out_reg tx_data; slwr 1b0; // 拉低写选通数据写入 EP2 FIFO up_cnt up_cnt 1b1; if (up_cnt 10d511) begin if (tx_last) begin up_state UP_PKT; // 非整包手动提交短包 end else begin up_state UP_HOLD; // 整包写完回到等待状态 end end end UP_HOLD: begin slwr 1b1; if (flagc 1b0) begin // flagc 低EP2 还有空间可写 up_cnt 10d0; up_state UP_WR; end end UP_PKT: begin slwr 1b1; pktend 1b0; // 通知 CY68013当前短包终止 up_state UP_IDLE; end endcase end end这个状态机里UP_WR状态下slwr被拉低一个时钟周期FD 总线上同时放上tx_data。CY68013 在 Slave FIFO 异步写模式下会在这个低电平期间采样数据。连续写时检查flagc低电平代表 EP2 当前 FIFO 没满可以继续写如果为高就停在UP_HOLD等一个周期再写保证不会把 FIFO 写爆。up_cnt从 0 数到 511一共写 512 次正好是一个高速 bulk 包的长度。AUTOIN 会自动把这一包提交给主机所以代码里看不到任何 USB 包处理逻辑。tx_last是短包信号当用户逻辑告诉状态机“这批数据不满 512 字节”就需要走UP_PKT拉低pktend。很多第一次调这块芯片的人会在这里翻车因为把PKTEND漏掉了短包永远发不出去。AUTOIN 模式下芯片会自动把 FIFO 里的 512 字节提交所以整包结束时并不需要PKTEND只需要回到UP_HOLD继续下一个包。4.3 下行状态机FPGA 从 EP6 把数据读出来下行方向是 PC 发数据到 FPGA主机先写入 EP6FPGA 再读出来。方向相反状态机也简单一些reg [3:0] down_state; localparam DN_IDLE 4d0, DN_RD 4d1, DN_DONE 4d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin down_state DN_IDLE; slrd 1b1; sloe 1b1; fifoaddr 2b10; // 默认选 EP6 end else begin case (down_state) DN_IDLE: begin fifoaddr 2b10; // 指向 EP6 if (flagb 1b0) begin // flagb 低EP6 有数据可读 down_state DN_RD; end end DN_RD: begin sloe 1b0; // 先打开输出使能 slrd 1b0; // 再拉低读选通 rx_data fd; // 采样数据总线 down_state DN_DONE; end DN_DONE: begin sloe 1b1; slrd 1b1; rx_valid 1b1; // 给下游一个有效脉冲 if (flagb 1b0) begin down_state DN_RD; // 还能连续读 end else begin down_state DN_IDLE; end end endcase end end读方向有一个容易被忽略的点必须先拉低SLOE让 FD 总线变成输出模式再拉低SLRD。如果顺序反了fd上可能还是高阻态读进来的数据就是不定值。DN_DONE状态下如果flagb仍然是低说明 EP6 里还有数据直接回到DN_RD继续读不用再回DN_IDLE。这样连续读的吞吐会好很多。但要注意rx_valid只拉高一个周期下游如果需要更宽的握手可以在外面用 FIFO 再缓冲一层。4.4 16 位模式下的字节序和包长16 位数据总线上FD[7:0]对应 USB 先传输的字节FD[15:8]对应当前 word 的高位字节。如果你的图像数据是高位在前直接搬上去PC 端读出来就会错位。常见做法是在 FPGA 里对tx_data做一次{tx_data[7:0], tx_data[15:8]}的字节交换或者在上位机解析时处理。还有一点16 位模式下FPGA 每拍写 2 字节512 字节的 packet只需要 256 拍。如果照抄 8 位模式的状态机但计数上限没改就会造成把数据切成两个半包AUTOIN 提交的包长度和预期不一样。我一般先明确自己用的是 16 位还是 8 位再去核对EPxFIFOCFG和状态机里的计数上限。4.5 仿真验证给状态机喂一个假的 USB 侧板级调试之前我习惯先跑一遍仿真。最简单的做法是写一个 testbench模拟flagc、flagb的电平变化再喂一组递增数给tx_data。仿真里能看到slwr是否按预期翻转fd上的数据是否跟着变化。initial begin clk 0; rst_n 0; flagc 1; flagb 1; #100 rst_n 1; flagc 0; // 模拟 EP2 FIFO 有空位 (posedge clk); tx_valid 1; tx_data 16hAAAA; (posedge clk); tx_valid 0; end仿真通过以后再上板做回环测试。回环测试是把 EP6 读出来的数据直接接到 EP2 的tx_data上上位机发一串固定数据读出来比对。这一步能快速判断问题在 FPGA 状态机还是固件配置避免两边同时排查。5. 常见问题排查Slave FIFO 调试中的五个翻车现场5.1 枚举正常就是传不了数据先查 IFCONFIG再看 SLOE/SLCS现象Windows 里设备管理器能看到设备上位机也能打开设备但 bulk 传输的读接口一直返回 0 字节。原因设备枚举成功只说明 USB 协议层是通的不代表 Slave FIFO 接口已经启用。最常见的是IFCONFIG没有切到 Slave FIFO 模式芯片还停在默认的端口模式FD 总线根本不受 FPGA 控制。另一个常见点是SLCS和SLOE没拉低FPGA 端三态门没打开数据总线一直浮空。解决先用逻辑分析仪或示波器抓 FPGA 到 FX2 的SLCS、SLOE、SLWR信号。SLCS在调试阶段可以直接绑到 0SLOE在做读操作时再拉低。然后重新烧录固件确认IFCONFIG的接口模式位是否正确。5.2 FLAGB 与 FLAGC 永远拉不低FIFORESET 和端点方向顺序现象FPGA 状态机一直卡在等待标志位不管怎么发数据FLAGB或FLAGC都纹丝不动。原因芯片上电后端点 FIFO 没有经过复位FIFO 指针处于未知状态标志位自然不变化。另外如果EP2CFG的 IN/OUT 方向和实际通信方向配反了FLAGC会永远表示错的状态。解决TD_Init里按 0x80、0x02、0x04、0x06、0x08、0x00 的顺序把所有端点 FIFO 复位一遍每条后都加SYNCDELAY。然后核对EP2CFG是不是 IN、EP6CFG是不是 OUT。我把这个顺序贴在固件头文件注释里每次改完配置都先检查这一步。5.3 速度只有 1MB/sAUTOIN 没开数据全都走 CPU现象功能正常数据也正确但速度惨不忍睹用上位机测下来只有 1MB/s 左右。原因这是最典型的性能陷阱。如果EP2FIFOCFG没开 AUTOIN 位FPGA 写入的 FIFO 数据不会自动提交而是等待 8051 固件去读取并转发整个链路变成 CPU 搬运吞吐直接掉一个量级。同样EP6FIFOCFG如果没开 AUTOOUT主机数据也不会自动进入 FIFO。解决确认EP2FIFOCFG的 AUTOIN 位和EP6FIFOCFG的 AUTOOUT 位都已置 1并设置EP2AUTOINLENH/L为 512。上位机一次读请求最好不小于 1MB避免频繁切换 USB 传输上下文。从那以后我每次测速前都会先读一遍这两个寄存器做确认。5.4 16 位模式数据错位WORDWIDE 没配对或三态总线段错了现象上位机读到的数据内容对但字节顺序错乱或者每次读出来的同一段数据不稳定。原因固件把端点 FIFO 配成了 16 位FPGA 里的inout三态门控制却没跟上。或者反过来FPGA 模块用 16 位固件却还是 8 位两边宽度不一致。另一个高频原因是读方向的SLOE和SLRD顺序错导致fd采样时还在高阻态。解决先统一固件和 FPGA 的WORDWIDE位。16 位模式下FPGA 的fd要有一个明确的方向控制信号只在写周期输出读周期保持高阻。然后用上位机回环发一组 0x00~0xFF 的递增数如果错位规律固定就很可能是字节序问题做一个总线交换即可。5.5 固件一下载设备就消失EEPROM 启动和重枚举的坑现象插上板子第一次能看到设备用下载工具把固件写进去以后设备反而从设备管理器里消失了只能重新插拔。原因部分板子为了免驱动加载接了 EEPROM上电后芯片从 EEPROM 启动固件。如果 EEPROM 里的 VID/PID 和固件描述符不一致重枚举后系统找不到匹配驱动设备就消失了。还有一种情况是固件里TD_Init之后的代码进入死循环8051 没能正常处理 USB 中断。解决调试阶段先把 EEPROM 摘下或改为无 EEPROM 启动模式每次都通过下载工具往 RAM 里加载固件。确认链路稳定后再把固件烧进 EEPROM。固件主循环里不要在TD_Poll放阻塞代码最好保持空的轮询让 USB 中断能持续响应。6. 二次开发思路从高速传输改成自定义采集系统拿到这套源码后我最推荐的改造方式不是马上加功能而是先在 FPGA 内部做一次回环。把rx_data直接接到tx_dataPC 发一串带顺序号的数据读回来比对顺序号和 CRC。链路通了再去接真正的采集模块。这样能明确区分“USB 通道没通”和“采集逻辑有 bug”。第二步是改功能端点。现在的项目固定用 EP2 做 IN、EP6 做 OUT。如果你的板子需要同时传控制命令和图像数据可以考虑把 EP4 配成第二个 IN 端点EP8 配成第二个 OUT 端点。FIFOADR的编码是固定的00 对应 EP201 对应 EP410 对应 EP611 对应 EP8。FPGA 状态机里的fifoaddr要和固件里的EPxCFG一一对应不能只改一边。第三步是加帧同步和校验。高速 bulk 传输只管字节流不保证应用层帧边界。一种常见的做法是 FPGA 在每帧前面插入 4 字节帧头帧起始标志、帧号、长度、校验和。无论上位机还是 FPGA都先找帧头再取数。代码里加一个tx_frame_ctrl状态在tx_valid有效前先发帧头即可数据路径不用动。我自己的教训是第一次调这个板子时为了省时间跳过了回环测试直接在固件和 FPGA 两边同时改最后查了三天发现只是 AUTOIN 没开。从那以后我每次拿到新的 FX2 工程都会强制把“上位机回环”和“寄存器 dump”两个步骤走一遍再谈业务逻辑。这一套做法帮你避开我踩过的坑希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表