做高速数据采集这些年,最难受的往往不是采集本身,而是数据怎么从板卡上搬出来。要么UART慢得只能传点调试信息,要么USB2.0跑满也就三四十兆字节每秒,想上USB3.0吧,FPGA这边直接做协议栈和物理层又重得让人劝退。直到我接触到基于CH569的这套方案——一颗内置USB3.0 PHY和控制器的RISC-V芯片,专门负责把FPGA采集到的高速数据实时上传到电脑,整个链路一下子清爽了。今天就把这套系统的设计思路、核心细节和实操过程完整拆一遍,适合正在做数据采集、图像传输、高速接口类项目的FPGA开发者参考,也适合想快速上USB3.0传输的MCU玩家了解整体架构。
1. 这套系统到底在解决什么问题
1.1 高速采集的瓶颈往往不在采集,而在传输
很多朋友刚接触FPGA时,以为难点在采样率、位宽、时序这些"采集端"问题。真正做过项目以后你会发现,ADC采样、传感器读取这类活儿,FPGA天生就能干,而且干得很漂亮。真正的瓶颈经常出现在"把数据送出去"这一步。
举个例子,一个100M采样率、12位的ADC,连续采集时每秒产生的数据量是100M × 2字节 = 200MB/s。这个速率跑UART完全是天方夜谭,GPIF、并行口直连GPIO也没有任何上位机软件能稳定接住。USB2.0理论峰值480Mbps,实际Bulk传输能稳定跑到35MB/s已经算不错了,连零头都不够。以太网倒是带宽大,但MAC、PHY、协议栈、上位机socket编程一套下来,工作量也不小,而且对于裸数据流传输,TCP的粘包、缓冲、延迟问题够你调一阵子。
USB3.0的SuperSpeed模式理论5Gbps,实际Bulk传输在PC端可以稳定跑出300MB/s以上的有效带宽(取决于主板、线缆、驱动、端点配置),基本覆盖绝大多数中高速采集场景。但问题是,USB3.0不是你想上就能上的。它既有复杂的协议栈,又有严格的物理层要求,差分阻抗、信号完整性、链路训练、电源管理……FPGA直接实现这些,工作量巨大,用现成的USB3.0 PHY芯片又贵又难买。
1.2 为什么不选纯FPGA方案或纯MCU方案
纯FPGA方案,比如用FPGA + USB3.0 PHY芯片 + 自己写USB控制器逻辑,这条路不是不能走,但设计周期非常长。USB3.0的协议层包含链路训练、电源管理、均衡、重放机制等一大堆状态机,验证和调试的坑又多又深。即便用了一些大厂的USB3.0 IP核,License费用、集成难度、调试周期也足够劝退很多人。除非你是做芯片验证这类特殊场景,否则不建议这么自虐。
纯MCU方案呢?很多高端MCU内部有USB HS甚至SS控制器,比如带USB3.0的ARM或带专用高速接口的芯片,但MCU本身去采集高速并行数据流,IO翻转速度和DMA吞吐能力都有限。你让MCU直接去接一个100Msps的ADC,光数据总线就忙不过来,更别提前端预处理了。
于是折中方案就出现了:FPGA负责采集、预处理和缓存,一颗带USB3.0的高速MCU负责把数据搬到PC。FPGA发挥并行采集和硬件加速的优势,MCU发挥内置USB3.0 PHY和控制器、开箱即用的优势,两边各干各最擅长的事。而且CH569本身就是RISC-V内核,开发流程熟悉以后,固件改起来比传统ARM还顺手,整套方案从硬件到软件都有很高的可复现性。
2. 系统架构与数据链路拆解
2.1 总体数据流与模块划分
整套系统的数据流可以概括成一条单向流水线:信号源 → FPGA采集模块 → FPGA预处理/转换 → 异步FIFO缓存 → CH569并行接口读取 → CH569内部DMA搬运 → USB3.0端点 → PC上位机。
模块划分要非常清晰,否则后面排查问题很容易各模块互相甩锅。建议按功能切成四个主要部分:
- 采集前端:ADC或各类传感器数据接入,完成时序采样、位宽对齐。不同传感器接口方式差异很大,比如模数转换器常用SPI或并行接口,MIPI摄像头则要走LVDS差分。
- 数据预处理:滤波、定点转换、格式封装等。这部分是可选的,但很多场景离不开。比如光采集原始数据不处理,上位机大量计算很费劲,在FPGA里先做一遍定点FIR、LMS均衡或者去马赛克,能大幅降低上位机压力。
- 缓存与跨时钟域:采集时钟和CH569读取时钟通常不是一个域,必须用异步FIFO隔离。同时FIFO也能吸收USB传输中PC端偶尔的调度延迟尖峰,避免数据丢失。
- 传输桥接:CH569运行固件,把并行FIFO接口收到的数据通过DMA搬进USB端点缓存,再由USB3.0控制器发送到主机。
每一层之间用握手信号做缓冲,比如FIFO的空满标志、CH569的请求读取信号等。这样上层速度波动不会直接传导到下层采集端,系统才能稳定长时间运行。
2.2 CH569在链路中扮演的角色
CH569在整套系统里更像一个"USB3.0桥接协处理器",而不是传统意义上的主控MCU。它不需要理解业务数据,只负责把外部并行接口的数据往USB端点上搬,所以固件逻辑可以做得非常轻量。
这颗芯片有几个特性在这个场景下特别关键:
- 内置USB3.0 SuperSpeed物理层和控制器的完整解决方案,硬件设计时不需要额外接昂贵的PHY芯片,这直接解决了我前面说的"USB3.0物理层难搞"的大麻烦。
- RISC-V内核主频可以跑到120MHz级别,配合DMA,做数据搬运绰绰有余。
- 提供并行FIFO接口,可以直接对接FPGA的通用IO,类似单片机读写外部FIFO的方式,时序逻辑清晰、容易握手。
- 支持自定义端点配置,Bulk传输可以设定多个端点、多种缓存大小,配合DMA能有效提升实际吞吐。
所以你看,CH569并不需要跑复杂的USB协议栈逻辑,固件里主要做三件事:初始化USB控制器、配置端点、在中断里响应并启动DMA传输。具体到工程实现,下面章节细说。
2.3 FPGA与CH569的接口协议与时序
FPGA和CH569之间的连接建议采用并行FIFO从模式,即FPGA侧把CH569当做一个可以写的FIFO设备。常用的信号组大致如下:
- 数据总线: 8位或16位并行数据线,我实际偏好在带宽足够的前提下用16位,这样等效数据率可以翻倍。
- 写使能(WR/WE)与读使能(RD/RE):指示总线上的数据是否有效。FPGA作为生产者,向CH569的FIFO写入时需拉低写使能。
- FIFO满标志(FULL/AFULL):CH569内部FIFO满时通知FPGA停止写入,这是反压机制的关键。
- FIFO空标志(EMPTY/AEMPTY):CH569从FIFO读取数据时,如果FIFO空,读操作不产生有效数据。
- 时钟:写时钟可以由FPGA提供,也可以由CH569提供,取决于数据手册的具体接口定义。这个细节务必以手册为准,否则时序对不上很容易出现写进去的数据错位。
时序上,最核心的是保证建立时间和保持时间满足要求,尤其是FPGA输出数据给CH569时,数据必须在写使能有效前保持稳定。如果接口电平不匹配,中间还要加电平转换或做IO标准配置。FPGA侧建议直接在综合工具里对IO约束,根据CH569手册的时序参数设置input delay/output delay,而不是全靠运气和肉眼观察波形。
3. CH569的RISC-V内核与USB3.0控制器,先讲透
3.1 一颗带USB3.0 PHY的RISC-V芯片能省多少事
做USB3.0设备端,传统方案绕不开USB3.0 PHY芯片和配套的控制器逻辑。PHY负责5Gbps高速串行信号的物理层收发,控制器负责USB协议层。这两个东西拆开卖都贵,集成到一起的更不多见。CH569把PHY、控制器和MCU核打包在一颗芯片里,光物料成本和PCB面积就能省一截,开发调试的复杂度也降低了一个量级。
它内部的USB3.0物理层支持SuperSpeed模式,对外提供SSTX_P/SSTX_N和SSRX_P/SSRX_N两组高速差分对,同时保留了USB2.0的DP/DM引脚做兼容。硬件设计时,高速差分对要求100欧姆差分阻抗控制,走线尽量短,远离时钟和电源干扰。参考时钟、电源退耦这些也要按手册要求做严格,不然容易出现设备偶尔识别不到的问题。
RISC-V内核本身,开发体验和常见的MCU差别不大。用官方推荐的MounRiver Studio或者RISC-V GCC工具链,写C代码、编译下载,流程很直接。如果你以前玩过STM32或者其它MCU,切过来基本没有学习成本。
3.2 端点配置与Bulk传输的关键参数
USB3.0设备端要往主机传数据,常用的是Bulk批量传输。Bulk传输没有等时传输的带宽保证,但有重传机制,数据可靠,特别适合数据采集。关键是端点缓存和DMA怎么配合。
USB协议里,Bulk端点有最大包大小,USB3.0下通常可以设到1024字节。单包越大、端点数越多、缓存越深,吞吐越有利。实际工程里,CH569允许配置多个Bulk端点,比如一个IN端点用来传采集数据,一个OUT端点用来接收主机控制命令。每个端点可以分配一定大小的FIFO缓冲区,数据从并行接口进来后由DMA直接写入端点FIFO,而无需CPU逐字节搬运。
要跑出高带宽,建议这样配置:
- 使用双缓存或四缓存机制,一组在DMA传输时,另一组可以被USB控制器读出,这样能有效避免"缓存切换间隙"造成的带宽损失。
- 中断不要做成每字节触发,而是等DMA搬完一块或者端点数据包发完再触发,减少CPU介入开销。
- 单次DMA传输长度尽量大,减少启动DMA和响应中断的次数。
我见过很多人把USB3.0速度跑不满,一查都是DMA长度太小、中断太频繁,带宽全耗在切换上了。
3.3 USB3.0硬件设计要点与Type-C方向
如果直接用USB3.0标准A型连接器,焊接和设备端相对简单,SSTX/SSRX差分对一一对应就行。如果板子要做USB Type-C接口,那就多出CC引脚的配置问题,因为Type-C支持正反插,需要根据CC引脚的电压判断方向,再用对应的USB3.0信号路由。CH569本身不带Type-C的方向逻辑,你需要在小板上额外加一个CC逻辑控制,或者用支持DRP的PD协议芯片来协商角色和方向。
从数据传输角度,USB3.0的信号完整性要求比USB2.0高不少。差分对要做100欧姆差分阻抗控制,尽量走内层、包地更好,连接器附近加ESD保护期间注意寄生电容别太大,以免劣化信号眼图。电源方面,USB3.0的SuperSpeed收发器对电源噪声敏感,VDD要加足够的去耦电容,最好独立LDO供电。硬件设计这些细节,前期不到位,后面调试时会用各种诡异现象回报你。
4. FPGA侧采集、缓存与流量控制
4.1 采集端设计:从ADC到定点预处理
FPGA采集端的具体电路跟你的信号源强相关,但从工程抽象角度来看,可以统一分成几个环节:接口时序控制、数据对齐、可选的预处理。
以并行ADC为例,比如AD7606这类常见芯片,FPGA的工作就是按照转换时序发起采样,在数据准备好后读回16位结果。这个过程涉及状态机设计、busy信号判断、时序余量检查。如果数据里偶发读到错误值,先用示波器看转换完成信号和数据总线时序是否满足芯片手册要求,很多时候问题出在代码里没留时序余量,而不是芯片坏了。
预处理环节,如果你想做FIR滤波、LMS均衡这类算法,建议采用定点数运算,避免浮点IP核占用过多逻辑资源。定点数要特别注意整数位宽和小数位宽的分配,先估算输入动态范围,再根据滤波增益确定小数位,否则很容易溢出或者精度不够。对比直接用浮点IP,定点方案在FPGA上可以做到每个时钟周期处理一个采样点,吞吐大、资源省,是在高速数据流场景下的常规选择。
4.2 异步FIFO与深度估算
跨时钟域处理是FPGA高速设计中躲不开的问题。采集端时钟可能是ADC的采样时钟,而CH569读取数据使用的是它那边的接口时钟,两个时钟频率和相位都没有确定性关系,此时绝不能直接用一个时钟域的寄存器去读另一个时钟域的信号,会触发亚稳态。
标准做法是插入异步FIFO。写时钟域写数据、写指针用格雷码同步到读时钟域,读时钟域读数据、读指针同步回写时钟域。用FPGA厂家提供的FIFO IP核是最省事的选择,Quartus和Vivado里都有异步FIFO的现成IP,只需要配置位宽、深度、读写时钟和满空标志。
FIFO深度该怎么定?最朴素的原则是:FIFO要吸收的最大瞬时数据差,等于写速率乘上最长阻塞时间。USB3.0传输中,PC端调度、驱动回调、DMA搬运都可能造成短时间的读端停顿,最长阻塞时间可能在几十微秒到几百微秒级别。以200MB/s写入速率、最长停顿200微秒计算,FIFO深度至少要容纳40KB。再留出安全余量,设计时按64KB来做比较从容。如果FPGA内部BRAM不够,可以外挂SRAM,或者降低批量包大小、调整DMA调度策略来缓解。
4.3 反压与握手状态机
光有深FIFO还不够,写数据的状态机必须能感知下游压力。用CH569的FIFO满标志(或可编程阈值标志)作为反压信号,当它拉高时,FPGA立即暂停写入,等标志释放后再继续。
这个反压逻辑看似简单,实现时最容易犯的错误是:状态机只判断"满信号"而忽略了FIFO快满的标志,导致临界情况下主控还没来得及响应,数据就溢出了。稳妥做法是使用可编程almost full标志,把阈值设在FIFO容量的四分之三左右,留出几个周期的响应余量。
同时,状态机设计要注意从反压中恢复时的时序。不要在标志释放后立刻写第一笔数据,先等时钟沿对齐,确保CH569那边已经准备好接收。FPGA复位信号也要小心,异步复位同步释放是基本要求,不然复位释放瞬间逻辑状态不确定,容易产生亚稳态问题,这在高速接口设计中是高频踩坑点。
5. 实操:把整条链路完整跑起来
5.1 硬件连接与电平匹配
我这里用的是常见的FPGA开发板和一块CH569核心板组合。先把两组芯片之间的接口引脚分好,数据线、写使能、读使能、满标志、空标志,逐条对照CH569手册确认信号方向和电平标准。
电平匹配这里要特别提醒:如果FPGA的IO电压是3.3V,而CH569的接口电压要求1.8V或2.5V,直接连过去轻则功能异常,重则烧坏IO。确定电平标准后,必要时加电平转换芯片,或者用FPGA的bank电压配置来匹配。很多高速采集项目最后调试不通,查了好久发现是电平标准不匹配,这种基础问题还是要一开始就确认好。
连接完成后,用示波器或者逻辑分析仪抓一下FPGA发出的写时序,看数据和写使能的相对关系是否符合CH569手册的建立时间要求。这一步看似浪费几分钟,但能省掉后面盲调的痛苦。
5.2 CH569固件:USB3.0+Bulk+DMA最小实现
我以最小能跑通的固件为例,代码逻辑大致如下,具体寄存器配置务必以官方头文件和例程为准:
// 伪代码示意,实际工程请以CH569官方库为准 void USB3_Init(void) { // 初始化USB3.0控制器,选择SuperSpeed模式 USB3_DeviceInit(); // 配置Bulk IN端点,最大包大小1024字节 USB3_EndpointConfig(EP_IN_BULK, BULK_PACKET_SIZE, BULK_DOUBLE_BUFFER); // 开启端点IN中断 USB3_EnableEndpointInterrupt(EP_IN_BULK); } void ExternalFifo_To_USB(void) { uint8_t* buf = GetDMABuffer(); // 从并行FIFO接口读取一块数据到内存缓冲区 ReadExternalFIFO(buf, TRANSFER_LEN); // 启动DMA,把数据搬到USB端点FIFO USB3_DMA_Start(EP_IN_BULK, buf, TRANSFER_LEN); } void USB3_EP_IN_Interrupt(void) { // 上一个DMA传输完成,立刻继续读下一块数据 ExternalFifo_To_USB(); }整个过程理解起来就是:不断从并行接口读数据进缓冲区,再由DMA送进USB端点,中断里接力下一块。注意不要在中断里做耗时长的操作,中断标志、状态处理尽量精简,否则会拖慢传输节奏。
我第一次跑的时候,忘了配端点缓冲为双缓冲模式,带宽只能到一半左右,后来改成双缓冲后吞吐立刻上来了。这部分优化空间很大,建议根据实际传输速率逐步调整DMA传输长度和缓冲策略。
5.3 FPGA逻辑:FIFO例化与写时序约束
FPGA侧直接在综合工具里例化异步FIFO IP,配置好位宽、深度,然后把写端口接到采集模块,读端口接到连接CH569的一组IO上。
以状态机为例,写CH569接口的简化思路:
// 伪代码示意,需要根据实际接口时序修改 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin wr_en <= 1'b0; state <= IDLE; end else begin case (state) IDLE: begin if (!ch569_fifo_full && !fifo_empty) begin data_out <= fifo_dout; wr_en <= 1'b1; // 数据先放总线,再拉低写使能 state <= WR_DATA; end end WR_DATA: begin wr_en <= 1'b0; // 一个时钟周期的写使能脉冲 fifo_rd_en <= 1'b1; // 同时读走FIFO下一笔数据 state <= IDLE; end default: state <= IDLE; endcase end end这里最关键的是数据有效要提前于写使能。CH569在写使能判定时,数据总线必须已经稳定,否则会采到不确定值。我习惯在写使能有效前一个周期就把数据放到总线上,保证建立时间足够。
IO时序约束也不能偷懒。在工程里给这些接口信号设置input/output delay,参考CH569数据手册里的时序参数填写。如果不做约束,工具默认的时序分析结果可能和实际物理表现不一致,板子跑起来就会偶发数据错位,时好时坏。
5.4 上位机收数验证与带宽测试
固件和FPGA都烧好后,把USB3.0线连到电脑。第一次插上时,系统枚举设备后需要安装驱动。如果CH569官方提供了驱动或INF文件,用官方的最省心;如果自己写应用,可以用WinUSB或libusb做免驱的Bulk通信,Windows下配置好INF,Linux下直接libusb就行。
验证带宽和数据的常见做法是:FPGA侧生成一个递增的计数序列当作测试数据,上位机收下来检查数据是否连续递增、错位、丢包。先小数据量跑,比如1MB,确认数据完整,再逐步增大到几十GB,观察速率稳定性。
实测下来,Bulk传输在普通电脑上跑到250MB/s以上是可行的,再往上提升就要检查主板USB控制器、线缆质量、DMA配置和上位机接收缓冲的大小了。很多时候上位机开线程收数据、处理数据的代码写得不好,反而会成为瓶颈。
6. 常见问题与排查手记
6.1 USB无法识别或枚举失败
这个现象通常不是固件逻辑的问题,而是硬件链路或初始化顺序的问题。优先检查这几个点:
- USB3.0差分对供电和参考时钟是否正常,SSTX/SSRX走线是否满足100欧姆差分阻抗。有的人用杜邦线飞线连接,高速信号根本传不过去,这不是软件能救的。
- CH569的复位时序是否正确,上电后等待电源稳定再释放复位。
- 如果USB2.0能识别但USB3.0识别不了,多半是SuperSpeed差分对信号质量问题,或者线缆被识别成USB2.0线。
- 固件里USB控制器初始化顺序有没有问题,比如先配置端点再使能设备,还是先使能再配置,以官方例程为准。
排查时先用逻辑分析仪看设备端的USB3.0链路训练是否完成,再看枚举阶段主机发出的控制请求是否响应,能快速缩小问题范围。
6.2 数据丢包与字节错位
链路已经通了的稳定性问题,先别怀疑USB协议,绝大多数情况下是FPGA与CH569之间的并行接口时序有问题。
字节错位一般是因为数据总线建立时间不够,或者写使能和数据变化同时发生,导致CH569采样到一半稳定一半不稳定的数据。用示波器同时抓数据线和写使能,看数据和写使能下降沿之间的相对关系,数据必须先稳定,写使能再下降。
丢包则可能是FIFO溢出。检查CH569的满标志是否正常反馈到FPGA,以及FPGA侧是只是判断满了暂停,还是快满了才响应。如果用的almost full阈值设置太高,响应时间不够,压力一大就会丢。调低阈值,留出足够响应余量,问题能解决大半。
6.3 实测带宽远低于理论值
如果只跑出几十MB/s,先别怀疑芯片能力,按这套思路排查:
- 端点是不是单缓冲?改成双缓冲或四缓冲,带宽往往直接翻倍。
- DMA传输长度是不是太小?每包传输从64字节改成1024字节甚至更大,中断频率降下来,吞吐会明显改善。
- 上位机接收缓冲够不够?如果上位机收得慢,USB控制器会因主机侧反压降低设备传输速率。检查应用读取数据的线程优先级和缓冲区分配。
- PC端是否接在USB3.0端口?很多机箱前置面板的USB口实际是USB2.0内部线,这个坑我踩过好几次。
把这些逐项排除,一般带宽都能回到合理的范围。
6.4 综合时序不满足与亚稳态
FPGA工程在布局布线后报告时序不满足,先看是哪些路径。如果是IO路径,通常是IO约束没写对,补上input delay/output delay再跑。如果是FIFO内部或跨时钟域路径,检查异步FIFO的时钟约束是否正确。
亚稳态的问题比较隐蔽,表象是系统偶尔跑飞、数据偶尔错误。异步FIFO本身解决的是跨时钟域的数据传递安全性,但如果你在其它地方直接跨时钟域采样了信号,就要用两级同步器或专用握手来保证安全。FPGA设计里务必坚持"每个跨时钟域信号都有明确处理"的原则,否则出问题真就是玄学。
布局和布线的区别这时候也有体会了:布局决定逻辑单元在芯片上的排布位置,布线决定物理走线,两者相互影响,直接决定时序是否收敛。遇到综合后的时序问题,先看约束是否合理,再看逻辑级数是否太深,必要时在关键路径上插流水线寄存器。
最后再分享一点个人体会。这套系统的核心价值,是把USB3.0这种"硬骨头"留给专用芯片,而让FPGA专注于自己擅长的采集和预处理,分工明确、可调试性强。实际做下来,最难的不是某个模块单独实现,而是两个芯片之间的握手和时序细节。如果你也准备做类似的高速采集系统,建议先打通最小链路——FPGA发计数序列、CH569收数、上位机收数校验,把整条通路跑通摸稳之后,再接真实信号源逐步升级。验证好主链路,再叠加预处理、多通道扩展、波形显示等能力就顺理成章了。