🐡

prk_firmware 入門

2021/09/20に公開

prk_firmware とは

prk_firmware とは、@hasmikin 氏が作成している、キーボード向けの新しいファームウェアである。prk_firmware の特徴は以下である。

  • RP2040 上で動作する
  • キーマップなどの定義を PicoRuby で記述できる
  • キーマップを定義後にコンパイル不要
  • キーマップやファームウェアの書き込みのためにツールは必要なくストレージへのドラッグアンドドロップで行える

詳しくは、先日行われた RubyKaigi Takeout 2021 での氏の発表をご覧いただきたい。

https://rubykaigi.org/2021-takeout/presentations/hasumikin.html

発表後も精力的に開発が進んでおり、以下のことができるようになっている。

  • pico_bootsel_via_double_reset に対応し、一般的な自作キーボードに実装されているリセットボタンの 2 回押しでファームウェアの書き込みモードへの移行が可能になった
  • COM ポートからのデバッグ情報読み込み

この記事の目的

この記事では、prk_firmware に興味を持って使いたくなった人が、現在発売されている自作キーボードキットを prk_firmware で入力をすることができるようになることを目指している。しかし、諸般の事情により、LED や OLED に対するサポートは行わない。

記事執筆時点での prk_firmware で動かせる自作キーボードの制限

RP2040 搭載の開発ボードが使えること

prk_firmware で動作するキーボードを使うためには、prk_firmware を動かすことが可能なマイコンがのったボードを用意する必要がある。先にも書いたが prk_firmware は RP2040 と呼ばれる Raspberry Pi 財団が作っている Rasberry シリコン上で動作する。RP2040 が直接のっている自作キーボードを筆者は知らないため、RP2040 がのっている開発ボードを用意することとなる。

このマイコンが載っているボードといえば、Raspberry Pi Pico であろう。ただし、この Raspberry Pi Pico が使われている自作キーボードは Gherkin for the Raspberry Pi Pico など存在はしているが、種類が殆んど無いのが現状である。

現在、自作キーボードによく使われる開発ボードといえば ProMicro である。この ProMicro と同じサイズかつピンアサインとなっている RP2040 搭載の開発ボードもある。それは、SparkFun がだしている Pro Micro - RP2040 と、せきごん氏が作られている PicoMicro である。これらを使えば、選べる自作キーボードの種類が多くなる。

さて、氏の発表中以下の 2 点が注意点として言われている。

  1. VCC ピンと RAW ピンがつながっているもの
  2. ボードの部品実装面が PCB 向きになっているもの

1 については、そのようになっている自作キーボードを筆者は知らない。もし、気になるのであれば、自作キーボードの購入店や作者に質問してみるのがよいだろう。

2 についてだが発表でも触れられている通り、SparkFun のボードを使うと発生する。ただし、背の高いコンスルーを使うことで使用できる。また、その際、ケースのスペーサーの高さも変更しないと、ボトムプレートなどが閉まらないということがある。また、キーボードが分割キーボードであり、PCB が右にも左にもなるタイプのリバーシブル基板の場合、ProMicro を刺す穴が 2 つある。これをその自作キーボードのビルドガイドに書かれている ProMicro の向きとは逆にして、指定されていないほうの穴にはめることで対応できることもある。なんにせよ、PicoMicro を使う場合は、この高さ問題は発生しない。

キーレイアウトに制限がある

記事執筆時点において、動作が確認されているキーボードのレイアウトは、左右分離型であれば左右のキー数が同じものです。ですので、Mint60 など一般的なキーボードを分割したような、左右のキー数が異なるようなレイアウトの動作が確認されていない。
ただし、これについては動作が確認されていないというだけど、動作しなくないとは限りません。動作方法については、後述する 「キーマトリクスと存在しないキーの定義について」 を参照してほしい。

QMK firmware から prk_firmware に移植する際に知っておきたいこと

ピンアサインについて

