键盘DIY——RK61改装成HHKB键位

背景

前段时间入手了一把HHKB,感觉键位设计真的不错,尤其是方向键和Home、End,可以在手指不离开打字键位的同时轻松按到,让手完全留在主键盘区。但生活也有几点不那么完美的:

  1. 用惯了之后,再用原来的键盘真的超级不舒服,一个是Ctrl,另一个是backspace,老是按错。
  2. 用背包来回带很麻烦,进公司要查包,坐地铁要安检。
  3. 手感……是个玄学,但是用了一段HHKB之后再用我的茶轴IKBC,感觉IKBC手感真的好,可能我更喜爱机械键盘那种更干脆的手感。

有了HHKB之后,打算把一直吃灰的RK61卖掉,感觉再也不需要它了。但是不小心把它外壳摔裂破相了,估计价格要大打折扣了。于是我想,干脆把RK61改成HHKB的键位,来解决上面的问题吧!

软硬件环境介绍

电脑:联想G410笔记本
操作系统:Debian 9/Linux Mint
调试工具:JLink V9仿真器
软件开发环境:STM32CubeIDE
开发板:YL-8开发板
电路板软件:KiCAD
3D模型制作:Blender/FreeCAD

关于RK61

RK61是我的第一把60%键盘,这把键盘有点坑,Fn的功能很有限,没有办法按出Home、End等键,用起来很不方便,买了之后用了一周就吃灰了。看官网目前的版本好像支持更多的组合键了,但是老键盘并不提供固件升级。

先拆开它看看。主要有两个芯片

键盘DIY——RK61改装成HHKB键位

  1. SH68F83
    这个芯片是一个支持USB协议的单片机,有线模式时的USB协议应该是这个芯片实现的。
  2. BCM20730模块
    基于博通的BCM20730芯片,这个芯片主要是为无线游戏控制器、键盘、3D眼镜等设计的。其中键盘相关功能我们可以重点关注一下。它支持最多8×20的硬件矩阵扫描,用来做键盘还是很方便的。

一开始思考的是把蓝牙模块的软件换掉,硬件不动。但是这个模块的资料很少,没找到足够的开发资料,冒然开搞后面卡壳失败风险太大。最终选择了比较稳妥的方案:直接把主控换掉。这样我们只要搞懂按键的电路就可以了。同时我对无线的需求不大,决定先搞成一个USB有线键盘算了。

虽然没有使用这个蓝牙模块,但是对它的研究还是很有用的。从BCM20730的数据手册上我们了解到,硬件矩阵键盘扫描使用的引脚是固定的。

  • P0-P7 row0-row7 (KSI0-KSI7)
  • P8-P27 column0-column19 (KSO0-KSO19)

有了这个信息,再看看蓝牙模块上这些引脚都是接出来的,我们就能通过蓝牙模块的引脚,比较方便地确定键盘的矩阵电路。

键盘DIY——RK61改装成HHKB键位

RK61一共61个键,有了前面的蓝牙模块对应的引脚,然后用万用表量一量确认一下,就能知道这些键分为8行8列,最后一行5个按键,其他行每行8个。具体分布如下:

键盘DIY——RK61改装成HHKB键位

这个矩阵键盘的电路的结构和GH60的很像,只是行列的排布有一点区别。大多数键盘的应该都差不多。 大家可以去下载GH60的电路看一看。

矩阵按键电路跟单片机的小矩阵键盘有点区别,每个按键串联了一个二极管。由于矩阵键盘在扫描时,会拉低一个扫描线,同时保持其他扫描线为高电平。但是当多个按键同时按下时,可能会形成回路拉低条其它扫描线,出现多条扫描线同时为低,如下图(1)。这个二极管可以阻止这个回路的形成,如下图(2)。

键盘DIY——RK61改装成HHKB键位

我们只要把矩阵键盘的行列都引出来,接到单片机的IO上就可以了。剩下的问题基本上就是软件实现USB设备、键盘扫描了。

STM32流程打通

软件环境

之前实则没用过STM32单片机,大部分都是用的51单片机。读一些资料后,开始搭建环境,经过尝试,觉得最省事的开发环境就是STM32CubeIDE了。基于Eclipse,集成了STM32CubeMX,装好后装了个VIM插件就直接开用了。由于不需要写太多代码,所以找个最省心的环境就可以了。

