从0开始制作机械键盘

机械键盘,造轮子,还是造键盘?

 

是的,没错,我要重新开始造轮子。

最开始萌生此想法的原因是想把一个USB接口的键盘改造成蓝牙键盘(别问我为什么不直接买一个)。想改的前提有两个:一是笔者用的是笔记本,总会碰到USB接口不够用的尴尬局面;二是笔者之前从事蓝牙设备的开发工作,具备将键盘改造成蓝牙接口的能力。

后来由于种种原因,想法被搁置了。正好最近有个朋友有需求要做一个键盘的转接板,是直接从原始的矩阵键盘接口转到USB接口,就是下面这货。

从0开始制作机械键盘_第1张图片

从0开始制作机械键盘_第2张图片

 

好吧,离蓝牙接口还有段距离,不过能做成USB的,那就一定做成蓝牙的。

言归正传,开始动手吧。

 

一、硬件篇

无非就是个矩阵键盘嘛,嗯,看一下这货的资料,14*8,好家伙,线挺多的,这是全尺寸键盘的节奏?不过IO口多还真不是问题,市面上MCU那么多,随便选就好了。找个易用的又带有USB接口的并且比较熟悉的,关键是手头上就有的,非stm32莫属了。

从0开始制作机械键盘_第3张图片

后面仔细一看,发现实物和上图的FPC引脚数对不上 ==!,没办法了,总不能拆了键盘去看键码吧……先搭个线完成功能吧。

STM32F103C8T6,引脚够用,成本够低,开发环境成熟,资源丰富。

下面是最小系统板,万能的XX上有卖,10+可以搞定,还包邮。

从0开始制作机械键盘_第4张图片

IO分配好,PA0-PA7作为列(COL)输入,配置为下拉输入,PB0-PB5作为行(ROW)输出,默认为推挽输出。USB为PA11、PA12,PA9、PA10分别为TX、RX作调试用。

硬件就这么多了。

 

二、软件篇

软件才是让人头大的东西。

1、搭建工程

不过好在ST官方还算给力,做出了一个叫做STM32CubeMX的东西,简直是想快速搭建工程配置外设者的福音。

打开STM32CubeMX,新建工程,stm32_usb_kb。

从0开始制作机械键盘_第5张图片

之所以选择C8而不是C6版本,是因为市场上C8的出货量大,再就是因为开发时可以选容量稍大的产品,到后期程序完善了、优化了,可以量产时候去用的更低容量的IC节省成本。

点OK确定,然后来配置外设。

首先配置RCC,选择使用外部高速时钟。

再配置SYS,主要使用SWD作为程序的下载和调试接口,不选JTAG的原因是JTAG要占用多个IO(主要是把我要用的PB口占用了)。Timebase Source选SysTick,会要用到计时功能 。

然后配置串口,这里选择USART1,配置成异步模式。

再配置USB,勾选框框,MiddleWares的USB选择Human Interface Description

然后在芯片的引脚上配置我们需要用到的引脚,PA及PAB口,直接在IO口上单击即可选择需要的功能。

配置好的界面如下所示:

从0开始制作机械键盘_第6张图片

从0开始制作机械键盘_第7张图片

这时发现Clock Configuration选项卡上出现X,这是因为修改了时钟源造成的,点进去,会提示是否选择自动配置,当然选yes了。

从0开始制作机械键盘_第8张图片

切换到配置页面,配置GPIO,可以统一进行配置。

从0开始制作机械键盘_第9张图片

点OK完成配置,然后在Project菜单中选择Generate Code

从0开始制作机械键盘_第10张图片

配置好工程名,选择好存放位置,选择好IDE,然后确定。

从0开始制作机械键盘_第11张图片

至此,一个最基本的带有USB功能的支持矩阵键盘扫描的工程诞生了。

 

2、测试矩阵扫描的基本功能

a)首先初始化一下IO口,没啥好说的。

void matrix_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;

/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET);

