USB设备枚举过程详解:从插入到正常工作

一、枚举过程概述:USB设备的"入职流程"

USB设备插入电脑后,需要经过一个完整的枚举过程才能正常工作。这个过程就像新员工入职:

  1. 报到登记:获取基本信息(设备描述符)

  2. 分配工号:设置唯一地址

  3. 详细登记:再次获取完整信息

  4. 了解岗位:获取配置信息

  5. 正式上岗:选择配置并激活


二、SETUP事务:控制传输的"指挥令"

SETUP事务详解

SETUP事务是控制传输的起始信号,用于发送"命令"给设备。它由三个阶段组成:

关键点:

  1. SETUP包永远是DATA0(不像普通数据包会交替)

  2. 地址和端点都是0(因为设备还没分配地址)

  3. 设备必须响应ACK(除非出严重故障)


三、Setup Data格式:8字节的"命令书"

8字节命令详解

SETUP事务的DATA0包中,固定包含8字节的命令数据,结构如下:

cs 复制代码
字节偏移 | 字段名          | 大小 | 说明
--------|-----------------|------|-------------------------------
0       | bmRequestType   | 1    | 请求类型(方向+类型+接收者)
1       | bRequest        | 1    | 具体请求代码
2-3     | wValue          | 2    | 参数值(依赖具体请求)
4-5     | wIndex          | 2    | 索引或偏移(依赖具体请求)
6-7     | wLength         | 2    | 数据阶段的长度

bmRequestType字段详解(最重要!)

1. D7位:数据传输方向
cs 复制代码
0 = Host-to-device (主机→设备) - 写操作
1 = Device-to-host (设备→主机) - 读操作
2. D6-D5位:请求类型(你的补充)
cs 复制代码
00 = Standard      (标准请求)   - USB规范定义
01 = Class         (类特定请求) - 设备类别定义
10 = Vendor        (厂商自定义) - 厂商特有
11 = Reserved      (保留)
3. D4-D0位:接收者(你的补充)
cs 复制代码
00000 = Device     (设备)       - 对整个设备操作
00001 = Interface  (接口)       - 对特定接口操作
00010 = Endpoint   (端点)       - 对特定端点操作
00011 = Other      (其他)       - 其他接收者
00100...11111 = Reserved(保留)

常用标准请求(bRequest字段)

请求代码 名称 作用
0x05 SET_ADDRESS 设置设备地址
0x06 GET_DESCRIPTOR 获取描述符
0x07 SET_DESCRIPTOR 设置描述符
0x08 GET_CONFIGURATION 获取当前配置
0x09 SET_CONFIGURATION 设置配置

四、控制传输的三种类型

1. 控制写(Control Write)

方向:数据从主机流向设备

cs 复制代码
建立阶段:SETUP事务(命令:写数据)
数据阶段:一个或多个OUT事务(发送数据)
状态阶段:IN事务(设备报告状态)

2. 控制读(Control Read)

方向:数据从设备流向主机

cs 复制代码
建立阶段:SETUP事务(命令:读数据)
数据阶段:一个或多个IN事务(读取数据)
状态阶段:OUT事务(主机发送0长度包,设备回应状态)

3. 无数据控制(No-data Control)

方向:只有命令,没有数据

cs 复制代码
建立阶段:SETUP事务(命令本身包含所有信息)
状态阶段:IN/OUT事务(根据命令方向确定)

数据包交替规则

  • 建立阶段:总是使用DATA0

  • 数据阶段:DATA0和DATA1交替

  • 状态阶段:总是使用DATA1(与数据阶段最后一个包交替)


五、实际抓包分析:获取设备描述符

对应图片

抓包数据解析

cs 复制代码
Packet 2434: SETUP包
  PID: 0xB4 = 10110100
      高4位=1011 → SETUP包
      低4位=0100 → 取反为1011,正确
  地址: 0x00(新设备,地址为0)
  端点: 0x00(端点0)
  CRC5: 0x08(校验正确)
  长度: 8字节