键盘DIY——RK61改装成HHKB键位

使用STM32CubeMX + Vscode + platformIO的同学,需要注意一下platformIO对CubeMx生成的USB库的支持不是太好,生成代码后不能直接编译通过,可能需要配置一番。
不得不说目前STM32的软件做得真方便,直接在图形界面里配置好了之后,按一下生成代码,所有初始化代码就全部生成了,很省事。

开发板hello world

用的开发板是许多年前买的YL-8开发板。还好在网盘里找到的它的电路图。买的开发板、模块什么的,一般都会把资料放到网盘里,防止哪天要用的时候,发现只剩板子,啥资料也没有了,真个好习惯,哈哈。
按照小开发板的电路图,在CubeIDE配置好引脚后,在主循环里加了两行代码,就实现了一个检测按键状态控制LED亮灭的小Demo。

int i = HAL_GPIO_ReadPin(button1_GPIO_Port, button1_Pin);
HAL_GPIO_WritePin(GPIOC, led1_Pin, i);

使用STM32的USB接口

为了实现USB键盘,先要会用STM32的USB模块,这块感觉挺麻烦的,好在网上有许多现成的例子,相关教程不再赘述了,大家需要的网上找一找就有了。
我这里遇到了一个棘手的问题:按照网上的例子配置完成后,电脑竟然不识别USB设备! 被这个问题困扰了好久,还买来一本《圈圈教你玩USB》来学习USB协议,但是还是不行。调试USB初始化代码,也没有报错。找来网上的例程烧进去,也没有反应。直到一天看书时注意到,USB主设备会根据从设备的上拉电阻来检测从设备,再看看YL-8开发板,USB接口并没有上拉电阻,而网上找的其它开发板的电路,则是带上拉电阻的。

键盘DIY——RK61改装成HHKB键位

难道说是STM32F103RC里面不带上拉电阻? 于是我接上了一个电阻试一了试,果然立马识别出USB设备了!

键盘DIY——RK61改装成HHKB键位

原来只是一个电阻的问题!后来又在网上看了不少开发板,许多mini开发板USB接口电路都没有接好。这种开发板一般也没有带USB相关的例程,使用时需要注意检查一下电路。正常情况下,在CubeIDE里配置好了USB设备,烧进单片机之后,USB设备就能正常识别的。如果不能正常识别,很可能就是电路有问题。
不过经过这个问题,倒是学到了不少USB相关的知识,推荐《圈圈教你玩USB》这本书,学到了不少东西。

键盘HID设备实现

为了实现键盘的功能,需要把USB设备配置为HID键盘。CubeIDE生成的默认设备是一个Joystick的,我们主要需要改两个地方,一个是配置描述符,把端点从鼠标改成键盘;另一个是要新建一个键盘的报告描述符,这里偷懒直接把圈圈的报告描述符拷过来了。这些改动大部分都在usbd_hid.c里。具体改动可以看在Github上找到:https://github.com/thelxz/xzkb
我们后面发报告要按报告描述符描述的格式来发,所以只要弄清楚报告描述符描述的格式就可以了。由于报告描述符用的圈圈的,所以报告格式和他的也是一样的,一共有八个字节:

键盘DIY——RK61改装成HHKB键位

第一个字节是特殊按键的状态,第二个字节是固定的0,之后的六个字节分别是同时按下的除特殊按键外的六个按键的UID。每个键对应的UID可以通过《USB HID Usage Tables》这个文档的”keyboard/keypad page”找到。
我一开始有点怀疑,只记录六个键够不够?后来试了下,由于功能键有单独的bit控制,除了功能键,我几乎不可能同时按下六个按键,所以如果不玩对按键要求比较高的游戏的话,这里一般就够用了。这点好像也是普通的USB键盘不能实现全键无冲的缘由,有一些特殊的方法可以实现全键无冲,但是我们这里就不思考了。
按这个格式生成报告了之后,使用下面的接口发送Report,没有意外的话,就电脑就可以收到单片机发送的按键了!

USBD_HID_SendReport(&hUsbDeviceFS, reportBuf, reportLen);

连接键盘和开发板

