前言
为什么想写这个呢,首先一方面是因为自己喜欢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 describing
manufacturer */
2, /*Index of string descriptor describing
product*/
3, /*Index of string descriptor describing the
device 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 describing
the 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 关于复合设备和组合设备
我倒不是去纠结两个概念的含义,只是想说目前比较主流的实现多媒体按键的方法就上面两种。其实多个接口一个设备的这种设备按照定义应该称之为组合设备。我下面讲的方法也是通过组合设备,即一个设备下多个接口描述符来实现的。
就是说一个配置描述符下可以挂多个接口描述符,每个接口做自己的事情,多媒体按键就是通过另外一个接口来实现的。
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设备,哪个端点的数据放置与缓冲区的哪个位置。而不管我们定义的端点是否为双向端点,即不管我们是否同时使用一个端点来收发数据,它的缓冲区描述表都在那里,都要占据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 关于按键冲突
为什么会有按键冲突?请看下面这张图。
以上图为例,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 modifiers
0x75, 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 report
0x95, 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 keys
0x95, 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岁生日,生日快乐,老藏。