prk_firmware でキーマップを定義する際、開発ボードのどのピンがキーのマトリクス読み込みに使われているかを Keyboard#init_pins で指定する必要がある。そのため、キーマップを定義しようとしている自作キーボードがどのピンを使っているかというのを知る必要がある。これは、QMK Firmware のソースの中の各キーボードが定義されているディレクトリの中にある config.h に書かれている。この中にある MATRIX_ROW_PINSMATRIX_COL_PINS で定義されている値になる。

QMK Firmware の config.h に書かれている値は、D4, C6, D7, E6 のような値で記述されている。これは、ProMicro のピンをポート番号で記述しているので、Keyboard#init_pins にはピン番号を渡す必要があるため、これを変換して記述しないといけない。変換について対応するピンがわからない場合は、筆者が QMK から変換するときに使っている自作のスクリプト中に対応表があるので、そちらを参考されたい。

https://github.com/takkanm/prk_keymap_generator/blob/eac61ff97b9679c292805e2b4a6793f71d61af9b/lib/prk_keymap_generator.rb#L9-L26

キーの定義順について

prk_firmware で Keyboard#add_layer でレイヤー毎のキーマップを定義する。これを定義する際に注意するポイントがある。

QMK Firmware では、LAYOUT マクロで、キーマップを定義する際の順序と、キーの読みだされる順序が指定されている。prk_firmware でスプリットキーボードのキーマップを記述する際は、これを少し意識する必要がある。

prk_firmware で Keyboard#add_layer をする際、左上から右に向かってキーコードを書いていくことになる。しかし、一部の分割キーボードでは、入力されるキーが定義した順序と逆向きになることがある。これには LAYOUT マクロのキーマップを定義する際の順序と、キーの読みだされる順序が関連してくる。

Keyboard#add_layer で指定した順でキーが定義されるのは、https://github.com/qmk/qmk_firmware/blob/ef5c6ea096033423c9b9e141c1fc94dcc41d84fa/keyboards/lets_split/rev2/rev2.h#L18-L33 のように、LAYOUT マクロに渡している一つ目の配列と二つ目の配列で定義されているコードで反転が行われているキーボードだ。https://github.com/picoruby 配下で公開されている crkbdclaw44 などは、このような定義になっている。

一方 https://github.com/qmk/qmk_firmware/blob/ef5c6ea096033423c9b9e141c1fc94dcc41d84fa/keyboards/lets_split/rev2/rev2.h#L37-L52 のように、一つ目の配列と二つ目の配列で順序が同じキーボードは、Keyboard#add_layer に渡す際に見た目の順序でキーを定義すると実際に入力されるキーが反転してしまう。現時点で、prk_firmware では、このようなキーボードをいい感じにサポートする機能はないため、Keyboard#add_layer に渡すキーマップの順序をいい感じにして渡す必要がある。

キーマトリクスと存在しないキーの定義について

自作キーボードの多くでは少ないピン数で多くのキーを読み込めるようにするため、キーマトリクスという方式が使われている。詳しくは、https://blog.ikejima.org/make/keyboard/2019/12/14/keyboard-circuit.html などを参考にしてもらいたい。

前述した Keyboard#init_pins で定義しているピンというのは、このキーマトリクスに使われているピンとなる。マトリクスは Col と Row の掛け合わせになるため、そのマトリクスに全てのキースイッチを割り当てると、格子配列のキーボードのように全ての Row で同じ Col 数のキーボードとなる。しかし、実際はそのように全てのマトリクスにキースイッチを割り当てらているものだけではない。

このように全てのマトリクスにキースイッチが割り当てられていない自作キーボードのレイアウトを Keyboard#add_layer で定義する際、キースイッチが割り当てられていない箇所を KC_NO で埋める必要がある。例えば、Claw44 向けのキーマップでは、https://github.com/picoruby/prk_claw44/blob/97cd7708b85b5f38b3f8ed163a7fb1069853dcab/keymap.rb#L22-L27 のように KC_NO が割り当てられている。