这一步比较简单,就是连线,我把行列分别连到了GPIOA和GPIOB上。唯一需要注意的地址是,最好找几个连续的引脚,这样后面编程会方便一些。

键盘DIY——RK61改装成HHKB键位

添加软件逻辑

实则本来是想直接用TMK的代码的,但是发现TMK对STM32的支持好像并不是太好,而且还使用了ChibiOS这个BIOS,比较复杂,想看懂需要花些时间。于是决定自己写,更简单可控。
大家可以直接看代码,主要逻辑都在keyboard.h和keyboard.c里。
使用了两个数组作为字典来表明两个层,默认情况下,每个按键对应的UID通过keyUID[0]翻译,当Fn按下时,对应的按键通过keyUID[1]来翻译。这个逻辑是很简单的,代码可以在这里找到。但这样做实则有一个小问题,列如使用F2(组合键Fn+2)重命名文档时,如果先松开了Fn,2这个键就会发出去,结果就把文档重命名成“2”了。不过这个问题不常出现,我们先不管它,后面再修。
完成这一步之后,我们的键盘实则就能用了!

键盘DIY——RK61改装成HHKB键位

但是很明显,这样有点丑,而且插的线偶尔会接触不良或脱落,导致一些按键失灵!看来为了把电路板塞到键盘壳子里,需要一个小一点的主控板了。

制作主控板

KiCAD介绍

正如之前提到的,网上好多比较小的开发板,有许多USB电路都不是直接连好的,就想着干脆自己做一个吧。发现目前PCB比以前便宜多了,10×10的才30来块钱,也好久没做过了,就试一下吧。
电路图软件试了试Eagle和KiCAD。很久以前用Eagle挺好用的,命令很方便,但是目前发现Eagle被Autodesk收购了,功能也有变化,列如显示3D库会直接跳到Autodesk网站,感觉Eagle已经变了,只好放弃它了。
反观KiCAD,最近发展好像很迅猛,装上之后发现功能比几年前强劲了许多,文档也很丰富了,提议大家试一试。如果是第一次用,提议先把中文文档浏览一遍。

电路图:

键盘DIY——RK61改装成HHKB键位

PCB和3D图:

键盘DIY——RK61改装成HHKB键位

键盘DIY——RK61改装成HHKB键位

PCB画完之后,可以生成gerber发现商家制作了。本来还担心KiCAD国内用的不多,PCB制作会有问题,结果却很顺利,文件发过去直接就做出来了。
软件的各种操作在官方文档里说的很全了,有一些觉得有用的点分享给大家:

  1. 布线模式设置为推挤(shove),可以在步线时把之前的线推走,默认没有打开

键盘DIY——RK61改装成HHKB键位

  1. 3D模型的制作

    方法1:对Blender比较熟悉的话,可以使用Blender制作3D模型, 导出OBJ之后FreeCAD选择材质,再导出wrl文件,KiCAD就可以导入了。Blender建模型比较快,但是导入KiCAD之后还需要重新调整大小,而且Blender的材质不能导出后就丢了。
    方法2:直接使用FreeCAD制作模型,导出STEP文件,这样的话,直接导入KiCAD里面就会带材质,而且大小也不需要调整。后面尝试了一下,这个流程是可以的,但是我FreeCAD用的不熟,建模很慢。

前面PCB的3D模型里,缺了一些元件的模型,用Blender做了几个:

键盘DIY——RK61改装成HHKB键位

焊接组装

PCB回来发现做工一般,便宜了质量果然有些下降,但也还好,没什么问题。由于想让它当个小开发板的,所以没有做太小,还加了一些按键和LED。

键盘DIY——RK61改装成HHKB键位

开工焊元件吧!结果发现自己动手能力已经严重退化了,镊子夹着元件抖得不行。还发现自己电路画错了,Reset键不灵,只好搞了个飞线,看一下最后效果,简直惨不忍睹…

键盘DIY——RK61改装成HHKB键位

上面黑乎乎的是松香,记得之前酒精是可以洗掉松香的,目前怎么洗了半天洗不掉,不知道是酒精不行还是松香不好。算了,就这样吧,反正放到壳子里也看不到。

键盘DIY——RK61改装成HHKB键位

