首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >体验CH32H417 超快的 USB 3.0 外设

体验CH32H417 超快的 USB 3.0 外设

作者头像
云深无际
发布2026-03-30 17:47:17
发布2026-03-30 17:47:17
1680
举报
文章被收录于专栏:云深之无迹云深之无迹

去年的推荐了WCH的一个MCU:

不知道大家还有没有印象
不知道大家还有没有印象

不知道大家还有没有印象

这个MCU不仅常规的IP很全,在老本行通讯连接上面更是顶呱呱,那这次我们就来一次USB测速。那我们要跑就跑最快的3.0.

但USB协议非常乱:

很明显是USB 3.0 Gen 1
很明显是USB 3.0 Gen 1

很明显是USB 3.0 Gen 1

还有一点需要注意的就是其实这个USB 3.0是兼容2.0的,就是接入了2.0的线序:

3.0的出现,2.0也兼容,之后我会说一下这个现象
3.0的出现,2.0也兼容,之后我会说一下这个现象

3.0的出现,2.0也兼容,之后我会说一下这个现象

一个完整的USB3.0接口有9根线
一个完整的USB3.0接口有9根线

一个完整的USB3.0接口有9根线

引脚定义这里也是精密排列的
引脚定义这里也是精密排列的

引脚定义这里也是精密排列的

也就是3.0
也就是3.0

也就是3.0

直观看就是有PHY:

而且都集成在这里,里面的USB外设都是完整的
而且都集成在这里,里面的USB外设都是完整的

而且都集成在这里,里面的USB外设都是完整的

时钟树上面USB3.0是从HSE这个主干路上面来的
时钟树上面USB3.0是从HSE这个主干路上面来的

时钟树上面USB3.0是从HSE这个主干路上面来的

这个在初始化的代码里面也可以看到,WCH是使用的自己的IDE,用起来感觉不错,都不需要怎么配置。

因为是双核设计,注意这个V3才是主机
因为是双核设计,注意这个V3才是主机

因为是双核设计,注意这个V3才是主机

WCH给每个MCU都有一个完善的固件包,里面有每个外设的demo,那这个USBSS也是有的。

在 CH32H417 的这个双核 + USBSS 项目中,时钟初始化分为“系统主时钟” 与 “USB 专用时钟”两部分,分布在两个位置:

系统主时钟(400MHz/480MHz)

这是 MCU 上电后最先执行的配置,负责让 V3F/V5F 核心跑起来;默认使用 HSE (外部晶振 25MHz)倍频。

所以还是设计的很易用
所以还是设计的很易用

所以还是设计的很易用

代码语言:javascript
复制
// system_ch32h417.c 中默认开启的宏
#define 
SYSCLK_400M_CoreCLK_V5F_400M_V3F_100M_HSE    
400000000
//这会将系统时钟配置为 400MHz ,其中 V5F 核跑 400MHz,V3F 核跑 100MHz。

USBSS(USB3.0)专用时钟

当代码执行到 Hardware() -> USBSS_Device_Init() 时初始化;USBSS_RCC_Init 它会调用 USBSS_PLL_Init(ENABLE) 开启 USBSS 专用的 PLL,并使能 RCC_HBPeriph_USBSS 、 PIPE时钟。

USBHS(USB2.0)专用时钟

当 USB3 枚举失败回退,或者代码显示调用 USBHS_Device_Init() 时初始化;USBHS_RCC_Init 关键代码如下,它独立配置了一个 480MHz 的 PLL 给 USB2.0 PHY 使用:

代码语言:javascript
复制
RCC_USBHSPLLCLKConfig
(RCC_USBHSPLLSource_HSE); // 源自 HSE
RCC_USBHSPLLReferConfig
(RCC_USBHSPLLRefer_25M); // 参考频率 25M
RCC_USBHS_PLLCmd
(ENABLE);                // 启动 PLL

