USB鼠标的报告描述符详解

上一篇文章提到了如何用STM32CubeMX自动生成一个USB鼠标的过程,其中包括比较复杂的报告描述符。这里,我们以USB鼠标的报告描述符为例,说一下报告描述符的结构和含义。

首先要说明的一点,报告描述符和设备描述符、配置描述符、接口描述符等的结构完全不一样,不能以后者的架构去解读。同时,报告描述符的复杂程度也很高,它涉及到了很多的定义:长条目、短条目、主条目、输入条目、输出条目、开集合、关集合、全局条目、局部条目等等,有的下面还包含其它的内容。所以,短时间是不可能全部理解。

我这里也不想写太多,毕竟写多了没人愿意看。我想换一种方式来描述,什么方式?

以目标为导向!

前期,我们先不考虑它包含的这些定义的含义,我们只需要知道它的目的是什么?

报告描述符!自然是用来描述报告的!这个报告又是什么?前一篇文章已经提到了,报告是一组数据,由USB设备发给USB主机,用来报告当前鼠标进行了哪些操作!

所以,我们可以知道,USB鼠标,把自己的动作以报告的形式发送给USB主机(也就是电脑),而电脑要解析这个报告,依据就是报告描述符!

明白了这一点,我们再来看报告描述符,就简单多了。

首先,看一个手册(Device Class Definition for Human Interface Devices (HID))里的截图,对报告描述符的解析流程有一个初步的认识。

感谢有道词典的赞助,这段话的意思是:

来自控件的一个或多个数据域由一个主项定义,并由前面的全局项和局部项进一步描述。本地项只描述由下一个主项定义的数据字段。全局项成为该描述符中所有后续数据字段的默认属性。

看不太懂没关系,继续。猜一下上面的那几行代码是什么意思,那就是报告描述符的一部分。直接看结果:

报告描述符里的数据,是以bit为单位进行描述的。

Report Size (3)就表示一组有3个bit,紧接着的一行:Report Count (2),就表示这样的组有2个。

Input,表示这两组数据是输入的(在USB协议中,输入、输出是相对USB主机来说的)。

Report Size (8),表示一组有8个bit,也就是一个字节的数据。

Input,数据输入。没有说这样的数据有几组,那么小组的个数就沿用之前的,还是2组。

Output,数据输出。小组大小、小组数量同上。

通过这种方式解释,是不是瞬间明白了报告描述符的解析思路?

接下来我们直接看USB鼠标的报告描述符,但看的不是STM32里提供的,因为它没有提供注释,看起来太痛苦,这里看报告描述符工具里自带的例程:

导出后,生成.h文件,内容如下:

char ReportDescriptor[50] = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x75, 0x05,                    //     REPORT_SIZE (5)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x06,                    //     INPUT (Data,Var,Rel)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};

其实,相比.h文件,工具里的描述符格式更好理解。因为工具里通过空格的多少,表明了哪些数据是一组。我们大致可以知道这些描述符的功能:

	用途页-桌面通用
	用途-鼠标
	开集合-应用
		用途-指针
		开集合-物理
			用途页-按键
			用途最小值-鼠标左键
			用途最大值-鼠标中键
			逻辑最小值-0:按键抬起
			逻辑最大值-1:按键按下
			报告数量-3:3个按键
			报告小组-1:1组
			数据输入:可变,独立,绝对值
			报告数量:1
			报告小组:5,为了和前面的3bit凑成一个字节
			数据输入:常量,独立,绝对值
			用途页-桌面通用
			用途:X轴
			用途:Y轴
			逻辑最小值:-127	
			逻辑最大值:127,表示鼠标的左右、上下移动
			报告数量:8
			报告小组:2,X一个,Y一个,刚好两个
			数据输入:可变、独立、相对值
		关集合
       关集合

有些报告描述符里还会在X、Y之后再加一个用途,就是滚轮。

总之,报告描述符虽然复杂一些,但结合这些实例以及相关文档,理解起来不是特别难。先说到这里,如果后面找到更简单的方法,再来更新!

我是单片机爱好者,MCU起航!

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据