ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于FPGA的CY7C68013A USB 2.0双向通信实现

基于FPGA的CY7C68013A USB 2.0双向通信实现 搞FPGA的兄弟应该都碰到过这个场景板子上的算法、逻辑跑得飞起但数据就是传不到电脑上或者电脑上发的指令根本进不了FPGA。串口太慢网口又得折腾协议栈这时候一颗USB接口芯片就能解决大问题。我这次做的“基于FPGA的cy7c68013a双向通信实验”目的就是打通一条PC到FPGA、FPGA到PC的双向高速数据通路把USB 2.0的480Mbps带宽真正用起来。这套方案在数据采集、图像传输、仪器仪表控制里非常常见适合正在做FPGA项目但还没搞定上位机通信的工程师也适合刚入门想系统学习USB通信栈的同学。文章不绕弯子直接讲硬件连接、固件配置、FPGA侧RTL设计、上位机API调用最后附上实测数据和踩坑记录照着做就能跑通。1. 方案选型为什么偏偏是CY7C68013A1.1 USB接口在FPGA通信里的定位FPGA和外界通信的方式看着很多但真到工程选型时往往很纠结。串口UART最简单但115200bps的速率传几百KB的数据都要等半天以太网速度快可一旦涉及TCP/IP协议栈资源开销和调试复杂度立刻上来了很多项目根本不需要那么复杂的网络功能。USB 2.0刚好卡在中间——带宽够用理论480Mbps协议相对简单开发门槛低而且PC端驱动成熟插上就能枚举。这里说的USB 2.0实际有效吞吐率受协议开销、调度间隔、驱动模式影响批量传输实测一般能跑到30到40MB/s左右对绝大多数数据采集、控制类应用绰绰有余。Cypress现在归Infineon的CY7C68013A就是USB 2.0时代的经典方案内部集成了一个8051微控制器和USB收发器支持Slave FIFO、GPIF等接口模式专门为外部处理器、FPGA、DSP访问设计。1.2 为什么不是FT232H、CH347或者其他USB3.0方案做方案对比时我还真纠结过几个方向。FT232H是FTDI的USB 2.0高速转FIFO芯片驱动非常成熟官方库也好用但它的FIFO接口一次只能处理一个方向想双向同时收发要么切换方向要么用两片麻烦。CH347是国产的价格便宜支持UART、SPI、JTAG但它的FIFO模式资料相对少而且社区案例不多遇到问题不好查。至于USB3.0方案比如CYUSB3014FX3性能确实强理论5Gbps但开发复杂度和成本都不是一个量级如果项目只需要USB 2.0没必要杀鸡用牛刀。CY7C68013A的优势在于它的Slave FIFO模式几乎是为FPGA量身定做的FPGA直接作为主控读写芯片内部的端点FIFO8051只是负责枚举和配置数据通路完全不经过CPU吞吐率高、延迟低。再加上这芯片出来十几年了资料、开源工程、网上踩坑贴一抓一大把遇到问题基本都能搜到答案。2. 硬件搭建与关键信号说明2.1 Slave FIFO模式一句话概括先把Slave FIFO模式讲清楚。这种模式下CY7C68013A芯片内部的8051处理器把USB端点FIFO的访问权交给外部主控也就是我们的FPGAFPGA通过一组类似SRAM的接口信号直接读写FIFO数据在USB总线和FIFO之间自动搬运不用8051干预。整个芯片相当于一个“USB转双口RAM”的桥PC往USB总线写数据数据自动落到FPGA可读的FIFO里FPGA往FIFO里写数据PC端就能通过USB读走。这模式最爽的一点是8051只在上电枚举时需要工作正常运行后完全可以不管数据流。我见过很多工程甚至把8051的时钟都省了直接用外部逻辑接管芯片就能稳定工作。2.2 硬件连接与引脚分配先说一个重要的概念USB端点的方向是站在主机PC视角定义的。所以“IN端点”是设备向PC传数据数据方向是FPGA到PC“OUT端点”是PC向设备传数据数据方向是PC到FPGA。在做FPGA逻辑时千万别搞反了否则数据死活传不对。我在实验板上用的是16位数据总线模式这样带宽利用率高。关键信号如下信号名方向相对FPGA功能说明FD[15:0]双向数据总线16位模式时一次传16bitIFCLK输入/输出接口时钟可配置为内部48MHz或外部时钟FIFOADR[1:0]输出选择要操作的端点FIFOEP200EP401EP610EP811SLCS输出片选信号低有效选中68013A的FIFO接口SLOE输出输出使能读操作时让68013A把数据驱动到FD总线SLRD输出读选通低有效每个有效沿从FIFO中读出一个数据SLWR输出写选通低有效每个有效沿把FD总线上的数据写入FIFOPKTEND输出强制提交数据包用于提交不完整长度的短包FLAGA/B/C输入端点FIFO状态标志可配置为“满”“空”“可编程”等连接方式FD总线直接接FPGA的普通IO建议加上串联电阻降低振铃FLAG信号最好在FPGA内部做同步处理后使用因为异步信号直接进状态机容易引发亚稳态。CLKOUT/IFCLK我用的是芯片内部产生的48MHz时钟这样省掉一颗外部晶振规划引脚时还更灵活。2.3 时钟设计内部时钟还是外部时钟CY7C68013A的IFCLK可以来自芯片内部48MHz、30MHz或24MHz也可以由FPGA外部输入。我实验里用内部48MHz省事。但要注意如果FPGA逻辑需要和IFCLK严格同步比如用状态机采样SLWR/SLRD时序建议把IFCLK引入FPGA做成全局时钟网络所有接口信号都以它为基准。如果用内部时钟FIFO接口信号本身是异步的FPGA侧必须做跨时钟域处理否则高速采集时大概率丢数据。这一点我在第一次调试时吃了大亏后面第6章细说。3. 固件配置让68013A变成一台FIFO机3.1 Slave FIFO模式的核心配置项CY7C68013A上电后8051会先跑固件程序完成USB枚举和设备描述符上报让PC认到这是一个自定义USB设备。然后我们通过寄存器配置把接口切到Slave FIFO模式。最重要的寄存器有三个。第一个是IFCONFIG0xE601控制接口模式和时钟bit7为1时启用Slave FIFO模式bit5和bit6控制接口时钟来源和频率bit4控制数据总线宽度0为8位1为16位。我配置为0xE3即Slave FIFO模式内部48MHz时钟16位总线。第二个是FIFORESET0xE604用于复位各个FIFO在切换端点或清空数据时非常有用。第三个是EP2CFG、EP6CFG等端点配置寄存器决定端点方向、缓冲类型、数据包大小、缓冲数量。我实验板的规划是EP2作为批量OUT端点PC发往FPGAEP6作为批量IN端点FPGA发往PC缓冲都设为2倍512字节。3.2 端点规划EP2下行、EP6上行整个双向通信的逻辑建立在两个端点上方向相反互不干扰。这里贴几个核心配置项寄存器设置值功能EP2CFG0xA0EP2有效批量传输OUT方向512字节包双缓冲EP6CFG0xE0EP6有效批量传输IN方向512字节包双缓冲EP2FIFOCFG0x01自动提交模式关闭手动提交16位数据宽度EP6FIFOCFG0x09自动提交模式关闭可写16位数据宽度固件代码里除了初始化寄存器还要注意处理USB设备的枚举描述符。如果不想从零写Cypress官方提供的FX2LP开发包里有SlaveFIFO的模板工程直接改改VID/PID、端点配置就能用。我用的VID/PID是0x04B4和0x8613Cypress默认值如果要做成正式产品记得换成自己的VID和PID否则和其他人共用默认驱动的设备会冲突。固件编译好之后用Cypress的USB Control Center工具选择“Download”把.hex文件下载到芯片。如果板子上有EEPROM可以把固件烧到EEPROM里实现上电自举以后插上USB线就能直接枚举不用每次手动下载。3.3 固件配置示例和烧录步骤很多初学者卡在“固件下载”这一步这里给个标准流程。首先用USB线连接开发板和PC此时板子默认没有固件但芯片内部有预置的“无固件”描述符PC会识别为一个名为“Cypress EZ-USB FX2LP (No EEPROM)”的设备。打开Cypress USB Control Center菜单里选择“Program File”加载编译好的.hex文件然后点下载。下载成功后设备会重新枚举名称变成固件里设定的设备名这就说明枚举成功了。如果是批量生产把固件用烧录器写进24C02 EEPROM就行。芯片上电时检测到EEPROM会自动加载固件这个特性在做独立产品时特别方便。4. FPGA侧RTL设计状态机和时序才是重头戏4.1 FPGA侧模块划分FPGA侧我分了三个模块接口时序控制模块、数据缓冲FIFO、用户逻辑回环测试用。接口时序控制模块是整个设计的核心它要严格按照68013A的时序来读写FIFO处理满/空标志防止数据溢出或读空。模块划分建议这样模块名功能关键端口usb_if_ctrl68013A接口时序控制FD[15:0]、SLWR、SLRD、SLOE、FIFOADR、FLAGfifo_rx接收缓冲PC→FPGA写侧接USB接口读侧接用户逻辑fifo_tx发送缓冲FPGA→PC写侧接用户逻辑读侧接USB接口user_logic功能逻辑测试时可做回环读写两个数据FIFO用内部FIFO做缓冲是为了解耦USB突发传输和FPGA内部的数据速率。USB端点FIFO受包长度、缓冲数量限制一次最多512字节如果FPGA内部逻辑处理慢突发数据会填满端点FIFO导致溢出反之如果FPGA逻辑产生的数据太快USB来不及搬走端点FIFO就会满。中间加一层大深度的FIFO能有效平滑双方的速度差异。4.2 写通道EP6数据上行时序与代码写通道指的是FPGA把数据写入EP6 FIFOPC端通过USB IN端点读走。68013A的写时序可以这样理解当SLWR有效低电平且FIFO未满时芯片在SLWR的上升沿采样FD总线上的数据写入所选FIFO。这里给出一个简化版的Verilog状态机完成FPGA到PC的数据发送。module usb_tx_channel ( input wire clk, // 系统时钟这里用48MHz input wire rst_n, input wire [15:0] tx_data, input wire tx_valid, output reg tx_ready, // FIFO可写 output reg slwr, // 写到68013A output reg sloe, // 读侧此通道不使用拉高 output reg slcs, // 片选常低 output reg [1:0] fifoadr, // 固定选EP610 input wire flag_ep6_full // EP6满标志 ); localparam IDLE 2d0, WRITE 2d1; reg [1:0] state; reg [15:0] data_reg; assign fifoadr 2b10; // 选择EP6 assign slcs 1b0; // 一直选中 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; slwr 1b1; sloe 1b1; tx_ready 1b0; end else begin case (state) IDLE: begin slwr 1b1; if (tx_valid !flag_ep6_full) begin data_reg tx_data; tx_ready 1b0; state WRITE; end else begin tx_ready 1b1; end end WRITE: begin slwr 1b0; // 拉低写选通 state IDLE; // 下个周期恢复高电平形成脉冲 end endcase end end endmodule这段代码的思路是空闲时检测“有数据且FIFO未满”一旦条件满足就把数据锁存并拉低SLWR一个周期68013A在SLWR上升沿采样数据。注意FLAG信号是异步的直接判断容易出问题实际工程中必须先把flag_ep6_full打两拍再使用。上面是简化版完整工程里我在每个输入信号后面都加了两级同步寄存器状态切换也会多等一个周期去补偿同步延迟。4.3 读通道EP2数据下行时序与代码读通道是PC把数据发到EP2 FIFOFPGA通过SLRD信号把数据读出来。读时序比写稍微复杂一点因为要让68013A先把数据驱动到FD总线上FPGA再根据时序采样所以SLOE必须正确控制。读操作的基本过程是先选中EP2 FIFO拉低SLOE让芯片驱动数据总线然后拉低SLRD在SLRD有效期间或上升沿之后采样FD总线数据。简化代码如下module usb_rx_channel ( input wire clk, input wire rst_n, input wire [15:0] fd_in, // 来自68013A的数据总线 output wire [1:0] fifoadr, output wire sloe, output reg slrd, output reg data_valid, output reg [15:0] data_out, input wire flag_ep2_empty ); assign fifoadr 2b00; // 选择EP2 assign sloe 1b0; // 一直使能输出驱动 reg flag_empty_sync; reg [1:0] state; localparam IDLE 2d0, READ 2d1, CAPTURE 2d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; slrd 1b1; data_valid 1b0; end else begin case (state) IDLE: begin slrd 1b1; data_valid 1b0; if (!flag_empty_sync) begin slrd 1b0; // 拉低读选通 state READ; end end READ: begin state CAPTURE; // 等待一个周期让数据稳定 end CAPTURE: begin data_out fd_in; // 采样数据 data_valid 1b1; slrd 1b1; state IDLE; end endcase end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin flag_empty_sync 1b1; end else begin flag_empty_sync flag_ep2_empty; end end endmodule这里的状态机用了“拉低SLRD后等一拍再采样”的做法是因为FD总线从芯片内部FIFO输出到总线上有建立时间如果拉低SLRD的同一拍就去采样大概率采到的是上一个数据或者毛刺。工程上稳妥起见采样点放到SLRD有效后的第2到第3个时钟周期。如果IFCLK频率高建议用示波器实测信号时序再微调采样点位置。4.4 带宽估算为什么实测只能到30MB/sUSB 2.0的理论速率是480Mbps也就是60MB/s但这是物理层速率包含了包开销、同步字段、EOP、CRC校验等。批量传输每包最大512字节数据但包与包之间还有USB协议规定的间隔再加上Windows主机对USB传输的调度也不是完全实时实际吞吐率不可能跑满。以我的经验EP6从FPGA持续发数据到PC16位模式下时钟48MHz每个IFCLK周期可传输2字节理论FIFO接口速率能到96MB/s但USB总线才是瓶颈。实测单向传输稳定在33MB/s左右双向同时传输时两个方向共享总线带宽各自会降到20到25MB/s。这个性能对绝大多数应用够用了。如果后续需要更高吞吐率可以考虑几个方向把端点FIFO缓冲配成4倍缓冲0xA0的bit3到bit2配置减少USB主机来不及取走数据时的等待或者在PC端使用异步读写、多线程增加缓冲区深度。但本质上限在USB 2.0协议到顶也就是40MB多余量。5. 上位机程序CyAPI库直接读写端点5.1 驱动和CyAPI库安装要让上位机识别这个设备需要安装驱动程序。Cypress官方的CyUSB驱动cyusb.sys可以用于Windows系统安装包在“Cypress Suite USB”或者EZ-USB开发包里都有。装上之后设备管理器里会看到“Cypress FX2LP”设备默认驱动名称是“CyUSB”。应用层开发官方提供了CyAPI库这是一套C类库封装了设备枚举、端点打开、批量读写等操作。如果你用C#也有对应的CyUSB.dll可以直接引用。这套库用起来非常方便不用直接面对WinUSB或者驱动开发的细节几行代码就能完成一次批量传输。5.2 核心代码实现双向收发我用C写了两个核心函数分别处理“PC发数据到FPGA”写EP2和“PC从FPGA读数据”读EP6。整体流程很简单创建设备对象打开设备找到对应端点调用XferData。#include CyUSBDevice.h #include CyUSBEndpt.h CCyUSBDevice* USBDevice new CCyUSBDevice(); // 创建设备对象 if (!USBDevice-Open(0)) { printf(打开设备失败\n); return -1; } // 找到EP2 OUT端点和EP6 IN端点 CCyBulkEndPoint* EP2OUT USBDevice-BulkOutEndPt; CCyBulkEndPoint* EP6IN USBDevice-BulkInEndPt; // 发送数据到FPGA BYTE sendBuf[512]; memset(sendBuf, 0xAA, sizeof(sendBuf)); // 填充测试数据 LONG len sizeof(sendBuf); if (EP2OUT-XferData(sendBuf, len)) { printf(发送 %d 字节成功\n, len); } else { printf(发送失败\n); } // 从FPGA读取数据 BYTE recvBuf[512]; LONG recvLen sizeof(recvBuf); if (EP6IN-XferData(recvBuf, recvLen)) { printf(接收 %d 字节成功\n, recvLen); }这里有几个注意事项。第一XferData的第二个参数是输入输出参数传入缓冲区大小函数返回实际传输的字节数使用前要初始化。第二批量IN端点如果设备端没有数据返回XferData会一直阻塞等待所以上位机读操作最好放到独立线程里避免界面卡死。第三读缓冲区长度建议设置成512的整数倍并且和FPGA端发送包长度匹配否则可能出现数据不完整的情况。5.3 验证实验回环测试怎么设计双向通信验证的第一步我建议先做回环测试排查链路问题非常有效。思路是FPGA侧把从EP2读到的数据原封不动写到EP6发送回去PC端先发一串递增数据再读取回环数据比对是否一致。上位机测试代码可以做这样的逻辑// 生成递增测试数据 BYTE testData[1024]; for (int i 0; i 1024; i) { testData[i] i % 256; } // 发送到FPGA LONG len 1024; EP2OUT-XferData(testData, len); // 读取回环数据 BYTE recvData[1024]; memset(recvData, 0, sizeof(recvData)); LONG recvLen 1024; EP6IN-XferData(recvData, recvLen); // 比对 bool match true; for (int i 0; i 1024; i) { if (recvData[i] ! testData[i]) { match false; break; } } printf(match ? 回环测试通过\n : 数据不一致\n);如果你手头没有现成的调试工具Cypress官方USB Control Center也能直接读写端点界面操作简单验证数据通路。它以“Bulk Out”和“Bulk In”页面分别对应OUT与IN端点填入数据就能发。先在这里验证通过再写自己的应用层代码排查问题时能把“硬件问题”和“软件问题”分开。6. 实测结果和常见问题排查6.1 实测波形与数据调试时我用示波器抓了FPGA接口侧的SLWR、FD总线、FLAG信号。整体时序比较干净SLWR低电平持续时间一个IFCLK周期数据在SLWR下降沿之前已经稳定到总线上。这里有个细节68013A的Slave FIFO写时序中数据在SLWR的上升沿被锁存所以只要你保证数据在SLWR上升沿之前建立且满足建立时间要求就没问题。我第一次写的时候把数据变化和SLWR下降沿放在同一个always块里结果数据还没稳定SLWR就下来了FPGA侧自己仿真没问题上板就丢数据后来改成先锁存数据再拉低SLWR问题消失。PC端用USB分析仪抓包看批量传输包大小稳定在512字节没有短包说明FPGA侧的数据打包逻辑没问题。单向传输持续跑了半小时没有出现丢包或挂死稳定在33MB/s。6.2 常见问题速查表现象可能原因解决办法PC识别不到设备固件没下载成功先用USB Control Center手动下载.hex再用EEPROM自举枚举成功但XferData超时端点方向配置错误核对EP2CFG/EP6CFG的方向位确认IN/OUT别搞混FPGA读不到PC发来的数据EP2 FIFO空标志一直为高检查SLOE信号是否拉低、FIFOADR是否选中EP2用示波器测FLAGFPGA发送的数据PC收不全数据块长度不足512字节使用PKTEND信号提交短包或者上位机按实际长度读取数据错位、第一个字节丢失FPGA采样点偏早或偏晚调整SLRD采样状态机的等待周期实测时序后确认双向同时传输时速度骤降USB总线上双向带宽共享调整包大小、使用多缓冲、PC端用异步读写经过FIFO后数据错乱跨时钟域处理不到位对FLAG等异步信号加两级同步器必要时用异步FIFO跨时钟6.3 避坑心得复位时序、异步标志、短包处理这三个问题几乎每个做FPGA68013A通信的人都会遇到。第一个是复位时序FPGA上电后不能立刻访问68013A因为此时8051还在枚举端点FIFO还没配置好。我见过不少工程直接让FPGA初始化就去拉SLWR结果数据全写进了未配置的FIFO丢了都不知道。正确做法是FPGA检测到USB枚举完成信号比如上位机握手成功之后再启动数据通路或者至少在FPGA侧加一个等待定时器上电后延迟1秒再操作。第二个是异步标志的同步问题。FLAGA/B/C这些状态信号和FPGA内部时钟不同步直接用会有亚稳态风险。我的做法是每个FLAG信号打两拍并且把同步后的标志作为状态机的判断条件。代价是状态判断会晚两个周期但对数据吞吐率影响很小换来的是稳定性大幅提升。第三个是短包处理。USB批量传输约定数据包长度小于最大包长度时表示一次传输结束。如果FPGA连续发送的数据长度恰好是512的倍数上位机可能一直等不到“短包”结束标志XferData就会阻塞。解决办法是用PKTEND信号在最后一包数据写完但不足512字节时拉低PKTEND一个周期强制68013A提交短包。如果你的应用每次传输长度正好是512的整数倍那就要在协议层加一个“结束标志”或者约定发送长度或者在PC端设置超时时间。7. 写在最后这套方案还能怎么扩展跑通双向通信之后后面的玩法就多了。我自己的板子在这个基础上做了数据采集前端FPGA控制ADC采样采样数据通过EP6高速上传到PCPC下发的控制字通过EP2配置FPGA内部寄存器实现真正的软件无线电数据链路。如果接到图像传感器上这套通路也能支撑一定分辨率的视频传输。一个很实用的扩展是把Slave FIFO的16位数据总线复用成“地址数据”模式在FPGA内部做一个寄存器读写总线这样上位机就能像访问寄存器一样控制FPGA逻辑的各个参数比每次发命令再解析方便得多。想再提升性能可以把批量传输改成同步传输但同步传输要求主机端和设备的带宽预留Windows下配置复杂不建议普通项目轻易尝试。最后再分享一个经验调这种FPGA加USB芯片的通信不要一上来就想着跑满吞吐率先把单次收发的正确性搞定再逐步加压到持续传输、双向并发。我踩过的坑里大多数不是方案问题而是时序细节、标志同步、包边界这些看起来不起眼的小地方。把这些搞干净了整个链路自然就稳了。
返回列表