用途设计思想
如果我们的设备,将自己枚举成HID设备以后,那么我们的HID描述符是必不可少的,报告描述符也是必需的。
报告描述符就是对传输数据的用途与格式的具体定义,他是拥有属于自己的一个格式定义。
报告描述符是以标签的方式排列而成,长度不定,后面跟随的括号中为该标签携带的数据。
下面是鼠标的报告描述符
上述是报告描述符的伪代码,我们实际运行的代码还是编译后的二进制数据。
我们可以使用USB-IF提供的HID描述符编辑工具(HID Descripor Tool)快速进行报告描述符的设计与转换。如下图,与上面是一样的。
最重要的,就是要理解这个报告描述符定义了一个怎样的数据结构,那么就要搞明白这个标签的的作用。如下图 他们可以分成 主标签 全局标签 局部标签。
我们在这个所有的标签中,Usage Page 与 Usage通常是报告描述符最先使用的的两个标签,也是最复杂的。这里我们重点讲一下这两个标签,一定要搞懂。
Usage Page 与 Usage标签的目的就是用来标识数据的类型,为操作系统正确处理数据提供依据
Usage Page就是一个大类,在他底下包含了许多子类(Usage)也就是具体用途,比如细分到键盘,鼠标。
如下图就是Usage Page 我们定义了许多大类(用途页)在他们左边都是对应得Page ID号
我们使用的ST官方例程是游戏操作杆 他是属于Generic Desktop Controls(通用桌面)
在我们的Generic Desktop Controls(通用桌面)下又定义了他的具体一些用途
每一个用途都也有一个Usage ID 他和 Page ID组成了一个32位数据(4字节),他们工作决定了主机怎样处理接收到的数据。但是,这个鼠标 键盘 具体在什么情况下 又能怎么用呢,这时候我们要理解上图中的用途类型(Usage Type)。
用途 又分成控制(Controls)数据(Data)集合(Collections) 这三个类别 。
我们这个集合 就是 相等于C语言中的花括号,用来管理 控制和数据类用途的。
我们先来说一下这个 控制类用途
就是我们的设备会发送一些数据,这些数据代表什么意思,首先就是我们的事件是否发生(例如我们的音量按钮是否按下了),然后就是某个条件是否满足(例如我们的电源开关是否打开),或者控制程度多少(音量能具体调到多大),
反正这个这个控制类用途,就是教我们设备怎么做事,触发什么样的事件,到时候我们的主机也会因为这个用途知道我们设备触发了什么事件。
逻辑上我们有五种控制:1. 线性控制 2. 开/关控制 3. 瞬间控制 4.单次控制 5.重触发控制
1.线性控制 例如我们的音量调节就是这个,
2.开关控制 不知道怎么说 ,解释起来有点绕,
第一种就是 我们至少传输两位数据,主机收到 “0” 表示开关没有变化,数据“1”表示开关S1处于开的状态,数据“-1”表示开关S2处于“关”状态。
第二种就是 我们只要发送一位数据就可以了就是主机需要连续收到“0”与“1”,就是按下又松开的过程
第三种就是“1”和“0”分别代表开和关 是一种绝对状态的表达方式。
3.瞬间控制 比如鼠标的轻触按键 低电平代表空闲 高电平代表有效 是一种绝对状态的表达方式
4.单次控制 从逻辑上来说 这种控制装置在触发某个事件后必须退回到原来的状态,否则就不会触发的。
5.重触发控制 就是按下就说明事件发生 ,如果一直按下就说明 一直在触发事件
比如我们的鼠标 给主机发送的数据都是 “1”与“0” 他们本身是没有任何意义的,但是附加上一个控制类用途后就不一样了,高电平按下就是“1”,低电平松开就是“0”,如果只发送一个1 我们的电脑就认为你一直在按下的状态(因为瞬间控制是优选状态 这就意味着对应按键在没有被按下时会恢复到原来的状态---弹起),而这个弹起的状态也要发送数据“0”,让主机知道。
然后就是数据类用途
我们一个报告描述符不管有多复杂,我们的系统看到的总是一个或多个报告,而每个报告都包含了了一些数据字段,这些数据字段又是若干个位或字节,他们都会对应一定的用途,多个用途又对应多个数据字段。
这些数据字段都是 Input,Output,Feature标签生成的
最重要的就是一个作用域的概念:
我们的全局与局部标签就相当于我们上述球体的各种属性,我们的 Input,Output,Feature标签就是用来生成这些球体(数据字段)。
当生产第一种球体时,所有参数已经确定,当我们仅改变颜色的时候之前的那些参数都还是有效的(作用域还在),那么这种参数就是属于全局标签。
如果我们每制造一个球体前必须设置某个参数(不然这个参数没办法生产),那么这个参数就是属于局部标签,
举个例子吧,如下图
首先我们要明白 Report Size和Report Count都是全局标签,他们的作用域可以一直存在的,除非再次出现Report Size和Report Count将他们改变。
Report Size设置了数据字段的大小,Report Count标签则表达了数据字段的数量
第一个Input表示生成了两个三位的数据字段。传输方向设备到主机 输入报告
第二个Input表示生成了两个八位的数据字段。传输方向设备到主机 输入报告
第三个Output表示生成一个了 两个八位的数据字段。 传输方向主机到设备 输出报告
报告描述符描述的数据字段都是从低字节的最低位开始的,所有的字节必须字节对齐
如下图 Z 字型。
假设我们想要定义四个数据字段,并且我们各个数据字段的大小都是两位,那么他们实际占用的只是一个字节,并不是我们想象的四字节。
我们一个完整的报告描述符必须至少包括用途(Usage Page Usage),逻辑值范围(Logical Minimum Logical Maximum),报告大小(Report Size),数量(Report Count)以及至少一个主标签(Input Output Feature),其他标签都是可选的。
Input Output Feature都是用来生成数据字段 其他都是修饰他们的。
Input 输入设备 从设备到主机
Output 输出设备 从主机到设备
Feature 是用来定义特征报告的
下面我们看个音量调节的描述符
一开始我们确定了他的用途页和用途(也就是先定义了他是多媒体的东西,后面缩小到他是一个调节音量的东西)
然后使用Logical Minimum 和 Logical Maximum 标签定义了数据的逻辑范围是-1到1:-1 0 1
-1 代表音量减少 0 表示音量没有调节 1表示音量增加
然后使用Report Size和Report Count定义了1个两位的数据字段(11b代表-1 00b代表0 01b代表+1)
最后使用Input标签生成数据字段
在这个途中我们一共使用了9位表示了数据字段的一些特征
第0位 用来描述数据字段是可变值(Data)还是固定值(Constant)
固定值(Constant)一般用来Feature报告或用于位填充 用于数据字段按照字节对齐
我们上面是音量调节装置 ,数据属于可变值,所以应该为Data。
第一位 Array 表示我们的数据字段可以由多个不同操作的其中一个触发,Variable表示我们每个字段就代表一个操作。
如果设置成 Variable 那么我们Report Count标签指定的数量就等于报告的字段数量
如果设置成Array,那么我们Report Count表示可以同时被触发的操作数量。
简单举个例子:
现在我们有一个设备,他有六个开关控制,我们可以生成六个一位的数据字段来代表(Report Size = 1,Report Count = 6),如果我们用Variable定义,那么这六个数据就是各代表一个开关的状态,但是我们要是成Array,他这六个数据,就不是一一对应的,,他表达的就是“最多返回六个有效数据”,比如我们的键盘,就允许一次性给主机发送最多六个有效的按键,它们可以是这几十个按键的任意六个,其实我们这里音量调节可以使用Array,但是没必要定义成Variable就行。数据字段可以是一位也可以一字节,也可以是多字节。
第二位表示数据字段是绝对值(Absolute)还是相对值(Relative),我们那个-1 0 1逻辑是相对值,上面开关第三个 的0 1 才是绝对值。所以音量调节是 Relative
第三位表示数据字段的数据达到极大值会返回到极小值(Wrap),还是保持极值(No Wrap),我们音量调节,达到最大就不会变了,所以这里设置成No Wrap。
第四位数据字段与操作刻度之间是线性关系还是非线性关系,这里我们设备成线性关系就行。
第五位表示数据字段代表的操作在不被触发的时候会自动恢复到初始状态(PreferredState),还是不会恢复到初始状态(No Preferred),这里的音量调节是会恢复到初始状态,所以设置成PreferredState。
第六位表示我们的控制设备是否存在发送代表没有意义状态的数据,例如,我们定义一位数据代表开关的控制状态(“0”代表“关”,1代表“开”),而开关不会再有其他的值,我们可以定义成No Null Position。比如我们键盘有104个按键,我们可以定义最小值和最大值逻辑值来代表这些按键,而不是每个按键都要有定义,在0~103分别代表104个按键事件,我们在没有按键按下的时候,就发送一个“不在定义范围”的数据,这种情况应该设置成Null State。
第七位表示特征数据不允许别主机改变(Non Volatile)或允许被主机改变(Volatile),这个位对Input 和 Output标签没有意义,所以设置成Non Volatile
第八位表示数据字段在不足八位时自动填充(Buffered)还是手动填充(Bit field)。
注意:我们Input,Output,Feature标签后面括号中至少会显示0~2位数据,如果其他默认状态,就是为0,不会显示出来。
我们看下图中
其实这个和上面那个音量调节的报告描述符意思差不多
只不过上面逻辑是-1到1,只需要一个两位数据就可以
这个逻辑是-127到127,要需要两个字节。(最高位是符号 0 = +,1 = -)
后面Input是一样的只是,默认状态下,HID描述符编辑工具不显示。
我们再来看一个,如下图
这里的逻辑值是0-100,所以只要有一个七位数据字段就够了,然后就是使用Absolute表示数据为绝对值,我们的No Preferred表示当用户对音量按钮进行调节时不会恢复到默认状态。
比较常用的标签我们也介绍的多了,接下来我们分析一下这下图的具体意思
首先我们从Usage_Page(Button)看起,Button用途如下,最多可以定义65535个按钮。
按钮 1~3f 分别对应鼠标的左键 中键 右键
后面USAGE_MINIMUM(Button 1)到USAGE_MINIMUM(Button 3)直接定义1~3三个按键,也可以
USAGE_MINIMUM(Button 1) USAGE_MINIMUM(Button 2) USAGE_MINIMUM(Button 3),不过这写的太麻烦了。
后面的逻辑值定义到0 1(Logical Minimum Logical Maximum),0代表不发生,1代表发生
Report Size和Report Count定义了3个1位数据字段,最后用INPUT生成了一个可变的绝对输入数据字段。
后面再用Report Size和Report Count定义了1个5位数据字段,然后使用INPUT生成了一个固定的常量输入字段,前面说过,数据字段必须按字节对齐,这是为了将上面的3个一位数据字段的前五位填充,如下图。
紧接着再次定义Usage Page(Generic Desktop)这个通用桌面用途页,定义了X与Y坐标的用途
他的逻辑值是-127到127,然后定义了两个八个字节的数据字段,x,y各占一个字节,最后用Input生成了一个可变的输入字段,最后整个输入报告就像上图所示。
从这个图中,我们也知道了X,Y位移量分别再 第二和第三字节,如果我们要让我们的PC鼠标实现移动功能,我们的位移数据就应该放在这两个字节这里。
这里有个注意的东西:就是上面数组定义了四个数据,但是我们上面却只定义了三种功能,其实是我们少了鼠标的滚轮功能,我们只要在USAGE(Y)下面在定义个USAGE(WHEEL)然后把
Report Count(2)改成Report Count(3)定义三个即可。如下图
最后就是那个集合COLLECTION,有六种,下面有解释
我们的鼠标使用了两种一个是应用集合 ,一个是实体集合
USAGE(MOUSE)是应用集合,应用集合用来标识一个HID设备或复杂设备的某些功能子集,操作系统使用该集合的用途将控制应用或驱动链接到设备。简单地说,操作系统将应用集合中定义的报告链接到鼠标驱动程序。
USAGE(Pointer)是实体集合,鼠标位置是由x,y轴的数据共同决定的,如果两个分开是不饿能确定鼠标的具体位置,所以我们使用一个实体集合,将两个用途集合在一起,相当于通知主机,这是x,y两个坐标表示一个点。