/*Configure GPIO pins : PA0 PA1 PA2 PA3
PA4 PA5 PA6 PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/*Configure GPIO pins : PB0 PB1 PB2 PB10
PB11 PB12 PB13 PB14
PB15 PB3 PB4 PB5
PB6 PB7 PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

 

 

b)按键扫描函数

uint16_t KS_ScanForKeyPress(uint8_t iActiveLine)
{
uint32_t outputRegister = ((uint32_t)1<< iActiveLine );
GPIOB->ODR = outputRegister;
uint32_t scannedInput = (GPIOA->IDR & 0x000000FF);
return scannedInput;
}

uint8_t KS_PrintScanLines(void)
{
uint16_t scannedInput;
uint8_t keyScans = 0;

for(uint8_t i = 0; i < 16; ++i)
{
scannedInput = KS_ScanForKeyPress(i);
if (scannedInput > 0)
{
// detected a key press
printf("Key press detected at %d/0x%04X\n", (i+1), scannedInput);
++keyScans;
}
}
return keyScans;
}

c)测试

int main(void)
{
uint8_t scanCode;

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USB_DEVICE_Init();

/* USER CODE BEGIN 2 */
printf("Hello, STM kb!\r\n");

/* Infinite loop */
while (1)
{
HAL_Delay(10);
scanCode = KS_PrintScanLines();
}
}

 

用每一行的线分别接触每一列的线,测试矩阵。

 

3、处理按键的逻辑

a)按键冲突问题

买键盘的时候,经常会听到全键无冲、六键无冲等关键字,尤其对喜欢玩游戏的朋友来说。关于其概念,想要了解的可以自行百度,在这里只需要要知道相邻两行两列矩阵上的三个键按下可能会出现另外一个未按下键的键值即可(针对是没在矩阵上做过处理的键盘来讲,加了二极管的键盘不在此列)。程序上要处理这种情况,当然,现在薄膜键盘的矩阵设计在一般的办公场景及大部分游戏场景很难会出现多键冲突的情况。

b)组合键处理

如按下ctrl键后又点击C(点击:点下去,然后立马释放)、按下Ctrl+alt+……等等。

c)功能键Fn

大多笔电按键较少,无法将常用的按键一次性布局,往往会提供Fn+其它键实现一些不能布下键的功能,为实现这种功能,程序上要实现多套键码,动态切换。

d)……(一些未想到的)

 

说了这么多,却没有敲半句代码实现。

虽然说是造轮子,但不是说从制造材料开始,那是原始人才会做的事。

我不是代码的生产者,而是代码的搬运工。

要善于借鉴前人的经验。

 

打开github,输入关键字“USB keyboard”,有一堆开源项目。反复查看代码,发现这家伙的比较适合移植,有很多Demo,平台近似,提交比较活跃,文档写得也比较清楚。

很多DIY机械键盘的实现也可用上此工程。

https://github.com/tmk/tmk_keyboard

从0开始制作机械键盘_第12张图片

里面有几篇文档值得关注一下,可以详细了解整个程序的结构及框架的实现。

及工程根目录下的README,通读好处多多。

 

4、移植到M3平台,MDK开发环境

这里不多说,只与各位看官提提几个要注意的地方。

a)github的工程是用GNU写的,在Keil MDK ARM编译器环境下要设置--gnu的编译选项,要么你就自己修改代码,如匿名结构体。

b)一些语法上的修改,如case Fn1...Fn9: 在ANSI C下是没有这种写法的,需要自行更改。其次是Keil下没有0b00001111这种表示方法。

c)代码移植选择tmk_core文件夹的内容即可,里面有avr平台的实现,可以对照改为STM32的实现,主要是初始化,定时器,串口输出。

d)参照keyboard/hbkb的代码,自行实现Matrix.c中的矩阵扫描部分,config.h文件中一些宏的开启等。宏的话,参照此文件下的设置即可,如果要实现更加高级的功能,看官自行研究。keymap.c矩阵的修改。

e)需要实现host_driver_t驱动,主要是报告描述,此结构位于report.h文件下。

从0开始制作机械键盘_第13张图片

 

贴一个我用到的代码目录:

从0开始制作机械键盘_第14张图片

建议:串口输出一定要实现好,对调试有莫大的帮助。(可在debug.c文件中开启要调试的部分)

从0开始制作机械键盘_第15张图片

5、测试

键码还没搞到手,先用单独的按键替代测试。接了按键“1”与ctrl键,输出正常,连接电脑也能正常打字,试试组合键也还行。

从0开始制作机械键盘_第16张图片

按下“1”

从0开始制作机械键盘_第17张图片

按下ctrl+1

从0开始制作机械键盘_第18张图片

三、总结篇