如果要改主频 ,去 V3F/User/system_ch32h417.c 改宏定义;如你要改 USB 时钟源 (比如换了晶振频率),去 Common/ch32h417_usbhs_device.c (USB2) 或 Common/ch32h417_usbss_device.c (USB3) 修改对应的 PLL 配置。

讲一下USB2.0的注意点

上面的时钟初始化看到了有对2.0的操作,这是为什么?其实是作为 USB 3.0 失败后的回退方案,或在不支持 USB 3.0 的主机上使用;入口在 ch32h417_usbhs_device.c 的 USBHS_Device_Init() 。

也就是高速版的2.0,也有完整的PHY
也就是高速版的2.0,也有完整的PHY

也就是高速版的2.0,也有完整的PHY

在2.0这里启用 EP0, EP1, EP3, EP4, EP5,且配置各端点的 DMA 地址 (指向 Data_Buffer 或独立 Buffer);设置最大包长 (512字节 for HS),预设响应状态 (ACK/NAK)。

然后初始化定时器 (TIM12),用于监测 USB 3.0 链路训练是否超时。如果超时未连接,中断服务程序会强制切换到 USB 2.0 模式。

初始化逻辑

代码逻辑采用的是 "优先尝试 USB 3.0,失败后回退到 USB 2.0" 的策略。系统上电时只会初始化 USB 3.0,如果连接失败(例如插在 2.0 接口上),才会关闭 3.0 并初始化 2.0。

具体流程如下:

上电默认只初始化 USB 3.0

在 hardware.c 的 Hardware() 函数中:

代码语言:javascript
复制
USB_Timer_Init( );          // 初始化超时定时器
USBSS_Device_Init( ENABLE );// 仅初始化 USBSS (3.0)
// 注意:此处没有调用 USBHS_Device_Init (2.0)

系统启动时,默认假设用户插入的是 USB 3.0 接口,并开始进行 3.0 链路训练。

通过定时器检测连接状态

在 USBSS_Device_Init 之后,系统会启动一个定时器(TIM12);如果在规定时间内(例如几次中断周期内)USB 3.0 链路没有成功建立(比如用户插的是 USB 2.0 线或接口),定时器中断 TIM12_IRQHandler 会被触发。

自动回退机制 (Fallback)

在 ch32h417_usbss_device.c 的 TIM12_IRQHandler 中:

代码语言:javascript
复制
if( u3_first_count > 2 ) // 超时判断
{
    // 1. 关闭 USB 3.0
    USBSS_Device_Init( DISABLE );
    
    // 2. 初始化并开启 USB 2.0
    USBHS_Device_Init( ENABLE ); 
    
    // 3. 停止检测定时器
    USB_Timer_Start( DISABLE );
}

此时,芯片才正式切换到 USB 2.0 高速模式工作。

智能重试机制

如果当前工作在 USB 2.0 模式下,且检测到了 USB 总线复位 (例如用户拔掉重插,或者主机复位总线),代码会尝试 切回 USB 3.0 再试一次;在 ch32h417_usbhs_device.c 的 USBHS_IRQHandler 中:

代码语言:javascript
复制
if( intflag & USBHS_UDIF_BUS_RST ) // 2.0 总线复位
{
    // 再次尝试初始化 3.0,看看是否换到了 3.0 接口
    USBSS_Device_Init( ENABLE );
    USB_Timer_Start( ENABLE );
}

USB2.0和3.0不是同时初始化 :同一时间只有一个控制器在工作,而且3.0 优先,总是先尝试建立 3.0 连接。

自动切换 :

插 3.0 口 -> 3.0 初始化成功 -> 保持 3.0 工作。

插 2.0 口 -> 3.0 初始化超时 -> 关闭 3.0 -> 初始化 2.0 -> 保持 2.0 工作。

这里枚举成功是这样的

在设备与驱动这里可以看到跑的是 WCH 自家驱动(不是 WinUSB)

Vendor ID : 0x1A86 / Product ID : 0x5537:WCH VID/PID

