USB固件开发(HID设备)
1. HID设备的识别
HID设备类除了有文档第一部分所述的一些标准描述符(包括设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符)外,还有自己的类专有描述符:
HID描述符
报告描述符
物理描述符
正确实现HID设备类专用描述符是主机成功识别HID设备的关键。HID描述符和报告描述符是必须要使用的,物理描述符一般不被使用。
1.1 HID描述符
HID描述符跟接口描述符、端点描述符类似,也是随配置信息一起返回给主机的,主机并不会单独发出请求来读取它。HID描述符在配置信息中的位置是紧接接口描述符。例如:
_Config_Descriptor:
.dw _Config_Descriptor_End-_Config_Descriptor //bLength: 0x09 byte
.dw 0x02 //bDescriptorType: CONFIGURATION
.dw _Config_Descriptor_Total-_Config_Descriptor //wTotalLength:
.dw 0x00
.dw 0x01 //bNumInterfaces: 1 interfaces
.dw 0x01 //bConfigurationValue: configuration 1
.dw 0x00 //iConfiguration: index of string
.dw 0xC0 //bmAttributes: self powered, Not Support Remote-Wakeup
.dw 0x32 //MaxPower: 100 mA
_Config_Descriptor_End:
_HID_Interface_Descriptor:
//Interface 1 (0x09 byte)
.dw 0x09 //bLength: 0x09 byte
.dw 0x04 //bDescriptorType: INTERFACE
.dw 0x01 //bInterfaceNumber: interface 0
.dw 0x00 //bAlternateSetting: alternate setting 0
.dw 0x01 //bNumEndpoints: 1 endpoints(EP1)
.dw 0x03 //bInterfaceClass: 人机接口设备(HID)类
.dw 0xff //bInterfaceSubClass: 供应商定义
.dw 0xff //bInterfaceProtocol 使用的协议:供应商定义
.dw 0x00 //iInterface: index of string
_HID_Interface_Descriptor_End:
_HID_Descriptor:
.dw 0x09 //bLength: 0x09 byte
.dw 0x21 //bDescriptorType: HID描述符类型编号
.dw 0x01, 0x10 //HID类协议版本号,为1.1
.dw 0x21 //固件的国家地区代号,0x21为美国
.dw 0x01 //下级描述符的数量
.dw 0x22 //下级描述符为报告描述符
.dw _ReportDescriptor_End-_ReportDescriptor, 0x00 //下级描述符的长度
_HID_Descriptor_End:
_Endpoint3:
.dw 0x07 //bLength: 0x07 byte
.dw 0x05 //bDescriptorType: ENDPOINT
.dw 0x83 //bEndpointAddress: IN endpoint 3
.dw 0x03 //bmAttributes: Interrupt
.dw 0x02, 0x00 //wMaxPacketSize: 2 byte
.dw 0x0A //bInterval: polling interval is 10 ms
_Config_Descriptor_Total:
HID描述符其实是为了提供下级描述符(如报告描述符)的信息。
下图更清楚地表述了各描述符之间的层次关系。
1.2 报告描述符
要解释报告描述符,首先得清楚什么是“报告”。“报告”是主机和HID设备之间进行数据交换的最小单位。也就是说,在主机完成对设备的识别之后,在具体应用上的数据交换就得以“报告”的方式进行。“报告”的类型有三种:输入报告、输出报告和特征报告。输入报告就是设备发给主机的报告,而输出报告就是主机发给设备的报告,特征报告是主机发给设备的报告,特征报告常在自定义HID设备中被用作主机向设备发送自定义数据。
报告描述符,顾名思义就是描述“报告”格式的,这个格式使主机和设备能遵循着同一个规则来解释一个报告中所含有的数据。与HID描述符不同,主机会发出单独的请求来读取报告描述符。关于报告描述符的组成,HID设备类定义文档中明确指出,一个报告描述符必须包含但不仅限于以下数据项:
输入(输出或特征)
用法(也可用“用法最小值与最大值”来定义一连串用法)
用法页
逻辑最小值
逻辑最大值
报告大小
报告计数
报告描述符看起来比较复杂,无论是HID设备类定义文档,还是其他参考书籍,都会花较大的篇幅来阐述它。要把它完全理解是需要一点时间的,而且就算是理解了也不一定能写出“像样”的报告描述符来。学习总有一个过程,入门才是最重要的,只要入了门,后面的事情就会慢慢变得简单,无需在一开始的时候就面面俱到。所以这里只对上面提到的必需的数据项进行解释及举例说明。
输入项(输出或特征)指明了报告的类型,其中隐含了报告的传输方向以及报告数据所具有的数学特性。
用法和用法页一起指明了数据项的用法,每个数据项都必须指明用法,否则主机端不能成功解析报告描述符。用法页是全局的,修饰列于其后的所有数据项,直到出现新的用法页为止;用法则是局部数据项,局部数据项只修饰列于其后的第一个主数据项内的数据项,一旦出现新的主数据项,那么用法必须重新指定。这其中隐含的意思是,每个主数据项前面都必须有修饰它的用法与用法页组合。(“用法”表示的是一个单独的用法,而“用法最小值”和“用法最大值”可以替代“用法”,代表某个范围的用法。)
逻辑最小值和逻辑最大值指明了报告所使用的数据值的范围,这个数据值是以逻辑单位为基础的,与报告大小有着对应关系。
报告大小指明数据项的位数。报告计数指明有多少个这样的数据项。
例如,定义以下数据项:
逻辑最小值(0)
逻辑最大值(0x7f)
报告大小(8)
那么它的意思就是,此报告中数据字段的大小是8位,本身可以表示0~255之间的任何数,但是逻辑值的范围被定义在0~127之间,所以实际上数据字段的数据不能超过127,否则视为无效报告。
再举一个例子:
逻辑最小值(0)
逻辑最大值(3)
报告大小(2)
这个例子的意思是,此报告中数据字段的大小是2位,逻辑值范围是0~3,那么数据字段的值与逻辑值是一一对应且相等的,即0(00b),1(01b),2(10b),3(11b)。
第三个例子:
再举一个例子:
逻辑最小值(-1)
逻辑最大值(1)
报告大小(2)
这个例子的意思是,此报告中数据字段的大小是3位,逻辑值范围是-1~1,那么数据字段的值与逻辑值是按左对齐的方式部分对应的,即数据字段值0(00b)对应逻辑值-1,数据字段值1(01b)对应逻辑值0,数据字段值2(10b)对应逻辑值1,数据字段值3(11b)无效。
这里举一个HID自定义设备的报告描述符的例子,这个例子比鼠标和键盘更简单。更具体的内容,譬如常用的鼠标和键盘,可以参看官方文档Device Class Definition for Human Interface Devices(HID).pdf 和HID Usage Tables.pdf。
_ReportDescriptor: //报告描述符
.dw 0x06, 0x00, 0xff //用法页,供应商自定义,修饰其下所有的主项
.dw 0x09, 0x01 //用法(供应商用法1),局部项,只修饰下面的“集合”主项。
.dw 0xa1, 0x01 //集合开始,主项
.dw 0x85, 0x1 //报告ID(1),全局项,可以修饰其下所有的主项,但是在这个报告描述中由于后面出现了新的报告ID,所以它只是修饰下面的“输入”主项。
.dw 0x9, 0x1 //用法(供应商用法1)
.dw 0x15, 0x0 //逻辑最小值(0),全局项,修饰下面所有的主项
.dw 0x26, 0xff, 0x0 //逻辑最大值(255),全局项,修饰下面所有的主项
.dw 0x75, 0x8 //报告大小(8),全局项,修饰下面所有的主项
.dw 0x95, 0x7 //报告计数(7),全局项,修饰下面所有的主项
.dw 0x81, 0x6 //输入(数据,变量,相对值),主项,说明此报告的属性
//下面开始一个新的主项目,前面提到的全局项仍对这个主项目有效,譬如报告大小等
.dw 0x09, 0x01 //用法(供应商用法1) ,局部项,修饰下面的“特征” 主项
.dw 0x85, 0x03 //报告ID(3),全局项,之前的报告ID项失效
.dw 0xb1, 0x6 //特征(数据,变量,相对值)
//下面开始一个新的主项目,前面提到的全局项仍对这个主项目有效,譬如报告大小等
.dw 0x09, 0x01 //用法(供应商用法1) ,局部项,修饰下面的“特征” 主项
.dw 0x85, 0x02 //报告ID(2),全局项,之前的报告ID项失效
.dw 0xb1, 0x06 //特征(数据,变量,相对值)
//下面开始一个新的主项目,前面提到的全局项仍对这个主项目有效,譬如报告大小等
.dw 0x09, 0x01 //用法(供应商用法1) ,局部项,修饰下面的“输出” 主项
.dw 0x85, 0x04 //报告ID(4),全局项,之前的报告ID项失效
.dw 0x91, 0x6 //输出(数据,变量,相对值)
.dw 0xc0 //结合结束
_ReportDescriptor_End:
以上描述符定义了4个不同的报告,用报告ID区分。HID设备定义文档上有讲,在一个报告ID之后而在下一个报告ID之前范围内的所有数据项都属于一个报告,发送报告时会把报告ID附在这个报告的前面义区分报告。
4. Windows HID编程接口
一般使用WriteFile或HidD_SetFeature来向设备发送数据(报告),使用ReadFile来读取设备发过来的数据(报告)。详情可以参考另一文章《Windows主机端与自定义USB HID设备通信详解》。