一、USB描述符
作为基础,首先要掌握USB设备的标准描述符(不管是什么类型的设备,都有这几个描述符存在),而不同类的设备,又有自己特殊的描述符,后面介绍不同类设备的时候,再进行介绍。
1. 设备描述符
每个USB设备都必须并且只有一个设备描述符(在程序中定义好设备描述符)。USB协议对设备描述符的定义如下:
设备描述符结构表
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
描述符的长度 (18Byte=0x12) |
1 |
bDescriptorType |
1 |
描述符类型(设备描述符 = 0x01) |
2 |
bcdUSB |
2 |
本设备使用的USB协议版本(1.1 or 2.0) |
4 |
bDeviceClass |
1 |
类代码 |
5 |
bDeviceSubClass |
1 |
子类代码 |
6 |
bDeviceProtocol |
1 |
设备所使用的协议 |
7 |
bMaxPacketSize0 |
1 |
端点0的最大包长 |
8 |
idVendor |
2 |
厂商ID |
10 |
idProduct |
2 |
产品ID |
12 |
bcdDevice |
2 |
设备版本号 |
14 |
iManufacturer |
1 |
描述厂商字符串的索引 |
15 |
iProduct |
1 |
描述产品字符串的索引 |
16 |
iSerialNumber |
1 |
产品***字符串的索引 |
17 |
bNumConfigurations |
1 |
可能的配置数(设备描述符下配置描述符的个数) |
说明:
1)bcdUSB是该设备所使用的USB协议版本号,长度2字节。比如可以取2.0或者1.1等版本号。需要特别注意的是,协议规定使用BCD码来表示版本号,比如:USB2.0协议就是0x0200,USB1.1协议就是0x0110。对照USB协议分析仪来看的时候,要注意,USB协议中使用的是小端结构,也就是低字节在前。比如说,USB2.0协议拆分成两个字节就是0x00 0x02,那么对照协议分析仪里面的数据就是:00 02 ;USB1.1在协议分析仪里面的数据就是:10 01。
2)bDeviceClass是设备所使用的类代码(XX类接口描述符码)。常用的类如下(根据协议,进行C宏定义):
//HID设备类接口描述符码 #define HID_CLASS 0x03 //音频类接口描述符码 #define Audio_CLASS 0x01 //视频类接口描述符码 #define Vedio_CLASS 0x0E //大容量设备类接口描述符码 MASS_STORAGE_CLASS 0x08 //杂项类或者混合类接口描述符码 #define MISC_CLASS 0xEF //厂商自定义的设备类接口描述符码 #define CUSTOM_CLASS 0xFF //特定应用类接口描述符码 #define DFU_DEVICE_CLASS 0xFE |
3)bDeviceSubClass设备所使用的子类代码。当类代码不为0也不是0xFF时,子类代码就得根据协议来进行赋值。当类代码为0的时候,子类代码也必须为0。
4)bDeviceProtocol是设备使用的协议。协议代码由USB协议规定。
注:
开发标准USB设备时,设备描述符中的 bDeviceClass、bDeviceSubClass、bDeviceProtocol这三个字段同时设置为0。然后在接口描述符中指定当前的设备类、子类以及协议类型(即:在接口中指定设备的类!),在PC端开发USB上位机软件的时候,需要特别注意的地方。 |
5)端点0的最大包长,取值可以是:8、16、32、64字节。注意,对应的十六进制数分别就是:0x08、0x10、0x20、0x40(分析源码和协议分析仪里面的数据,注意进行转换)。
6)关于厂商ID (2Byte),在开发中,可以随意设定一个值。真正做产品,要使用公司的ID(向USB协会申请),避免侵权。对于插入的设备,主机是依靠厂商ID号、产品ID号、产品***来安装驱动的。
7)产品ID是生产厂商自己定义的,比较*。
8)bcdDevice设备版本号。同一个产品,升级之后(比如固件修改,新增功能),可以通过修改设备版本号来进行区别。
9)iManufacturer是描述厂商字符串的索引值。如果设为0,则表示该USB设备没有厂商字符串。主机单独获取厂商字符串的时候,下发的标准请求数据包中,wValue域的第一个字节【低字节】就是厂商字符串的索引值,而高字节就是描述符的类型(字符串描述符0x03)。
厂商字符串就是一串普通的字符串,在设备描述符中,有三个非0的索引值:厂商字符串的索引值为1;产品字符串的索引值为2;产品***字符串的索引为3。设备在收到主机的字符串描述符请求之后,根据索引值,将对应的字符串数据返回给主机。
所以,如果解析到主机字符串描述符请求的数据包,如果wValue=0x0301,则表示主机请求获得厂商字符串。
10)iProduct是描述产品的字符串的索引值。同样的,如果设置为0,则表示该USB设备没有产品字符串。第一次插上设备时,提示发现新硬件,并显示设备的名称,其实这里显示的信息就是从产品字符串中获取的。实验:可通过修改产品字符串,再编译固件烧录,插入设备,就可以看到提示信息了。
同理,当主机请求产品字符串的时候,wValue这个域的值应该是:0x0302
11)iSerialNumber是设备的***字符串的索引值。同样的,如果设置为0,则表示该USB设备没有设备***。最好一个产品指定一个唯一的***,因为有可能主机会结合产品***和VID、PID来进行设备的区分和加载对应的驱动。同理,当主机请求***字符串的时候,wValue这个域的值应该是:0x0303
2. 配置描述符
一个USB设置至少有一个配置描述符。
标准配置描述符结构
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
该描述符的长度(9 Byte=0x09) |
1 |
bDescriptorType |
1 |
该描述符的类型(配置描述符是0x02) |
2 |
wTotalLength |
2 |
配置描述符集合 的总长度 |
4 |
bNumInterfaces |
1 |
该配置下的接口数 |
5 |
bConfigurationValue |
1 |
该配置的值 |
6 |
iConfiguration |
1 |
描述该配置的字符串的索引值 |
7 |
bmAttributes |
1 |
该设备的属性 |
8 |
bMaxPower |
1 |
该设备所需的电流(单位 2mA) |
说明:
1)wTotalLength是整个配置描述符集合的总长度,配置描述符集合就包括:配置描述符自身的长度、接口描述符、类特殊描述符、端点描述符等。
2)bNumInterfaces是该设备所支持的接口数量。通常来说,功能单一的设备就只有一个接口(比如鼠标、键盘),而复合设备则具有多个接口(比如音频设备,一般是HID + UAC)。
3)bConfigurationValue:一个USB设备可以有很多个配置。bConfigurationValue就是每个配置的标识(ID)!接下来会讲到设置配置这个标准请求,进行设置配置这个请求的时候,主机会发送一个配置值,如果某个配置的bConfigurationValue和主机请求的配置值相匹配,就表示该配置被**,USB设备就使用这个配置。(主机决定,设备使用哪个配置)
4)iConfiguration为0,则表示没有字符串来描述该配置描述符。
5)bmAttributes:大小为一字节,不同的位,表示不同的特性。
(1)第7位D7是保留的,必须为1。 (2)第6位D6表示供电方式:1设备自供电;0设备是总线供电的。 (3)第5位D5表示是否支持远程唤醒:1 支持远程唤醒 0 不支持远程唤醒。 (4)第0位到第4位D4~D0是保留的,默认为0。 |
6)bMaxPower:大小为1字节。表示设备从总线获取的最大电流量,单位是2mA。比如,如果需要200mA的电流,那么该字节的值就是100(100 x 2mA = 200mA)。
3. 接口描述符
接口描述符不能单独返回,要附着配置描述符后一并返回(Host请求配置描述符集合后,Device返回一堆数据)。
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
描述符的长度(9 Byte) |
1 |
bDescriptorType |
1 |
该描述符的类型(0x04) |
2 |
bInterfaceNumber |
1 |
该接口的编号(从0开始) |
3 |
bAlternateSetting |
1 |
该接口的备用编号 |
4 |
bNumEndpoints |
1 |
该接口所使用的端点数 |
5 |
bInterfaceClass |
1 |
该接口所使用的的类 |
6 |
bInterfaceSubClass |
1 |
该接口所使用的的子类 |
7 |
bInterfaceProtocol |
1 |
该接口所使用的的协议 |
8 |
iInterface |
1 |
描述该接口的字符串的索引值 |
说明:
1)bInterfaceNumber:当一个配置有多个接口时,每个接口的编号都不相同,从0开始递增对一个配置的接口进行编号。这里需要注意,对照一下,配置描述符里面,支持多少个接口(这个域:bNumInterfaces)。
2)bAlternateSetting:备用端口号,也是从0开始。一般很少用到该字段(开发UAC就必须用到这个字段),设置为0即可。Alternate:备用
3)bNumEndpoints:是该接口使用的端点数,不包括0端点。如果该字段设置为0,那么表示没有非0端点。
4)bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol:分别是接口所使用的类、子类、和协议,对应的代码(编号)由USB协议来定义。跟设备描述符中的意义很相像,设备描述符也有类似的三个域 (如果要开发自己的上位机,则这三个字段都设置为0xFF)。
5)iInterface:如果设置为0,则表示没有字符串。
4. 端点描述符
端点描述符不能单独返回,要附着配置描述符后一并返回(Host请求配置描述符集合后,Device返回一堆数据)。
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
该描述符的长度(7 Byte) |
1 |
bDescriptorType |
1 |
该描述符的类型(0x05) |
2 |
bEndpointAddress |
1 |
该端点的地址及输入输出属性 |
3 |
bmAttributes |
1 |
该端点的传输类型属性 |
4 |
wMaxPacketSize |
2 |
该端点支持的最大包长 |
6 |
bInterval |
1 |
端点的查询时间 |
说明:
1)bEndpointAddress:大小1字节,端点的地址。
最高位,也就是第7位D7:表示该端点的传输方向。1表示输入(像Input的第一个字母) 0表示输出(像Output的第一个字母) D6~D4:保留位,默认为0。 D3~D0:才是端点号 |
2)bmAttributes是端点的属性。
(1)最低两位D1~D0:表示该端点的传输类型。0为控制传输;1为等时传输;2为批量传输;3为中断传输。两个位,也就四种情况:00 01 10 11 (2)如果是非等时传输(控制传输、批量传输、中断传输中的一种),那么D7~D2都为0 (3)如果该端点时等时传输,则: D3~D2表示同步的类型:0为无同步、1为异步、2为适配、3为同步。 D5~D4表示用途:0为数据端点、1为反馈端点、2为暗含反馈的数据端点、3是保留值。 D7~D6:保留,设为0。 |
3)wMaxPacketSize:大小2字节,表示该端点每次传输的最大包长(单位:字节)。关于端点的最大包长,需要注意两点,其一是USB协议规定的最大包长上限,其二是需要注意结合实际开发场合来规定端点最大包长(比如:开发UAC的时候,端点最大包长要根据采样率、通道数等来计算并设置,否则PC端就不能正确进行重采样,声音就会失真)。
4)bInterval:表示端点查询的时间。对于中断端点以及同步传输,表示端点的轮询时间间隔,而对于块传输,该字段没有意义(介绍到具体的类设备时,该字段会作为重点解析的字段)。USB协议中对该字段的描述如下图:
5. 字符串描述符
主机在获得配置描述符集合之后,会下发获得语言ID的请求(索引值为0),以及获得字符串描述符的请求。在USB协议中,字符串描述符是可选的。在设备描述符中,申请了三个非0的索引值:
1:是厂商字符串的索引值 2:是产品字符串的索引值 3:是产品***的索引值 |
主机通过索引值来获取对应的字符串数据。索引值为0则表示获取的是语言ID字符串。字符串描述符的结构很简单,如下:
首先是语言ID描述符的结构
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
该描述符的长度(4字节=0x04) |
1 |
bDescriptorType |
1 |
描述符类型(字符串类型为0x03) |
2 |
wLANGID |
2 |
语言ID号 |
字符串描述符(产品+厂商+产品***)的结构
偏移量/字节 |
域 |
大小/字节 |
说明 |
0 |
bLength |
1 |
该描述符的长度(n) |
1 |
bDescriptorType |
1 |
描述符类型(字符串类型为0x03) |
2 |
bString |
N |
UNICODE编码的字符串 |
说明:
1)语言ID,只使用美式英语的一种,即0x0409。
2)bString字段使用的是UNICODE编码的字符串,使用两个字节来表示一个字符。
3)如果设备描述符中的iManufacturer、iProduct、iSerialNumber都设置为0的话,主机就不会下发对设备字符串描述符的请求(调试),所以说,开发过程中,字符串描述符并不是必须的。但是,针对产品的研发,它又是必须的。
6. 各个描述符之间的关系
1)一个设备,只有一个设备描述符。
2)一个设备描述符可以包含多个配置描述符。
3)一个配置描述符可以包含多个接口描述符。
4)一个接口描述符可以包含多个端点描述符。
描述符之间的包含关系如下图(站在集合的角度去理解):
首先要知道,USB描述符之间的关系是一层一层的,理解了这一点,有助于我们去分析整个设备枚举过程(下一篇介绍)。Host获取描述符的顺序,是从最顶层开始,获取的顺序为:设备描述符---------->配置描述符-------->……。
二、请求
请求和描述符一样,有标准的设备请求,而对于不同类的设备,又有自己特定的请求。先介绍标准的设备请求,对于特殊的请求,介绍每个不同的类设备时,再做解析。
1. 标准设备请求的数据结构
USB协议中规定,标准请求的长度为8个字节。在设备枚举过程中,Host会下发一系列的标准请求,设备端需要去解析这些标准请求(SETUP事务),并作出正确响应,设备才能成功枚举。成功枚举之后,才能调用相关接口进行数据通信。
8字节的标准请求结构如下:
bmRequestTtype(1 Byte) |
bRequest(1 Byte) |
wValue(2 Byte) |
wIndex(2 Byte) |
wLength(2 Byte) |
0xXX |
0xXX |
0xXXXX |
0xXXXX |
0xXXXX |
每个域的解析如下表
标准请求结构表
偏移量 |
域 |
大小/字节 |
取值类型 |
字段的描述 |
0 |
bmRequestTtype |
1 |
位图 |
当前请求的特性,每个位的取值以及含义如下: D7:数据阶段,数据的传输方向。也就是Host请求设备返回数据还是Host告诉设备它即将要发出数据。 0:主机--->设备(输出) 1:设备--->主机(输入)
D6~D5:请求的类型 0:表示这是一个标准请求 1:这是一个特定的类请求 2:这是一个厂商自定义的请求 3:缺省值
D4~D0:请求的接收方 0:这是发给设备的请求 1:这是发给接口的请求 2:这是发给端点的请求 3:不是以上三者之一 4~31:缺省值 |
1 |
bRequest |
1 |
数值 |
请求码 |
2 |
wValue |
2 |
数值 |
看具体的请求,不同的请求含义不同 |
4 |
wIndex |
2 |
数值 |
看具体的请求,不同的请求含义不同 |
6 |
wLength |
2 |
数值 |
数据过程的需要传输的字节数(0或非0) |
对于标准的请求,D6~D5 = 00,USB协议规定了11个标准请求,请求码(bRequest)如下表。
标准请求代码表
bRequest |
代码值 |
GET_STATUS |
0(0x00) |
CLEAR_FEATURE |
1(0x01) |
SET_FEATURE |
3(0x03) |
SET_ADDRESS |
5(0x05) |
GET_DESCRIPTOR |
6(0x06) |
SET_DESCRIPTOR |
7(0x07) |
GET_CONFIGURATION |
8(0x08) |
SET_CONFIGURATION |
9(0x09) |
GET_INTERFACE |
10(0x0a) |
SET_INTERFACE |
11(0x0b) |
SYNCH_FRAME |
12(0x0c) |
下面重点介绍三个标准请求。
2. GET_DESCRIPTOR请求
获取描述符请求。该请求由Host发出,设备端解析请求,并返回指定描述符数据到Host,是设备枚举过程中用的最多的一个请求。
2.1 获取设备描述符请求结构
bmRequestTtype(1 Byte) |
bRequest(1 Byte) |
wValue(2 Byte) |
wIndex(2 Byte) |
wLength(2 Byte) |
10000000B=80H (0x80) |
GET_DESCRIPTOR (0x06)
|
描述符的类型和索引号 |
0或字符串描述符的语言ID号 |
描述符的长度 |
对每个域的解析如下
1)wValue有两个字节,低字节表示:索引号。即同一类描述符里面的哪一个描述符。
例1:字符串描述符可分为厂商字符串、产品字符串、产品***字符串,对应的索引值分别为1、2、3。如果索引值为0,则表示获取的是语音ID字符串。
例2:假如一个设备有多种配置(定义了多个配置描述符),那么索引号就是配置描述符的ID号(bConfigurationValue)。
wValue的高字节表示描述符的类型,USB协议规定不同的类型由一个唯一的码值来表示,如下表:
标准描述符类型及其对应的编号
描述符的类型 |
编号 |
设备描述符(Device Descriptor) |
1(0x01) |
配置描述符(Configuration Descriptor) |
2(0x02) |
字符串描述符(String Descriptor) |
3(0x03) |
接口描述符(Interface Descriptor) |
4(0x04) |
端点描述符(Endpoint Descriptor) |
5(0x05) |
2)wIndex
这个域是专门为获取字符串描述符而设置的。当主机请求获取其他描述符时,这个域的值一定是0;当主机请求获取那三个字符串描述符时,这个域是字符串的语言ID号,一般用的是美式英语,值固定为0x0409,该值就是主机获取字符串描述符语言ID的时候,设备返回的值。所以,主机获取设备字符串描述符的时候,顺序一般都是(假如在设备描述符中设置字符串描述符的索引值为非0):
A. 最先请求的是字符串描述符的语言ID B. 请求厂商字符串 C. 请求产品字符串 D. 请求产品***字符串 |
3)wLength
数据过程,要求设备返回的数据长度。设备端返回的数据可以小于这个值,比如枚举过程中,Host(win7)会一次性请求长度为0xFF的配置描述符。
注:
(1) 如果设备工作在全速模式(Full-Speed Mode)下,那么主机端获取标准描述符,只有三个标准请求:A.获取设备描述符的请求;B.获取配置描述符的请求;C.获取字符串描述符的请求。对于接口描述符和端点描述符,是在主机端请求配置描述符集合的时候,一并返回。 (2) USB协议规定,总线的传输方式是串行方式,并且是LSB在前,……,MSB在最后。这是分析BusHound或者协议分析仪上的数据需要注意的地方。 |
3. SET_ADDRESS请求
主机在收到第一个数据包(设备描述符数据包),解析无误后,接下来就进入设置地址阶段。
设置地址请求也是一个USB标准设备请求。这是一个分配地址的过程(主机给设备分配一个地址)。既然是标准请求,那么它也是有8个字节的数据。指定的地址包含在wValue字段中。
主机从地址为0的设备获取设备描述符,一旦第一次成功获取到设备描述符之后,主机就会立刻发送设置地址的请求,减少设备使用公共地址0的时间(每个设备插入,都先被复位,默认的地址为0,也就是0地址是所有的USB设备的初始化地址,即可以理解为公共地址)。
3.1 设置地址请求的结构
设置地址的请求结构如下:
bmRequestType |
bRequest |
wValue(2Byte) |
wIndex(2Byte) |
wLength(2Byte) |
数据过程 |
0x0000 0000 0x00 |
SET_ADDRESS 0x05 |
设备地址 |
0x0000 |
0x0000 |
没有 |
说明:
(1) 设置地址的请求过程,是没有数据的,所以,数据长度就是0。
(2) 索引也用不着(请求字符串描述符,索引才用的上),所以也为0。
3.2 实例
主机下发的数据包为: 00 05 1D 00 00 00 00 00
解析: 数据方向【主机--->设备】 设置地址请求 地址数据为0x001D=29
协议分析仪中的地址数据
分析:wValue域的值是0x001D。转成十进制就是29,也就是主机分配给设备的地址是29。USB数据传输使用的是小端模式,低字节在前,所以,在协议分析中的数据就是:1D 00。
4. SET_CONFIGURATION请求
设置配置的请求下发后,设备端的USB控制器进入配置状态。设备端根据配置值,使用对应的配置,USB设备才能正常工作。
4.1 设置配置的请求结构
bmRequestType |
bRequest |
wValue(2 Byte) |
wIndex(2 Byte) |
wLength(2 Byte) |
数据过程 |
0x00 |
0x09 |
配置描述符的ID |
0x0000 |
0x0000 |
没有 |
说明:
(1) 在设置配置请求中,wValue的第一字节(低字节)为配置的值。当该值与某配置描述符中的配置编号一致时(相等时),表示选中的是该配置,接下来设备就使用这个配置。
4.2 实例
一个USB设备可以有很多个配置。bConfigurationValue就是每个配置的标识!主机请求设置配置的时候,会下发一个配置值,如果某个配置的bConfigurationValue和主机请求的配置值相匹配,就表示该配置被**,USB设备就使用这个配置(由主机决定,设备使用哪个配置)。
设置配置请求是一个输出请求,根据所请求的配置值,使能相应的端点。设备收到之后,返回一个0长度的状态数据包。设备收到非0的配置值之后,才会使能非0端点。否则会禁用非零端点。
分析协议分析仪上的数据:
结合上面协议分析仪里面的数据,wValue=0x0001。也就是说,主机告诉设备,使用的是编号为1的配置。在程序中,假如定义了一个配置描述符,并且该配置的编号是1。实际上,主机请求的配置编号都是存在的,因为按照枚举过程的先后顺序,Host已经先获取到了配置描述符,所以主机下发的请求中,配置值是合法的。同理,主机也可以通过解析配置描述符数据就可以知道,当前设备总共有多少个配置。
三、设备端状态变化
USB设备在枚举完成之前,控制器会有一系列的状态变化。这些状态分别是:连接状态(attached)、供电状态(powered)、默认状态(default)、地址状态(address)、配置状态(configures)、挂起状态(suspended)。这些状态都可以根据具体的IC设计情况,配置寄存器,让USB控制器产生事件中断。
图:枚举过程中设备状态