cs 复制代码
Packet 2435: DATA0包(8字节命令数据)
  数据: 80 06 00 01 00 00 40 00
  让我们逐一解析:
  
  字节0: 0x80 = 10000000B
    D7=1 → Device-to-host(读)
    D6-D5=00 → Standard(标准请求)
    D4-D0=00000 → Device(接收者是设备)
  
  字节1: 0x06 → GET_DESCRIPTOR(获取描述符)
  
  字节2-3: 0x0001 → wValue
    高字节=0x00 → 描述符索引(通常为0)
    低字节=0x01 → 描述符类型(1=设备描述符)
  
  字节4-5: 0x0000 → wIndex(这里为0)
  
  字节6-7: 0x0040 → wLength=64(主机想读64字节)
    注意:设备描述符实际只有18字节
  
  CRC16: 0xBB29(校验正确)
cs 复制代码
Packet 2436: ACK包
  PID: 0x4B = 01001011
      高4位=0100 → ACK包
      低4位=1011 → 取反为0100,正确
  设备回应ACK,表示收到命令

这个SETUP事务的意义

主机说:"设备0,端点0,请把你的设备描述符发给我,我想读64字节。"


六、数据阶段:实际读取描述符

数据阶段过程

SETUP事务后,主机发起IN事务读取数据:

cs 复制代码
事务1:读取前8字节
  IN包:请求设备发送数据
  DATA1包:设备发送前8字节描述符
  ACK包:主机确认收到
  
事务2:读取后8字节(如果描述符更长,继续)
  IN包:请求更多数据
  DATA0包:设备发送剩余数据
  ACK包:主机确认收到
  
... 直到读取完整描述符

关键点:

  1. 第一次读取只读8字节:主机先读8字节获取端点0最大包大小

  2. 实际设备描述符18字节:分多次IN事务读取

  3. 数据包交替:DATA0和DATA1交替


七、状态阶段:完成控制传输

状态阶段分析

对于控制读(GET_DESCRIPTOR):

  1. 主机发送OUT包:地址+端点0,DATA1包(0长度数据)

  2. 设备回应ACK:表示成功完成

抓包数据:

cs 复制代码
Packet 3637: OUT包
  PID: 0x87 = 10000111 → OUT包
  地址: 0x??(已分配的新地址)
  端点: 0x00
  
Packet 3638: DATA1包
  数据长度: 0字节(状态阶段的特征)
  
Packet 3639: ACK包
  设备回应ACK,完成整个控制传输

八、完整枚举过程详解

步骤1:第一次获取设备描述符(获取基本信息)

cs 复制代码
1. SETUP事务:GET_DESCRIPTOR(设备描述符)
   bmRequestType = 0x80(读,标准,设备)
   bRequest = 0x06(GET_DESCRIPTOR)
   wValue = 0x0100(设备描述符,索引0)
   wLength = 8(只读前8字节,获取bMaxPacketSize0)

2. 数据阶段:IN事务读取8字节
   获取端点0最大包大小(重要!)

3. 状态阶段:OUT事务完成

为什么只读8字节?

  • 设备描述符第7字节是bMaxPacketSize0(端点0最大包大小)

  • 主机需要知道这个值才能正确通信

  • 后续通信都基于这个包大小

步骤2:设置设备地址(分配唯一ID)

cs 复制代码
1. SETUP事务:SET_ADDRESS(无数据控制)
   bmRequestType = 0x00(写,标准,设备)
   bRequest = 0x05(SET_ADDRESS)
   wValue = 新地址(如0x05)
   wLength = 0(无数据阶段)

2. 状态阶段:IN事务完成
   设备在状态阶段后启用新地址

关键细节

  • 设备在状态阶段完成后才使用新地址

  • 之前所有通信使用地址0

  • 之后所有通信使用新地址

步骤3:再次获取完整设备描述符

cs 复制代码
使用新地址,获取完整的18字节设备描述符
主机可以请求全部18字节,因为已经知道端点0最大包大小

步骤4:获取配置描述符

cs 复制代码
1. SETUP事务:GET_DESCRIPTOR(配置描述符)
   bmRequestType = 0x80(读,标准,设备)
   bRequest = 0x06(GET_DESCRIPTOR)
   wValue = 0x0200(配置描述符,索引0)
   wLength = 配置总长度(从设备描述符获得)

2. 数据阶段:读取完整的配置描述符
   包括所有接口描述符、端点描述符等

3. 状态阶段:完成

配置描述符的特点

  • 一次性返回所有相关描述符(配置+接口+端点)

  • wTotalLength字段告诉主机总长度

  • 主机根据接口类型加载相应驱动

步骤5:设置配置(激活设备)

