浅谈 USB 枚举过程
一、概述
在我们的产品应用中,不管是鼠标、键盘、还是其他产品等等,有很多设备都离不开 USB 接口,我们不仅要清楚如何进行 USB 的硬件设计,也要懂得 USB 的具体协议规范,才能看懂对应的代码流程。那么下面我们就来了解下USB 的枚举流程。
二、USB 设备状态
USB 枚举完成之前,USB 设备要经过一系列的状态变化,才能够最终完成枚举。
在 USB 设备接入主机到枚举完成的过程可分为以下的状态变化:连接状态、供电状态、默认状态、地址状态、配置状态、挂起状态。其中,当设备变成配置状态时,即可认为 USB 设备和 USB 主机间的枚举完成。首先,连接状态指的是 USB 设备需要先和主机建立起物理上的连接,USB 设备检测是否连接到主机以及 USB 主机检测是否有 USB 设备连接是 USB 协议能够进行后续工作的前提。简单说来,检测连接状态的必要条件就是在 USB 设备接入时某些状态发生了变化。USB 硬件和协议上可通过检测 VBUS 、D+、D- 这些信号线的电平信号状态来进行检测连接的方法,当然,USB 主机和设备也必须要具有检测断开的能力。关于检测连接的具体协议部分,大家感兴趣的可以去详读 USB 协议规范对应章节,在此不在赘述。
- 供电状态,USB 设备可以从 USB 的 VBUS 上获取电源,或者通过外部电源获取电源,这一种称为自供电设备。而我们比较常见的是通过 VBUS 供电的设备称为总线供电设备。
- 默认状态指的是 USB 设备进入供电状态后,在被复位之前,不能响应总线上的任何事务。只有当 USB 设备被复位,处于默认状态后,才会响应主机发送过来的请求。
- 地址状态,指的是 USB 设备在被复位后,且在 USB 主机给 USB 设备设置一个新的地址之前,所有的 USB 设备都使用默认的 0 地址与主机进行通信。当 USB 设备在收到主机发送的设置地址请求之后,USB 设备就会得到一个唯一的地址。USB 设备会一直保存这个地址并使用它与主机进行通信,直到设备被复位或者断开。即使 USB 设备在获取唯一地址之后进入挂起状态,设备依然会保留这个地址,并在总线恢复后继续使用该地址。在设备挂起期间,主机也不能把该唯一地址分配给其他的 USB 设备。
- 配置状态,指的是在 USB 设备功能能够使用之前,USB 设备和 USB 主机必须协商确定功能相关的配置项,所有的配置项都以描述符的形式提供,其中配置状态所需的描述符主要有以下 6 种:设备描述符、配置描述符、字符串描述符、接口描述符、端点描述符、设备限定描述符,当然,这里还有一种特殊的描述符称为接口关联描述符,用于将一组有关的描述符关联起来共同描述符一个特定的功能。USB 主机通过获取描述符请求来获得 USB 设备相关的描述符,通过设置接口和设置配置来设置设备的相关配置项。一旦相关功能参数被配置后,USB 设备就可以正常工作了。正常的 USB 设备在收到 USB 主机发出的复位信号后会进入默认状态,之前所指定的地址、所配置的接口和配置项都将无效,需要由 USB 主机重新指定设备地址,获取描述符并配置相关配置项,才能够使设备重新工作。
- 挂起状态,指的是 USB 设备在 USB 总线持续 3ms 没有活动时,设备就会自动进入挂起状态。有挂起则意味着有恢复状态,这两个状态切换时 USB 协议实现低功耗的一种机制。进入挂起状态后,USB 设备要维持所有的内部状态。
三、USB 枚举流程
根据上面的状态变化过程,我们可以对 USB 协议进一步的了解。当 USB 主机检测到 USB 设备插入后,就要对设备进行枚举了。其实枚举的作用就是通过协议读取设备的相关信息,知道设备是什么样的设备,然后进行通信,这样主机端就可以根据这些信息安装合适的驱动程序了。USB 设备状态从初始的连接状态到最终的配置状态的变化过程就是整个枚举流程。枚举流程可分为以下几个步骤:获取设备描述符、设置设备的地址、获取完整设备描述符、获取配置描述符、获取字符串描述符、获取限定描述符、配置设备的配置描述符。
- 获取设备描述符,这是开始枚举的第一步,主机通过获取描述符请求来获取设备的描述符,对于高速设备和低速设备,用于控制传输的端点 0 支持的最大包长度是确定的,分别是 64 和 8 字节,但对于全速设备,端点0 支持的最大包长度可以是 8、16、32、64 字节,在 USB 主机获取到设备的设备描述符之前,USB 主机无法确定 USB 设备端点 0 的最大包长度,这样 USB 主机获取设备描述符的请求有可能不能正常完成。在双方通信前,USB 主机必须要指定端点0上的最大包长度,以便于正确检测控制传输种数据阶段的结束,假设 USB 主机端指定的 USB 端点 0 的最大包长度为 64 字节,而实际的 USB 设备端点 0 的最大包长度为 8 字节,由于 USB 设备描述符长度为固定的 18 字节,当主机发送第一个获取设备描述符请求时将会要求设备端发送 18 字节的数据,USB 设备在收到请求后,准备发送 18 字节的数据,但实际最大包长度为8字节,所以18字节需要分三个事务去发送完成,但主机端指定的最大包长度为 64 字节,当主机接收到第一个 8 字节的事务时,就认为数据阶段已经完成,而 USB 设备此时正在准备发送第二个 8 字节的事务,导致这个控制传输由于双方的通信状态不同步而无法正常完成。这也是复位后主机为什么需要再次获取设备描述符,也就是上面说到的针对一些 USB 设备,设备描述符可能需要分多次获取,这样有可能获取的描述符是不完整的,而复位后是要获取设备描述符的全部 18 字节。针对这个问题,不同 USB 主机的做法也有些不同,比如 Windows 7 系统在第一次获取设备描述符后,直接进行复位,将设备状态重置为初始状态。而由于设备端所支持的最小的最大包长度为8 ,此时主机一定能获取到设备描述符的前8个字节,而设备描述符的第8个字节就是设备端点0所支持的最大包长度,所以主机可以在后续的控制传输中使用这个设备端的最大包长度来保证双方正常通信。而 Ubuntu 的 USB 主机做法则有些不同,主机端第一次获取设备描述符只获取其前8字节,不管 USB 设备端点的最大包长度是多少,USB 设备只能发送8字节的数据,同时通信双方同步结束数据阶段。之后 USB 主机读取 USB 设备描述符的第8个字节,得到设备端点0的最大包长度,用于后续的控制传输。
- 设置设备的地址,主机发送设置地址请求来设置设备的地址,请求中的 Setup 数据内包含主机为设备分配的新地址,设备收到地址后,进入地址状态,在该设备未复位或断开之前,设备会一直使用该地址与主机通信,在本次传输中,USB 主机和设备都仍然使用地址 0 进行通信。在设备收到设置地址请求后,必须要在 50ms内处理该请求并完成该请求的状态阶段,在完成状态阶段后,可以有2ms 的恢复时间,在这个时间内 ,USB 主机不能使用其刚刚指定的地址与设备通信,在这个时间之后,设备必须要使用该新地址与主机进行通信,同时,设备不能对发送到旧地址的任何请求产生回复吗,除非新旧地址一致。
- 获取完整设备描述符,前面我们讲到了设备描述符部分,并且在上面的新地址设置完成后,主机与设备将会用新的地址通信,USB 主机会用新地址与设备进行通信,来获取完整的 18 字节的USB 设备描述符。USB 设备描述符的第18字节表示该设备配置描述符的数目。
- 获取配置描述符,由于每个设备配置描述符的长度都不一样,USB 主机无法提前得知设备的配置描述符长度,因此配置描述符的数据中必须要有表示长度的部分,由于第9字节代表该配置下 USB 设备的最大消耗电流值,因此主机将会先获取配置描述符的前9个字节,若该配置下的最大消耗电流值超过了USB 主机可提供的额度,该配置将不可用,如果设备中的所有配置都不可用,该设备也会不可用,这将会中止整个枚举流程。
- 获取字符串描述符,该部分主要包括了设备的一些字符串格式的信息,如设备制造商、产品信息和序列号、语言种类等。
- 获取限定描述符,USB 主机可能会尝试去获取设备的限定描述符,以获取USB 设备在其他速度模式下的信息。USB 设备也不一定会支持限定描述符,在这种情况下,USB 设备将会回复STALL 表示设备不支持该描述符,这种控制传输的失败并不影响设备的正常枚举和使用。
- 配置设备的配置描述符,主机在获取设备的某个配置描述符之后,会解析该描述符中所有的接口信息,和端点信息,之后主机就可以发出设置配置请求来通知 USB 设备使用哪一个配置。该请求命令中包含一个配置值,这个值是主机从设备的配置描述符中解析得来的,设备收到请求后,会判断配置值是否合法,若合法,设备就进入配置状态,设备会根据配置值选择对应的配置描述符,进行初始化操作,完成初始化后,设备就具备了与主机进行数据传输的能力,此时枚举阶段结束。以上则是整个枚举的过程,后续的 USB 通信将主要发生在配置描述符的接口或端点描述符所描述符的端点上。
四、总结
通过上面的了解,我们就能够详细地熟悉整个 USB 的枚举过程了,每个部分的流程和细节我们都可以通过对 USB 协议规范去了解,也可通过画流程图的方式增加自己的理解记忆。
五、参考资料
【1】USB2.0.pdf
【2】微控制器USB 的信号和协议实现
【3】《device class definition for human interface device (HID)》.pdf