关于STM32的USB设备库DIY机械键盘

news/2024/11/24 19:49:28/

前言

为什么想写这个呢,首先一方面是因为自己喜欢DIY一些小玩意,另一方面关于USB-HID的东西断断续续的学习了不少东西,想总结整理一下。其次就是网络上关于STM32制作USB-HID的案例很多,我这里尽量说一些不一样的,以便大家参考,内容主要还是关于机械键盘方面的。

本来早就应该写的,自己懒,然后又喜欢拖延,最近想着利用下班的时间将它陆续整理完善吧。

DIY机械键盘相比于客制化机械键盘更为彻底,也没有客制化出来的机械键盘好用,我追寻的只是DIY给我带来的乐趣与成就感。

如果只是想要一把好用的键盘,建议还是买一把量产键盘。客制化烧钱,有这个钱,能卖高至少一个等级的量产键盘。

前后做了三种键盘,GH60,Keycool84,还有标准的87配列。有的送人了,有的还在自己手上。其中GH60和Keycool84的PCB是自己设计的,代码也是自己敲的,很丑陋,代码习惯不好,就不放出来献丑了,后面主要还是告诉各位实现原理与方法。

一 STM32的设备库与USB协议

STM32的设备库中有很多的例程,比如DFU,MSC,HID等等,或者直接使用STM32CubeMX生成的CoustomHID工程也可以一样使用,如何移植,如何让自己的PCBA在电脑上成功的识别为一个HID设备,我这里就不过多赘述了,网上有大量的例子可以参考。因为库中的例子都是依托STM32的评估板来做的,所以修改起来会比较麻烦,倒不如后一种方法省事。

重点说一句,就是HID设备的特别之处就在于HID描述符和报告描述符,你这个设备发送给电脑的数据有啥用,需要电脑给你什么数据,都是通过报告描述符来实现的。而这两个描述符中的各个数据有什么作用,在HID Usage Table这个文档中都有说明,而且,写的很详细。但是,但是,但是是英文文档,又臭又长,看了下一段,忘了上一段。所以不想去看的也可以直接复制下面的描述符直接用。

1.1 设备描述符

const uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] ={0x12,                       /*bLength:18Bytes */USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/0x00,                       /*bcdUSB */0x02,0x00,                       /*bDeviceClass*/0x00,                       /*bDeviceSubClass*/0x00,                       /*bDeviceProtocol*/0x40,                       /*bMaxPacketSize:64Bytes*/0x83,                       /*idVendor (0x0483)*/0x04,0x12,                       /*idProduct = 0x5750*/0x34,0x00,                       /*bcdDevice rel. 2.00*/0x02,1,                          /*Index of string descriptor describingmanufacturer */2,                          /*Index of string descriptor describingproduct*/3,                          /*Index of string descriptor describing thedevice serial number */0x01                        /*bNumConfigurations*/}

关于设备描述符,我只说一个点,即idVendor和idProduct这两个数据。这两个数值是不能随便定义的,但是你随便定义也没有关系。我在调试的过程中发现这样的一个问题,如果我更改了譬如配置描述符的内容,但是没有更改上面这两个数据,电脑就显示上一次的设备,而不是你更改了的设备。只有在更改了这两个数据以后,PC才会重新识别这个设备。这个情况在Win7上是如此。Win10没有出现过。

1.2 配置描述符

    0x09, /* bLength: Configuration Descriptor size */USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */CUSTOMHID_SIZ_CONFIG_DESC,/* wTotalLength: Bytes returned */0x00,0x01,         /* bNumInterfaces: 1 interface */0x01,         /* bConfigurationValue: Configuration value */0x00,         /* iConfiguration: Index of string descriptor describingthe configuration*/0x80,         /* bmAttributes: Self powered */0x64,         /* MaxPower 200 mA: this current is used for detecting Vbus */

