第二章:USB枚举过程详解

USB枚举,说白了就是设备和主机互相认识的过程。我经常跟团队里的新人说,搞懂枚举,你就掌握了USB通信的一半。今天咱们就一步步拆解这个过程。

2.1 设备插入检测

当USB设备插入端口时,物理层会先发生变化。D+和D-这两条数据线上的电平会改变——全速设备会把D+拉高,低速设备则拉高D-。主机端的根集线器检测到这个变化后,会通知USB主机控制器。

嗯,这里要注意:主机并不是立即就开始通信。它会先等待100毫秒左右,让设备稳定下来。我见过不少新手工程师忽略这个延时,结果枚举总是失败。

关键点:主机检测到设备插入后,会发送总线复位信号。这个复位信号会持续至少10毫秒,把设备状态重置到默认状态。

2.2 复位(Reset)

复位操作其实很简单——主机把D+和D-都拉低,持续10毫秒以上。设备收到复位信号后,会做以下几件事:

  • 把地址重置为0(默认地址)
  • 准备好接收控制传输
  • 配置端点0(默认控制端点)

我曾经在一个项目中遇到过复位时序问题。设备对复位信号的响应太慢,主机以为设备没反应,反复复位。最后发现是设备端的去耦电容太大,导致电平变化太慢。换了个小电容就解决了。

经验之谈:复位完成后,设备必须在10毫秒内准备好接收Setup包。这个时间窗口很关键,我建议你在固件里加个超时处理。

2.3 设置地址(Set Address)

复位之后,设备还在默认地址0上。主机需要给它分配一个唯一的地址。这个过程分两步:

  1. 主机发送Set Address请求,告诉设备新地址
  2. 设备收到后,必须在50毫秒内切换到新地址

这里有个坑:Set Address请求本身是在旧地址上完成的。设备收到请求后,不能立即切换地址,要等到状态阶段完成后才能切换。你想想看,如果设备提前切换了地址,那状态阶段的握手包就发不回去了。

// Set Address请求格式
// bmRequestType: 0x00 (主机到设备,标准请求)
// bRequest: 0x05 (SET_ADDRESS)
// wValue: 新地址 (1-127)
// wIndex: 0
// wLength: 0

// 示例:设置地址为2
// 主机发送: 0x00 0x05 0x02 0x00 0x00 0x00 0x00 0x00

注意:地址0是保留地址,只能用于枚举过程。设备完成枚举后,必须使用非零地址。我见过有些设备固件写错了,一直用地址0通信,结果主机根本不理它。

2.4 获取描述符(Get Descriptor)

地址分配好了,主机就要开始了解这个设备到底是什么。它会通过Get Descriptor请求来获取设备的各种信息。描述符就像设备的身份证,里面包含了设备类型、厂商ID、产品ID、端点信息等。

获取描述符的顺序是这样的:

  • 先获取设备描述符(只取前8字节,了解端点0的最大包大小)
  • 再获取完整的设备描述符(18字节)
  • 然后获取配置描述符
  • 接着获取接口描述符和端点描述符
  • 最后获取字符串描述符(如果有的话)

我个人习惯在固件里把描述符写得尽量完整。有些主机对描述符的要求很严格,少一个字段都可能导致枚举失败。我记得有一次调试一个HID设备,主机就是认不出来,最后发现是报告描述符里少了个结束标记。

描述符类型 长度(字节) 包含信息
设备描述符 18 USB版本、设备类、厂商ID、产品ID等
配置描述符 9 配置属性、功耗、接口数量等
接口描述符 9 接口编号、端点数量、协议类型等
端点描述符 7 端点地址、传输类型、最大包大小等

核心要点:主机通过描述符来加载合适的驱动程序。如果描述符写错了,轻则枚举失败,重则蓝屏死机。我建议你在写描述符时,对照USB规范逐字节检查。

2.5 配置(Set Configuration)

这是枚举的最后一步。主机根据获取到的描述符信息,选择一个合适的配置,然后发送Set Configuration请求。设备收到这个请求后,就要按照配置描述符里的要求,初始化相应的端点、分配缓冲区、准备数据传输。

配置完成后,设备就进入就绪状态了。主机可以开始进行数据传输。不过要注意,有些设备支持多个配置,主机可以选择其中一个。但大多数设备只有一个配置,所以这个步骤通常就是走个过场。

说白了,配置这一步就是告诉设备:「好了,我已经了解你了,现在开始干活吧!」

避坑指南:我曾经遇到过一个问题:设备在收到Set Configuration后,需要花很长时间来初始化硬件。结果主机等不及,超时了。解决方案是在配置描述符里把bMaxPower设大一点,让主机知道这个设备需要更多启动时间。

2.6 枚举过程总结

整个枚举过程,从设备插入到配置完成,通常只需要几十毫秒。但每个环节都有可能出现问题。我建议你在调试时,用USB分析仪抓包看看,哪个步骤卡住了,一目了然。

下面这张图展示了枚举的核心流程:

USB枚举流程 设备插入检测 总线复位(Reset) 设置地址(Set Address) 获取描述符(Get Descriptor) 配置(Set Configuration) 枚举完成

这张图把枚举的五个核心步骤串起来了。每个步骤都有它的意义,缺一不可。你在实际调试时,可以对照这个流程,看看设备卡在哪一步。