看似常用且简单的东西还是是有一些技术含量在里面的。

1、以前没有好好写过矩阵键盘的扫描代码,现在写过了并且记忆深刻了;

2、了解了一些与编译器相关的关键字,如__attirbute__ (weak);

3、KEIL MDK ARM下支持GNU语法编译方法:project->options->C/C++下,添加--gnu

a)keil中不支持GNU中的匿名结构体,需要在结构体前添加#pragma anon_unions或者在编译选项中添加--gnu

b)“--gnu”实际上是在ARMCC中加入支持GNU扩展格式,当然实际上Keil是可以添加GCC的编译器的,通过keil菜单中“Project > Manage > Components, Environment, Books..”添加,前提是你得提前装好GCC编译器。官方原文:http://www.keil.com/arm/gnu.asp

4、Keil环境中,用memset()对结构体“清零”时要注意四字节对齐,否则会出错。可在定义结构体的末尾(;前面)添加__attirbute__ ((aligned(4))),如uint8_t EXTERN_DHCPBUF[1024] __attribute__((aligned(4)));

5、移植过来的代码与平台结合性不强,可以移植大部分平台上去了,也可以采取各种通讯接口及协议。蓝牙键盘的改造过程有望了。

 

四、拖拉篇

本来前三节写好就要发的,结果一直没拿到键码,拖到现在,于是有了这一节。

很早之前就想买一把机械键盘玩玩了,无奈要不就是价格太贵,要不就是按键手感不符合。

正好有了之前的基础,倒是有机会做一把键盘了。

一般的机械键盘组成部分:轴(关键),键帽,卫星轴,键盘PCBA,底壳,如果用的是3脚的轴还需要按键的固定钢板。

轴的分类不多说(主要是我也说不清楚 ==!),看好介绍,然后买个试轴器自己感受,再决定上手什么轴。X宝上大把的。

用习惯了笔记本,发现小键盘区用得并不多,再就是希望尽可能的节省成本吧,决定做60键的键盘,小巧,方便携带出去装B。自己做好配列,60键还是足够用的。

GitHub上找找,结果有开源的PCB,但自己用的IC不一样,还是得改改。

链接在此:https://github.com/komar007/gh60

下载后发现是KiCAD格式的,找遍若干方法,还是没能将其转换为AD的格式。最终没办法,自己重新画矩阵,重新画封装。

当然,不是傻傻的去弄。

提供一方法:

AD由Gerber反向生成PCB:

1)新建CAM文件,分别导入gerber和drill文件;

2)必要时调整一下Layer Order,Tables->Layers Order;

3)关键:Tools->Netlist->Extract;

4)File->Export->Export to PCB;

5)网络表是重命名的网络表,可以从原理图重新导入网络表。

至此,得到可供“使用”的PCB,仅仅是用于提取封装、外形尺寸及定位。

对比原设计,我的设计添加了些自己的东西,如添加了灯的接口:

从0开始制作机械键盘_第19张图片

  • 为每一排按键添加了独立的灯控制电路,限于MCU只有四个PWM口,只好把最后两排接到同一个PWM口上。这样,便可独立控制每一排灯了。
  • 为CAPS Lock添加了单独的灯,可以知道当前Caps的状态。
  • 将Mini USB接口换成了更常用的插件Micro USB(现在安卓手机大行其道,Micro USB线还是更常见一些)。
  • 留出了矩阵及灯的接口,方便用其它平台打造键盘,比如蓝牙,wifi等等。

3D预览的PCB:

从0开始制作机械键盘_第20张图片

从0开始制作机械键盘_第21张图片

实物:

 

从0开始制作机械键盘_第22张图片

 

从0开始制作机械键盘_第23张图片

 

做得比较着急,还是出了一些问题,比如丝印上漏了个W,少打一个孔,少一电阻等等小问题……

目前PCB的功能还只是能打字,还没有把灯及固件升级的功能做上去,等闲一点的时候再买材料完善了。

等完善好了我会将PCB、原理图及固件源代码放到CSDN或Github上,欢迎有需要的人士下载或提出宝贵的建议~

 

 

 

PS:本人踩过的坑也全部在本文写明,绝不藏私。如果真有愿意动手的朋友,相信也会迎难而上的。

你可能感兴趣的:(平时做的小玩意,STM32,机械键盘,GH60,tmk_keyboard,STM32CubeMX)