USB学习【10】描述符-HID描述符

发布于:2025-05-15 ⋅ 阅读:(9) ⋅ 点赞:(0)

1.前言

HID描述符功能上面相对独立一些,所以单分一篇专门整理。
原文链接:https://blog.csdn.net/weiaipan1314/article/details/112504129

2.HID描述符概述

USB设备中有一大类就是HID设备,即Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘、游戏手柄等,主要用于人与计算机进行交互。HID设备可以作为低速、 全速、高速设备用。由于HID设备要求用户输入能得到及时响应,故其传输方式通常采用中断传输。

在USB协议中,HID设备的类型定义放置在接口描述符中,USB的设备描述符和配置描述符中不包含HID设备的信息。因此,对于某些特定的HID设备,可以定义多个接口,一个接口为HID设备类即可。
当USB一个设备为HID设备时:

  1. 设备描述符中:
设备描述符 数值 备注
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
  1. 具体的定义在接口描述符中:

阅读源文档发现,这里面还有一个USB发展的渊源,一开始USB是打算用子类一条条的罗列各种用途的,比如,键盘,鼠标,音响,游戏杆等等,但是人类太喜欢玩花活了,仅仅是键盘都能组装成实际上百种不同的设备,这个不可能穷尽呀,所以HID报告描述符诞生了,哎,你尽管自己玩花活,只要你用我给你的标准,随便组装,你写个说明书,到时候我把你的组装拆成标准件,分发给各自的驱动即可。
所以binterfaceClass,bInterfaceSubClass已经不是当初的含义了,只遗留了是不是在BIOS环境运行呀之类的残余功能了,其他的全部交给描述符就OK了。

接口描述符 数值 备注
bInterfaceClass 0x03 表示HID为设备
bInterfaceSubClass 0x00
0x01
对无需支持引导的HID设备
对支持引导的USB设备(鼠标、键盘),BIOS环境可使用的意思
bInterfaceProtocol
当bInterfaceSubClass为0x00
此值无效
bInterfaceProtocol
当bInterfaceSubClass为0x01
设置为0x01:键盘接口
设置为0x02:鼠标接口

在这里插入图片描述

3.HID描述符组成

当一个USB设备被定义为HID设备的时候,设备必须实现HID描述符。首先要明确的一点是HID描述符不能单独返回给USB主机,主机会请求获得配置描述符集合,配置描述符集合主要由标准配置描述符、接口描述符、HID描述符、端点描述符,报告描述符和物理描述符是单独返回给USB主机。HID描述符组成如下:
在这里插入图片描述

1.bLength

描述符长度。HID描述符长度不是固定的,长度多少与描述符中包含的下级描述符个数相关。如果只有一个下级描述符,也就是不包括可选的部分,一共有9字节的长度,如果有多个下级描述符,按照长度往上加。

2.bDescriptorType:描述符类型,设置为0x21。

3.bcdHID:HID设备所遵循的HID版本号,为4位16进制的BCD码。1.0即0x0100,1.1即0x0101,2.0即0x0200。

4.bCountryCode:HID设备国家/地区代码,如下表格自行查询。
在这里插入图片描述
5.bNumDescriptor:HID设备支持的下级描述符的数量。在这里大家一定要注意,下一级描述符的类型有两种,报告描述符和物理描述符,对于HID设备报告描述符和物理描述符可以有多个,但是至少有一个报告描述符,物理描述符是可选的,bNumDescriptor表示报告描述符和物理描述符的个数总和。由于HID设备至少需要包括一个报告描述符,故其值至小为0x01,一般的HID设备也为1,也就是有一个报告描述符,物理描述符很少用到。
6.bDescriptorTyep
在这里插入图片描述
7.wDescriptorLength
下级描述符的长度,下级描述符第1个必须是报告描述符,所以这里存放报告描述符的长度,每种HID设备的报告描述符长度是不一样的,比如STM32某HID设备的报告描述符长度为43字节,代码如下:

