请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
从软件的角度,制作USB HID设备,需要理解的知识包括几大块:
1) USB协议的基本架构和软件架构;
2) USB描述符,包括标准描述符和类描述符;
3) USB命令,包括标准命令和类命令。
当然,如果能从字节序上(比如USB包的构成)了解USB通信的过程,对理解整个USB协议的构成也很有好处。但在编程中,不需要理解到这个地步。大部分介绍USB协议的书籍,以及USB规范中,都很详细地描述了这些过程,非常建议读一读。
在前一篇中,已经简要地概述了USB协议的基本架构和软件架构。本篇主要介绍USB描述符中的标准描述符,这是所有USB设备都要支持的描述符。
1 USB描述符概述
为了方便USB主机对USB设备进行管理,USB-IF对USB设备的功能采用了分层结构,包括设备层、配置层、接口层和端点层。图1给出了一个复合设备的例子,展示了USB设备的分层结构。
这四层的作用分别为:
- 设备层。说明USB设备的主要类型特征(如设备类别、接口、端点等属性),保障设备枚举过程的正常进行。
- 配置层。选择不同的失败配置满足USB主机对设备功能的选择,可选择复式的设备接口功能,如图1展示的选择鼠标、键盘和游戏杆的复合功能。
- 接口层。将具体功能分类,不同的功能对用不同的操作方式。
- 端点层。针对特定的设备功能,选择不同的端点,提供不同的数据管道,与USB主机进行数据通讯。
为了描述USB设备的这些特征,USB规范定义了相应结构的描述符,包括设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符等。表1给出了USB1.1下各种USB描述符的类型值。
表1 USB1.1的描述符类型值
类型 | 描述符 | 描述符值 |
标准描述符 | 设备描述符(Device Descriptor) | 0x01 |
配置描述符(Configuration Descriptor) | 0x02 | |
字符串描述符(String Descriptor) | 0x03 | |
接口描述符(Interface Descriptor) | 0x04 | |
端点描述符(Endpoint Descriptor) | 0x05 | |
类描述符 | 集线器类描述符(hub descriptor) | 0x29 |
人机接口类描述符(HID) | 0x21 | |
厂商自定义 | 0xFF |
其他版本的USB规范,还定义了其他类型的描述符,比如USB 2.0中的设备限定描述符(Device_Qualifier)、USB 3.2中的二进制设备对象存储描述符(Binary Device Object Store,简称BOS)等。这些内容在博客中不会涉及,可在USB-IF的官网下载相关的USB规范文档了解。
下面对标准描述符进行详细介绍。
2 USB标准描述符
不管哪种USB设备,都必须提供标准描述符,用于告知主机设备本身的属性。以下介绍的内容,会比较枯燥,建议使用Bus Hound或USB逻辑分析仪等工具,去抓取USB设备的识别和通信包,实际体会这些描述符的用法。
如图2,抓取的是自制的USB HID设备信息。设备描述符中,含有自定义的厂商ID和产品ID,分别为0x8765和0x4321。
USB主机会发送USB命令给USB设备,图2中的GET_DESCRIPTOR是常用的获取描述符命令。USB设备根据命令所要求的,给出对应的描述符。本篇主要讲述描述符的结构,USB命令将在后续的博客中讲述。
2.1 设备描述符
USB的设备描述符用于表示USB设备的一般信息,如制造商ID、产品序列号等。一个USB设备有且只有一个设备描述符,它是USB主机所读取的第一个描述符,其结构如表2所示。
表2 设备描述符的结构
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 描述符字节数长度(0x12) |
1 | bDescriptor | 1 | 常量 | 描述符的类型(0x01) |
2 | bcdUSB | 2 | BCD码 | USB设备支持的协议版本号 |
4 | bDeviceClass | 1 | 类 | 设备类代码 |
5 | bDeviceSubClass | 1 | 子类 | 子类代码,更加bDeviceClass来定 |
6 | bDevicePortocol | 1 | 协议 | 协议码 |
7 | bMaxPacketSize0 | 1 | 数字 | 端点0的最大包长度 |
8 | idVendor | 2 | ID | 厂商ID(由USB-IF赋值) |
10 | idProduct | 2 | ID | 产品ID(由厂商赋值) |
12 | bcdDevice | 2 | BCD码 | 设备发行号(BCD码) |
14 | iManufacturer | 1 | 索引 | 厂商信息的字符串描述符索引值 |
15 | iProduct | 1 | 索引 | 产品信息的字符串描述符索引值 |
16 | iSerialNumber | 1 | 索引 | 设备序列号信息的字符串描述符索引值 |
17 | bNumConfigurations | 1 | 数字 | 配置描述符数目 |
设备描述符结构中的bMaxPacketSize0,它用来告知USB主机设备所支持的最大数据长度。
bDeviceClass表示设备所述的类别,如果此值为0,则表示每一个配置中的每个接口来指明它所属的类别(即在接口描述符中给出设备类),并且各接口独立工作。如果此值为0xFF,则由供应商自定义该设备类。介于两者之间的值0x1~0xFE,表示USB规范中定义的某个设备类,比如0x03表示HID设备类。它和bDeviceSubClass、bDeviceProtocol共同规定了设备的类别和采用的协议,更具体的分类定义,可以参考USB-IF官网。
设备描述符中的iManufacturer、iProduct和iSerialNumber,使用字符串描述符的索引值来进行描述,索引值为0表示没有字符串描述符对其进行描述。通过此索引值和USB命令Get_Descriptor,可以得到对应的字符串描述符。
2.2 配置描述符
USB规范中,USB设备可以有一个或者多个配置描述符,每个配置描述符提供了设备特定的配置。在设备描述符中的bNumConfigurations提供了配置描述符的个数,任何时刻只有一种配置处于工作状态。
配置描述符中提供了在该配置下设备的接口数目,一个设备的不同配置描述符可能包含不同数目和特性的设备接口。图1中的设备配置1,就包含了鼠标功能接口和键盘功能接口两种接口。此外,配置描述符中还会描述设备的供电方式(自供电/总线供电)、最大耗电量等信息。其结构如表3所示。
表3 配置描述符的结构
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 描述符的字节数长度(0x09) |
1 | bDescriptorType | 1 | 常量 | 配置描述符的类型(0x02) |
2 | wTotalLength | 2 | BCD码 | 配置信息的总长(包括配置、接口、端点和设备类及厂商定义的描述符) |
4 | BnumInterfaces | 1 | 类 | 该设备所支持的接口数目 |
5 | bConfigurationValue | 1 | 子类 | 配置值 |
6 | iConfiguration | 1 | 协议 | 描述该配置的字符串描述符索引值 |
7 | bmAttributes | 1 | 数字 | 配置特性:D7:保留 D6:自供电 D5:远程唤醒 D4…0:保留 |
8 | MaxPower | 1 | 数字 | 该配置下所需最大总线电流(2mA为单位) |
2.3 接口描述符
USB设备的接口是端点的集合,负责完成该设备的特定功能,比如数据的输入和输出。接口描述符用来表示在USB设备中,各个接口的特性,包括接口的端点个数、所述的设备类和子类等。
拥有多个接口的USB设备,如果设备描述符中描述的bDeviceClass不为0,则表示接口之间是互斥关系,否则接口相互独立,每个接口有自己的类号、子类号和协议号。类号、子类号和协议号的定义,与设备描述符中的定义是一致的。接口描述符的结构如表4所示。
表4 接口描述符的结构
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 描述符的字节数长度(0x09) |
1 | bDescriptorType | 1 | 常量 | 接口描述符的类型(0x04) |
2 | bInterfaceNumber | 1 | 数字 | 接口号(从0开始) |
3 | bAlternateSetting | 1 | 数字 | 可选设置的索引值 |
4 | bNumEndpoint | 1 | 数字 | 此接口的端点数量(不计默认端点0) |
5 | bInterfaceClass | 1 | 类 | 接口所属的类值 |
6 | bInterfaceSubClass | 1 | 子类 | 接口所属子类的值 |
7 | bInterfaceProtocol | 1 | 协议 | 协议码,根据上面的两个值而定 |
8 | iInterface | 1 | 索引 | 表示此接口的字符串描述符的索引值 |
2.4 端点描述符
USB规范中,端点描述符用于指出USB设备端点的特性,包括其所支持的传输类型、传输方向等。端点0没有端点描述符,其他端点必须包含端点描述符。
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0为双向外,其他均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小,以及端点地址,其结构如表5所示。
表5 端点描述符的结构
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | 数字 | 描述符的字节数长度(0x07) |
1 | bDescriptorType | 1 | 常量 | 端点描述符的类型(0x05) |
2 | bEndPointAddress | 1 | 端点 | 描述了端点的地址、方向 Bit3…0:端点号 Bit6…4:保留,为0 Bit7:传输方向,如果是控制端点则忽略 0:输出端点(主机到设备) 1:输入端点(设备到主机) |
3 | bmAttributes | 1 | 位图 | 端点传输类型 Bit1…0:传送类型 00B=控制传送 01B=实时传送 10B=批量传送 11B=中断传送 |
4 | wMaxPacketSize | 2 | 数字 | 接收/发送的最大数据包长度 |
6 | bInterval | 1 | 数字 | 周期数据传输端点的时间间隙 |
2.5 字符串描述符
字符串描述符是可选的,它描述了制造商、设备名称或序列号等信息。它使用的是Unicode编码,并支持多语言。USB主机要求获得字符串描述符时,需要用一个16位的语言标识出语言类别。比如,常用的语言标识1033表示美国英语,而2052表示中文。其他的语言标识,可以在微软的网站上找到 。
USB主机请求得到某个字符串描述符时分为两步,首先向USB设备发送USB命令Get_Descriptor,命令的wIndex字段设置为0,设备将返回描述语言标识的字符串描述符;然后,USB主机根据需要的语言,向USB设备发送命令Get_Descriptor,在命令对应的字段中设置字符串的索引值和语言标识,得到需要的字符串描述符。
字符串描述符有两种格式。第一种用来指明所用的语言标识,如表6所示。
表6 指明语言标识的字符串描述符
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | N+2 | 描述符的字节数长度(N+2字节) |
1 | bDescriptorType | 1 | 常量 | 字符串描述符的类型(0x03) |
2 | wLANGID[0] | 2 | 数字 | 语言标识(LANGID),码0 |
… | … | … | … | … |
N | wLANGID[x] | 2 | 数字 | 语言标识(LANGID),码x |
第二种为Unicode字符串描述符,包含了非NULL结尾的Unicode字符串,如表7所示。
表7 Unicode字符串描述符
偏移 | 域 | 大小 | 值 | 描述 |
---|---|---|---|---|
0 | bLength | 1 | N+2 | 描述符的字节数长度(N+2字节) |
1 | bDescriptorType | 1 | 常量 | 字符串描述符的类型(0x03) |
2 | bString | N | 数字 | Unicode编码的字符串 |
在实际编写代码中,接口描述符、端点描述符一般是和配置描述符在同一数组中的。后续代码编写时,我们再来看实际的情况。
本篇篇幅较长,也许是目前写的最长的博客了。主要是为了后续查询方便,将标准描述符相关的细节都包含了。
下一篇讨论USB的HID类描述符。
1,327 total views, 1 views today