关于配置描述符,bNumInterfaces说的是你这个设备有多少个接口。还有最后一个0xC8这个数据表示你这个设备需要从总线上获取多少电流,这个视你设备功耗决定。0xC8 * 2 = 400mA,最大能获取500mA的电流。

1.3 接口描述符

    0x09,         /* bLength: Interface Descriptor size */USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */0x00,         /* bInterfaceNumber: Number of Interface */0x00,         /* bAlternateSetting: Alternate setting */0x02,         /* bNumEndpoints,except endpoint 0 */0x03,         /* bInterfaceClass: HID */0x01,         /* bInterfaceSubClass : 1=BOOT, 0=no boot */0x01,         /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */0,            /* iInterface: Index of string descriptor */

关于接口描述符,如果只需要一个接口,直接复制粘贴就能用。另外注意的是,多接口复合设备中,只能有一个Boot设备,即bInterfaceSubClass这个变量,只有一个接口可以为1,其他的为0。具体为什么,我也不清楚,有知道的朋友可以回复一下,谢谢。因为我有一次定义了两个Boot接口,设备没有枚举成功。

1.4 HID描述符

    0x09,         /* bLength: HID Descriptor size */HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */0x10,         /* bcdHID: HID Class Spec release number */0x01,0x00,         /* bCountryCode: Hardware target country */0x01,         /* bNumDescriptors: Number of HID class descriptors to follow */0x22,         /* bDescriptorType */CUSTOMHID_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */0x00,

关于HID描述符,CUSTOMHID_SIZ_REPORT_DESC这个值需要根据报告描述符的长度来修改。

1.5 端点描述符

    0x07,          /* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */0x81,          /* bEndpointAddress: Endpoint Address (IN) */0x03,          /* bmAttributes: Interrupt endpoint */0x08,          /* wMaxPacketSize: 8 Bytes max */0x00,0x05,          /* bInterval: Polling Interval (5 ms) */0x07,	/* bLength: Endpoint Descriptor size */USB_ENDPOINT_DESCRIPTOR_TYPE,	/* bDescriptorType: *//*	Endpoint descriptor type */0x02,	/* bEndpointAddress:Endpoint Address (OUT) */0x03,	/* bmAttributes: Interrupt endpoint */0x01,	/* wMaxPacketSize: 1 Bytes max  */0x00,0x05,	/* bInterval: Polling Interval (5 ms) */

关于端点描述符,bEndpointAddress表示端点地址,表示当前这个接口所需要的端点资源,输入(相对于主机而言)端点最高位为1,输出(相对于主机而言)端点最高位为0。然后说一句,HID设备一般都是使用中断端点进行数据传输。wMaxPacketSize表示该端点上数据传输的数量。bInterval表示主机查询设备数据的时间间隔,如果设置的太长,则键盘输入延迟很高,深有体会。

1.6 报告描述符

    0x05, 0x01, // USAGE_PAGE (Generic Desktop)0x09, 0x06, // USAGE (Keyboard)0xa1, 0x01, // COLLECTION (Application)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x01, // LOGICAL_MAXIMUM (1)0x75, 0x01, // REPORT_SIZE (1)0x95, 0x08, // REPORT_COUNT (8)0x81, 0x02, // INPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x08, // REPORT_SIZE (8)0x81, 0x03, // INPUT (Cnst,Var,Abs)0x95, 0x05, // REPORT_COUNT (5)0x75, 0x01, // REPORT_SIZE (1)0x05, 0x08, // USAGE_PAGE (LEDs)0x19, 0x01, // USAGE_MINIMUM (Num Lock)0x29, 0x05, // USAGE_MAXIMUM (Kana)0x91, 0x02, // OUTPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x03, // REPORT_SIZE (3)0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6)0x75, 0x08, // REPORT_SIZE (8)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0xFF, // LOGICAL_MAXIMUM (255)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)0x81, 0x00, // INPUT (Data,Ary,Abs)0xc0, // END_COLLECTION   /* 63 */