//
// 报告描述符
//
const unsigned char ReportDesc[0x2b] =   // Report descriptor
	{
	0x05,0x01,		/* Usage Page (generic desktop) */
	0x09,0x06,		/* Usage (keyboard) */
	0xA1,0x01,		/* Collection */
	0x05,0x07,		/*   Usage Page 7 (keyboard/keypad) */
	0x19,0xE0,		/*   Usage Minimum = 224 */
	0x29,0xE7,		/*   Usage Maximum = 231 */
	0x15,0x00,		/*   Logical Minimum = 0 */
	0x25,0x01,		/*   Logical Maximum = 1 */
	0x75,0x01,		/*   Report Size = 1 */
	0x95,0x08,		/*   Report Count = 8 */
	0x81,0x02,		/*  Input(Data,Variable,Absolute) */
	0x95,0x01,		/*   Report Count = 1 */
	0x75,0x08,		/*   Report Size = 8 */
	0x81,0x01,		/*  Input(Constant) */
	0x19,0x00,		/*   Usage Minimum = 0 */
	0x29,0x65,		/*   Usage Maximum = 101 */
	0x15,0x00,		/*   Logical Minimum = 0 */
	0x25,0x65,		/*   Logical Maximum = 101 */
	0x75,0x08,		/*   Report Size = 8 */
	0x95,0x01,		/*   Report Count = 1 */
	0x81,0x00,		/*  Input(Data,Variable,Array) */
	0xC0};			/* End Collection */
#endif

4.报告描述符的概念和作用

报告描述符就是描述报告(HID接口上传输事务中的数据)的说明书,也是一种数据结构。
首先为什么要有这个复杂的东东存在?其实这里的复杂就是为了大局的简化。
一开始USB协议是打算把世界上所有的HID类型都罗列出来,你把你选的设备类型告诉我,我给你安排对应的驱动,最后发现天赋型选手能把设备玩出花来,即使能穷尽所有的可能性,嗯,我认为不可能穷尽,毕竟天赋型选手的能力很恐怖。

举一个例子,一开始我打算键盘设置一个类型,鼠标一个类型,你告诉我这是一个键盘或者鼠标,我来安排驱动,结果你发现了,现在的键盘五花八门,有鼠标键盘一体的,有鼠标键盘带摇杆的,还有鼠标带很多按键的,一一列举不太可能,比如有一个人鼠标带10000个按键,你不可能把鼠标+1按键,鼠标+2按键。。。。一一罗列出来吧?
所以想了一个办法,我把所有的基础类型分离出来,键盘一列,鼠标一列,按钮一类,你任意组装,组装给我说明书,我按照说明书分解,调用不同的驱动就好啦。
要满足这个要求,就要发明一个数据描述语言,报告描述符。

主机在对HID设备进行枚举的时候,会拿到报告描述符,之后给设备发送数据或者接收到设备数据会按照报告描述符对数据的每一位进行解析。

报告描述符如果全面讲解可以看hid1_11.pdf和hut1_21_0.pdf

5.报告描述数据语言的结构

很多高手,都会把通用性,全局项,主项的功能一个个罗列出来,其实这个都是最后实现的部分而不是构思的部分。
就像解题一样,你总要先把解题思路搞清楚,在去写步骤吧,先把步骤学明白了,没有思路去框架,会越来越乱的。

下面就是框架:

5.报告描述符中的通用项(Item)

报告描述符是由一个一个通用项组成,每一个通用项可以描述一个或者多个相同功能的数据(比如同时描述8个按钮),包括数据的用途和各种属性。

item有两种类型,Short Item和Long Item,结构组成如下,一般我们接触到的都是Short Item。

在 Short Item 中,起始的 1 Byte 代表了这个 Item 的大小和类型,如下:
在这里插入图片描述
bSize :代表后面的数据,最大 4 bytes。

bType :代表了这个 Item 是什么类型的 Item,这里有三种大的类型:Main(0x00)、Global(0x01)、Local(0x02)。

bTag :代表在对应的 Item 下的更加细的分类,也可以称之为标签。
在这里插入图片描述

对于 bSize + bType + bTag 的 1 Byte 的组合详见下表:
在这里插入图片描述

4.Main、Global、Local标签功能
非常核心的核心: 在阅读 Report 的时候,都是先用Global和Local标签说明数据项的各种相关属性,然后最后用一个Main标签进行输入输出说明,Main标签的出现就是一个数据项的结束。

