目录
前言
设备描述符
配置描述符
配置描述符
接口描述符
HID描述符
端点描述符
HID类报表描述符
输入输出数据解析
其他
前言
看了稚辉君的瀚文键盘的源码之后对于键盘全键无冲的实现还是存在很多疑问。1、HID报表描述符的内容到底什么意思?2、瀚文源码里的键值映射函数最终生成的报表不理解。然后就从头开始研究了一下USB。对着描述符里的每一个参数找资料,把所有描述符都对一遍。现在已经大概理解了全键无冲键盘的配置方式和报表含义。
实践选用的是沁恒的CH573F,这颗芯片支持USB 2.0 Host和Device,支持最大64字节数据包。在官网下载他们的官方Demo,并在USB Device里面找到CompoundDev这个工程来修改我的全键无冲键盘。
设备描述符
const uint8_t MyDevDescr[0x12] = {
0x12, //bLenght
0x01, //bDescriptorType
0x00,0x02, //bcdUSB
0x00, //bDeviceClass
0x00, //bDeviceSubClass
0x00, //bDeviceProtocol
0x40, //bMaxPacketSize0
0x3d,0x41, //idVendor
0x07,0x21, //idProduct
0x00,0x01, //bcdDevice
0x01, //iManufacturer
0x02, //iProduct
0x03, //iSerialNumber
0x01 //bNumConfigurations
};
bLenght--固定长度18
bDescriptorType--01:代表这段数据属于设备描述符
bcdUSB--USB2.0版本 这里是bcd编码 00 02代表2.0版本
bDeviceClass--类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的
bDeviceSubClass--子类型代码(由USB分配).如果bDeviceClass值是0,一定要设置为0.其它情况就跟据USB-IF组织定义的编码
bDeviceProtocol-- 协议代码(由USB分配).如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH
bMaxPacketSize0--最大包长,芯片所支持的最大长度
idVendor--厂商ID
idProduct--产品ID
bcdDevice--版本号,自己定义 这里是bcd编码 00 01代表1.0版本
iManufacturer--厂商字符串索引(只要不为1主机就会找设备拿这个字符串
iProduct--产品字符串索引 (只要不为1主机就会找设备拿这个字符串
iSerialNumber--设备序列号字符串索引(只要不为1主机就会找设备拿这个字符串
bNumConfigurations--可能的配置数.定义设备以当前速度支持的配置数量
配置描述符
一般CfgDescr这个集合里面会包含:配置描述符、接口描述符、HID描述符(HID
设备才需要)、端点描述符。USB主机会一次或分包拿完整个配置描述符集合。
const uint8_t MyCfgDescr[0x34] = {
//配置描述符
0x09, //bLenght
0x02, //bDescriptorType
0x34,0x00, //wTotalLength
0x01, //bNumInterfaces
0x01, //bConfigurationValue
0x00, //iConfiguration
0xA0, //bmAttributes
0x32, //MaxPower
//接口描述符,键盘功能
0x09, //bLenght
0x04, //bDescriptorType
0x00, //bInterfaceNumber
0x00, //bAlternateSetting
0x01, //bNumbEndpoints
0x03, //bInterfaceClass
0x01, //bInterfaceSubClass
0x01, //bInterfaceProtocol
0x00, //iInterface
//HID类描述符
0x09, //bLenght
0x21, //bDescriptorType
0x11,0x01, //bcdHID
0x00, //bCountryCode
0x01, //bNumDescriptors
0x22, //bDescriptorType
0x39,0x00, //wDescriptorLength
//端点描述符
0x07, //bLenght
0x05, //bDescriptorType
0x81, //bEndpointAddress
0x03, //bmAttributes
0x0A,0x00, //wMaxPacketSize
0x01 //bInterval
};
配置描述符
bLenght--配置描述符的长度 9
bDescriptorType--02:代表这段数据属于配置描述符
wTotalLength--整个配置描述符集合的总长度
bNumInterfaces--指该配置配备的接口数量,也表示该配置下接口描述符数量
bConfigurationValue--作为Set Configuration的一个参数选择配置值
iConfiguration--用于描述该配置字符串描述符的索引
bmAttributes--供电模式选择.Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒.
MaxPower--设备所需电流,1代表2mA 0x32=100mA
接口描述符
bLenght--接口描述符的长度 9
bDescriptorType--03:代表这段数据属于接口描述符
bInterfaceNumber--该接口的编号
bAlternateSetting--用于为上一个字段选择可供替换的位置.即备用的接口描述符标号
bNumbEndpoints--端点0以外的端点数
bInterfaceClass--类代码 3:HID类
bInterfaceSubClass--子类代码:1是 0否 支持BIOS
bInterfaceProtocol--协议代码:00其他,01键盘,02鼠标
iInterface--字符串描述符的索引
HID描述符
bLenght--HID描述符的长度 9
bDescriptorType--0x21:代表这段数据属于HID描述符
bcdHID--HID的版本号1.11
bCountryCode--国家代码,如果不说明,该字段为0
bNumDescriptors--类别描述符数目(至少有一个报表描述符)
bDescriptorType--该类别描述符的类型
wDescriptorLength--HID报表描述符的长度
端点描述符
bLenght--端点描述符的长度 7
bDescriptorType--05:代表这段数据属于端点描述符
bEndpointAddress--USB设备的端点地址;Bit7方向1/0:IN/OUT(对于控制端点可以忽略);Bit6-4,保留;BIt3-0:端点号;
bmAttributes-- 端点属性.Bit7-2保留(同步有定义)BIt1-0:00控制,01同步,02批量,03中断
wMaxPacketSize--端点收发信息包的长度
bInterval--传输间隔 1:每1个帧 2:每2个帧 4:每4个帧 8:每8个帧
HID类报表描述符
全键无冲的实现主要靠的就是报表描述符这里配置。
const uint8_t KeyRepDesc[KEYBOARD_HID_REPORT_DESC_SIZE] = {
0x05, 0x01, // Usage Page (Generic Desktop),
0x09, 0x06, // Usage (Keyboard),
0xA1, 0x01, // Collection (Application),
// bitmap of modifiers(功能按键)
0x05, 0x07, // Usage Page (Keyboard),
0x95, 0x08, // Report Count (8),
0x75, 0x01, // Report Size (1),
0x15, 0x00, // Logical Minimum (0),
0x25, 0x01, // Logical Maximum (1),
0x19, 0xE0, // Usage Minimum (Keyboard LeftControl),
0x29, 0xE7, // Usage Maximum (Keyboard Right GUI),
0x81, 0x02, // Input (Data, Variable, Absolute),
// bitmap of keys(普通按键)
0x05, 0x07, // Usage Page (Keyboard),
0x95, 0x78, // Report Count (120),
0x75, 0x01, // Report Size (1),
0x15, 0x00, // Logical Minimum (0),
0x25, 0x01, // Logical Maximum (1),
0x19, 0x00, // Usage Minimum (0),
0x29, 0x65, // Usage Maximum (101),
0x81, 0x02, // Input (Data, Variable, Absolute),
// LED output report
0x05, 0x08, // Usage Page (LEDs)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x19, 0x01, // Usage Minimum (Num Lock 1)
0x29, 0x03, // Usage Maximum (Scroll Lock 3)
0x91, 0x02, // Output (Data,Var,Abs)
//output凑共1byte(无实际用处)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x91, 0x01, // Output (Cnst,Var,Abs)
0xC0 // End Collection
};
0x05, 0x01, // Usage Page ,表示用途页为通用桌面设备
0x09, 0x06, // Usage ,表示用途为键盘
0xA1, 0x01, // Collection,表示应用集合,必须要以 END_COLLECTION 来结束它
// bitmap of modifiers(功能按键)
0x05, 0x07, // Usage Page (Key Codes),
0x95, 0x08, // Report Count (8),报告的个数为 8,即总共有 8 个 bits
0x75, 0x01, // Report Size (1),定义多少个bit代表一个数据
0x15, 0x00, // Logical Minimum (0),逻辑的最小值
0x25, 0x01, // Logical Maximum (1),逻辑的最大值
0x19, 0xE0, // Usage Minimum (Keyboard LeftControl),用的途最小值
0x29, 0xE7, // Usage Maximum (Keyboard Right GUI),用的途最大值
0x81, 0x02, // Input (Data, Variable, Absolute), 0x81代表这是输入报告的格式,0x02代表数据格式
0x05:可以看到首先告诉别人我是属于键盘功能,在HID Usage Tables这份文档中可以查到
0x07对应的就是键盘,而且在下面还能查找到键盘每个按键对应的键值。
0x95:告诉别人我的数据里一共代表了8个键值
0x75:告诉别人我是1个bit代表一个有效数据(一个byte刚好代表8个键)
0x15:一个数据的逻辑最小值0,键值就是按下和松开两种状态只需要0和1表示
0x25:一个数据的逻辑最小值1
0x19:用的途最小值意思就是第一位数据对应的第一个实际键值编号
0x29:用的途最大值对应的最后一个键值编号
0x81:最后是定义输入报告的数据类型
总结:先告诉别人我这个报告的是键盘数据,上传数据里有8个键值一个bit代表一个键值,松开和按下两种状态只需要1个bit就能表示最大值是1最小值是0。然后就是定义这个8个键值分别是哪8个,第一个是左边的CTRL最后一个是右边的GUI键(具体键值序号和顺序查阅:HID Usage Tables)。基本上这样就算设置完键盘代表8个功能键的的第一个byte。
同理配置剩下的15个byte(我配置输入长度是16byte除去第一个byte,普通按键有15个byte最多支持120个按键)
0x05, 0x07, // Usage Page (Key Codes),
0x95, 0x78, // Report Count (120),
0x75, 0x01, // Report Size (1),
0x15, 0x00, // Logical Minimum (0),
0x25, 0x01, // Logical Maximum (1),
0x19, 0x00, // Usage Minimum (0),
0x29, 0x65, // Usage Maximum (101),
0x81, 0x02, // Input (Data, Variable, Absolute),
配置到这里把程序编译烧录连接到电脑就能检测到你的设备是一个HID键盘,如果没有找到就要检查一下你的所有报表描述符。
输入输出数据解析
输出报告处理
case UIS_TOKEN_OUT:
{
len = R8_USB_RX_LEN;
if(SetupReqCode == 0x09)
{
PRINT("[%s] Num Lock\t", (pEP0_DataBuf[0] & (1<<0)) ? "*" : " ");
PRINT("[%s] Caps Lock\t", (pEP0_DataBuf[0] & (1<<1)) ? "*" : " ");
PRINT("[%s] Scroll Lock\n", (pEP0_DataBuf[0] & (1<<2)) ? "*" : " ");
}
}
break;
CH573的CompoundDev这个Demo里的这段代码是处理LED灯的输出报告,输出报告是上面HID类报表描述符配置的。测试方法,接上你的开发板的串口打印,用其他键盘或者开发板自己上报分别按下Num Lock、Caps Lock、Scroll Lock,看看输入报告能不能正常更新指示灯的状态。(指示灯的状态并不是键盘自己记录的,每次键盘连接到电脑或者指示灯状态有更新的时候,电脑都会主动输出指示灯的状态)
输入报告
/*********************************************************************
* @fn DevHIDKeyReport
*
* @brief 上报键盘数据
*
* @return none
*/
void DevHIDKeyReport(uint8_t key)
{
HIDKey[2] = key;
memcpy(pEP1_IN_DataBuf, HIDKey, sizeof(HIDKey));
DevEP1_IN_Deal(sizeof(HIDKey));
}
//main
while(1)
{
mDelaymS(1000);
DevHIDKeyReport(0xFF);
mDelaymS(100);
DevHIDKeyReport(0x00);
}
尝试一下上传普通键值,在第三个byte上传一个0xFF。测试结果如下图;对一下HID Usage Tables文件里的键盘键值表,8~15刚好对应的的就是E~L。
全键无冲表示普通按键的后面15个byte和第一个byte的表示方式相同。标准键盘和全键无冲第一个byte的格式是一样的。但8byte的标准键值报表最多只能支持6个普通按键,而且用一个byte来表示一个键值,两种配置的区别就在这里。到这里已经消除了我对瀚文源码里键值映射函数和上报数据格式的疑问。(我只是没想到电脑能够这么聪明,我配置6键的时候他能够理解我上传的就是键值编码,配置全键按照位置排序代替键值的时候也能够正常识别。也希望有大佬给我讲解讲解这一点)
功能键*8 | 保留 | key1 | key2 | key3 | key4 | key5 | key6 |
标准的配置方法
优点:兼容性强(HID标准的键值报表电脑bios能识别)
缺点:最多只能同时按下6个普通按键
无冲的配置方法
优点:支持同时按下多个键
缺点:电脑只有进了系统才能正常使用、键盘和电脑要处理更多键值而增加负荷(对于如今的电脑只是九牛一毛)
其他
键盘按键测试:键盘按键测试在线 - 键盘测试工具 - 键盘连键检测 (bmcx.com)
USB中文网里面的USB和HID解析非常详细:USB中文网 - USB技术开发交流 (usbzh.com)文章来源:https://www.toymoban.com/news/detail-493652.html
USB学习视频: 《USB技术应用与开发》第一讲:认识USB传输_哔哩哔哩_bilibili文章来源地址https://www.toymoban.com/news/detail-493652.html
到了这里,关于USB HID键盘实现全键无冲解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!