关于报告描述符,具体每一个数据什么意思,可以参考前面说的文档HID Usage Table。这个就是标准6KRO键盘的报告描述符。

至于还有 字符串 描述符,不说了,有用,用处不大,不修改直接用,没有一点关系。

二 多媒体按键的实现方法

2.1 关于复合设备和组合设备

我倒不是去纠结两个概念的含义,只是想说目前比较主流的实现多媒体按键的方法就上面两种。其实多个接口一个设备的这种设备按照定义应该称之为组合设备。我下面讲的方法也是通过组合设备,即一个设备下多个接口描述符来实现的。
说明如下,才发现没有Visio,就用这个简单示意一下

就是说一个配置描述符下可以挂多个接口描述符,每个接口做自己的事情,多媒体按键就是通过另外一个接口来实现的。

2.2 多媒体按键的实现方法

如果一个HID设备中需要实现标准键盘,鼠标,多媒体按键功能,第一种方法就是通过三个接口来实现。第二种方法就是只使用两个接口,第二个接口通过报告ID这么一个字段,来向PC机报告我这一包数据是鼠标的数据还是多媒体按键数据。还有第三种方法,就是前面说的复合设备,即把多个设备作为一个hub,这样也可以实现(这个方法我没有证实过)。

    /* system control */0x05, 0x01,                      // USAGE_PAGE (Generic Desktop)0x09, 0x80,                      // USAGE (System Control)0xa1, 0x01,                      // COLLECTION (Application)0x85, 0x02,                      //   REPORT_ID (2)0x15, 0x01,                      //   LOGICAL_MINIMUM (0x1)0x26, 0xb7, 0x00,                //   LOGICAL_MAXIMUM (0xb7)0x19, 0x01,                      //   USAGE_MINIMUM (0x1)0x29, 0xb7,                      //   USAGE_MAXIMUM (0xb7)0x75, 0x10,                      //   REPORT_SIZE (16)0x95, 0x01,                      //   REPORT_COUNT (1)0x81, 0x00,                      //   INPUT (Data,Array,Abs)0xc0,                            // END_COLLECTION    /* 24 *//* consumer */0x05, 0x0c,                      // USAGE_PAGE (Consumer Devices)0x09, 0x01,                      // USAGE (Consumer Control)0xa1, 0x01,                      // COLLECTION (Application)0x85, 0x03,                      //   REPORT_ID (3)0x15, 0x01,                      //   LOGICAL_MINIMUM (0x1)0x26, 0x9c, 0x02,                //   LOGICAL_MAXIMUM (0x29c)0x19, 0x01,                      //   USAGE_MINIMUM (0x1)0x2a, 0x9c, 0x02,                //   USAGE_MAXIMUM (0x29c)0x75, 0x10,                      //   REPORT_SIZE (16)0x95, 0x01,                      //   REPORT_COUNT (1)0x81, 0x00,                      //   INPUT (Data,Array,Abs)0xc0,                            // END_COLLECTION    /* 25 */

上面的代码中,仅展示了一个例子,使用Report ID来完成Consumer数据和System Control数据的区分。比如报告ID为2的字段表示这一帧的数据是System Control数据,如果报告ID为3,则表示这次的数据是Consumer数据。

额外提一句,如果使用的报告ID,那么发送给PC机的数据就需要附上报告ID这个字节,且这个ID必须在所有数据的最前面。

2.3 如何在键盘的工程上添加这个功能

1、首先在usb_desc.c文件中得定义这个多媒体的报告描述符,也有在里面包含鼠标的数据的,这个随意,只要复合HID标准即可。然后在对应的.h文件中也要声明这个变量。
2、修改前面说的配置描述符,bNumInterfaces这个字段更改为2,表示有两个接口。
3、在第一个接口描述符后面,添加第二个接口的描述符,bInterfaceNumber这个字段改为1,这是序号为1(起始序号为0)的接口描述符,然后依次添加这个接口的HID描述符和端点描述符。
4、在usb_conf.h中添加相应的端点资源。用到哪个端点,就添加哪个端点。

