この記事はキーボード #2 Advent Calendar 2024の14日目の記事です。

13日目は、aoi(さとり)さんの人には人のキーマップでした。 キーボードを作ってると自分をみつめなおしてるのは、そうだなぁと思いました。 あと、ミスタイプのレートが下がっていくと、どこかでHIDが自分の一部だと思える点があるのを連想しました。

今年も3つのカレンダーがあります。


今年はRaspberryPi Picoや、そのチップであるRP2040を搭載したキーボードをいくつか作った。

8ビットマイコンであるAVRと比べると比較にならないほど沢山のフラッシュメモリを積んでいたり、 そもそもCPUも高速なので、もっと複雑な処理を行う事ができるはずだ。

それらのキーボードで私が使ってるファームウエアがKMK Firmwareだ。

KMK Firmwareは、CircuitPython上で動くファームウエアで、 Pythonで書かれているので、 カスタマイズが容易であったり、 環境のセットアップがないPCからでもファイル編集ができれば、 キーマップを書き換えることができる。

今回はKMK Firmwareで自分がやってるTipsをまとめておく。

KMK Firmwareは活発に開発が続けられていて、 APIの互換性がない変更もあるので注意。

今回は次のgit commit番号を元に解説してる: b4d4f6a1202b52f878811a631db73985f35eae58

インストール

  1. CircuitPythonを入れる。

RP2040をファームウエア書き込みモードにする。 Picoの場合はBOOTSELを押しながらリセット。

RPI-RP2という名前のボリュームが見えるのでマウント。 このボリュームは、RP2040に入っている書き換えられないファームウエアが出している。

公式サイトから adafruit-circuitpython-raspberry_pi_pico-*.uf2をダウンロード

RPI-RP2ボリュームにこのファイルをコピー

自動で再起動して、CIRCUITPYボリュームが見えるようになる。 こっちは、CircuitPythonが出してるボリュームだ。

  1. KMK Firmwareを入れる。

KMK Firmwareのレポジトリをcloneしてくる。 レポジトリ中のkmkディレクトリをCIRCUITPYボリューム直下に置く。

設定の準備

code.pyというファイルに設定を書く。 主に、ハードウエア設定とキーマップの設定。 この2つは別ファイルに書くべきという流儀もあるが、面倒なので1ファイルに書いてる。

ハードウエア

KMKKeyboardクラスを定義してる。

例:

from kmk.kmk_keyboard import KMKKeyboard as _KMKKeyboard
import board
from kmk.scanners import DiodeOrientation

class KMKKeyboard(_KMKKeyboard):
    col_pins = (  # 列に繋ってるピンのリスト
        board.GP0,
        board.GP1,
        board.GP2,
        board.GP3,
        board.GP4,
        board.GP5,
        board.GP6,
        board.GP7,
        board.GP8,
        board.GP9,
    )
    row_pins = ( # 行に繋ってるピンのリスト
        board.GP10,
        board.GP11,
        board.GP12,
        board.GP13,
    )
    # ダイオードの向き
    #diode_orientation = DiodeOrientation.COL2ROW
    diode_orientation = DiodeOrientation.ROW2COL

    # 論理キーのリスト、各要素はマトリクス内の位置を示す。
    coord_mapping = [
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
        10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
                        34, 35,
    ]

キーマップの定義

例:

keyboard = KMKKeyboard()

from kmk.keys import KC

keyboard.keymap = [
  [
     KC.A,
     # .....
  ]
]

if __name__ == '__main__':
    keyboard.go()

このcode.pyをCIRCUITPYのルートに置いてやるとその場で書き変わる。

デバッグ出力

/dev/ttyACM0にターミナルが出て、そこにデバッグ出力がでてるので見れる。

良くわかんなくなったら初期化する

ファームウエア書き込みモードにし、このサイトからダウンロードした、 flash_nuke.uf2を書き込むと全部消せる。

セットアップからやりなおす。

ストレージをオフにする。

キーマップが完成したら、デバッグ出力と設定用ストレージは不要だ。 かといって、常にオフだと困るので、特定のキーを押しながらリセットされた時だけストレージが見えるようにしたい。

boot.pyという名前で次のようなコードを足してCIRCUITPYに置く。

import board

from kmk.bootcfg import bootcfg

bootcfg(
    sense=board.GP10,  # 読むピン
    source=board.GP0,  # 読む時にLOWに落とすピン
    cdc_console=False,
    storage=False
)

キーテスト用マクロ

マクロを使って各キーのIDを入力するレイヤを作って、 全部のキーが効くか試すといい。

from kmk.keys import KC
from kmk.modules.macros import Macros
macros = Macros()
keyboard.modules.append(macros)
keyid_layer = []
for i in range(32):  # キーの数だけ繰り返す。
    keyid_layer.append(KC.MACRO(str(i),"\n"))

keyboard.keymap = [keyid_layer]

参照

キーマップ設定ヘルパ

次のようなヘルパ関数exを作って、キーマップを設定してる。

import re

def ex(str):
    keys = re.sub('[ \n]+', ' ', str).strip().split(' ')
    return [KC[k] for k in keys]

例:

KC["Ac"]   = KC.HT(KC.A    , KC.LCTRL)
KC["Zs"]   = KC.HT(KC.Z    , KC.LSHIFT)
KC["Xa"]   = KC.HT(KC.X    , KC.LALT)
KC["Cg"]   = KC.HT(KC.C    , KC.LWIN)
KC[",g"]   = KC.HT(KC[','] , KC.RWIN)
KC[".a"]   = KC.HT(KC['.'] , KC.RALT)
KC["/s"]   = KC.HT(KC['/'] , KC.RSHIFT)
KC[";c"]   = KC.HT(KC[';'] , KC.RCTRL)

keyboard.keymap = [
  ex('''
Q  W  E  R  T  Y  U  I  O  P
Ac S  D  F  G  H  J  K  L  ;c
Zs Xa Cg V  B  N  M  ,g .a /s
          SPC  ENT
  ''')
]

ジョイスティックをつける。

プレステとかについてるアナログのやつ。 次のような感じのモジュールを作って使ってる。

何でデフォルトで入ってないんだろう?

from kmk.modules import Module
from kmk.kmktime import PeriodicTimer
import board
import analogio
from kmk.keys import AX

class Joystick(Module):
    def __init__(
        self,
        x,
        y,
    ):
        self.x = x
        self.y = y

        self.timer = PeriodicTimer(20)

        self.center_x = 0
        self.center_y = 0

    def during_bootup(self, keyboard):
        x = 0
        y = 0
        for i in range(100):
            x += self.x.value
            y += self.y.value
        self.center_x = int(x / 100)
        self.center_y = int(y / 100)

    def before_matrix_scan(self, keyboard):
        if not self.timer.tick():
            return
        x = self.x.value - self.center_x
        y = self.y.value - self.center_y
        x = int(x / 1000)
        y = int(y / 1000)
        AX.X.move(keyboard, -x)
        AX.Y.move(keyboard, -y)

    def after_matrix_scan(self, keyboard):
        return

    def before_hid_send(self, keyboard):
        return

    def after_hid_send(self, keyboard):
        return

    def on_powersave_enable(self, keyboard):
        return

    def on_powersave_disable(self, keyboard):
        return

keyboard.modules.append(Joystick(
  analogio.AnalogIn(board.A0),
  analogio.AnalogIn(board.A1),
))

まとめ

KMK Firmwareはカスタマイズの幅が広いので、 もっといろいろやっていきたい。


今回は、mk3(2023)とKMKで動いてるcurvyキーボードで書きました。

明日はeswaiさんのSolidPythonの話です。