ARTICLE DETAIL

资讯详情

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

Cy7c68013A USB2.0上行速度测试:从40MB/s优化到52MB/s

Cy7c68013A USB2.0上行速度测试:从40MB/s优化到52MB/s 说到Cy7c68013A搞USB2.0高速采集的基本没有不知道的。这颗芯片全称EZ-USB FX2LP内部集成了8051内核、USB2.0收发器和FIFO接口价格便宜、资料成熟从数据采集卡、逻辑分析仪到视频采集端到处都能看到它的影子。但几乎每个刚上手的人都会碰到同一个问题规格书上写480Mbps也就是60MB/s的理论带宽真测出来怎么只有几MB/s甚至几十KB/s。到底是芯片不行还是自己代码没写对这篇教程就专门解决这件事。这里说的“上行速度测试”指的是设备到主机方向的数据流也就是你从FPGA、ADC或者传感器采集到的数据通过Cy7c68013A往电脑里灌的实际速率。我会把整个测速流程从头到尾拆开固件里怎么配置端点、上位机用什么API、测试代码怎么写、实测结果怎么调优连常见坑一起列出来。适合正在做USB2.0数据采集的朋友也适合选型阶段想验证这颗芯片能否满足带宽需求的硬件工程师。1. 为什么Cy7c68013A的速度测试这么重要1.1 USB2.0的带宽天花板到底在哪先说理论值。USB2.0 High-Speed的物理层速率是480Mbps换算过来是60MB/s这是很多初学者记在脑子里的第一个数字。但你要清楚这是总线上的原始位速率不是你能用的数据吞吐量。USB协议栈是分帧传输的High-Speed下一个帧周期是125usBulk传输在每个微帧里的可用事务数量是有限制的再加上包起始、同步、EOP、CRC和握手这些协议开销Bulk模式实际能跑到的净荷速率大概在40MB/s到53MB/s之间。53MB/s基本就是USB2.0 Bulk传输的物理极限能稳定摸到50MB/s以上已经算非常健康了。Cy7c68013A的特别之处在于它把USB协议引擎和8051内核放在一起但真正的高吞吐场景下8051根本不过来搬运数据。芯片内部有4KB的FIFO可以配置成Slave FIFO模式让外部逻辑直接和USB端点FIFO对接USB硬件自动把FIFO里的数据打包成Bulk事务发出去。这时候8051的核心工作只是初始化寄存器、处理控制传输和描述符请求数据通道完全绕过CPU这样才能跑出接近USB2.0极限的速度。1.2 真正影响上行速度的三个瓶颈我见过很多人把测速结果不好归咎于芯片本身但实际上问题往往出在三个地方。第一个是数据通路设计。如果你让8051在TD_Poll里把ADC采样值一个一个写到端点FIFO里那速度会非常难看因为8051本身是增强型51内核性能上限就在那里哪怕跑48MHz指令周期也不快。Cypress官方有个BulkLoop固件就是把Bulk OUT收到的数据原样从Bulk IN发回去很多人拿它测速结果只有3MB/s左右然后得出“Cy7c68013A速度不行”的结论。真实原因不是芯片不行而是这个固件设计上就是让8051参与数据搬移速度被CPU拖死了。第二个是PC主机侧的接收逻辑。USB设备端的吞吐能力再强如果上位机没有及时发起Bulk IN事务、缓冲区的时机又处理不好速度照样上不去。很多人用同步方式一小块一小块地读每读完一次都要重新发起请求协议开销占比极大速度自然被砍半。第三个是物理链路和驱动。Windows下的USB驱动栈、主板USB控制器的处理策略、线材质量都会直接影响实际吞吐。后置直插、不经过HUB、用短而粗的线往往是测速稳定的前提。1.3 测试前的准备清单开始之前先把东西备齐。硬件方面我建议准备一块Cy7c68013A开发板或自己画的板子一个能产生数据的FPGA或者外部逻辑作为数据源一台Windows电脑最好在后置USB口直插。如果你手头没有FPGA也可以用Cy7c68013A内部的GPIF接口或者直接在固件里模拟一个高频数据生成器但那样很难跑到极限速度测出来只能算参考。软件方面需要安装Cypress的CySuite USB驱动包里面包含了CyUSB.NET库和驱动。系统是Windows 10或11的话可能还要处理驱动签名的问题这个后面会讲。固件开发用Keil C51上位机用Visual Studio加C#这些都是主流方案资料也最好找。2. 固件侧先把数据通道打通2.1 数据流设计为什么必须用Slave FIFO测速之前先把固件思路定下来。目标很明确让外部数据不需要经过8051就能直接进USB端点FIFO再由USB硬件自动上传到PC。Cy7c68013A提供了两种常见模式一种是Ports模式IO口模拟并行接口由8051参与读写另一种是Slave FIFO模式外部逻辑像控制普通FIFO一样直接操作SLRD、SLWR这些信号8051只做前期初始化。测高速上行选Slave FIFO几乎是唯一正解。Slave FIFO的操作很像在操作一个双端口RAM。外部逻辑检测到FLAGB比如FIFO非满之后拉低SLWR在数据总线上放一个字节或一个字再给一个时钟沿数据就被写入端点FIFO。同步模式下每个时钟周期最多传一个字节也就是接口吞吐上限等于接口时钟频率。所以固件里IFCONFIG的时钟设置很关键如果你把IFCONFIG配成内部30MHz时钟那接口理论上限就是30MB/s想超过它就得上40MHz甚至48MHz外部时钟。我自己的习惯是先把接口配成同步Slave FIFO内部时钟选48MHz模式。这样接口带宽能达到48MB/s左右基本贴近USB2.0实际可用带宽不会成为测速链路中的新瓶颈。2.2 IFCONFIG与端点寄存器配置固件里最重要的寄存器是IFCONFIG它在0xE601地址。Slave FIFO同步模式、内部48MHz时钟的典型配置值我会写成0xE3附近的组合。需要提醒的是不同开发板上的CLKOUT相位和外部逻辑时序要求不一样这个值要按自己的板子去调不能无脑抄。端点上上行数据走Bulk IN端点。FX2LP有EP2、EP4、EP6、EP8四个大端点每个都可以独立配置为Bulk/Interrupt/Isochronous以及缓冲深度。我通常用EP2做上行配置成Bulk、512字节包长、四重缓冲。四重缓冲的意思是FIFO里同时有四个512字节的缓冲区USB硬件可以一边往外发包外部逻辑一边往另一个缓冲里写避免一方等另一方。EP2CFG的配置值是0xA0对应BULK IN、512字节、四重缓冲。注意如果开了四重缓冲EP2和EP4会被合并成一个大FIFOEP6和EP8合并成另一个这个在端点地址分配上要心里有数。本教程只测上行所以EP6、EP8可以不用管但必须确保它们没有被错误地配置成IN端点否则上位机可能枚举出多个Bulk IN端点反而容易选错。2.3 固件初始化代码下面给一个精简但能用的初始化示例基于Keil C51和FX2LP的头文件。实际工程里还需要包含USB描述符表、设置VID/PID等内容这里只展示和测速直接相关的部分。#define IFCONFIG 0xE601 #define EP2CFG 0xE601 0x02 #define FIFORESET 0xE600 0x04 #define FIFOFLUSH 0xE600 0x06 void TD_Init(void) { // 设为48MHz CPU时钟 CPUCS 0x12; // Slave FIFO同步模式内部48MHz时钟 IFCONFIG 0xE3; // EP2: BULK IN512字节四重缓冲 EP2CFG 0xA0; // 先复位FIFO避免上电后内部状态异常 FIFORESET 0x80; FIFORESET 0x02; FIFORESET 0x00; // 固件初始化时先把EP2 FIFO清一遍 FIFOFLUSH 0x02; }这里有几个细节要解释。FIFORESET写0x80是进入复位状态写0x02是复位EP2对应的FIFO最后写0x00退出复位。如果你后面把EP6也配置成Bulk IN那还要把0x06加进去。FIFOFLUSH是可选的但复位后清一下可以保证FIFO指针归零不然外部逻辑第一次写入的可能被卡在奇怪的位置。TD_Poll函数在这个方案里基本是空的因为数据不经过8051。但有一点不能忽略USB控制传输、标准请求的响应这些仍然由8051处理。如果你在固件里重载了USB中断处理函数一定要确保它不会阻塞TD_Poll。我遇到过有人在中断服务程序里做大量打印导致设备偶尔枚举失败排查了很久才发现是中断处理时间过长的问题。2.4 固件侧不能踩的坑第一个坑是端点缓冲深度选得太小。默认情况下EP2是双缓冲如果改成单缓冲外部逻辑连续写入时会频繁撞上“FIFO满”吞吐量立刻掉一截。四重缓冲是性价比最高的选择它让USB硬件和外部写逻辑之间有了更好的流水线深度。第二个坑是PKTEND信号。在Slave FIFO模式下外部逻辑写完一批数据后如果当前包长度没有凑满512字节USB硬件不会自动把短包发出去。这时候必须用PKTEND信号强制提交当前包。有些测试场景里FPGA侧数据长度不是512字节对齐的如果不处理PKTEND上位机会一直等不到完整数据包测速程序就会卡死或者超时。连续测试时外部逻辑要确保要么填充到512字节要么在每个合理批次结束后触发一次PKTEND。第三个坑是EEPROM引导。很多Cy7c68013A板子上会放一个I2C EEPROM保存固件也有跳线选择“C0 load”还是“USB boot”。如果EEPROM里有旧固件或者EEPROM损坏新烧写的固件可能根本不运行。测速之前先确认设备枚举出来的VID/PID和你固件描述符里设置的一致这一步能排除掉一大半“设备异常”问题。3. 上位机测速程序编写3.1 驱动与CyUSB.NET环境配置上位机开发我一般用C#搭配CyUSB.NET库这个库在安装CySuite之后可以在安装目录里找到CyUSB.dll。驱动方面Cypress提供的CyUSB.sys在Windows 10和Windows 11上都是可用且签名的但如果你自己改了VID/PID可能需要用Zadig工具把驱动替换成WinUSB或者安装对应的INF文件。WinUSB是一个比较干净的选择特别是调试阶段。它性能不差而且不用折腾签名问题。但如果你要用CyUSB.NET的高级API比如AsyncBeginBulkIn那就得确保设备挂在CyUSB.sys上因为CyUSB.NET的核心类是和这个驱动配套的。这里有个经验如果测速时发现BeginBulkIn一直返回失败先检查设备管理器和驱动绑定而不是改代码。新建一个C#控制台项目引用CyUSB.dll然后在代码顶部using CyUSB。需要注意CyUSB.NET是32位/64位各有不同版本如果你的工程是AnyCPU最好强制x86或x64避免位数不匹配导致类型加载失败。这个小问题新手很容易遇到。3.2 C#核心代码枚举设备与异步Bulk传输代码的核心逻辑分三步枚举设备、选择Bulk IN端点、循环读取并统计速率。using CyUSB; // 枚举所有Cypress设备 CyUSBDevice[] devices; int count CyUSBDevice.DeviceCount(out devices); CyUSBDevice device null; for (int i 0; i count; i) { if (devices[i].VendorID 0x04B4 devices[i].ProductID 0x8613) { device devices[i]; break; } } if (device null) { Console.WriteLine(未找到设备); return; } CyBulkEndPoint inEp device.BulkInEndPt; if (inEp null) { Console.WriteLine(没有可用的Bulk IN端点); return; } byte[] buffer new byte[inEp.MaxPacketSize * 64]; long totalBytes 0; object lockObj new object(); bool stop false;再说一下设备查找的部分。0x04B4是Cypress的VID0x8613是常见FX2LP默认PID。如果你固件里改了PID这里也要跟着改。更稳妥的做法是从设备管理器里查出来或者让程序遍历所有设备并打印VID/PID然后按实际值填进去。接下来是读取循环。我这里用异步BeginBulkIn加WaitBulkIn的方式原因后面会解释。int transferred 0; while (!stop) { CyAsyncContext context; inEp.BeginBulkIn(buffer, buffer.Length, out context); if (inEp.WaitBulkIn(1000, out transferred)) { lock (lockObj) { totalBytes transferred; } } else { // 超时处理可以重试或者跳出 inEp.Abort(); } }注意这里每次Begin之前都确保上一次Wait已经结束不能同时对同一个端点发起多个异步读否则底层驱动会报错。有些库版本允许排队多个请求但为了兼容性和稳定性一个请求一个请求来是最保险的。在真实项目里循环一般放在后台线程里跑UI线程只负责定时刷新速率显示这样就算接收线程短暂卡住界面也不会崩溃。3.3 速率统计怎么算才科学速率统计看着简单其实很容易算错。最不科学的做法是把整个测试周期的平均速度当瞬时速度。比如跑了10分钟平均40MB/s但这中间可能一开始只有5MB/s后来稳定在50MB/s平均值掩盖了真实情况。我采用时间窗统计每1秒采样一次long lastBytes 0; DateTime lastTime DateTime.Now; while (!stop) { // ... 接收代码 ... DateTime now DateTime.Now; double deltaSeconds (now - lastTime).TotalSeconds; if (deltaSeconds 1.0) { long currentTotal 0; lock (lockObj) { currentTotal totalBytes; } double speed (currentTotal - lastBytes) / deltaSeconds; speed speed / (1024.0 * 1024.0); // 转成MiB/s Console.WriteLine(上行速度: {0:F2} MiB/s, speed); lastBytes currentTotal; lastTime now; } }这里我故意用MiB/s而不是MB/s因为很多协议分析和文件大小统计都基于1024进制。如果团队里别人的计算结果不同多半是1024和1000进制混用导致的。建议代码里统一测试报告里写清楚单位避免交流时鸡同鸭讲。时间窗也别设得太短。有人用100ms窗口去统计速率就会跳来跳去因为USB传输天然是有突发性的在一个很短的窗口里可能刚好某次等待超时速度显示就掉了。1秒窗口既能反映趋势又能滤掉瞬间抖动是比较好的折中。3.4 异步接口与同步接口的选择为什么我用BeginBulkIn而不是直接用XferData同步读关键在于超时处理能力。XferData会阻塞线程如果设备端突然停止发送数据调用会一直卡在那里你无法在超时后做恢复操作程序只能靠强制退出。BeginBulkIn加WaitBulkIn的模型允许你指定超时时间超时后可以Abort当前事务再做FIFO复位或重新初始化端点。这在长期运行的采集程序里非常实用。另外异步模型也更容易和多线程UI结合在WPF或WinForms项目里你不希望UI线程被USB读操作堵死。代码写到这里固件和上位机都齐了接下来就是通电实测。但别急着高兴第一次测速往往会给你泼冷水。4. 实测过程与结果解读从40MB/s到52MB/s的调优记录4.1 默认配置下的实测数据我以自己的开发验证板为例环境如下Cy7c68013ASlave FIFO同步48MHzFPGA作为外部数据源持续向EP2 FIFO写数据数据模式是伪随机数或者递增计数。上位机用上面那套C#代码缓冲区设成32KB窗口1秒。第一次跑结果大约在38MB/s到42MB/s之间浮动。这个结果其实已经不算差说明固件和Slave FIFO都工作正常。但距离50MB/s还有距离而且速率波动很明显1秒内的读数能差4MB/s。这时候先别怀疑硬件大概率是上位机接收节奏没跟上或者缓冲深度不够导致USB总线偶尔空等。4.2 缓冲大小与线程调度优化第一波调优从上位机下手。把接收缓冲区从32KB提升到256KB并且在程序里设置接收线程优先级为AboveNormal。原理很简单缓冲区越大一次BeginBulkIn的请求窗口越长驱动层可以做更多事务调度线程优先级提高后即使系统其他进程占用CPU接收线程也能更及时地被唤醒。改完后速率稳定在44MB/s到46MB/s。提升是有的但没到理想值。继续查发现我用的主板USB控制器是老的EHCI控制器它在USB2.0 Bulk传输上性能中规中矩。换成机器后置的另一个USB口情况也没本质变化。接着我把目光转向FPGA侧的数据写入方式。之前FPGA是每个时钟周期都往FIFO写中间没有做任何批量暂停理论上没问题。但我发现FPGA侧的写FIFO深度太小只有16个字节导致它一旦被USB端拉低FLAGBFIFO满立刻就把数据源堵住造成链路上游反压。我把FPGA内部缓冲加深到4KB并且让数据源以512字节为一批次写入配合PKTEND逻辑整体速率立刻上到49MB/s到50MB/s。4.3 线材、HUB、USB控制器的影响测速过程中我换过三根USB线。第一根是某仪器附带的短线质量很好稳定49MB/s第二根是1.8米的普通打印线也能跑到46MB/s左右但偶尔会跌到40MB/s第三根是那种地摊买的长线直接降到30MB/s附近而且设备偶尔掉线。USB线材对高速信号质量的影响就是这么明显做高速采集最好不要在这里省钱。HUB的影响也很大。直插主板后置USB口是最稳的接在USB3.0 HUB的USB2.0口上速率会掉到30MB/s左右因为HUB芯片本身会引入额外的调度延迟。如果你必须用HUB至少选一个高质量的工业级HUB而且要确保接的是HUB的USB2.0口不是USB3.0口部分HUB的USB3.0口向下兼容USB2.0时的表现并不好。4.4 用数据说话多组实测对比我把不同配置下的测试结果整理成一张表方便对照排查配置组合缓冲区大小线程优先级FPGA侧缓存实测速率范围初版固件默认上位机32KBNormal16B36~42MB/s上位机缓冲加大256KBNormal16B42~46MB/s上位机缓冲线程提升256KBAboveNormal16B44~46MB/s全量优化256KBAboveNormal4KB512B批次49~50MB/s全量优化优质短线256KBAboveNormal4KB512B批次50~52MB/s从表格能看出上位机优化和链路物理质量都能挤出几个MB/s的提升但要想突破48MB/s瓶颈往往在固件和FPGA侧的数据流设计。这也印证了开头那句话测速是一个系统问题不是单点问题。5. 常见问题与排查技巧实录5.1 设备枚举失败和驱动签名问题最常见的故障是设备插上去显示“未知设备”或者“CyUSB Device”上有个黄色感叹号。先排除硬件问题确认板子上电确认USB口供电充足不要用键盘口旁边的弱供电口。然后检查固件到底有没有跑起来。最简单的方法是拔掉EEPROM或者按住板子上的EEPROM选择跳线强制设备以默认的“无固件”模式枚举此时VID/PID应该是0x04B4/0x8613。如果无固件模式能识别但烧了固件反而枚举失败九成是固件里的描述符表有问题或者USB中断处理卡住了。驱动签名方面Windows 10/11对驱动要求严格。如果安装INF时提示“数字签名”问题最省事的办法是用Zadig把驱动切换为WinUSB。注意切换后CyUSB.NET的有些高级API可能不可用但基本Bulk传输没问题。我建议调试阶段尽量用Cypress自带驱动稳定之后再考虑WinUSB。5.2 速率波动大怎么办速率忽高忽低首先确认是不是上位机统计窗口太短。先改成1秒窗口如果波动幅度还是超过10%再去查固件和FPGA。一个容易忽视的原因是外部数据源不均匀比如FPGA里的FIFO时而满时而空这会直接导致USB总线上的Bulk事务不连续上位机看到的就是速率锯齿形波动。另一个原因是USB控制器自动进入低功耗模式。在Windows的设备管理器里找到USB根集线器把“允许计算机关闭此设备以节约电源”关掉。很多测速不稳定的机器都有这个毛病特别是笔记本上。5.3 传一段时间就断流长时间跑测速跑着跑着上位机就收不到数据设备管理器里设备还在但程序超时。这个问题多半出在设备端没有正确提交短包或者FIFO复位逻辑没做。比如FPGA侧的数据长度如果是8192字节它会分成16次512字节事务发出去最后一批正好是512字节USB控制器会自动把满包发走没问题。但如果数据长度是8190字节最后只有510字节外部逻辑没有触发PKTEND那这批数据就一直悬在FIFO里上位机永远等不到一个完整的短包后续数据又因为FIFO满而写不进去整个链路就堵死了。解决办法是在FPGA逻辑里加一个计数器每次写完一批数据后判断剩余长度是否小于512字节是的话就触发一次PKTEND。固件侧也别忘了添加看门狗处理。虽然Slave FIFO模式下8051不搬数据但看门狗超时会导致整个芯片复位如果复位后固件重新初始化上位机连接描述符可能对不上。在TD_Poll里定期喂狗是个好习惯。5.4 测速数值虚高或偏低的根源有些人在很短的时间内测出50MB/s然后宣称能达到极限这不科学。短时间测试只能代表瞬时吞吐不能代表稳定吞吐。要测至少60秒以上的持续传输看稳定后的平均值和波动范围才算一个可信的“上行速度”。反过来看测出十几MB/s也未必是代码问题。我还见过有人用CyControlCenter自带的简单transfer测试工具来测那个工具的设计目标不是高速传输它内部缓冲区很小没办法跑满带宽。测速必须用自己的、针对吞吐量优化的程序或者至少在CyUSB.NET的基础上自己封装大缓冲异步读取才有参考意义。另外USB控制器的选择对数值影响很大。有的机器USB2.0口由扩展卡或HUB提供性能参差不齐。我建议测速就在主板原生USB2.0口上测如果主板只有USB3.0口也在BIOS里看一下有没有“XHCI Hand-off”之类的选项尽量让系统使用原生EHCI驱动避免xHCI控制器跑USB2.0设备时额外调度带来的损耗。一点经验收尾测速这个事表面看是写个代码读数据实际牵扯到固件、硬件时序、驱动、操作系统调度和物理链路每一环都可能吃掉带宽。我自己测Cy7c68013A踩得最多的坑不是芯片本身而是忘了它是整个链路上的一环光优化一边永远到不了极限。如果只让我给一条建议那就是先把上位机的异步大缓冲读取写好再回过来调固件和FPGA。上位机这端一旦稳定后面定位硬件问题会轻松很多。最后再啰嗦一句测速的时候多看几个时间点的读数别被前一两秒的“超常发挥”迷惑稳定的数字才是能交付给项目的数字。
返回列表