cs 复制代码
1. SETUP事务:SET_CONFIGURATION(无数据控制)
   bmRequestType = 0x00(写,标准,设备)
   bRequest = 0x09(SET_CONFIGURATION)
   wValue = 配置值(通常为1)
   wLength = 0

2. 状态阶段:完成
   设备激活该配置下的所有接口和端点

重要:即使设备只有一种配置,也必须执行此步骤!


九、枚举过程总结表

步骤 SETUP事务命令 目的 关键数据
1 GET_DESCRIPTOR(设备) 获取端点0最大包大小 wLength=8
2 SET_ADDRESS 分配唯一地址 wValue=新地址
3 GET_DESCRIPTOR(设备) 获取完整设备信息 wLength=18
4 GET_DESCRIPTOR(配置) 获取配置信息 wLength=总长度
5 SET_CONFIGURATION 激活设备 wValue=配置值

十、常见问题解答

Q1:为什么枚举过程中设备地址从0开始?

A:所有新设备默认地址为0,用于初始通信。主机分配新地址后,设备在状态阶段后切换到新地址。

Q2:如果设备描述符长度超过端点0最大包大小怎么办?

A:主机分多次IN事务读取。例如,如果端点0最大包为8字节,18字节描述符需要:

  • 第1次IN:8字节(DATA1)

  • 第2次IN:8字节(DATA0)

  • 第3次IN:2字节(DATA1)

Q3:枚举失败可能的原因?

  1. 设备没有正确响应SETUP事务

  2. 描述符格式错误

  3. 端点0最大包大小设置不合理

  4. 设备耗电超过总线供电能力

Q4:枚举完成后设备如何工作?

A:枚举完成后:

  1. 设备有唯一地址

  2. 驱动程序已加载

  3. 配置已激活

  4. 可以开始正常数据传输(批量、中断、同步)


十一、实际开发注意事项

固件开发者:

  1. 必须正确实现标准请求:GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION

  2. 描述符必须准确:特别是端点0最大包大小

  3. 及时响应:在规定时间内响应主机请求

驱动开发者:

  1. 遵循枚举流程:按标准步骤操作

  2. 错误处理:处理设备无响应、描述符错误等情况

  3. 资源管理:正确分配设备地址,避免冲突

调试技巧:

  1. 使用USB分析仪:捕获和分析枚举过程

  2. 检查描述符:确保格式和内容正确

  3. 模拟主机:使用工具模拟主机行为进行测试


十二、枚举过程完整示例

假设一个USB鼠标的枚举过程:

cs 复制代码
1. 设备插入,D+被拉高,主机检测到全速设备
2. 主机复位设备
3. 第一次GET_DESCRIPTOR:读取8字节,得知端点0最大包为8
4. SET_ADDRESS:分配地址5
5. 第二次GET_DESCRIPTOR:读取完整18字节设备描述符
6. GET_DESCRIPTOR:读取配置描述符(9+9+7=25字节)
   发现是HID设备(接口类=0x03)
7. 加载HID驱动程序
8. SET_CONFIGURATION:激活配置
9. 枚举完成,开始定期轮询中断端点

这个完整的枚举过程确保了USB设备的即插即用特性,是USB系统能够自动识别和配置设备的核心机制。理解这个过程对于开发和调试USB设备至关重要。

相关推荐
oMcLin2 小时前
Ubuntu 22.04 系统通过 SSH 远程登录失败:如何解决 SSH 配置文件错误导致的登录问题
数据库·ubuntu·ssh
笨鸟先飞的橘猫2 小时前
mongo权威指南(第三版)学习笔记
笔记·学习
molaifeng2 小时前
深度解密 Go 语言调度器:GMP 模型精讲
开发语言·golang·gmp
技术小泽2 小时前
java转go速成入门笔记篇(一)
java·笔记·golang
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-27-事务安全-事务日志-事务日志框架
java·开发语言
Noushiki2 小时前
RabbitMQ 进阶 学习笔记2
笔记·学习·rabbitmq
♛识尔如昼♛2 小时前
C 基础(4) - 字符串和格式化输入输出
c语言·开发语言
代码游侠2 小时前
复习——SQLite3 数据库
linux·服务器·数据库·笔记·网络协议·sqlite
Hello.Reader2 小时前
Flink OLAP Quickstart把 Flink 当成“秒级交互查询”的 OLAP 服务来用
数据库·sql·flink