枚举可以理解为主机按不定的顺序向USB设备讨要设备信息,好给它分配资源,若枚举不成功,就放弃分配资源,免得浪费资源。一般都是使用中断传输方式通信。
常用的描述符有以下几种:01H、设备描述符 02H、配置描述符 03H、字符串描述符 04H、接口描述符 05H、端点描述符
21H:HID描述符 22H:HID报告
一个设备只能有一个设备描述符,而一个设备描述符可以包含多个配置描述符(bNumConfigurations ),一个配置描述符又可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。
以下为HID描述符:(一个USB设备同时包含键盘和鼠标,使用2个接口)
一、设备描述符:Device descriptor
hid_device_descriptor =
{
0x12 , // bLength 该段描述符总长18个,不可变
0x01, // bDescriptorType:常用的如下0x01:设备 0x02配置 0x03字符 0x04接口 0x05端点 0x21HID
0x0200, // bcdUSB USB版本号: 1.1--0x0110 2.0--0x0200 3.0--0x0300
0x00, // bDeviceClass HID 不使用接口联合描述字与下面一起设置为00H
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
8, // bMaxPacketSize0 端点0最大包的大小 USB2.0:低速--8 全速:8、16、32、64 高速:64
0x1223, // idVendor VID
0x3F07, // idProduct PID
0x1110, // bcdDevice 厂商指定的设备版本号
0x01, // iManufacturer 指向描述制造商字符串的索引
0x02, // iProduct 指向描述产品的字符串索引
0x00, // iSerialNumber 指向设备序列号的字符串索引
0x01 // bNumConfigurations 定义配置描述符的数量
};
二、配置描述符
hid_configuration_descriptor =
{
0x09, // bLength 长度9个,不可变
0x02, // bDescriptorType 配置描述符
0x3b00, // wTotallength= 9+(9+9+7)+(9+9+7) 配置描述符+(接口描述符+HID描述符+端点描述符)*接口数
配置描述符信息总的大小,包括接口描述符、端点描述符等等
0x02, // bNumInterfaces 接口数量=2(键盘+鼠标)
0x01, // bConfigurationValue Set_Configuration命令需要的参数值
0x00, // iConfiguration 配置字符串索引
0xa0, // bmAttributes bit7=1 bit6:1--自供电 0--总线供电 bit5:1--远程唤起 0--不支持 bit[4:0]=0
0x32 // MaxPower (in 2mA units) 50*2mA=100mA
};
三、接口配置符
keyboard_interface_descriptor =
{
0x09, // bLength 长度9个,不可变
0x04, // bDescriptorType 接口描述符
0x00, // bInterfaceNumber 接口0 (接口从0开始,键盘定义0,鼠标定义1)
0x00, // bAlternateSetting 接口索引值
0x01, // bNumEndpoints 端点个数1(端点0不可用,比如EP1)
0x03, // bInterfaceClass (3 = HID)
0x01, // bInterfaceSubClass 接口子类型:01为Boot Device,键鼠在BIOS下就启动
0x01, // bInterfaceProcotol 接口协议:00--None 01--Keyboard 02--Mouse
0x00 // iInterface 描述该接口的字符串索引
};
mouse_interface_descriptor =
{
0x09, // bLength 长度9个,不可变
0x04, // bDescriptorType 接口描述符
0x01, // bInterfaceNumber 接口1 不同接口
0x00, // bAlternateSetting 接口索引值
0x01, // bNumEndpoints 端点个数1(端点0不可用,比如EP2)
0x03, // bInterfaceClass (3 = HID)
0x01, // bInterfaceSubClass 接口子类型:01为Boot Device,键鼠在BIOS下就启动
0x02, // bInterfaceProcotol 接口协议:00--None 01--Keyboard 02--Mouse
0x00 // iInterface 描述该接口的字符串索引
};
四、HID描述符
keyboard_hid_descriptor =
{
0x09, // bLength 长度9个,不可变
0x21, // bDescriptorType HID描述符
0x0110, // bcdHID HID专属版本号
0x00, // bCountryCode 国家代码
0x01, // bNumDescriptors 附属类描述字的数目1个
0x22, // bDescriptorType 描述字类型:报告
HID_KEYBOARD_REPORT_DESCRIPTOR_SIZE // 键盘HID报告描述字总字节数,比如:0x75,0x00,低字节在前
};
mouse_hid_descriptor =
{
0x09, // bLength 长度9个,不可变
0x21, // bDescriptorType HID描述符
0x0110, // bcdHID HID专属版本号
0x00, // bCountryCode 国家代码
0x01, // bNumDescriptors 附属类描述字的数目1个
0x22, // bDescriptorType 描述字类型:报告
HID_MOUSE_REPORT_DESCRIPTOR_SIZE // 鼠标HID报告描述字总字节数,比如0x34, 0x00,低字节在前
};
五、端点描述符
hid_keyboard_endpoint1_in_descriptor =
{
0x07, // bLength 长度7个,不可变
0x05, // bDescriptorType 端点描述符
0x81, // bEndpointAddress bit[7]:1--IN 0--OUT 地址为EP1,输入
0x03, // bmAttributes 传输类型(中断--03H)
0x08, // MaxPacketSize_LSB 端点1最大信息包尺寸
0x00, // MaxPacketSize_MSB
0x08, // bInterval 轮询间隔 一帧为8个中断间隔
};
hid_mouse_endpoint2_in_descriptor =
{
0x07, // bLength 长度7个,不可变
0x05, // bDescriptorType 端点描述符
0x82, // bEndpointAddress bit[7]:1--IN 0--OUT 地址为EP2,输入
0x03, // bmAttributes 传输类型(中断--03H)
0x08, // MaxPacketSize_LSB 端点1最大信息包尺寸
0x00, // MaxPacketSize_MSB
0x08, // bInterval 轮询间隔 一帧为8个中断间隔
};
说完上述几个重要描述符后,我们再来看字符串描述符.
在USB中,字符串描述符是可选的,也就是可有可无的角色,USB并没有强制规定必须有,但是一般产品是有的,至少能说明生产厂家,产品信息等,要不然这个产品看谁还敢用.哈哈哈...
如果设备没有字符串描述符,那么在设备描述符、配置描述符、接口描述符等处的字符串索引值必须为0,要不然在枚举过程中,USB主机会尝试去获取字符串描述符,而刚好你又没有,那么枚举就会失败,所以必须指定为0
字符串描述符使用UNICODE编码,可以支持多种语言,所以字符串描述符首先要指定语言ID,语言ID代码可以参考这个网站:http://www.usb.org/developers/docs/USB_LANGIDs.pdf,例如:简体中文的ID值为0x0804,美式英语ID值为0x0409。
语言ID字符串描述符结构定义如下:
在枚举过程中,USB主机会向USB设备发送GET_DESCRIPTOR请求,同时wValue字段高字节为描述符类型,字符串描述符的类型为0x03,低字节为字符串描述符索引值,对于语言ID的索引为0,其它字符串描述符索引由设备描述符指定,wIndex字段为语言ID。
字符串描述符结构定义如下:
bLength为描述符长度,bDescriptorType为描述符类型,字符编码统一采用UNICODE编码,UNICODE采用两个字节字节表示一个字符,如果是英语字符的话,那就很简单了,直接在ASCII码前面补上一个为0x00的字节数据就组成UNICODE编码了,如果是其它语言的话,网上有很多的UNICODE编码转换工具,可以直接拿来使用就行了。
最后补上我的USB鼠标字符串描述符信息。
语言ID信息:
厂商字符串描述符如下:
我在Virtual Box中捕获的信息,所以厂商字符串为VirtualBox。
产品字符串描述符信息如下:
主机通过标准请求命令来获得以上HID描述符和HID报告:
标准USB设备请求命令共有11个,大小都是8个字节,具有相同的结构,由5 个字段构成(字段是标准请求命令的数据部分),结构如下(括号中的数字表示字节数,首字母bm,b,w分别表示位图、字节,双字节):
bmRequestType(1) +bRequest(1) +wvalue(2) +wIndex(2) +wLength(2)
一、bmRequestType:
bit[7]: 说明请求的传输方向 1--设备到主机(IN) 0--主机到设备(OUT)
bit[6:5]:00--标准请求命令 01--专门类请求 10--用户定义的请求 11--保留
bit[4:0]:00000--接收者为设备 00001--接收者为接口 00010--接收者为端点 00011--接收者为其他元件 其他设置保留
二、bRequest:
请求命令代码,在标准的USB命令中,每一个命令都定义了编号,编号的值就为字段的值,编号与命令名称如下(要注意这里的命令代码要与其他字段结合使用,可以说命令代码是标准请求命令代码的核心,正是因为这些命令代码而决定了11个USB标准请求命令):
1、Get Status (00H) 获取状态
wValue:0000H wIndex:0000H(设备)、接口号或端点号 wLength:0002H
A:[To Device]获取设备的状态:
位0:自供电(0表示总线供电;1表示自供电).
位1:远程唤醒(0表示不支持远程唤醒;1表示远程唤醒).
位2~15:保留.
一般选择总线供电,不支持远程唤醒,所以返回数据就是0x0000.
B:[To Interface]获取接口的状态:
接口状态的16位字节全部保留,所以返回数据就是0x0000.
C:[To Endpoint]获取端点的状态:
位0:Halt(0表示端点允许;1表示端点禁止).
位1~15:保留(复位为0).
2、Clear Feature (01H) 清除特性
、Set Feature (03H) 设置特性
wValue:所要使能的特征 wIndex:0000H(设备)、接口号或端点号 wLength:0000H
A:[To Device]设置设备的远程唤醒功能,并返回一个空包.
B:[To Endpoint]禁止端点.
4、Set Address (05H) 设置地址
wValue:新的设备地址,范围0001H到007FH wIndex:0000H wLength:0000H
A:设置设备地址.
5、Get Descriptor (06H) 获取描述符
wValue:高字节--描述符类型 低字节--描述符索引 wIndex:0000H或ID wLength:需返回的字节数
A:[To Device]获取设备描述符:
描述当前USB协议的版本号.设备端点0的FIFO大小.USB设备的ID号等.
B:[To Configuration]获取配置描述符:
描述USB设备接口个数及是否有自供电能力等.
C:[To Interface]获取接口描述符:
描述端点0以外的物理端点个数等信息.
D:[To Endpoint]获取端点描述符:
描述端点0各端点的传输类型和最大信息包大小和端点的传输方向(IN/OUT).
6、Set Descriptor (07H) 设置描述符(可选,无法更新)
wValue:高字节--描述符类型 低字节--描述符索引 wIndex:0000H或ID wLength:需传输给设备的字节数
7、Get Configuration (08H) 获取配置信息
wValue:0000H wIndex:0000H wLength:0001H
8、Set Configuration (09H) 设置配置
wValue:低字节规定了一个配置,若此值与设备支持的配置匹配,设备将实现所请求配置 wIndex:0000H wLength:0000H
A:[To Configuration]设置配置描述符.
B:[To Interface]设置接口描述符.
C:[To Endpoint]设置端点描述符.
9、Get Interface (0AH) 获取接口信息
wValue:0000H wIndex:接口号(bInterfaceNumber) wLength:0001H
10、Set Interface (0BH) 设置接口
wValue:要选择的替代设置(bAlternateSetting) wIndex:接口号(bInterfaceNumber) wLength:0000H
11、SYNCH_FRAME(0CH)
wValue:0000H wIndex:0000H wLength:0006H
用于设备设置和报告一个端点的同步帧.
一个描述设备描述符和描述配置描述符过程如下图:
HID设备描述符
温习了以上内容,我们再来看看HID协议与这些描述符之间的关系。
当插入USB设备后,主机会向设备请求各种描述符来识别设备。
为了把一个设备识别为HID类别,设备在定义描述符的时候必须遵守HID规范。
从框图中,可以看出除了USB标准定义的一些描述符外,HID设备还必须定义HID描述符。另外设备和主机的通信是通过报告的形式来实现的,所以还必须定义报告描述符;而物理描述符不是必需的。还有就是HID描述符是关联于接口(而不是端点)的,所以设备不需要为每个端点都提供一个HID描述符。
接口描述符中bInterfaceClass的值必须为0x03,bInterfaceSubClass的值为0或1,为1表示HID设备符是一个启动设备(Boot Device,一般对PC机而言才有意义,意思是BIOS启动时能识别并使用您的HID设备,且只有标准鼠标或键盘类设备才能成为Boot Device。如果为0则只有在操作系统启动后才能识别并使用您的HID设备)。
USB HID类描述符的结构 |
||||
偏移量 |
域 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
此描述符的长度(以字节为单位) |
1 |
bDescriptorType |
1 |
常量 |
描述符种类(此处为0x21即HID类描述符) |
2 |
bcdHID |
2 |
数字 |
HID规范版本号(BCD码),采用4个16进制的BCD格式编码,如版本1.0的BCD码为0x0100,版本为1.1的BCD码为0x0110 |
4 |
bCountryCode |
1 |
数字 |
硬件目的国家的识别码(BCD码)(见表3) |
5 |
bNumDescritors |
1 |
数字 |
支持的附属描述符数目 |
6 |
bDescriptorType |
1 |
常量 |
HID相关描述符的类型 0x21:HID描述符 0x22:报告描述符 0x23:物理描述符 |
7 |
wDescriptorLength |
2 |
数字 |
报告描述符总长度 |
9 |
bDescriptorType |
1 |
常量 |
用于识别描述符类型的常量,使用在有一个以上描述符的设备 |
10 |
wDescriptorLength |
2 |
数字 |
描述符总长度,使用在有一个以上描述符的设备 |
报告描述符
报告描述符比较复杂,它是以item形式排列组合而成,无固定长途,用户可以自定义长度以及每一bit的含义。item类型分三种:main,global和local,其中main类型又可分为5种tag:
- input item tag:指的是从设备的一个或多个类似控制管道得到的数据
- output item tag:指的是发送给一个或多个类似控制管道的数据
- feature item tag:表示设备的输入输出不面向最终用户
- collection item tag:一个有意义的input,output和feature的组合项目
- end collection item tag:指定一个collectionitem的终止
每一个main item tag(input,output,feature)都表明了来自一个特定管道的数据的大小,数据相对还是独立,以及其他相关信息。在此之前,global和local item定义了数据的最大值和最小值,等等。local item仅仅描述下一个main item定义的数据域,而global item是这一个报告描述符中所有后续数据段的默认属性。
一个报告描述符可能包含多个main item,为了准确描述来自一个控制管道的数据,一个报告描述符必须包括以下内容:
- input(output,feature)
- usage
- usage page
- Logical Minimum
- Logical Maximum
- Report Size
- Report Count
下面用一个三键鼠标举例说明:
Usage Page (Generic Desktop); //global item
Usage (Mouse); //global item
Collection (Application); //Start Mouse collection
Usage (Pointer); //
Collection (Physical); //Start Pointer collection
Usage Page (Buttons)
Usage Minimum (1),
Usage Maximum (3),
Logical Minimum (0),
Logical Maximum (1) ; //Fields return values from 0 to 1
Report Count (3),
Report Size (1); //Create three 1 bit fields (button 1, 2, & 3)
Input (Data, Variable, Absolute); //Add fields to the input report.
Report Count (1),
Report Size (5); //Create 5 bit constant field
Input (Constant), ;Add field to the input report
Usage Page (Generic Desktop),
Usage (X),
Usage (Y),
Logical Minimum (-127),
Logical Maximum (127); //Fields return values from -127 to 127
Report Size (8),
Report Count (2); //Create two 8 bit fields (X & Y position)
Input (Data, Variable, Relative); //Add fields to the input report
End Collection; //Close Pointer collection
End Collection; //Close Mouse collection
item的数据格式有两种,分别是短item和长item。
短item格式
bSize |
0:0个字节 1:1个字节 2:2个字节 3:4个字节 |
bType |
0:main 1:global 2:local 3:保留 |
bTag |
item类型 8:input 9:output A:collection B:feature C:end collection |
长item,其bType位值为3,bTag值为F
bDataSize |
0:0个字节 1:1个字节 2:2个字节 3:4个字节 |
bLongItemTag |
0:main 1:global 2:local 3:保留 |
data | 数据 |
物理描述符用来描述行为特性,是可选的。
USB HID类可采用的通信管道
所有的HID设备通过USB的控制管道(默认管道,即端点0)和中断管道与主机通信。
控制管道主要用于以下3个方面:
接收/响应USB主机的控制请示及相关的类数据
在USB主机查询时传输数据(如响应Get_Report请求等)
接收USB主机的数据
中断管道主要用于以下两个方面:
USB主机接收USB设备的异步传输数据
USB主机发送有实时性要求的数据给USB设备
从USB主机到USB设备的中断输出数据传输是可选的,当不支持中断输出数据传输时,USB主机通过控制管道将数据传输给USB设备。
表1、USB HID规范定义的HID设备可用端点 |
||
管道 |
要求 |
说明 |
控制(端点0) |
必须 |
传输USB描述符、类请求代码以及供查询的消息数据等 |
中断输入 |
必须 |
传输从设备到主机的输入数据 |
中断输出 |
可选 |
传输从主机到设备的输出数据 |
HID设备6种特定请求
HID类请求(命令)包格式 |
|||
偏移量 |
域 |
大小 |
说明 |
0 |
bmRequestType |
1 |
HID设备类请求特性如下: 因而,针对HID的设备类请求,仅仅10100001和00100001有效 |
1 |
bRequest |
1 |
HID类请求(参考下表) |
2 |
wValue |
2 |
高字节说明描述符的类型 0x21:HID描述符 0x22:报告描述符 0x23:物理描述符 低字节为非0值时被用来选定实体描述符。 |
4 |
wIndex |
2 |
2字节数值,根据不同的bRequest有不同的意义 |
6 |
wLength |
2 |
该请求的数据段长度 |
HID类请求 |
||
数值 |
HID类请求描述符 |
注释 |
0x01 |
GET_REPORT |
主机用控制传输从设备接收数据,所有HID类设备都要支持这个请求; |
0x02 |
GET_IDLE |
主机读取设备当前的空闲速率,设备可以不支持此请求; |
0x03 |
GET_PROTOCOL |
仅仅适应于支持启动功能的HID设备(Boot Device) |
0x09 |
SET_REPORT |
设备用控制传输接收主机的数据,设备可以不支持此请求; |
0x0A |
SET_IDLE |
设置闲置状态,设备可不支持此请求; |
0x0B |
SET_PROTOCOL |
仅仅适应于支持启动功能的HID设备(Boot Device) |
GET_REPORT:主机通过控制端点获取一个Report
域 |
值 |
描述 |
bmRequestType |
0xA1 |
|
bRequest |
0x01 |
|
wValue |
高字节表示报告类型 0x01:input 0x02:output 0x03:feature other:reserved 低字节表示ReportID,如不使用设为0 |
|
wIndex |
HID的interface索引值 |
|
wLength |
Report长度 |
|
Data |
Report内容 |
SET_REPORT:主机发送一个Report给设备,用以设置input,output或者feature
域 |
值 |
描述 |
bmRequestType |
0x21 |
|
bRequest |
0x09 |
|
wValue |
高字节表示报告类型 0x01:input 0x02:output 0x03:feature other:reserved 低字节表示ReportID,如不使用设为0 |
|
wIndex |
HID的interface索引值 |
|
wLength |
Report长度 |
|
Data |
Report内容 |
GET_IDLE
域 |
值 |
描述 |
bmRequestType |
0xA1 |
|
bRequest |
0x02 |
|
wValue |
高字节0 低字节表示ReportID,如不使用设为0 |
|
wIndex |
HID的interface索引值 |
|
wLength |
1 |
|
Data |
空闲速率 |
SET_IDLE
域 |
值 |
描述 |
bmRequestType |
0x21 |
|
bRequest |
0x0A |
|
wValue |
新的速率 低字节表示ReportID,如不使用设为0 |
|
wIndex |
HID的interface索引值 |
|
wLength |
0 |
|
Data |
无 |
GET_PROTOCOL
域 |
值 |
描述 |
bmRequestType |
0xA1 |
|
bRequest |
0x03 |
|
wValue |
0 |
|
wIndex |
HID的interface索引值 |
|
wLength |
1 |
|
Data |
0 = Boot Protocol 1 = Report Protocol |
SET_PROTOCOL
域 |
值 |
描述 |
bmRequestType |
0x21 |
|
bRequest |
0x0B |
|
wValue |
0 = Boot Protocol 1 = Report Protocol |
|
wIndex |
HID的interface索引值 |
|
wLength |
0 |
|
Data |
无 |