自作キーボードを作った話


この記事はキーボードカレンダー2019#2の18日目の記事です。

はじまり

周りのエンジニアが全員メカニカルキーボードを使っているような職場環境なので自分もキーボードを組んでみることにしました。とはいえキーボードキットも安くはないので、どんなデザインが良いのか、試作機を自作してみることにしました。デザインファイルやファームウェアのソースコードは全てGitHubに公開しています。

デザイン

ErgoDox系なのでキー数多めです。OpenSCADを使ってデザインしたものを3Dプリントしています。片面の横幅は約15cmです。たいていの家庭用の3Dプリンターだと印刷できる底面積は15cm x 15cm程度なので、今回のデザインがギリギリの横幅となります。

OpenSCADではスクリプトを書いて形状をデザインします。デザインをするといっても、GitHubで公開されているプロジェクトファイルをベースとしたため、キーの場所を替えるだけの簡単なお仕事でした。片面だけをデザインしてからmirror([1,0,0])コマンドで反転させると反対側の面もできます。

キースイッチはケースにはめ込んで固定します。試作機のためボトムプレートは作っていません。滑り止めのゴム足の代わりにゴムトリムを挟み込んでいます。

配線はゆかりさんのブログを参考にしています。キーボードは片面5行×7列ですが、6行×6列のマトリクスとして空中配線しています。しっかりとはんだ付けしないと入力不良を起こす原因となるため、ピンの足にワイヤを巻きつけたりダイオードの足同士を絡ませたりしてからはんだ付けしています。

ファームウェア

職場ではGoを書いています。そこでファームウェアをTinyGoで書いてみることにしました。

2019年12月現在、TinyGoが動くマイコンは限られます。自作キーボードで一般的に使われているProMicroはTinyGoでは使えません。また、TinyGoはUSB HIDやBLE HIDのドライバーを提供していないという問題もあります。そこで今回はATMega328pRN42を接続する構成を取っています。RN42はUART接続型のBluetoothモジュールでありHIDデバイスとして振る舞うことができます。キーの送信はマイコンからUSB HID Keyboard scan codeを送信するだけです。

// SendKeyboardReport sends a raw keyboard report for scan codes and modifier keys.
// The data byte array should be specified in scan codes or encoded values
// and the length should be 6.
// The modifier byte is a bit mask value that represents modifier key status.
// See 5.3.3 Raw Report Mode in datasheet.
func (d Device) SendKeyboardReport(data []byte, modifier byte) error {
    if len(data) != 6 {
        return errors.New("length of data should be 6")
    }

    header := []byte{0xFD, 0x09, 0x01, modifier, 0x00}
    _, err := d.Write(header)
    if err != nil {
        return err
    }
    _, err = d.Write(data)
    return nil
}

RN42を使うとマイコンボードや開発言語を問わず自作キーボードを作れます。別バージョンとして、GR-CITRUSを使ったMicroPythonバージョンも作っています。ハードウェアだけではなくソフトウェアも自分好みに作り込めるのがRN42を使ったキーボード自作の良いところだと思います。

余談ですが、デバイス周りのコードを書くときはuartなどのデバイス固有のインターフェースをコンストラクタやイニシャライザで外から受け取るようにしておくとあとからテストコードを書きやすくなると思います。

class RN42:
    def __init__(self, uart):
        self.uart = uart

    def write(self, key):
        self.uart.write(key)

    def send_keyboard_report(self, data, modifier):
        if len(data) != 6:
            return

        header = bytearray(b'\xFD\x09\x01\x00\x00')
        header[3] = modifier
        self.uart.write(header)
        self.uart.write(data)

おまけ

100円ショップで売られているおゆまるとUVレジンでキーキャップの複製もやってみました。軸の穴の型をとるときには先が尖ったものではなくマイナスドライバーで押しこんであげると良いです。#1200〜#2000のヤスリで形を整えたあとにコンパウンドで磨いています。ネイルのトップコートでコーティングしてあげても良いようです。

詳しくはこちら。
https://twitter.com/takjn/status/1187036320108929024
https://twitter.com/takjn/status/1187359440883707904

おわりに

キーレイアウトを試すための試作機のつもりで作りましたが、十分実用になっています。見た目とか打鍵感とか打鍵音とかはキットには遠く及びませんが、自分で書いたファームウェアで動くキーボードでそのファームウェアを書くという体験はセルフホスティング的な面白さがあって良いです。

この記事はこの記事で紹介した自作キーボードで書きました。