前面提到通用项主要分为Main、Global、Local,大家肯定会问为什么要分类,他们三个到底是用来做什么的,下面一一讲解。
Main类标签 :主要用来对数据项的描述(Input、Output、Feature)和管理(Collection、End Collection)。

  • 描述数据项:比如一个这个数据项到底是主机输入还是输出;

  • 管理数据项:比如把多个数据项组织到一起。

Global类标签:对数据项的描述,这个主要用来描述数据项的用途、数据逻辑范围、数据物理范围、数据大小、数据个数,它的用法和我们平时看到的全局变量特别像,当定义之后,之后的所有数据项都是使用定义的属性,除非再此定义,如下详细说明。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/21e151f241304962989c22cf08c6afcb.png = =x300)
Local类标签:对数据项的描述,和Global标签的作用类似,它的用法和我们平时看到的局部变量特别像,就是作用于不一样,只在本数据内项生效。
在这里插入图片描述
5.核心标签功能
1.Usage Page和Usage

用于描述数据项的用途,这个标签非常复杂,当UsagePage变化时Usage子功能也会跟着变化,下面会详细讲解。​也可以阅读文档“HID Usage Tables”获得更多使用细节,大家可以关注我,然后点击下载获取本书电子版,也可以去USB IF官网下载。

2.Logical Maximum

描述数据逻辑单元中的范围值。这是变量或数组项将要报告的最大值。例如设备报告的一个电压值读数是1V,而一个单位是 2mV ,则 Logical Maximum 值等于 500 。

3.Logical Minimum

描述数据逻辑单元中的范围值。这是变量或数组项将要报告的最小值。

4.Physical Maximum

描述数据真实的最大值,比如电压最大为2V,一个单位是 2mV ,则该值最大仍是2000mV,表示物理真实值。

5.Physical Minimum

描述数据真实的最小值。

6.Report Size

以位(bit)为单位指定数据的大小,无符号整数。

7.Report Count

无符号整数,指定总共需要多少个同类型的数据。

8.Input

此项数据为设备到主机的。

9.Output

此项数据为主机到设备的。

10.Report ID

报告的ID,一个报告描述符可以描述多个HID功能,主机通过报告的ID就知道是哪个HID功能硬件发送的数据。举个例子,如果一个USB HID设备为键鼠一体的设备,就可以通过Report ID把键盘和鼠标的数据描述分开,这样可以实现面向对象管理,其实就是相当于实现了多个报告,设备在发送数据的时候,第一个字节永远是Report ID,如果是鼠标发送事务第一个字节(Report ID)为1,如果是键盘发送事务第一个字节(Report ID)则为2,这样分开描述数据其实也就是分开了事务功能本身,如果一个很复杂的HID设备在设计的思路上可以分治而行,可以大大减小设计难度。

11.Collection

用于对数据描述项进行组织和管理,和End Collection一起使用

6.STM32某鼠标设备报告描述符详细说明
在这里插入图片描述

上面的报告描述符实现一个鼠标设备,主要有3个数据描述项组成。描述符总共描述了4个字节的报告(数据),数据描述项1描述了数据的第1个字节前3bit,代表鼠标的3个按钮,数据描述项2为了描述数据对齐操作,数据描述项3描述了数据的第2-4字节,代表鼠标的左右移动、上下移动、滚轮滚动的数据,这4个字节都是设备到主机的数据。其他的细节在注释里面讲解的很清楚。

假设设备给主机发送了当前最新4个字节的数据(报告),先低字节后高字节:0x01 0x00 0x00 0x00,主机会解析到鼠标左键被按下,鼠标没有移动,滑轮也没滚动,就是这样子理解的。
7.报告描述符用途查询
报告描述符Usage page和Usage可以通过hut1_21_0.pdf(HID Usage Tables )文档查询,首先每一个Usage Pages下面都包含多种Usage,下面会说明所有的Usage Pages,每一个Usage Pase下面的Usage大家可以直接点击查看。

在这里插入图片描述
我们可以点击Generic Desktop Page (0x01)去查询下面更具体的Usage,如下:
在这里插入图片描述
8.报告描述符生成工具介绍
我们可以用HID Descriptor Tool生成报告描述符,也可以直接打开(open file)一些标准设备的报告描述符进行学习,工具如下图,大家可以根据自己的硬件定义自己的报告描述符。
在这里插入图片描述
在这里插入图片描述