前言
为什么想写这个呢,首先一方面是因为自己喜欢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 关于复合设备和组合设备
我倒不是去纠结两个概念的含义,只是想说目前比较主流的实现多媒体按键的方法就上面两种。其实多个接口一个设备的这种设备按照定义应该称之为组合设备。我下面讲的方法也是通过组合设备,即一个设备下多个接口描述符来实现的。
就是说一个配置描述符下可以挂多个接口描述符,每个接口做自己的事情,多媒体按键就是通过另外一个接口来实现的。
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 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岁生日,生日快乐,老藏。