Driver : CH375W64.SYS (Version: 3.5.2025.8 Date: 2025-08-21)说明枚举出来的是 WCH 自定义类(Class=WCH),由它家的内核驱动接管,而不是 Windows 自带的 WinUSB/libusbK, 自家驱动通常会用更激进的队列深度/URB 管理,吞吐更容易拉满。

简单讲讲USB的知识

如果对这个外设从寄存器出发解读,那就太无聊了,我想可以通俗一点~(主要是着重理解端点这个东西)

USB 3.0 的工作过程本质上是: Host 和 Device 围绕“端点(Endpoint)”不断交换“数据包(DPH)和控制包(TP)”,端点就是“数据通道的编号 + 方向 + 类型 + 状态机”。

USB 不是“随便发数据”,而是:Host 永远是发起者Device 永远是被动响应者,所有数据都必须走某一个端点;端点是 USB 协议里最核心的抽象。

什么是“端点(Endpoint)”

这是最容易误解的地方,端点是一个“逻辑通信通道”,由 4 个要素唯一确定:

要素

含义

Endpoint Number

端点号(0–15,USB3 常用 0–7)

Direction

IN(Device→Host) or OUT(Host→Device)

Transfer Type

Control / Bulk / Interrupt / Isochronous

状态

Ready / Busy / Halt / NRDY / 等

端点可以看作是协议层的“虚拟管道”。

一个端点长什么样(抽象视图)

Device 视角

代码语言:javascript
复制
          USB 3.0 链路
               │
        ┌──────┴──────┐
        │   Endpoint  │  ← 这是“端点”
        │   (EP n IN) │
        └──────┬──────┘
               │
        DMA / FIFO / SRAM

Host 并不知道你 DMA 在哪,它只知道: “EP n,方向 IN,现在能不能给我数据?

端点在 USB 里的地位有多高?

USB 协议 = 围绕端点进行调度

Host 在干什么?

Host 的核心循环就是:

代码语言:javascript
复制
for each endpoint:
    if endpoint ready:
        发事务
    else:
        等 ERDY / 等待

Device 在干什么?

Device 的核心逻辑是:

代码语言:javascript
复制
端点有数据?
    是 → 准备好 → ERDY / ACK
    否 → NRDY / 等待

USB 3.0 的“工作过程”整体长什么样

我们从 上电 → 插入 → 枚举 → 传输 这个完整流程走一遍。

阶段 0:物理层 & 链路建立(USB 3.0 特有)

在 USB 3.0:

  1. SS PHY 建立链路
  2. LINK 状态机从 Detect → Polling → U0
  3. 进入 U0(可传输态)

这一步在 RM 里看到的是 LINK 寄存器、U1/U2/U3

需要注意:这一步还没端点,只是“线能不能用”。

阶段 1:枚举(端点 0 的专属工作)

所有 USB 设备,永远只有一个 EP0**, EP0 永远是 Control 类型, **EP0 同时支持 IN + OUT

枚举阶段发生了什么?

代码语言:javascript
复制
Host → EP0 OUT : SETUP 包(我要认识你)
Device → EP0 IN : 描述符数据

也就是Device 先只需要实现 EP0,其他端点此时 还不存在 / 未使能;所以在 USBSS 里看到:UEP0_TX_CTRLUEP0_RX_CTRL这是整个 USB 世界的起点

阶段 2:配置端点(端点“出生”的时刻)

Host 在枚举过程中会说:

“我需要:一个 Bulk IN EP1,一个 Bulk OUT EP1,最大包长 1024,支持突发”

这一步完成后:EP1 IN / EP1 OUT 才真正“存在”

然后Device 在内部:给 EP1 分 DMA / FIFO,设置端点类型,标记为 ready / idle‘’端点不是一开始就有的,是“被配置出来的”

阶段 3:正常数据传输(围绕端点的循环)

这是真正关心的部分。

USB 3.0 里一次“典型数据传输”怎么走(非常重要)

Bulk IN(Device → Host) 举例。

Host 想从 EP1 IN 读数据

代码语言:javascript
复制
Host:EP1 IN,你有数据吗?