为了降高度,一些不用的元件去掉了,把SWD调试口接了一个MicroUSB连到外面去了,方便下载程序。这样处理是偷懒了,后面有心情时再研究一下怎么通过USB升级程序或者把USB接口复用一下,只留一个接口。
装上壳子,外面看起来还不错吧!

键盘DIY——RK61改装成HHKB键位

细节处理

安装支脚、防滑垫

RK61是没有支脚的,不是太舒服,而且原来的防滑垫也老化了,掉了两个,都需要处理一下。

支脚网上有卖的,挺好看的,但是大都需要运费,就自己打了个孔,装了个六角铜柱,套上了一个小橡胶帽来防滑就可以了,橡胶帽是原来吃鸡手柄带的电容屏按键帽,防滑效果还是挺好的。

键盘DIY——RK61改装成HHKB键位

下面加了一个原来买键盘托带的橡胶垫,用螺丝刀的六角刀头在螺丝处打一个孔。

键盘DIY——RK61改装成HHKB键位

Fn功能键优化

目前来处理Fn键松开后,已经按下的键功能发生变化的问题。为了解决这个问题,不能只思考目前按键的状态,还必须思考到上次按键的状态。代码的变化主要有:

  1. 按键的level不是全局的,而是每个按下的键都有一个自己的level。
  2. 在按键扫描时,记录上次扫描状态,如果这个键上次扫描就已经按下了,它的level状态就不受目前Fn状态的影响,而是直接复制上次的level信息。

功耗优化

目前键盘的功能基本上搞完了,但是心里一直有一个疑惑:我写的程序一直循环检查按键状态,就算等待的时间,也是在原地循环,这样电路的功耗会不会很大?为了方便的搞清这个问题,采购了一个测量USB电流的小设备。这个工具主要是用来测量充电器的功率的,但是准确到了mA级,测量键盘的功率应该也是可以的。测量结果如下:

键盘DIY——RK61改装成HHKB键位

我们做的键盘功耗是有一点大,比达尔优的104键还大一点。但是再看HHKB Pro2,好家伙,功耗竟然是我的三倍多!难道说是内部集成了USB HUB的缘由?不知道目前新的HHKB功耗降下去没有。
优化功耗可以从两方面入手:

  1. 把延时程序的循环查询改成Sleep
  2. 降低MCU的频率

我们先看一看HDL_Delay() 这个延时函数:

__weak void HAL_Delay(uint32_t Delay) //注意 __weak
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }
  while ((HAL_GetTick() - tickstart) < wait)
  { //这里是用循环在等时间到达
  }
}

发现这个函数是个弱函数,我们只要按照它重新实现一个就可以,不需要修改HAL库。我们把它的循环等待里加一行sleep函数,让它在等待时进入低功耗状态,等systick来了的时候,会从sleep状态唤醒它,检查时间是否到达。代码如下:

void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;
  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }
  while ((HAL_GetTick() - tickstart) < wait)
  {
      HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
}

其中的HAL_PWR_EnterSLEEPMode()函数可以调用WFI,进入低功耗状态。看一下修改完成后的功率:

键盘DIY——RK61改装成HHKB键位

效果还是很明显的,功耗降低了约50%。下面再试试降低时钟频率,把AHB总线的时钟4分频了一下,再看键盘的功耗:

键盘DIY——RK61改装成HHKB键位

又降了40%!虽然只有两点比较简单的改善,但是效果还是相当不错的,目前键盘的功率已经降到了60mW,试了试比我手上所有的USB设备都省电,就先这样吧!

完工

我们的键盘基本上完工了,总结一下情况吧:
主控:STM32F103RET6
代码量:< 500 行(不含自动生成代码)
接口:MiniUSB,SWD接口
按键:61键,国产红轴,Fn组合键类似HHKB
源码:https://github.com/thelxz/xzkb
电路PCB:https://github.com/thelxz/xzkb_pcb

键盘DIY——RK61改装成HHKB键位

由于只有周日有时间,改装键盘的过程前前后后经历了好几周,但是收获还是许多的,了解了USB协议,学会了KiCAD,抢救了一下我的动手能力,也完成了我们最初的目标:HHKB再也不用来回带了。
目前这个键盘已经成为在家的主力键盘了。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共1条

请登录后发表评论