ARTICLE DETAIL

资讯详情

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

ARM9升级RISC-V双核:USB3.0数据采集实测105MB/s的替代方案

ARM9升级RISC-V双核:USB3.0数据采集实测105MB/s的替代方案 把一个跑了七八年的ARM9数据采集板换成RISC-V双核同时把和上位机之间的USB从2.0拔到USB3.0这不是一个拍脑袋的升级。项目要求从原来的4通道16bit1MSPS直接提到8通道16bit5MSPS持续业务带宽从8MB/s涨到80MB/s以上老平台物理上就过不去。做完CH32H417这个替换方案之后实测持续吞吐105MB/s双核CPU还有余量整机温升比原来还低。这篇文章把替代过程中的选型思考、硬件改动、固件链路、实测数据和踩过的坑都写出来给正在考虑从ARM9往RISC-V迁移、或者准备把采集设备升级到USB3.0的朋友一个参考。先说结论USB3.0不是简单的“换个高速口”它会把MCU的内核性能、DMA能力、内存带宽、电源和PCB设计全部拉出来重新考试。CH32H417这套双核RISC-V方案能跑出105MB/s持续吞吐靠的不是USB3.0的5Gbps理论带宽而是把采集任务和USB协议栈拆到两个核上再配合足够深的DMA缓冲把数据通路上的每一个瓶颈都提前堵住。1. 老平台的瓶颈不是嘴上说说的1.1 新需求一出来数据量先算了一笔账这个项目是做多通道同步数据采集设备前级是FPGA控制多片ADC采样数据通过并行总线交给主控主控再负责打包、加时间戳、上传到上位机。旧产品的指标是4通道、16bit、1MSPS一路的原始数据量是 16bit × 1MSPS 2MB/s四路一共8MB/s。对USB2.0 High Speed来说这个负载虽然在理论上可行但实际已经很勉强了。新需求直接跳到8通道、16bit、5MSPS单路就是10MB/s八路就是80MB/s。这还没算上位机要求的自定义帧头、通道状态字、时间戳和实时FFT结果把这些协议头加上实际业务带宽要按8590MB/s来设计。而USB2.0 High Speed的理论上限是480Mbps也就是60MB/s扣掉协议开销、SOF中断、端点调度损耗之后实际BULK传输能到40MB/s已经算优秀。所以链路层这一刀就切死了USB2.0在这个项目里没有讨论空间。1.2 ARM9单核在40MB/s时已经快被榨干了很多人会下意识觉得“USB2.0有60MB/s跑40MB/s不是还有余量吗”实际完全不是这么回事。BULK传输并不是数据自己从DMA跑到USB控制器里就完了固件还要处理DMA完成中断、搬运描述符、拼接数据包、维护多个端点状态、响应上位机的控制请求。ARM9这颗内核本身没有NEON这类SIMD单元缓存也小内存访问延迟高一旦数据量上来CPU开销会随着吞吐非线性增长。旧平台在4通道8MB/s的负载下还算稳但在实验室里模拟过连续读取、上位机同时开波形绘制和文件存储的场景CPU占用经常冲到80%以上。如果直接把数据量翻到80MB/s单核ARM9大概会在DMA中断风暴里直接崩溃不是算力不够的问题是中断处理和内存搬运把流水线彻底打死了。1.3 选型清单上为什么出现RISC-V双核当时不是没考虑过Cortex-A系列的MPU比如跑Linux的方案。但项目对启动时间、实时性、成本、功耗都有要求开机要秒级进入采集状态不能等Linux根文件系统挂载产品长期在工业现场运行成本敏感而且前级已经有FPGA做时序控制主控不需要跑重型应用只需要把数据快速搬走。RISC-V双核正好卡在这个需求点上。CH32H417被选中的原因很直接它集成了USB3.0相关控制器省掉了一颗外部USB bridge芯片双核架构可以把采集和USB协议栈分开面市时间比同价位的Cortex-M系列方案更合适工具链和SDK虽然需要适应但整体开发周期可控。我把新旧方案的关键差异整理成了表格对比项旧方案 ARM9新方案 CH32H417内核400MHz级单核无FPU双核RISC-V带FPU和DSP扩展USB接口USB2.0 High SpeedUSB3.0 SuperSpeed理论链路带宽480Mbps实际BULK约35~45MB/s5Gbps实际受MCU总线限制持续业务吞吐36MB/s左右105MB/s左右CPU余量单核跑满基本无余量两个核分担主频和占用都留有余地这个表格不是说ARM9一无是处在当年那个场景它是对的。但新需求的链路带宽和数据处理量跨了一个量级继续在ARM9上挤牙膏已经没有意义。2. 双核到底怎么分工才能把USB3.0喂饱2.1 Core0和Core1的任务划分双核方案如果只是“一个核干活另一个核闲着”那还不如继续用高主频单核。USB3.0数据采集想跑出性能核心是把“采”和“传”解耦。我的做法是Core0跑采集控制和数据前处理。它负责从FPGA侧读取采样数据拼接通道状态和时间戳做必要的实时处理然后把整理好的数据块写入共享内存环形队列。Core1跑USB3.0设备协议栈和上位机命令解析。它只管从共享队列取数据往USB3.0 BULK端点搬同时处理上位机发来的开始、停止、参数配置等控制命令。两个核之间不搞复杂的信号量而是用共享内存里的无锁环形队列加上核间中断。Core0只写Core1只读队列的读写指针用原子操作维护。这样避免了“两个核抢同一把锁”的性能灾难也让USB协议栈的中断延迟变得更稳定。实测下来这个分配符合预期。80MB/s数据量下Core0的负载在55%65%之间Core1的负载在20%30%之间两个核都没有跑满后续再做算法扩展还有空间。2.2 USB3.0端点的DMA和缓冲描述符USB3.0的BULK端点和USB2.0在底层逻辑上不是一回事。USB2.0一个BULK事务只能传一个包控制器和固件处理完一个包再去处理下一个USB3.0支持突发传输一次可以连传多个包主机和设备之间的调度效率高很多。但突发能力不是自动生效的。CH32H417的USB3.0控制器使用内存里的缓冲描述符列表来管理工作项固件需要为每个端点准备一块连续内存描述符里指定起始地址、数据长度、完成标志位控制器会自己去DMA搬运。我在项目里把每个缓冲块设为16KB环形深度做到64块缓冲描述符和缓冲区本身都按64字节对齐。这里有一个特别值得注意的点USB3.0的BULK端点在SuperSpeed Companion描述符里有一个MaxBurst字段如果这个字段不配置或者配置成0设备虽然能枚举成USB3.0但每轮事务只能传一个包实际带宽会直接掉到USB2.0水平。这个坑后面还会细说。2.3 缓冲深度是背压问题的最后一道防线上位机不读数据的时候USB3.0链路会把背压传回设备侧但决定丢不丢数据的是设备内部缓冲够不够深。USB3.0的调度有微帧和突发主机端的xHCI控制器可能在一段时间内连续来读也可能因为系统调度暂时“消失”如果环形队列只有几KB一个调度抖动就会丢数据。我们把USB侧的缓冲池设计成可以缓存约20ms的采样数据按80MB/s算就是1.6MB左右。这个深度让Core1可以在上位机短暂卡顿的时候仍然把数据从共享队列搬走等恢复后再追上。代价是RAM占用变大但对采集设备来说丢一包数据比晚几十毫秒严重得多。3. 从USB2.0改成USB3.0硬件与固件到底改了什么3.1 Type-C接口和CC逻辑的选择这次硬件上最大的变化还不是USB控制器本身而是物理接口从传统的USB-B方口换成了USB3.0 Type-C。很多人以为Type-C只是换了个形状实际上CC引脚和角色检测逻辑要单独处理。如果设备是纯USB3.0 Device也就是只作为外设被主机读取那么CC1和CC2各接一个5.1kΩ下拉电阻到地就能满足Type-C规范里的设备识别要求。但我们的设备希望在现场偶尔能临时做主机去读取别的USB设备也就是需要OTG/DRP能力这时候5.1kΩ就不够了。我的做法是加一颗支持DRP的CC逻辑芯片通过I2C和CH32H417通信由固件读取当前的角色状态再决定内部USB控制器工作在Device模式还是Host模式。如果不想写复杂的TCPC状态机直接用这种带固件的CC芯片是最省事的。配套的还要加负载开关和USB MUX把CC逻辑、VBUS电源路径和USB控制器串在一起否则角色切换时数据和电源的时序对不上。3.2 PCB上USB3.0差分对处理USB3.0工作在5Gbps对PCB的要求比USB2.0严格得多。USB2.0那套“线别太乱就行”的做法在这里行不通。USB3.0一共有两对高速差分信号TX对和RX对。Layout时至少要保证差分对内等长、差分阻抗按8590Ω设计、参考层完整、尽量少打过孔。如果层叠和阻抗控制做不到TX对和RX对之间容易串扰轻则链路自动降级到USB2.0重则枚举失败。我们第一版板子就吃过这个亏这个放到后面踩坑部分详细说。另外Type-C连接器附近如果加了ESD保护器件寄生电容一定要小否则5Gbps信号眼图会塌掉。选ESD器件的时候要专门找针对USB3.0/超高速信号优化过的型号不要随便拿一颗普通的USB2.0 ESD管顶上。3.3 固件枚举和描述符的变化固件侧不是把USB MAC从2.0换成3.0就能跑USB3.0的枚举过程增加了很多东西。设备描述符里的bcdUSB要改成0x0300必须提供BOS描述符和SuperSpeed Device Capability描述符每个BULK端点还要加一个SuperSpeed Companion描述符。和USB2.0相比最容易漏的就是Companion描述符里的MaxBurst字段。USB3.0的BULK端点默认最大包长是1024字节MaxBurst表示一轮突发能连续传多少个包。我们最终把MaxBurst配成8也就是一次最多传8KB这个设置对吞吐影响非常大。还有一个经验采集设备优先考虑用厂商自定义类加WinUSB方式而不是简单的CDC虚拟串口。CDC也支持USB3.0但串口协议本身有额外的流控和字节流语义会把链路效率拉低。用BULK端点直接传原始数据帧上位机用libusb或WinUSB API读取吞吐和延迟都可控得多。4. 实测性能持续吞吐、延迟和CPU占用4.1 测试环境和方法性能不能靠“感觉快了不少”我把测试方法固定下来方便和客户、团队沟通。主机用一台Win10的xHCI控制器连接线用一根经过认证的USB3.0 Type-C线长度不超过1米。测试工具分三层一是USB抓包工具看链路速率和事务分布二是自研上位机压力程序每1秒统计一次数据量三是板卡固件内部打印CPU占用率和环形队列水位。测试工况是持续2小时满负荷采集8通道16bit5MSPS上位机同时把数据写盘并实时绘图。这样测出来的数字比单纯跑一个BULK回环程序更有参考价值。4.2 新旧方案实测数据对比我整理了一组我们在相同采集任务下测到的数据指标ARM9旧方案CH32H417新方案持续BULK有效带宽36MB/s105MB/s包含协议开销峰值回环带宽44MB/s210MB/s单包事件上传时延约3ms约0.4ms满负载CPU占用单核92%以上Core0约60%Core1约25%2小时连续运行丢包高负载下有零星丢包0丢包前提是上位机及时读取整机功耗较高略低持续105MB/s对USB3.0来说并不算快因为USB3.0的物理层还有很大余量但对这个采集项目已经够了前端8通道80MB/s数据加上协议头之后90MB/s左右105MB/s意味着还有15%左右的头带宽余量。更关键的是CPU没有跑满以后要加滤波算法或者加密传输还有地方腾挪。4.3 为什么是3倍提升而不是8倍USB3.0理论带宽是5Gbps换算过来约625MB/s是USB2.0的10倍还多。但MCU方案的瓶颈从来不在链路层而在芯片内部。数据要从前级FPGA或ADC接口进到芯片内部RAM再被USB控制器DMA读走。这个过程受内部总线带宽、DMA描述符处理能力、内存访问冲突的影响。双核虽然把采集和传输分开了但两边的数据都要经过共享内存总线上不可能做到625MB/s的纯理论值。CH32H417能稳定跑105MB/s对我们这种信号链已经很健康了硬去追200MB/s的持续吞吐性价比会非常低。做选型的时候一定要想清楚你要的是“链路余量”还是“端到端持续带宽”。如果只是链路余量USB3.0绰绰有余如果要的是端到端持续带宽就要看MCU内部DMA和总线能力不能只看PHY速率。5. 替换过程中最值得记录的五个坑5.1 板子插上只枚举成USB2.0链路协商失败第一版样机做出来后插到电脑上永远只亮USB2.0怎么配置描述符都没用。USB3.0和USB2.0是两套独立的信号控制器内部可以同时支持枚举成2.0说明超高速链路根本没有完成握手。我拿眼图看TX信号又查了CC电阻都没问题。最后用万用表和放大镜一个个对差分线发现USB3.0座子和控制器之间的RX差分对有一对线序接反了。这种问题在USB2.0时代几乎不会暴露因为USB2.0信号速率低插反不走线序错误但USB3.0的协议握手非常讲究物理层一对线序错就整体不工作。排查链路协商问题建议按这个顺序来先看CC检测是否成功再看VBUS和地连接是否可靠然后用示波器确认LFPS握手时双向信号都有最后用抓包工具确认设备有没有发出有效的SuperSpeed Ready信号。不要一上来就怀疑固件。5.2 MaxBurst配置成0性能直接腰斩驱动和硬件都稳定之后第一次测持续吞吐只有40MB/s和USB2.0差不多。一开始我还以为是上位机读取速度的问题后来看USB抓包事务发现每一轮BULK突发只有1个包也就是说USB3.0的突发传输根本没有生效。原因就是SuperSpeed Companion描述符里的MaxBurst默认值是0。SDK例程里没有强调这个字段很多人把它当成保留字段实际上它直接决定了主机一次能连续搬多少包。把MaxBurst改成8之后持续吞吐从40MB/s跳到100MB/s以上效果立竿见影。所以拿到新的USB3.0 MCU工程先检查BULK端点描述符wMaxPacketSize是不是1024MaxBurst是不是大于0BOS描述符是不是带全了。这三个不配好USB3.0就是个摆设。5.3 DMA缓冲没有64字节对齐数据偶尔错位项目跑到中期出现了一个非常隐蔽的问题数据偶发错位有时候一包数据里前半段是通道A后半段却变成了通道B用上位机解析时看不出规律重启上位机又好了。排查到最后是DMA缓冲对齐问题。USB控制器的DMA引擎对内存地址有对齐要求标准做法是64字节对齐。我一开始用结构体数组直接作为缓冲区结构体大小不是64的整数倍导致某些缓冲块的物理地址没有落在对齐边界上。控制器做DMA读的时候一次读操作可能跨越两个不连续的地址区域读出来的数据就是拼凑出来的。解决方案很简单所有USB DMA缓冲区和描述符都用64字节对齐属性声明并保证单个缓冲区大小也是64字节的整数倍。这种问题在线调试很难看出来只能从头设计时就把对齐规则定死。5.4 中断太频繁核1被BULK完成中断打爆早期版本里Core1每收到一个BULK包完成中断就去处理一次然后把下一个缓冲区交给控制器。单个包的完成中断在USB2.0时代还能接受到了USB3.0突发模式下一秒钟上万次中断CPU大量时间浪费在上下文切换和中断入口出口上。后来改成“批量完成通知”控制器完成多个缓冲区之后才触发一次中断Core1用一个状态位记录有哪些缓冲区已经完成然后一次性处理。中断频率降了一个数量级Core1的CPU占用从接近60%降到25%左右。USB3.0的BULK端点不像USB2.0那样一个包一个包磨数据是成批来的固件必须也用批处理的思路去接不能沿用旧习惯。5.5 高负载下设备突然掉枚举最终查到电源纹波整机连续跑高负载测试时刚开始一切正常跑到十几分钟主机突然弹出“设备无法识别”重新插拔又恢复。这种问题最容易误判成固件bug因为设备掉线没有规律而且普通负载下复现不了。用示波器同时看VBUS、USB PHY电源和主控供电发现高负载时USB PHY那路电源的纹波峰值超过100mV而该电源轨在数据手册里的推荐要求要严格得多。高负载时DMA和USB控制器同时工作瞬态电流变大供电网络压降和纹波一起把PHY的锁相环干扰了超高速链路就丢了。解决方向是检查电源网络的去耦电容和平滑电感。把USB PHY电源轨旁边的电容换成低ESR的大容量组合再增加靠近PHY引脚的0.1μF高频去耦电容纹波降下来之后连续跑48小时再没有掉枚举。这个坑提醒我USB3.0对电源的敏感度比USB2.0高很多硬件设计阶段就要把PHY供电当作模拟电路对待不能用数字供电的经验随便插几个电容完事。6. 这个方案值不值得抄能抄到什么程度6.1 开发成本和周期从ARM9换到CH32H417硬件开发量主要集中在USB3.0差分走线、Type-C相关逻辑和电源设计上MCU外围结构比原来还简单。软件开发量主要在USB3.0描述符、DMA缓冲管理和双核通信上。整体开发周期大概比原计划多了两周全部耗在USB3.0链路稳定性和掉枚举问题上。如果之前做过USB3.0设备时间能压到一周以内。工具链方面没有想象中痛苦。RISC-V的GCC工具链和OpenOCD调试方案已经比较成熟用惯了传统IDE的人可能需要几天适应但不会成为项目卡点。SDK里USB3.0例子虽然不是特别完整但寄存器层和描述符层都可以直接参考关键还是要吃透原理。6.2 适合这样替换的场景如果满足下面几条CH32H417这类RISC-V双核USB3.0方案值得优先考虑设备需要持续传输60MB/s以上的数据USB2.0链路已经明显不够。数据采集和传输协议可以明确拆分成两个任务双核分工能带来实际收益。产品对BOM成本和功耗敏感不太想上一颗Cortex-A处理器加外部USB bridge。启动时间要求快不能等Linux系统完全启动。反过来如果设备需要跑Linux、需要大内存、需要复杂的应用层界面那还是老老实实用MPUMCU用USB3.0的强项是“把数据搬出去”不是“把系统跑起来”。6.3 后续还能往哪个方向优化这次实测持续105MB/s峰值回环能到210MB/s说明芯片还留了余量。后续如果再往上冲我会优先优化协议头把每帧的固定开销压下来再把DMA缓冲块从16KB加大到32KB让控制器每次突发能搬更多数据。另一个方向是给上位机增加异步批量读取线程避免Windows驱动的调度抖动影响持续吞吐。我自己做完这个项目最大的体会是换主控只是替代的第一步真正难的是把数据通路从头到尾打通。USB3.0把MCU方案的瓶颈从链路搬到了芯片内部总线和固件架构上谁先把这条通路理顺谁就能用一颗几百兆主频的RISC-V双核做出以前需要FPGA加高成本MPU才能跑的采集设备。如果你也在做类似的替换建议先拿最小系统跑通USB3.0枚举和BULK回环再上整机信号链不要一上来就把所有功能堆在一起联调。
返回列表