这在 USB 3.0 里不是一个包,而是一组事务:

Device 的端点状态判断

情况 A:端点有数据

代码语言:javascript
复制
Device → Host : DPH(数据包头 + 数据)
Host → Device : ACK-TP(我收到了,还能再收 N 个)

这时:DMA 消耗一段 buffer,端点序列号 +1

情况 B:端点暂时没准备好

代码语言:javascript
复制
Device → Host : NRDY-TP

意思是:

“现在别烦我,等我准备好了我再叫你”

Device 什么时候“再叫你”?

当 DMA / FIFO 准备好后:

代码语言:javascript
复制
Device → Host : ERDY-TP

意思是:

“我准备好了,你可以继续问我了”

Host 收到 ERDY 后重新发起事务

这就构成了一个完整闭环

代码语言:javascript
复制
请求 → NRDY → ERDY → 重新请求 → DPH → ACK

这就是在寄存器里看到的:NRDY,ERDY,ACK,NUMP(还能接多少包)

为什么 USB 3.0 这么强调 ERDY / NRDY?

因为 USB 3.0 是“无轮询、事件驱动”的高速总线:不再像 USB 2.0 那样傻轮询,而是允许 Device 主动告诉 Host:“我忙”,“我好了”;这也是为什么:USB 3.0 才能到 5Gbps,其中DMA + 端点 + 突发的设计非常重要。

把“端点”映射回 CH32H417 USBSS

现在再看 USBSS:

Device 侧看到的东西:

RM 中的概念

实际含义

UEPn_TX / RX

第 n 个端点

TX / RX

IN / OUT

CHAIN

多 buffer 链

FIFO

端点内部缓存

DMA_ADDR

端点数据来源

ERDY / NRDY 位

端点状态机输出

Host 侧看到的东西:

RM 中的概念

实际含义

UH_TX / RX

Host 发起的事务

ACK_NUMP

对端还能接多少包

TX_FAILED

Deferred(端点忙)

ERDY_IF

Device 叫你继续

端点不是硬件资源,而是 USB 协议定义的“数据通道状态机”; USB 3.0 的工作过程,就是 Host 和 Device 围绕端点不断交换: DPH(数据) + TP(控制),并用 ERDY / NRDY / ACK 来做高速流控。

开始测速

这个是官方给出的结果,开起来不错
这个是官方给出的结果,开起来不错

这个是官方给出的结果,看起来不错

官方提供了一个测速工具
官方提供了一个测速工具

用测速工具进行测试

就是上传,下载,以及一发一收做回环测试,我整理了测速的结果:

测试模式

上传速度

下载速度

单向下载

450.64 MB/s

单向上传

447.54 MB/s

双向同时

395.16 MB/s

395.12 MB/s

USBSS(USB 3.0 SuperSpeed),批量端点(Bulk Endpoint)。

端点配置

端点大小:1024 Bytes

上传端点:2,3

下传端点:1,3

测试时间:60 秒

数据包大小:4,194,304 Bytes(4MB buffer)

下行(Host → Device)

代码语言:javascript
复制
数据量:28366077952 B
时间:60.000 s
平均速度:450.64 MB/s

换算验证

代码语言:javascript
复制
28366077952 / 60 ≈ 450.64 MB/s(软件显示 450.64 MB/s)

数据一致

上行(Device → Host)

代码语言:javascript
复制
数据量:28160557056 B
时间:60.016 s
平均速度:447.54  MB/s

换算验证

代码语言:javascript
复制
28160557056 / 60.016 ≈ 447.54 MB/s

合理

双向回环测试(同时上传 + 下载)

上传

代码语言:javascript
复制
21932015616 B
60.094 s
395.16 MB/s

下载

代码语言:javascript
复制
21936209920 B
60.000 s
395.12 MB/s

双向几乎对称

与 USB 3.0 理论值对比

USB 3.0 SuperSpeed 理论速率

物理层速率:5 Gbps

8b/10b 编码