この KC_NO を埋める位置についても、QMK Firmware の LAYOUT マクロの定義が関係している。LAYOUT マクロの二つ目の配列で KC_NO と書かれている位置が Keyboard#add_layer でも KC_NO を定義する必要がある位置となる。https://github.com/qmk/qmk_firmware/blob/d9ca201f79dce27de667c638ed6404bd4d10146a/keyboards/claw44/rev1/rev1.h#L11-L20

さて、先ほど「左右のキー数が異なるようなレイアウトの動作が確認されていない。」と書いた。左右のキー数が異なるようなレイアウトのキーボードでもキーマトリクスが使われている。そのため、LAYOUT マクロの定義を確認し、KC_NO を埋めていけば動作する可能性がある。「動作する」と断定で書いていないのは、現時点で動作を確認した報告がないためである。

実際の例 : Zinc 向けの keymap.rb を作成する

では、最後に実際に販売されている自作キーボードを prk_firmware で動くようにしてみたいと思う。

prk_firmware の keymap.c には、テンプレート的に書かないといけない箇所や、ピンのアサイン変換などがある。そこで、筆者は QMK Firmware の定義をコピペしていき、ベースとなるスクリプトを用意している。今回は、これを使っていく。https://github.com/takkanm/prk_keymap_generator をクローンし、bin/prk_keymap_generator を実行し、表示される内容にあわせて QMK Firmware に定義されている内容を入力していくと、keymap.rb のひな型ができるようになっている。

もちろん、このスクリプトを使わずとも自分で記述することも可能である。

今回は、Zinc という 40% のスプリットキーボードの keymap.rb を作成する。

Zinc のファームウェアが定義されている https://github.com/qmk/qmk_firmware/tree/d9ca201f79dce27de667c638ed6404bd4d10146a/keyboards/zinc の中を参考にしつつ、prk_keymap_generator を実行する。

> prk_keymap_generator
Is split keyboard? [y/N] : y
MATRIX_ROW_PINS in config.h : F6, F7, B1, B3
MATRIX_COL_PINS in config.h :  F4, D4, C6, D7, E6, B4
add layer ? [y/N] : y
layer name: default
add Row or n(o) :  KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,                      KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_BSPC,
add Row or n(o) : KC_LCTL, KC_A,    KC_S,    KC_D,    KC_F,    KC_G,                      KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT
add Row or n(o) : KC_LSFT, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,                      KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_ENT
add Row or n(o) :       KC_ESC,  ADJUST,  KC_LGUI, KC_LALT, LOWER,   KC_SPC,                    KC_SPC,  RAISE,   KC_LEFT, KC_DOWN, KC_UP,   KC_RGHT
add Row or n(o) : n
add layer ? [y/N] : y
layer name: raise
add Row or n(o) : KC_GRV,  KC_1,    KC_2,    KC_3,    KC_4,    KC_5,                      KC_6,    KC_7,    KC_8,    KC_9,    KC_0,    KC_DEL,
add Row or n(o) : _______, KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,                     KC_F6,   KC_MINS, KC_EQL,  KC_LBRC, KC_RBRC, KC_BSLS,
add Row or n(o) :  _______, KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,                    KC_F12,  _______, _______, _______, _______, _______,
add Row or n(o) :  _______, _______, _______, _______, _______, _______,                   _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
add Row or n(o) : n
add layer ? [y/N] : y
layer name: lower
add Row or n(o) : KC_TILD, KC_EXLM, KC_AT,   KC_HASH, KC_DLR,  KC_PERC,                   KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, _______,
add Row or n(o) :  _______, _______, _______, _______, _______, _______,                   KC_MINS, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE,
add Row or n(o) : _______, _______, _______, _______, _______, _______,                   _______, _______, _______, KC_HOME, KC_END,  _______,
add Row or n(o) : _______, _______, _______, _______, _______, _______,                   _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
add Row or n(o) : n
add layer ? [y/N] : N
finish!

コマンドが完了すると、そのディレクトリに keymap.rb というファイルができている。中身は以下のようになっている。

while !$mutex
  relinquish
end

kbd = Keyboard.new

kbd.split = true

