1. USB 协议基础回顾:电气特性、拓扑结构与枚举过程

各位同学,咱们正式开始。做 USB 开发这么多年,我最大的感触是——很多人把 USB 想得太复杂了。其实你只要把底层那套逻辑理清楚,后面移植协议栈、调 HID 设备,都是水到渠成的事。

这一章,咱们先打好地基。我会从电气特性、拓扑结构、枚举过程三个角度,把 USB 2.0 和 USB 3.0 的核心差异讲透。嗯,都是我在项目里踩过坑之后才真正理解的东西。

1.1 USB 2.0 与 USB 3.0 电气特性差异

先问一个问题:USB 2.0 和 USB 3.0 的线,能混用吗?

答案是不能。至少不能直接对插。为什么?因为它们的电气特性完全不同。

特性 USB 2.0 (Hi-Speed) USB 3.0 (SuperSpeed)
信号线数量 4 根(VBUS, GND, D+, D-) 9 根(VBUS, GND, D+, D-, SSTX+, SSTX-, SSRX+, SSRX-, GND_DRAIN)
信号类型 差分半双工 差分全双工(发送/接收独立通道)
传输速率 480 Mbps 5 Gbps(Gen1)/ 10 Gbps(Gen2)
终端匹配 45Ω 对地(D+/D-) 差分 100Ω(SSTX/SSRX 对)
驱动电压 3.3V(差分摆幅约 400mV) 1.0V(差分摆幅约 200mV)
编码方式 NRZI + 位填充 8b/10b(Gen1)/ 128b/132b(Gen2)

关键点:USB 3.0 保留了 USB 2.0 的 D+/D- 线,用于兼容低速/全速/高速通信。但 SuperSpeed 数据走的是独立通道。所以 USB 3.0 设备插到 USB 2.0 口上,能工作,但只能跑 2.0 的速度。

我记得有一次调试一个 USB 3.0 摄像头,死活只能跑 480 Mbps。查了半天,发现是线缆里的 SSTX 和 SSRX 差分对焊反了。嗯,这种低级错误,犯过一次就再也不会犯了。

再说说终端匹配。USB 2.0 的 D+ 和 D- 各需要 45Ω 对地电阻,用来做信号反射抑制。而 USB 3.0 的 SSTX 和 SSRX 是差分对,需要 100Ω 差分匹配。如果你在 PCB 布局时把这两种匹配搞混了,信号质量会惨不忍睹。

避坑指南:我曾经在一个项目中,把 USB 3.0 的 SSRX 差分对走线走了 90° 直角,结果眼图测试直接 fail。差分对走线必须等长、对称,转角用 45° 或圆弧,别偷懒。

1.2 USB 拓扑结构:Host、Hub、Device

USB 的拓扑结构,说白了就是一棵树。根是 Host,叶子是 Device,中间可以挂 Hub。

你想想看,为什么 USB 不用菊花链?因为 USB 是主从架构,所有通信必须由 Host 发起。Device 不能主动说话。这个设计简化了协议,但也带来了一个限制——设备之间不能直接通信。

USB 拓扑结构示意图 USB Host (根) Root Hub Hub 1 Hub 2 HID Device Mass Storage Hub 3 HID Device 说明: Host 是唯一的主控者,Hub 负责扩展端口,Device 是被管理的终端。 最多支持 7 层 Hub 级联,地址空间最多 127 个设备。

这张图你应该能看懂。Host 下面挂 Root Hub,Root Hub 下面可以挂 Hub 或 Device。Hub 可以继续级联,但最多 7 层。为什么是 7 层?因为 USB 规范里规定了最大传播延迟,超过 7 层 Hub 信号就来不及了。

我个人习惯,在设计系统时尽量控制 Hub 级联不超过 3 层。层数越多,枚举时间越长,而且电源管理也更复杂。

1.3 USB 枚举过程详解

枚举,说白了就是 Host 认识 Device 的过程。你插上一个 U 盘,系统怎么知道它是 U 盘?怎么知道它支持什么协议?怎么给它分配地址?

整个过程分为 4 个阶段:复位 → 地址分配 → 描述符获取 → 配置选择。咱们一个一个说。

1.3.1 复位(Reset)

设备插入后,Host 会检测到 D+ 或 D- 上的电平变化。然后 Host 发送复位信号——把 D+ 和 D- 同时拉低至少 10ms。设备收到复位后,回到默认状态:地址为 0,等待 Host 的下一步指令。

小技巧:复位时间不能太短。我见过有些设备对复位时间很敏感,少于 10ms 可能不响应。保险起见,我一般给 20ms。

1.3.2 地址分配(Set Address)

复位完成后,Host 发送 SET_ADDRESS 请求,给设备分配一个唯一地址(1~127)。设备收到后,更新自己的地址,并返回 ACK。从此以后,Host 就用这个地址和设备通信。

这里有个细节:SET_ADDRESS 请求是在默认地址 0 上发送的。分配完成后,设备切换到新地址。所以这个请求是唯一一个在地址 0 上完成的控制传输。

1.3.3 描述符获取(Get Descriptor)

地址分配完了,Host 开始问设备:你是谁?你能干什么?

Host 通过 GET_DESCRIPTOR 请求,依次获取以下描述符:

  1. 设备描述符(Device Descriptor):18 字节,包含 VID、PID、USB 版本、最大包大小等。
  2. 配置描述符(Configuration Descriptor):9 字节,包含接口数量、供电方式、最大电流等。
  3. 接口描述符(Interface Descriptor):9 字节,包含接口类型(如 HID、Mass Storage)、端点数量等。
  4. 端点描述符(Endpoint Descriptor):7 字节,包含端点地址、传输类型、最大包大小等。
  5. 字符串描述符(String Descriptor):可选,包含厂商名、产品名、序列号等。

注意:HID 设备还有一个特殊的 HID 描述符,用于描述报告描述符的长度和版本。这个在后续章节会详细讲。

我刚开始做 USB 开发时,经常搞混描述符的层级关系。后来我画了一张图,每次写固件前先对照一遍,再也没出过问题。

USB 描述符层级结构 设备描述符 (Device Descriptor) 配置描述符 (Configuration Descriptor) 接口描述符 (Interface Descriptor) HID 描述符 (HID Descriptor) 端点描述符 (Endpoint Descriptor) 注意: HID 描述符是 HID 类设备特有的,位于接口描述符之后。 端点描述符可以有多个(IN/OUT),每个端点对应一个传输通道。

1.3.4 配置选择(Set Configuration)

描述符获取完成后,Host 发送 SET_CONFIGURATION 请求,选择一个配置。设备收到后,按照该配置初始化端点、分配缓冲区,然后进入就绪状态。至此,枚举完成,设备可以正常通信了。

枚举过程中,所有通信都走 控制传输(Control Transfer),使用端点 0。这是 USB 协议里唯一一个双向端点,专门用于控制命令。

避坑指南:我曾经在一个项目中,设备返回的描述符长度不对。Host 读到一半就卡住了,枚举失败。后来我用 USB 分析仪抓包才发现,是固件里描述符的 bLength 字段写错了。所以,写描述符时一定要对照规范,逐字节核对。

1.4 本章小结

这一章咱们把 USB 的底子打了一遍。电气特性上,USB 2.0 和 USB 3.0 从信号线数量、驱动电压到编码方式都完全不同,不能混用。拓扑结构上,Host 是老大,Hub 是中间人,Device 是小弟,层级不能超过 7 层。枚举过程分四步:复位、地址分配、描述符获取、配置选择,每一步都有严格的时序要求。