#define BTABLE_ADDRESS      (0x00)/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x30)
#define ENDP0_TXADDR        (0x70)/* EP1  */
/* tx buffer base address */
#define ENDP1_TXADDR        (0xB0)/* EP2  */
/* Rx buffer base address */
#define ENDP2_RXADDR        (0xF0)/* EP3  */
/* Tx buffer base address */
#define ENDP3_TXADDR        (0x130)

比如说,我这里用到了三个端点,除了端点0 (控制端点)以外,端点1用来发送键盘数据,端点2用来接收键盘上灯的数据,端点3用来发送多媒体数据。

重点来了!!!

这里每一个宏定义后的值并不是可以随便定义的,如果翻看一下STM32的参考手册USB章节,发现USB模块中有512字节的缓冲区,这个缓缓区和CAN是共用的。但重点不在这里,如果我把端点0的ENDP0_RXADDR定义为0x10,那么这样USB设备接收的数据或者发送的数据就会出错。为什么呢?看下图。
USB的分组缓冲区对应的缓冲区描述表项定位
包缓冲区中有一个叫缓冲区描述表的东西,这个东西的位置也位于包缓冲区中。它的意义就在于告诉USB设备,哪个端点的数据放置与缓冲区的哪个位置。而不管我们定义的端点是否为双向端点,即不管我们是否同时使用一个端点来收发数据,它的缓冲区描述表都在那里,都要占据8个字节。而且这个缓冲区描述表的位置就位于包缓冲区的开始的位置。也就是说,如果我使用了三个端点,那么ENDP0_RXADDR的值最小也得是3*8 = 0x18。至于你定义在0x30这个位置,只是说对于包缓冲区的 空间利用不充分,因为缓冲区描述表后面的部分是都可以用来作为数据缓冲区的,但如果定义的小了,则会造成通信异常。
5、这里修改好以后,在usb_prop.c文件中,需要定义如下几个函数。并在相应头文件中声明。

uint8_t *CustomHID_GetHIDDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &CustomHID_Hid_Descriptor);
}uint8_t *Meida_GetHIDDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &Media_Hid_Descriptor);
}uint8_t *CustomHID_GetReportDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &CustomHID_Report_Descriptor);
}uint8_t *Media_GetReportDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &Media_Report_Descriptor);
}

6、最后。看第一条注释语句,这里为什么是这样,有知道的朋友,欢迎指点迷津。曾经头皮挠破了,不曾想是这里的原因。

RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{uint8_t *(*CopyRoutine)(uint16_t);/* 复合设备一定要将此语句注释掉,否则只识别第一个设备 */
//   if (pInformation->USBwIndex != 0)         
//     return USB_UNSUPPORT;    CopyRoutine = NULL;if ((RequestNo == GET_DESCRIPTOR)&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))&& (pInformation->USBwIndex0 < 3)){if (pInformation->USBwValue1 == REPORT_DESCRIPTOR){if (pInformation->USBwIndex0 == 0){CopyRoutine = CustomHID_GetReportDescriptor;}else if (pInformation->USBwIndex0 == 1){CopyRoutine = Media_GetReportDescriptor;}}else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE){if (pInformation->USBwIndex0 == 0){CopyRoutine = CustomHID_GetHIDDescriptor;}else if (pInformation->USBwIndex0 == 1){CopyRoutine = Meida_GetHIDDescriptor;}}} /* End of GET_DESCRIPTOR */

7、好了,到这里,一个带多媒体按键的键盘组合设备差不多是完成了,除了还需要对其端点的回调函数定义。插上电脑,应该可以正确的识别为一个设备了。

三 六键无冲与全键无冲

3.1 关于按键冲突

为什么会有按键冲突?请看下面这张图。

这里以一个普通的4个按键的矩阵为例来说明。
以上图为例,2×2的键盘矩阵,一般键盘的扫描方式,都是按列扫描,A,B,C,D都是连接在单片机的IO上,A,B设置为输入,然后C,D设置为输出,先让C输出1,D输出0,分别读取A,B引脚的电平,如果此时S3按下,应该是A读到0,B读到1。然后再让D输出1,C输出0,分别读取A,B电平,应该是A读到0,B也读到0。这样就可以获得S1-S4的按键状态了。
还有一种按键扫描方式,不过很少用,先让A,B输入,C,D输出,让C,D同时输出1,读取A,B电平,保存为temp1,然后再让A,B为输出,C,D为输入,A,B同时输出1,读取C,D电平,保存为temp2,然后根据temp1和temp2的组合值来判断是哪个按键按下。

按键冲突的出现:当按键S1,S2,S3同时按下时,无论采用上述的哪种扫描方法,对于单片机来说,判断结果都是S1,,S2,S3,S4这四个按键都被按下了。

按键冲突的解决办法:
1:通过改变按键的布局,比如,W,E,S,D这四个按键在某一个游戏操作时容易被同时按下,这就需要在原理图设计时不能把这四个按键放置为上图的形势。
2:二极管的单向导电性。如下图。
加入二极管后的电路
因为按键冲突的实质原因是由于电路形成通路导致的,而二极管因为其特性而将上述可能出现的情况完全解决了,当然缺点就是成本增加了。

3.2 关于六键无冲

网上查找键盘的数据格式,大多都是如下的一个说明。

键盘发送给PC的数据每次8个字节
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6 BYTE7 BYTE8
定义分别是:
BYTE1 –
|–bit0: Left Control是否按下,按下为1
|–bit1: Left Shift 是否按下,按下为1
|–bit2: Left Alt 是否按下,按下为1
|–bit3: Left GUI 是否按下,按下为1
|–bit4: Right Control是否按下,按下为1
|–bit5: Right Shift 是否按下,按下为1
|–bit6: Right Alt 是否按下,按下为1
|–bit7: Right GUI 是否按下,按下为1
BYTE2 – 暂不清楚,有的地方说是保留位
BYTE3–BYTE8 – 这六个为普通按键
键盘经过测试。
例如:键盘发送一帧数据 02 00 0x04 0x05 00 00 00 00
表示同时按下了Left Shift + ‘a’+‘b’三个键

这就误导我以为键盘就只能一次最多发送六个按键数据(除了Ctrl等控制按键外)。这也就有了六键无冲的由来。因为一次最多发送六个按键的数据。后来网上找了很多的资料,也去看了看HID v1.11这个协议的部分内容。

结果如下:为什么一次最多只能发送六个按键值,这是因为只有上述的这个格式的键盘在BIOS下才能有效。至于在系统环境下,你想发送多少个按键值,随意,你开心就好。因此,我完全可以将报告描述符更改为如下形势。

    0x05, 0x01, // USAGE_PAGE (Generic Desktop)0x09, 0x06, // USAGE (Keyboard)0xa1, 0x01, // COLLECTION (Application)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x01, // LOGICAL_MAXIMUM (1)0x75, 0x01, // REPORT_SIZE (1)0x95, 0x08, // REPORT_COUNT (8)0x81, 0x02, // INPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x08, // REPORT_SIZE (8)0x81, 0x03, // INPUT (Cnst,Var,Abs)0x95, 0x05, // REPORT_COUNT (5)0x75, 0x01, // REPORT_SIZE (1)0x05, 0x08, // USAGE_PAGE (LEDs)0x19, 0x01, // USAGE_MINIMUM (Num Lock)0x29, 0x05, // USAGE_MAXIMUM (Kana)0x91, 0x02, // OUTPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x03, // REPORT_SIZE (3)0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x95, 0x0D, // REPORT_COUNT (14)		// 更改这一句0x75, 0x08, // REPORT_SIZE (8)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0xFF, // LOGICAL_MAXIMUM (255)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)0x81, 0x00, // INPUT (Data,Ary,Abs)0xc0, // END_COLLECTION   /* 63 */

这样六键无冲键盘一下子就成了14键无冲键盘,这个值是随意可以设置的。当然,这里更改了,端点描述符也需要更改一下数据包的大小。
如果你乐意,你想就这样把键盘设置为全键无冲键盘,比如104配列的键盘,除了8个控制按键外,完全可以把REPORT_COUNT的值改为96。

3.3 关于全键无冲与实现方式

上面其实已经讲了全键无冲,顾名思义就是所有按键按下都不会有冲突,电脑都可以响应我们所按下的所有按键。

这里主要讲一下实现的方式。实现方式有很多种,我讲一下我知道的几种。

1:3.2节讲的方法,通过更改报告描述符的方式。

2:复合设备,定义一个组合设备。比如一个接口发送六个字节的数据。那么我定义一个五个接口的组合设备,这样我是不是可以做到30键无冲了呢。

3:最常用的方法就是,把每一个按键映射为一个字节的一个位,以前一个按键就需要一个字节,现在一个字节就可以表示8个按键,这样,只需要16个字节就能表示128个按键了,除开其中有几个bit位无意义之外,这样一个全键盘的104个按键就能通过一个16字节的USB数据包来表示了。
这种映射为bit方式的报告描述符如下:

    0x05, 0x01,                           // Usage Page (Generic Desktop),0x09, 0x06,                           // Usage (Keyboard),0xA1, 0x01,                           // Collection (Application),// bitmap of modifiers0x75, 0x01,                           //   Report Size (1),0x95, 0x08,                           //   Report Count (8),0x05, 0x07,                           //   Usage Page (Key Codes),0x19, 0xE0,                           //   Usage Minimum (224),0x29, 0xE7,                           //   Usage Maximum (231),0x15, 0x00,                           //   Logical Minimum (0),0x25, 0x01,                           //   Logical Maximum (1),0x81, 0x02,                           //   Input (Data, Variable, Absolute), ;Modifier byte// LED output report0x95, 0x05,                           //   Report Count (5),0x75, 0x01,                           //   Report Size (1),0x05, 0x08,                           //   Usage Page (LEDs),0x19, 0x01,                           //   Usage Minimum (1),0x29, 0x05,                           //   Usage Maximum (5),0x91, 0x02,                           //   Output (Data, Variable, Absolute),0x95, 0x01,                           //   Report Count (1),0x75, 0x03,                           //   Report Size (3),0x91, 0x03,                           //   Output (Constant),// bitmap of keys0x95, 0x78,                           //   Report Count (120),0x75, 0x01,                           //   Report Size (1),0x15, 0x00,                           //   Logical Minimum (0),0x25, 0x01,                           //   Logical Maximum(1),0x05, 0x07,                           //   Usage Page (Key Codes),0x19, 0x00,                           //   Usage Minimum (0),0x29, 0x77,                           //   Usage Maximum (),0x81, 0x02,                           //   Input (Data, Variable, Absolute),0xc0                                  // End Collection   /* 57 */

数据包的第一个字节仍旧表示控制按键。

四 STM32的DFU升级方式

这里以后专门出一篇文章来写吧,和本文的关系不大,主要是方便没有烧写工具的朋友可以自己更改代码,做一把自己想要的键盘。
DFU很简单,相比于HID,DFU,USB更多的则是用来做MSC设备,当然,对于电子工程师来说,更有用的设备当然是CDC设备啦。这样可以节省一颗CH340的芯片。仅仅只是调试时使用,一颗CH340未免太浪费,好歹几块钱一颗呢。

后记

犹记得大学毕业时,找工作面试,网龙网络的一个工程师问我,“++a和a++有什么区别?“现在想来,当初真是初生牛犊不怕虎,什么都不懂,想着岗位高薪就去面试。

毕业后工作好些年,遇到了很多的良师益友,从他们身上学到了不少东西,这里肯定要感谢他们的分享与不吝赐教。所以我觉得学会了,理解了,就拿出来大家一起分享。正如,文明的进步依靠的是不同文明之间的碰撞与融合。

我后面没有附上这次DIY键盘的源代码,这是因为我在代码中使用了很多的全局变量,没有用结构体去封装。使用了很多的if else语句,CPU运行效率低下。也许它实现了一个键盘的功能,但真不能算一个合格的工程。其实代码这些东西都可以参考参考tmk方案或者qmk方案,里面的有一些代码都写的很精简而有效率。这两个方案在github上是开源的。

这篇文章,断断续续写了快一个月,很多是删了重写的,旨在为了让更多的人都能看懂。不想因为我的随意,而使这篇文章而变得晦涩难懂。如果我写的大家都看不懂,那简直是一坨狗屎,也就失去了分享的意义。

一晃已经进入自己的第26个年头了,可我依旧是一条年轻的小咸鱼,谨以此纪念自己的25岁生日,生日快乐,老藏。


http://www.ppmy.cn/news/715085.html

相关文章

colmak键盘_萌神进化 IKBC 新POKER2机械键盘体验

对于喜欢IKBC的玩家们而言今年是令人欣喜的一年&#xff0c;因为IKBC陆续推出了众多的新品供大家选择和体验。除了经典的POKER系列获得了更新&#xff0c;全新推出的时光机系列产品也凭借着新奇丰富的特性在2016年度机械键盘市场中获得了亮眼的表现。时至年关&#xff0c;IKBC又…

解决 BigDecimal 类型属性整数返回到前端不显示.00的问题

一、继承 JsonSerializer 类&#xff0c;重写 serialize 抽象方法 public class BigDecimalSerializer extends JsonSerializer<BigDecimal> {Overridepublic void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) t…

Mybatis-Plus学习2 IService+ServiceImpl

service层写法&#xff0c;继承ISerice&#xff0c;泛型为实体类对象 public interface UserService extends IService<User> { } impl层写法&#xff0c;继承ServiceImpl&#xff0c;实现service&#xff0c;泛型为Mapper对象实体类对象 public class UserServiceImp…

标签软件如何批量生成公司名片

随着社会竞争日益激烈&#xff0c;现在的公司员工不管在什么场合下&#xff0c;都会带着自己的名片&#xff0c;那么如果公司员工较多的情况下如何用中琅标签打印软件来批量生成公司名片呢&#xff0c;下面我们就来详细看一下&#xff1a; 首先&#xff0c;我们可以根据标签纸的…

打印名片程序的练习

打印名片程序: 输入姓名,电话号码,性别,最后打印出来名片 "" 打印名片程序:输入姓名,电话号码,性别,最后打印出来名片 控制姓名长度为6-20 电话号码长度11 性别只能允许输入男或女 每一样信息不允许为空def card():while True:name = input("请输入姓名…

名片打印也可以私人订制 高附加值打印方案新选

【方案解读】在职场与社交活动中&#xff0c;一身高级得体的衣装会让你气场大开&#xff0c;同样&#xff0c;一张个性化高端名片也会彰显你的与众不同。所以如何让你看起来比别人更有“高级感”&#xff0c;一张“私人订制”的个性化名片就非常重要。 某公司李总在回忆时表示…

python打印名片信息_Python的格式化输出--制作名片

效果图 代码如下&#xff1a; name input("Your name:") age int(input("Your age:")) job input("Your job:") salary int(input("Your salary:")) msg ‘‘‘ This is %s -------- My age :%d My job :%s My salary :%d end --…

名片管理系统-python

黑马程序员课程中的综合应用-名片管理系统 笔记 系统要求&#xff1a; 1.程序启动&#xff0c;展示名片管理系统欢迎界面&#xff0c;并显示功能菜单 2.用户用数字选择不同的功能&#xff1b; 3.根据功能选择&#xff0c;执行不同的功能&#xff1b; 4.用户名片需要记录用户的…