USB键盘与HID报表描述符关系
报告描述符确实非常复杂,要看懂协议真得比较难(因为只有英文的),我见到的国内教材也没有对这部分写得很详细的,有的也只是部分内容的翻译而已。
一般建议用示例报告描述符修改一下就可以了,《Hid Usage Table》里给出了一些常见应用的报告描述符。对于键盘类应用,通常用已有的示例已经够用了。
“如果是102键,一个8字节的输入数据报告怎么可能对应到102键(假如每一位对应一个键的ON/OFF,也只能对应64个键啊”,应该是你对键盘输入机制不理解造成的,包括传统PS2接口键盘是同样的机制。当键盘一个键按下到弹起过程,其实会发两个数据,一是按下时,会发送对应键按下的数据包,弹起时又会发一次此键没有按下的状态数据包。
为什么第一个字节对应这几个键呢,可以看报告描述符,第一个字节的用途定义(USAGE_MINIMUM)是:
//用途最小值,这里为左ctrl键
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//用途最大值,这里为右GUI键,即window键
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
查查《Hid Usage Table》,是不是正好对应八个特殊功能键?
那为什么说用位来代表八个键呢,而不是一个字节代表呢,同样看报告描述符,由逻辑最小值(LOGICAL_MINIMUM)、逻辑最大值(LOGICAL_MAXIMUM)、报告大小(REPORT_SIZE0)、报告个数(REPORT_COUNT)决定的:
//逻辑最小值为0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1
0x75, 0x01, // REPORT_SIZE (1)
//报告的个数为8,即总共有8个bits
0x95, 0x08, // REPORT_COUNT (8)
因为逻辑最大值是1,最小值是0,而且报告大小是1个bit,而且报告数为8个,所以正好是一个字节,注意这里不能用八个字节来表示。如果报告大小REPORT_SIZE是8,即8位,报告数REPORT_COUNT为1,那就是一个字节,如果报告数REPORT_COUNT也是8,那就是八个字节。看一看报告里的对那一个字节的输出报告定义可以加强认识。
学习报告描述符应先理解报告描述符的结构组成。下面是提取出来的关于输入报告的定义,第一个字节:
//表示用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//用途最小值,这里为左ctrl键
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//用途最大值,这里为右GUI键,即window键
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
//逻辑最小值为0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1
0x75, 0x01, // REPORT_SIZE (1)
//报告的个数为8,即总共有8个bits
0x95, 0x08, // REPORT_COUNT (8)
//输入用,变量,值,绝对值。像键盘这类一般报告绝对值,
//而鼠标移动这样的则报告相对值,表示鼠标移动多少
0x81, 0x02, // INPUT (Data,Var,Abs)
//上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键
//分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。
//它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,
//否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,
//需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示
//特殊键,例如ctrl,shift,del键等
注意,对于输入或输出都要规定用途页(USAGE_PAGE),上面规定的用途页是键盘。第二个字节:
//这样的数据段个数为1
0x95, 0x01, // REPORT_COUNT (1)
//每个段长度为8bits
0x75, 0x08, // REPORT_SIZE (8)
//输入用,常量,值,绝对值
0x81, 0x03, // INPUT (Cnst,Var,Abs)
由于报告有继承性,所以第二个字节的用途页、用途最大最小值、逻辑最大最小值是和第一个字节一样的,但最后那句,定义了第二个字节是个常量,所以它应该是不变的,所以这里没有实际意义。
后面6个字节:
//报告个数为6
0x95, 0x06, // REPORT_COUNT (6)
//每个段大小为8bits
0x75, 0x08, // REPORT_SIZE (8)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值255
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
//用途页为按键
0x05, 0x07, // USAGE_PAGE (Keyboard)
//使用最小值为0
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
//使用最大值为0x65
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
//输入用,变量,数组,绝对值
0x81, 0x00, // INPUT (Data,Ary,Abs)
//以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个按键,所以可以同时
//有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统
//无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一
//个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个
//字节分别为相应的键值,以次类推
后面六个字节为什么是一个字节代表一个键呢,同样是由逻辑最小值(LOGICAL_MINIMUM)、逻辑最大值(LOGICAL_MAXIMUM)、报告大小(REPORT_SIZE0)、报告个数(REPORT_COUNT)决定的,用途最小值0和最大值对应hid usage table里的定义,参考表Table 12: Keyboard/Keypad Page。其实逻辑最大值和最小值之差为255,其实一个字节就可以表示255个键了。只不过一个字节不能同时表示所有键按下。
那么上面定义的报告描述符能用一个数据包表示多少个键按同时下呢,答案是8+6=14个,8个特殊功能键,用第一个字节的八个位表示,最后面的6个字节只能表示6个键同时按下。现在举几个例子来验证一下你对输入报告定义的8个字节的理解。
1、只按下左Shift时发送的八个字节:
按下时发送一次:0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00
弹起时发送一次:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
2、只按下A键时:
按下时发送一次:0x00 0x00 0x04 0x00 0x00 0x00 0x00 0x00
弹起时发送一次:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
3、同时按下A键和左Shift键时:
按下时发送一次:0x02 0x00 0x04 0x00 0x00 0x00 0x00 0x00
弹起时发送一次:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
4、同时按下左Ctrl左Alt和A键时:
按下时发送一次:0x05 0x00 0x04 0x00 0x00 0x00 0x00 0x00
弹起时发送一次:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
5、同时按下A键和B键时:
按下时发送一次:0x00 0x00 0x04 0x05 0x00 0x00 0x00 0x00
弹起时发送一次:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00