一、控制传输的三个阶段
USB控制传输(Control Transfer)包含三个阶段:
1. Setup阶段 (必需) - SETUP令牌 + DATA0数据包 + ACK握手
2. Data阶段 (可选) - IN/OUT令牌 + DATAx + ACK
3. Status阶段 (必需) - IN/OUT令牌 + DATA1(长度=0) + ACK
关键问题:如何通过Setup阶段的DATA0包内容,判断后续Data阶段是IN(读)还是OUT(写)?
二、SETUP数据包结构
Setup阶段的DATA0包固定为8字节,格式如下:
偏移 字段名 大小 说明
────────────────────────────────────────────────
0 bmRequestType 1字节 请求类型和方向
1 bRequest 1字节 具体请求代码
2-3 wValue 2字节 请求参数
4-5 wIndex 2字节 索引/选择器
6-7 wLength 2字节 数据阶段的字节数
核心字段:
- bmRequestType – 最高位D7决定数据方向
- wLength – 决定是否有Data阶段
三、bmRequestType字段详解(关键!)
3.1 字段结构
bmRequestType (1字节 = 8位)
D7 D6 D5 D4 D3 D2 D1 D0
│ └─┬─┘ └──────┬──────┘
│ │ │
│ │ └─ Recipient (接收者)
│ │ 00000 = Device (设备)
│ │ 00001 = Interface (接口)
│ │ 00010 = Endpoint (端点)
│ │ 00011 = Other (其它)
│ │
│ └─ Type (请求类型)
│ 00 = Standard (标准请求)
│ 01 = Class (类请求)
│ 10 = Vendor (厂商请求)
│ 11 = Reserved (保留)
│
└─ Direction (数据方向) ← **这是关键位!**
0 = Host-to-Device (OUT, 主机→设备, 写)
1 = Device-to-Host (IN, 设备→主机, 读)
3.2 方向判断规则
D7 = 0(主机到设备):
Data阶段使用 OUT 令牌
主机 --OUT令牌--> 设备
主机 --DATAx--> 设备
设备 --ACK--> 主机
Status阶段使用 IN 令牌(反向握手)
主机 --IN令牌--> 设备
设备 --DATA1(0字节)--> 主机
主机 --ACK--> 设备
D7 = 1(设备到主机):
Data阶段使用 IN 令牌
主机 --IN令牌--> 设备
设备 --DATAx--> 主机
主机 --ACK--> 设备
Status阶段使用 OUT 令牌(反向握手)
主机 --OUT令牌--> 设备
主机 --DATA1(0字节)--> 设备
设备 --ACK--> 主机
D7 = 无关紧要(wLength = 0):
如果wLength = 0,没有Data阶段
直接进入Status阶段,使用 IN 令牌
四、常见请求示例
4.1 GET_DESCRIPTOR(读设备描述符)
SETUP数据包内容:
偏移 字段 十六进制 二进制 说明
───────────────────────────────────────────────────────────
0 bmRequestType 0x80 10000000 D7=1 → Device-to-Host
│└─┬─┘└──┬──┘
│ 00 00000
│ 标准 设备
└─ IN方向
1 bRequest 0x06 GET_DESCRIPTOR
2-3 wValue 0x0100 描述符类型=Device(1)
4-5 wIndex 0x0000 索引=0
6-7 wLength 0x0012 请求18字节
后续流程:
Setup阶段:
主机 --SETUP令牌--> 设备
主机 --DATA0[80 06 01 00 00 00 12 00]--> 设备
设备 --ACK--> 主机
Data阶段(因为D7=1,使用IN):
主机 --IN令牌--> 设备
设备 --DATA1[12 01 00 02 ...]--> 主机 (设备描述符18字节)
主机 --ACK--> 设备
Status阶段(反向,使用OUT):
主机 --OUT令牌--> 设备
主机 --DATA1[](0字节)--> 设备
设备 --ACK--> 主机
4.2 SET_ADDRESS(设置设备地址)
SETUP数据包内容:
偏移 字段 十六进制 二进制 说明
───────────────────────────────────────────────────────────
0 bmRequestType 0x00 00000000 D7=0 → Host-to-Device
│└─┬─┘└──┬──┘
│ 00 00000
│ 标准 设备
└─ OUT方向(但wLength=0)
1 bRequest 0x05 SET_ADDRESS
2-3 wValue 0x0003 新地址=3
4-5 wIndex 0x0000 索引=0
6-7 wLength 0x0000 无数据阶段!
后续流程:
Setup阶段:
主机 --SETUP令牌--> 设备
主机 --DATA0[00 05 03 00 00 00 00 00]--> 设备
设备 --ACK--> 主机
Data阶段:
(无,因为wLength = 0)
Status阶段(wLength=0时默认使用IN):
主机 --IN令牌--> 设备
设备 --DATA1[](0字节)--> 主机
设备 --ACK--> 主机
设备在Status完成后切换到新地址3
4.3 SET_CONFIGURATION(设置配置)
SETUP数据包内容:
偏移 字段 十六进制 二进制 说明
───────────────────────────────────────────────────────────
0 bmRequestType 0x00 00000000 D7=0 → Host-to-Device
1 bRequest 0x09 SET_CONFIGURATION
2-3 wValue 0x0001 配置值=1
4-5 wIndex 0x0000
6-7 wLength 0x0000 无数据阶段
后续流程:
Setup阶段:
主机 --SETUP令牌--> 设备
主机 --DATA0[00 09 01 00 00 00 00 00]--> 设备
设备 --ACK--> 主机
Data阶段:
(无)
Status阶段:
主机 --IN令牌--> 设备
设备 --DATA1[](0字节)--> 主机
主机 --ACK--> 设备
4.4 厂商自定义请求(写数据到设备)
SETUP数据包内容:
偏移 字段 十六进制 二进制 说明
───────────────────────────────────────────────────────────
0 bmRequestType 0x40 01000000 D7=0 → Host-to-Device
│└─┬─┘└──┬──┘ D6-5=10 → Vendor
│ 10 00000 D4-0=00000 → Device
└─ OUT方向
1 bRequest 0xA0 厂商自定义命令
2-3 wValue 0x1234 参数
4-5 wIndex 0x0000
6-7 wLength 0x0004 要写4字节
后续流程:
Setup阶段:
主机 --SETUP令牌--> 设备
主机 --DATA0[40 A0 34 12 00 00 04 00]--> 设备
设备 --ACK--> 主机
Data阶段(因为D7=0且wLength>0,使用OUT):
主机 --OUT令牌--> 设备
主机 --DATA1[AA BB CC DD]--> 设备 (4字节数据)
设备 --ACK--> 主机
Status阶段(反向,使用IN):
主机 --IN令牌--> 设备
设备 --DATA1[](0字节)--> 主机
主机 --ACK--> 设备
4.5 厂商自定义请求(从设备读数据)
SETUP数据包内容:
偏移 字段 十六进制 二进制 说明
───────────────────────────────────────────────────────────
0 bmRequestType 0xC0 11000000 D7=1 → Device-to-Host
│└─┬─┘└──┬──┘ D6-5=10 → Vendor
│ 10 00000 D4-0=00000 → Device
└─ IN方向
1 bRequest 0xA1 厂商自定义命令
2-3 wValue 0x5678
4-5 wIndex 0x0000
6-7 wLength 0x0010 要读16字节
后续流程:
Setup阶段:
主机 --SETUP令牌--> 设备
主机 --DATA0[C0 A1 78 56 00 00 10 00]--> 设备
设备 --ACK--> 主机
Data阶段(因为D7=1,使用IN):
主机 --IN令牌--> 设备
设备 --DATA1[...16字节...]--> 主机
主机 --ACK--> 设备
Status阶段(反向,使用OUT):
主机 --OUT令牌--> 设备
主机 --DATA1[](0字节)--> 设备
设备 --ACK--> 主机
五、判断流程总结
5.1 设备端判断逻辑
void handle_setup_packet(uint8_t *setup_data) {
uint8_t bmRequestType = setup_data[0];
uint8_t bRequest = setup_data[1];
uint16_t wValue = *(uint16_t *)&setup_data[2];
uint16_t wIndex = *(uint16_t *)&setup_data[4];
uint16_t wLength = *(uint16_t *)&setup_data[6];
// 判断数据方向
bool is_device_to_host = (bmRequestType & 0x80); // D7位
if (wLength == 0) {
// 无数据阶段,直接进入Status
// Status使用IN令牌(设备发送0字节DATA1)
prepare_status_in();
}
else if (is_device_to_host) {
// Data阶段:设备→主机(IN)
// Status阶段:主机→设备(OUT,反向握手)
prepare_data_in(wLength);
prepare_status_out();
}
else {
// Data阶段:主机→设备(OUT)
// Status阶段:设备→主机(IN,反向握手)
prepare_data_out(wLength);
prepare_status_in();
}
// 根据bRequest执行具体操作
switch (bRequest) {
case 0x06: handle_get_descriptor(wValue, wIndex, wLength); break;
case 0x05: handle_set_address(wValue); break;
case 0x09: handle_set_configuration(wValue); break;
// ...
}
}
5.2 判断流程图
收到SETUP令牌和DATA0包
↓
解析bmRequestType字段
↓
检查wLength
↓
┌────┴────┐
│wLength=0│
└────┬────┘
↓
无Data阶段
↓
Status用IN
┌────┴────┐
│wLength>0│
└────┬────┘
↓
检查D7位
↓
┌───┴───┐
│ D7? │
└┬─────┬┘
│ │
0│ │1
│ │
OUT方向 IN方向
│ │
Data用OUT Data用IN
Status用IN Status用OUT
六、批量传输(Bulk Transfer)的方向判断
批量传输不使用SETUP,方向由端点描述符决定:
6.1 端点描述符中的方向位
bEndpointAddress (1字节)
D7 D6 D5 D4 D3 D2 D1 D0
│ └──┬───┘ └────┬────┘
│ │ │
│ │ └─ Endpoint Number (端点号 0-15)
│ └─ Reserved (保留,必须为0)
│
└─ Direction (方向)
0 = OUT (主机→设备)
1 = IN (设备→主机)
6.2 示例
端点描述符:
偏移 字段 值 说明
──────────────────────────────────────────
0 bLength 0x07 描述符长度
1 bDescriptorType 0x05 ENDPOINT类型
2 bEndpointAddress 0x81 D7=1 → IN端点
D3-0=1 → 端点1
3 bmAttributes 0x02 Bulk传输
4-5 wMaxPacketSize 0x0040 最大包64字节
6 bInterval 0x00 轮询间隔(批量传输忽略)
传输方向:
bEndpointAddress = 0x81
D7 = 1 → IN端点
端点1用于 设备→主机 的批量传输
实际传输:
主机 --IN令牌[EP1]--> 设备
设备 --DATA0/DATA1[...]--> 主机
主机 --ACK--> 设备
对比:
bEndpointAddress = 0x02
D7 = 0 → OUT端点
端点2用于 主机→设备 的批量传输
实际传输:
主机 --OUT令牌[EP2]--> 设备
主机 --DATA0/DATA1[...]--> 设备
设备 --ACK--> 主机
七、完整示例:GET_DESCRIPTOR的三个阶段
7.1 Setup阶段
时刻T0:主机发送SETUP令牌
令牌包:SETUP + Addr=0 + EP=0 + CRC5
时刻T1:主机发送DATA0包
DATA0包:SYNC + PID(DATA0) +
[80 06 01 00 00 00 12 00] + ← 8字节Setup数据
CRC16 + EOP
解析:
bmRequestType = 0x80 (10000000)
D7=1 → Device-to-Host (IN方向)
D6-5=00 → Standard请求
D4-0=00000 → Device
bRequest = 0x06 (GET_DESCRIPTOR)
wValue = 0x0100 (Device描述符)
wIndex = 0x0000
wLength = 0x0012 (18字节)
时刻T2:设备发送ACK
握手包:SYNC + PID(ACK) + EOP
7.2 Data阶段(IN方向)
时刻T3:主机发送IN令牌(因为D7=1)
令牌包:IN + Addr=0 + EP=0 + CRC5
时刻T4:设备发送DATA1包(第一个数据包用DATA1)
DATA1包:SYNC + PID(DATA1) +
[12 01 00 02 00 00 00 40 ...] + ← 设备描述符18字节
CRC16 + EOP
时刻T5:主机发送ACK
握手包:SYNC + PID(ACK) + EOP
7.3 Status阶段(反向,OUT)
时刻T6:主机发送OUT令牌(反向握手)
令牌包:OUT + Addr=0 + EP=0 + CRC5
时刻T7:主机发送DATA1包(0字节)
DATA1包:SYNC + PID(DATA1) +
[] + ← 0字节数据
CRC16 + EOP
时刻T8:设备发送ACK
握手包:SYNC + PID(ACK) + EOP
控制传输完成!
八、关键要点总结
8.1 方向判断核心
| 判断依据 | 位置 | 含义 |
|---|---|---|
| bmRequestType.D7 | Setup包[0]的最高位 | 0=OUT写, 1=IN读 |
| wLength | Setup包[6-7] | 0=无Data阶段, >0=有Data阶段 |
| bEndpointAddress.D7 | 端点描述符[2]的最高位 | 批量/中断传输方向 |
8.2 三阶段方向组合
| bmRequestType.D7 | wLength | Data阶段 | Status阶段 |
|---|---|---|---|
| 0 (OUT) | 0 | 无 | IN |
| 0 (OUT) | >0 | OUT | IN |
| 1 (IN) | >0 | IN | OUT |
| 1 (IN) | 0 | 无 | IN |
8.3 记忆技巧
- Setup包的D7位决定Data方向
- Status阶段总是反向(除了wLength=0,默认IN)
- 批量/中断传输看端点描述符的D7位
- 等时传输无握手包,只看端点描述符D7位
完成! 这份笔记详细解释了如何通过SETUP数据包的bmRequestType字段判断后续Data阶段的方向。