kbd.init_pins(
  [ 27, 26, 22, 20 ],
  [ 29, 4, 5, 6, 7, 8 ]
)

kbd.add_layer :default, %i[
  KC_TAB KC_Q KC_W KC_E KC_R KC_T KC_Y KC_U KC_I KC_O KC_P KC_BSPC
  KC_LCTL KC_A KC_S KC_D KC_F KC_G KC_H KC_J KC_K KC_L KC_SCLN KC_QUOT
  KC_LSFT KC_Z KC_X KC_C KC_V KC_B KC_N KC_M KC_COMM KC_DOT KC_SLSH KC_ENT
  KC_ESC ADJUST KC_LGUI KC_LALT LOWER KC_SPC KC_SPC RAISE KC_LEFT KC_DOWN KC_UP KC_RGHT
]

kbd.add_layer :raise, %i[
  KC_GRV KC_1 KC_2 KC_3 KC_4 KC_5 KC_6 KC_7 KC_8 KC_9 KC_0 KC_DEL
  _______ KC_F1 KC_F2 KC_F3 KC_F4 KC_F5 KC_F6 KC_MINS KC_EQL KC_LBRC KC_RBRC KC_BSLS
  _______ KC_F7 KC_F8 KC_F9 KC_F10 KC_F11 KC_F12 _______ _______ _______ _______ _______
  _______ _______ _______ _______ _______ _______ _______ _______ KC_MNXT KC_VOLD KC_VOLU KC_MPLY
]

kbd.add_layer :lower, %i[
  KC_TILD KC_EXLM KC_AT KC_HASH KC_DLR KC_PERC KC_CIRC KC_AMPR KC_ASTR KC_LPRN KC_RPRN _______
  _______ _______ _______ _______ _______ _______ KC_MINS KC_UNDS KC_PLUS KC_LCBR KC_RCBR KC_PIPE
  _______ _______ _______ _______ _______ _______ _______ _______ _______ KC_HOME KC_END _______
  _______ _______ _______ _______ _______ _______ _______ _______ KC_MNXT KC_VOLD KC_VOLU KC_MPLY
]

kbd.define_mode_key :RAISE, [ :KC_NO, :raise, 120, 150 ]
kbd.define_mode_key :LOWER, [ :KC_NO, :lower, 120, 150 ]

kbd.start!

さて、QMK Firmware の LAYOUT マクロの定義内容を見ると、Keyboard#add_layer へのキーマップの渡し方がこのままだと、右手が左右逆になってしまうのがわかる。しかし、実際にキーが読まれる順序で定義をすると、キーマップを変更したいときの脳へのコストが増えてしまう。

そこで、今回は Keyboard#add_layer に渡す配列を左右ごとに別の配列にし、右手側になる配列は reverse し、最後に一つの配列に展開するという方針をとることにした。CRuby であれば Array#reverseArray#flatten を使えばすぐに終わるのだが、PicoRuby の VM バックエンドである mruby/c にはそれらのメソッドがないため、自前で定義する必要がある。

最終的に以下のようにして、キーが入力できるようになった。

while !$mutex
  relinquish
end

kbd = Keyboard.new

kbd.split = true

kbd.init_pins(
  [ 27, 26, 22, 20 ],
  [ 29, 4, 5, 6, 7, 8 ]
)

class Array
  def reverse
    ary = self
    reverse_ary = []
    ary.each do |elm|
      reverse_ary.unshift elm
    end
    reverse_ary
  end

  def flatten
    flat_ary = []
    ary = self

    ary.each do |elm|
      if elm.class == Array
        elm.each do |e|
          flat_ary.push e
        end
      else
        flat_ary.push e
      end
    end
    flat_ary
  end
end