有效数据率:

代码语言:javascript
复制
5 Gbps × 0.8 = 4 Gbps
4 Gbps / 8 = 500 MB/s

实际测到:

模式

理论上限

实测

单向下载

~500 MB/s

450.64 MB/s

单向上传

~500 MB/s

447.54 MB/s

双向同时(上行)

~500 MB/s

395.16 MB/s

双向同时(下行)

~500 MB/s

395.12 MB/s

单向效率

代码语言:javascript
复制
单向下载效率:450.64 / 500 = 90.1%
单向上传效率:447.54 / 500 = 89.5%

也就是说:

代码语言:javascript
复制
单向传输链路利用率约 89%–91%

这是一个 非常正常、甚至偏好的数值;实际中考虑到:协议开销,链路层 header,USB transaction overhead,DMA 切换,FIFO flush,PC 主机栈开销,83% 是健康数值。

双向效率

代码语言:javascript
复制
双向同时传输时,单方向有效吞吐约为 395 MB/s,
对应单方向链路利用率约 79%。

双向下降的原因:主机 xHCI 调度,双向 DMA 争用,片上总线争用,FIFO arbitration,缓存带宽限制;这个值也合理。

首先 USBSS PHY 的 5Gbps SuperSpeed 链路应当是真正建立成功了,因为单向吞吐已经达到 450 MB/s 量级; 其次 Bulk 端点、DMA 与 FIFO 配置整体是健康的,否则很难稳定跑到接近 400 MB/s 以上的双向并发吞吐。

小结

本次 USBSS Bulk 端点测速结果显示:单向传输速度可达 450.64 MB/s(下载)和 447.54 MB/s(上传),双向并发传输时上下行均约 395 MB/s;相对于 USB 3.0 SuperSpeed 500 MB/s 的理论有效上限,单向链路利用率约为 89%–91%,双向同时传输时单方向利用率约为 79%。 整体性能表现已经相当接近 USB 3.0 实际工程可达到的高水平,说明该 USBSS 控制器、Bulk 端点、DMA 与片上总线协同工作是比较健康的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系统主时钟(400MHz/480MHz)
  • USBSS(USB3.0)专用时钟
  • USBHS(USB2.0)专用时钟
  • 讲一下USB2.0的注意点
  • 初始化逻辑
    • 上电默认只初始化 USB 3.0
    • 通过定时器检测连接状态
    • 自动回退机制 (Fallback)
    • 智能重试机制
  • 简单讲讲USB的知识
  • 什么是“端点(Endpoint)”
  • 一个端点长什么样(抽象视图)
  • 端点在 USB 里的地位有多高?
    • Host 在干什么?
    • Device 在干什么?
  • USB 3.0 的“工作过程”整体长什么样
    • 阶段 0:物理层 & 链路建立(USB 3.0 特有)
    • 阶段 1:枚举(端点 0 的专属工作)
    • 枚举阶段发生了什么?
  • 阶段 2:配置端点(端点“出生”的时刻)
    • 阶段 3:正常数据传输(围绕端点的循环)
  • USB 3.0 里一次“典型数据传输”怎么走(非常重要)
  • Host 想从 EP1 IN 读数据
  • Device 的端点状态判断
    • 情况 A:端点有数据
    • 情况 B:端点暂时没准备好
  • Device 什么时候“再叫你”?
  • Host 收到 ERDY 后重新发起事务
  • 为什么 USB 3.0 这么强调 ERDY / NRDY?
  • 把“端点”映射回 CH32H417 USBSS
    • Device 侧看到的东西:
    • Host 侧看到的东西:
  • 开始测速
    • 端点配置
    • 下行(Host → Device)
    • 换算验证
    • 上行(Device → Host)
    • 换算验证
    • 双向回环测试(同时上传 + 下载)
    • 上传
    • 下载
    • 与 USB 3.0 理论值对比
    • USB 3.0 SuperSpeed 理论速率
    • 实际测到:
  • 单向效率
  • 双向效率
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档