hsrzq 发表于 2023-5-31 11:11:20

一种STC-USB模拟键盘全键无冲的实现思路

本帖最后由 hsrzq 于 2023-5-31 11:37 编辑

我们都知道,一个键盘报文最大8个字节,其中:
1. 第0个字节是Shift、Ctrl、Alt、Win这些修饰键,左右两组正好8位一个字节
2. 第1个字节功能保留,仅做填充用
3. 剩下6个字节每个字节各表示一个键码
因此,除开那几个修饰键外,其它的的键最多能表示6个。这在大多数时候都够用了,但是如果不够用了呢?甚至需要全键无冲呢?




实现思路一

既然一个键盘最多只能报告6个按键,那我多模拟几个键盘出来不就好了?
可以参考 https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=572&pid=3705 这个代码,将键盘HID多报告几次即可。不过要注意的是,STC8/STC32只有5个端点(端点0不能用),因此最多只能报告5个键盘,最多也只能做到30键无冲。


实现思路二

既然最多支持6个键的原因是REPORT_COUNT为6,那我改一下这个值不就行了?
char code KEYBOARDREPORTDESC =
{
    0x05,0x01,            //USAGE_PAGE(Generic Desktop);
    0x09,0x06,            //USAGE(Keyboard);
    0xa1,0x01,            //COLLECTION(Application);
    0x05,0x07,            //USAGE_PAGE(Keyboard);
    0x19,0xe0,            //USAGE_MINIMUM(224);
    0x29,0xe7,            //USAGE_MAXIMUM(255);
    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);
    0x75,0x08,            //REPORT_SIZE(8);
    0x95,0x01,            //REPORT_COUNT(1);
    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,0x5E,            //REPORT_COUNT(94); 最多94个键无冲同时按
    0x81,0x00,            //INPUT(Data,Array);
    0x05,0x08,            //USAGE_PAGE(LEDs);
    0x19,0x01,            //USAGE_MINIMUM(1);
    0x29,0x03,            //USAGE_MAXIMUM(3);
    0x15,0x00,            //LOGICAL_MINIMUM(0);
    0x25,0x01,            //LOGICAL_MAXIMUM(1);
    0x75,0x01,            //REPORT_SIZE(1);
    0x95,0x03,            //REPORT_COUNT(3);
    0x91,0x02,            //OUTPUT(Data,Variable,Absolute);
    0x75,0x05,            //REPORT_SIZE(5);
    0x95,0x01,            //REPORT_COUNT(1);
    0x91,0x01,            //OUTPUT(Constant);
    0xc0,                   //END_COLLECTION;
};

不过要注意的是,STC8/STC32的1、2、3IN端点只有64Bytes,因此只能使用4、5端点,并且还需要修改一下配置描述符的端点描述部分。
    0x09,                   //bLength(9);
    0x04,                   //bDescriptorType(Interface);
    0x00,                   //bInterfaceNumber(0);
    0x00,                   //bAlternateSetting(0);
    0x02,                   //bNumEndpoints(2);
    0x03,                   //bInterfaceClass(HID);
    0x00,                   //bInterfaceSubClass(No Boot); 非标准键盘,不支持BIOS使用
    0x01,                   //bInterfaceProtocol(Keyboard);
    0x00,                   //iInterface(0);

    0x09,                   //bLength(9);
    0x21,                   //bDescriptorType(HID);
    0x01,0x01,            //bcdHID(1.01);
    0x00,                   //bCountryCode(0);
    0x01,                   //bNumDescriptors(1);
    0x22,                   //bDescriptorType(HID Report);
    0x41,0x00,            //wDescriptorLength(65);

    0x07,                   //bLength(7);
    0x05,                   //bDescriptorType(Endpoint);
    0x84,                   //bEndpointAddress(EndPoint4 as IN); 使用端点4
    0x03,                   //bmAttributes(Interrupt);
    0x60,0x00,            //wMaxPacketSize(96); 最大96字节(修饰符1字节+填充1字节+键码94字节)
    0x0a,                   //bInterval(10ms);

    0x07,                   //bLength(7);
    0x05,                   //bDescriptorType(Endpoint);
    0x04,                   //bEndpointAddress(EndPoint1 as OUT);使用端点4
    0x03,                   //bmAttributes(Interrupt);
    0x01,0x00,            //wMaxPacketSize(1);
    0x0a,                   //bInterval(10ms);


实现思路三