kbd.add_layer :default, [
  %i( KC_TAB  KC_Q   KC_W    KC_E      KC_R  KC_T ),   %i( KC_Y     KC_U       KC_I    KC_O      KC_P KC_BSPACE ).reverse,
  %i( CTL_ESC KC_A   KC_S    KC_D      KC_F  KC_G ),   %i( KC_H     KC_J       KC_K    KC_L KC_SCOLON  KC_QUOTE ).reverse,
  %i( KC_LSFT KC_Z   KC_X    KC_C      KC_V  KC_B ),   %i( KC_N     KC_M   KC_COMMA  KC_DOT  KC_SLASH   KC_RSFT ).reverse,
  %i( KC_ESC  ADJUST KC_LALT CMD_LANG2 RAISE KC_SPACE ), %i( KC_ENTER LOWER ALT_LANG1 KC_DOWN     KC_UP   KC_RGHT ).reverse,
].flatten

kbd.add_layer :lower, [
  %i( KC_TAB  KC_EXLM KC_AT   KC_HASH     KC_DLR  KC_PERC ),  %i( KC_CIRC  KC_AMPR  KC_ASTER    KC_LPRN     KC_RPRN KC_BSPACE ).reverse,
  %i( CTL_ESC KC_LABK KC_LCBR KC_LBRACKET KC_LPRN KC_QUOTE ), %i( KC_MINUS KC_EQUAL KC_LCBR     KC_RCBR     KC_PIPE KC_GRAVE ).reverse,
  %i( KC_LSFT KC_RABK KC_RCBR KC_RBRACKET KC_RPRN KC_DQUO ),  %i( KC_UNDS  KC_PLUS  KC_LBRACKET KC_RBRACKET KC_BSLS KC_TILD ).reverse,
  %i( _______ _______ _______ _______     _______ _______ ),  %i( _______  _______  KC_MNXT     KC_VOLD     KC_VOLU KC_MPLY).reverse,
].flatten

kbd.add_layer :raise, [
  %i( KC_GRAVE KC_1    KC_2    KC_3    KC_4    KC_5 ),     %i( KC_6    KC_7    KC_8    KC_9     KC_0     KC_DEL ).reverse,
  %i( CTL_ESC  KC_F2   KC_F10  KC_F12  KC_LPRN KC_QUOTE ), %i( KC_LEFT KC_DOWN KC_UP   KC_RIGHT KC_RIGHT XXXXXXX ).reverse,
  %i( _______  KC_F7   KC_F8   KC_F9   KC_F10  KC_F11 ),   %i( KC_F12  _______ _______ _______  _______  _______ ).reverse,
  %i( _______  _______ _______ _______ _______ _______ ),  %i( _______ _______ KC_MNXT KC_VOLD  KC_VOLU  KC_MPLY ).reverse,
].flatten

kbd.define_mode_key :LOWER, [ :KC_NO, :lower, 120, 150 ]
kbd.define_mode_key :RAISE, [ :KC_NO, :raise, 120, 150 ]
kbd.define_mode_key :ALT_LANG1,   [ :KC_LANG1,             :KC_LALT,                     120,              400 ]
kbd.define_mode_key :CMD_LANG2,   [ :KC_LANG2,             :KC_RGUI,                     120,              400 ]
kbd.define_mode_key :CTL_ESC,     [ :KC_ESCAPE,            :KC_LCTL,                     120,              150 ]

kbd.start!

https://gist.github.com/takkanm/bf937b83c67c42d801a31af20132d244

最後に

筆者は、自作キーボードに 2018 年ぐらいからはまっている。QMK Firmware でキーマップを書き換えるには、コンパイルや書き込みツールが必要だったり、QMK Firmware が破壊的な更新をしてきて、今までコンパイルできていたのができなくなったりといった大変さを感じることもあった。そのような経験をしていると、prk_firmware の Ruby ファイルを書き、それをドラッグアンドドロップすれば動くというのは強力なものである。

しかし、prk_firmware はできたばっかりである。まだ、定義など不便なところがあるかもしれない。これについては、自作キーボードの仕組みなどを知っていて、Ruby を書くことができれば、難しいことはわからずとも修正していくことができる。

ぜひ使ってみて、もっとこうしたいという気持ちができたら、フィードバックをしていきたいと思う。

Discussion