默认键盘描述符的第一个字节,一个字节就可以表示8个键,那么我们是不是也可以模仿一下,用少量的字节表示大量的键呢?
    // 部分配置描述符
    0x09,                   //bLength(9);
    0x04,                   //bDescriptorType(Interface);
    0x00,                   //bInterfaceNumber(0);
    0x00,                   //bAlternateSetting(0);
    0x02,                   //bNumEndpoints(2);
    0x03,                   //bInterfaceClass(HID);
    0x00,                   //bInterfaceSubClass(No Boot); 非标准键盘,不支持BIOS使用
    0x01,                   //bInterfaceProtocol(Keyboard);
    0x00,                   //iInterface(0);

    0x09,                   //bLength(9);
    0x21,                   //bDescriptorType(HID);
    0x01,0x01,            //bcdHID(1.01);
    0x00,                   //bCountryCode(0);
    0x01,                   //bNumDescriptors(1);
    0x22,                   //bDescriptorType(HID Report);
    0x41,0x00,            //wDescriptorLength(65);

    0x07,                   //bLength(7);
    0x05,                   //bDescriptorType(Endpoint);
    0x81,                   //bEndpointAddress(EndPoint1 as IN);
    0x03,                   //bmAttributes(Interrupt);
    0x16,0x00,            //wMaxPacketSize(16); 最大16字节(修饰符1字节 + 普通键15字节/120位)
    0x0a,                   //bInterval(10ms);

    0x07,                   //bLength(7);
    0x05,                   //bDescriptorType(Endpoint);
    0x01,                   //bEndpointAddress(EndPoint1 as OUT);
    0x03,                   //bmAttributes(Interrupt);
    0x01,0x00,            //wMaxPacketSize(1);
    0x0a,                   //bInterval(10ms);

键盘报告描述符
char code KEYBOARDREPORTDESC =
{
    0x05,0x01,            //USAGE_PAGE(Generic Desktop);
    0x09,0x06,            //USAGE(Keyboard);
    0xa1,0x01,            //COLLECTION(Application);
    0x05,0x07,            //USAGE_PAGE(Keyboard);
    // 修饰键
    0x19,0xe0,            //USAGE_MINIMUM(224);
    0x29,0xe7,            //USAGE_MAXIMUM(231);
    0x15,0x00,            //LOGICAL_MINIMUM(0);
    0x25,0x01,            //LOGICAL_MAXIMUM(1);
    0x75,0x08,            //REPORT_SIZE(8);
    0x95,0x01,            //REPORT_COUNT(1);
    0x81,0x02,            //INPUT(Data,Variable,Absolute);

    // 第一组
    0x19,0x00,            //USAGE_MINIMUM(0);
    0x29,0x66,            //USAGE_MAXIMUM(102);
    0x15,0x00,            //LOGICAL_MINIMUM(0);
    0x25,0x01,            //LOGICAL_MAXIMUM(1);
    0x75,0x67,            //REPORT_SIZE(103);
    0x95,0x06,            //REPORT_COUNT(1);
    0x81,0x02,            //INPUT(Data,Variable,Absolute);

    // 第二组
    0x19,0x74,            //USAGE_MINIMUM(116);
    0x29,0x84,            //USAGE_MAXIMUM(132);
    0x15,0x00,            //LOGICAL_MINIMUM(0);
    0x25,0x01,            //LOGICAL_MAXIMUM(1);
    0x75,0x11,            //REPORT_SIZE(17);
    0x95,0x06,            //REPORT_COUNT(1);
    0x81,0x02,            //INPUT(Data,Variable,Absolute);

    0x05,0x08,            //USAGE_PAGE(LEDs);
    0x19,0x01,            //USAGE_MINIMUM(1);
    0x29,0x03,            //USAGE_MAXIMUM(3);
    0x15,0x00,            //LOGICAL_MINIMUM(0);
    0x25,0x01,            //LOGICAL_MAXIMUM(1);
    0x75,0x01,            //REPORT_SIZE(1);
    0x95,0x03,            //REPORT_COUNT(3);
    0x91,0x02,            //OUTPUT(Data,Variable,Absolute);
    0x75,0x05,            //REPORT_SIZE(5);
    0x95,0x01,            //REPORT_COUNT(1);
    0x91,0x01,            //OUTPUT(Constant);
    0xc0,                   //END_COLLECTION;
};

Logical Minimum (0)/Logical Maximum (1)表明这是一个OOC(On/Off Control)类型的条目,即每一个bit可表示一个键的开(1)或关;
第一组Usage Minimum (0)/Usage Maximum (102)表明每一位代表的是哪个真正的键值,具体键值表可以参考usb官方文档https://usb.org/sites/default/files/hut1_4.pdf 的第10节《Keyboard/Keypad Page (0x07)》。
第二组Usage Minimum (116)/Usage Maximum (132)功能上与第一组完全相同,是另一组表明剩余位代表的真正键值。
由于第一组只有103个键,所以另找了17个键将报告补全成16个字节(128位,修饰符8位+第一组103位+第二组17位)
当USB主机端收到以下报告时:
0b10010010 0b00110000 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0b00010000
就可以解析出来
0b10100001 → 左Ctrl,右Shift,右Alt
0b00110000 → Keyboard a/A,Keyboard b/B
0b00010000 → Keyboard Volume Down



已知问题
思路二和思路三都修改了标准的键盘描述符,这会造成只有在进入操作系统后才能使用

8051启蒙者 发表于 2023-5-31 15:43:16

涨知识了!感谢分享~

windman 发表于 2023-10-20 00:51:25

楼主思路清奇,膜拜中。。。。。
页: [1]
查看完整版本: 一种STC-USB模拟键盘全键无冲的实现思路