自分で設計したキーボードをZMK対応にするにはどうすりゃあいいんだろうという話です。 僕もErgoDoxの改造基板でキーボードを作ったことがありますが、そういうときの話です。
今回は、手持ちの Corne を自設計のキーボードということにして環境作ってみます。
ZMKのドキュメントはよく作られていてここにあります。
自分のキーボードをZMK対応にしようとすると、キーマップを変える以上のことが必要になりますが、それについても、ドキュメントに書いてあります。
ドキュメントには全部一通り目を通しておくべきですが、自作ボードを対応させるのであれば、 ZMKのキーボードの論理構成について あたりはとくに読んでおく必要があります。
独自キーボードをZMKに対応させる方法については以下の2つの項目に書いてあります。
まずはNew Keyboard Shieldを見ていきます。
新しいshildを追加するということは、ZMKのソースコードに新しいキーボード用のディレクトリツリーをを用意していくということなのですが、Noteを見ると、プロトタイプとかハンドワイヤの自作とかだったら、zmk-configの方を使って作るのがお勧めとのこと。
たしかに、作っている途中だといろいろ変更するだろうし、場合によっては公開しないのかもしれない。ZMKのソースもかなり活発に開発が進んでいるようだから、自作の方をいじりながらZMKのソースにも追随するのはなかなか面倒だと思う。
ということで、zmk-configに入れる方法でやってみることにします。
具体的には、
When following the rest of this guide, replace the app/ directory in the ZMK main repository with the config/ directory in your user config repository. For example, app/boards/shields/<keyboard_name> should now be config/boards/shields/<keyboard_name>.
ということで zmk-config/config ディレクトリで作業するということのようですね。
キーボードの名前はYpsilon Corne で ycorne にします。
ディレクトリを以下のように作成
cd zmk-config/config
mkdir -p boards/shields/ycorne
cd boards/shields/ycorne
ファイルを作っていきます。
■ Kconnfig.shield
Kconfig.shieldファイルには対象キーボード用の個別の設定を入れるとあります。そもそもKconfigはZephyrの設定ですからそういうことなのでしょう。
実際には、キーボードの名前というか機種名を設定しています。
分割キーボードの場合は、それぞれが別のキーボードの扱いになるので、ここは2つの設定が入ります。
corneから流用すると以下のような感じ。
config SHIELD_YCORNE_LEFT
def_bool $(shields_list_contains,ycorne_left)
config SHIELD_YCORNE_RIGHT
def_bool $(shields_list_contains,ycorne_right)
■ Kconfig.defconfig
Kconfig.shieldで定義したキーボードのパラメータの設定をする場所です。
以下はcorneの定義です。 解読結果をはさみますが、1つのファイルです。
if SHIELD_YCORNE_LEFT
config ZMK_KEYBOARD_NAME
default "Ypsilon Corne"
config ZMK_SPLIT_BLE_ROLE_CENTRAL
default y
endif
左手側をビルドするときだけのパラメータです。
PC本体と通信するのは左だからか、こちらに名前(ZMK_KEYBOARD_NAME)を設定します。ここにある名前がPC側に通知されます。
ZMK(Zephyr?)では、分離型のキーボードは、PCと通信する側を「central」、PCとでなくcentralと接続する側を「peripheral」と呼びます。
以下はcentral/periferal共通の設定です
if SHIELD_YCORNE_LEFT || SHIELD_YCORNE_RIGHT
config ZMK_SPLIT
default y
corneは左右分離型です。
以下はディスプレイ関連です。
if ZMK_DISPLAY
config I2C
default y
config SSD1306
default y
config SSD1306_REVERSE_MODE
default y
endif # ZMK_DISPLAY
corneのディスプレイはI2C接続のOLEDでSSD1306はそのドライバICです。 取り付け方向にあわせて表示の向きを逆にしているようです。
以下はLVGLの設定です。
LVGLは Light and Versatile Graphics Library とのことです。
if LVGL
config LVGL_HOR_RES_MAX
default 128
config LVGL_VER_RES_MAX
default 32
config LVGL_VDB_SIZE
default 64
config LVGL_DPI
default 148
config LVGL_BITS_PER_PIXEL
default 1
choice LVGL_COLOR_DEPTH
default LVGL_COLOR_DEPTH_1
endchoice
endif # LVGL
endif
画面サイズと、DPI(Dot Per Inch)と、ピクセル毎のビット数と、色数、はわかるけど、VDBって何でしょう。
ZMKのソースを探ってみると、 zmk/zephyr/lib/gui/lvgl/lvgl.c でこんな風に定義されてます。 画面描画用のバッファのサイズを計算するために使う値ですね。 なんで64だろう。100で割ってるのがよくわからないなぁ。
#define BUFFER_SIZE (CONFIG_LVGL_BITS_PER_PIXEL * ((CONFIG_LVGL_VDB_SIZE * \
CONFIG_LVGL_HOR_RES_MAX * CONFIG_LVGL_VER_RES_MAX) / 100) / 8)
#define NBR_PIXELS_IN_BUFFER (BUFFER_SIZE * 8 / CONFIG_LVGL_BITS_PER_PIXEL)
/* NOTE: depending on chosen color depth buffer may be accessed using uint8_t *,
* uint16_t * or uint32_t *, therefore buffer needs to be aligned accordingly to
* prevent unaligned memory accesses.
*/
static uint8_t buf0[BUFFER_SIZE] __aligned(4);
#ifdef CONFIG_LVGL_DOUBLE_VDB
static uint8_t buf1[BUFFER_SIZE] __aligned(4);
#endif
■ メインファイル キーボードにファームウェア載せるには、そのキーボード特有の構成などを記述するファイルが必要です。
.dtsiファイルがその一つです。dtsiは DeviceTree Source Include の略です。
corneのdtsiファイルは corne.dtsi ファイルです。
まずは、キーボードの構成の記述です。 OLEDが付いていて、キーボードのスキャン方式と、後述するトランスフォームを設定しています。
#include <dt-bindings/zmk/matrix_transform.h>
/ {
chosen {
zephyr,display = &oled;
zmk,kscan = &kscan0;
zmk,matrix_transform = &default_transform;
};
続いて、トランスフォームはキーボードがキーをスキャンする行列マトリクスと実際のキーの物理配置(Raw, Col)の対応関係の記述です。
これが無くてもキーボードのマッピングの設定はできますが、かなりわかりにくくなるでしょう。
default_transform: keymap_transform_0 {
compatible = "zmk,matrix-transform";
columns = <12>;
rows = <4>;
// | SW1 | SW2 | SW3 | SW4 | SW5 | SW6 | | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 |
// | SW7 | SW8 | SW9 | SW10 | SW11 | SW12 | | SW12 | SW11 | SW10 | SW9 | SW8 | SW7 |
// | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 | | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 |
// | SW19 | SW20 | SW21 | | SW21 | SW20 | SW19 |
map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,11)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,11)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,11)
RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8)
>;
};
corneは4行12列のマトリクスを持っています。 0行0番目,RC(0,0)にスキャンされるのは、左キーボードの左上のキーでというようなことが記述されます。
corneの初期の構成では、キーボードの端の方を切り取って片側5列の構成にすることもできました。その構成のときに使うトランスフォームも用意されています。
five_column_transform: keymap_transform_1 {
次の記述は、ボードのポートとと行の対応です。
列の対応はどこにあるんだろう。
kscan0: kscan {
compatible = "zmk,kscan-gpio-matrix";
label = "KSCAN";
diode-direction = "col2row";
row-gpios
= <&pro_micro 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
;
};
// TODO: per-key RGB node(s)?
};
これでキーボードの設定はおわり。
次は、I2Cの設定。 I2CはOLEDの接続に使います。
設定内容はなんとなくわかるものも、ちっともわからないものもありますね。
&pro_micro_i2c { status = "okay";
oled: ssd1306@3c {
compatible = "solomon,ssd1306fb";
reg = <0x3c>;
label = "DISPLAY";
width = <128>;
height = <32>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <31>;
segment-remap;
com-invdir;
com-sequential;
prechargep = <0x22>;
};
};
■ Shield Overlay
次は.overlayファイル。
さきほど列の設定がありませんでしたが、分離キーボードは列のアサインが左右別々だからその設定も個別にしなければならないということですね。
名前がoverlayなのは基本の設定に被せるからですね。
以下は、左用 corne_left.overlay
```text
#include "ycorne.dtsi"
&kscan0 {
col-gpios
= <&pro_micro 21 GPIO_ACTIVE_HIGH>
, <&pro_micro 20 GPIO_ACTIVE_HIGH>
, <&pro_micro 19 GPIO_ACTIVE_HIGH>
, <&pro_micro 18 GPIO_ACTIVE_HIGH>
, <&pro_micro 15 GPIO_ACTIVE_HIGH>
, <&pro_micro 14 GPIO_ACTIVE_HIGH>
;
};
+++
このように列の対応が記述されています。
右用は、同様ですが、配線の構造が同じなら、左から右にスキャンするためには列の順番が逆になり、また、左手側と右手側で順にスキャンするためにオフセットが設定されています。
```text
#include "ycorne.dtsi"
&default_transform {
col-offset = <6>;
};
&kscan0 {
col-gpios
= <&pro_micro 14 GPIO_ACTIVE_HIGH>
, <&pro_micro 15 GPIO_ACTIVE_HIGH>
, <&pro_micro 18 GPIO_ACTIVE_HIGH>
, <&pro_micro 19 GPIO_ACTIVE_HIGH>
, <&pro_micro 20 GPIO_ACTIVE_HIGH>
, <&pro_micro 21 GPIO_ACTIVE_HIGH>
;
};
設定ファイルですが、分離型キーボードの場合は、3つ必要です。
まずは、corne.confファイル。 左右共通の設定が入ります。
# Uncomment the following lines to enable the Corne RGB Underglow
CONFIG_ZMK_RGB_UNDERGLOW=y
CONFIG_WS2812_STRIP=y
# Uncomment the following line to enable the Corne OLED Display
CONFIG_ZMK_DISPLAY=y
UNDERGROWはアンダーグローだからまあいいとしてWS2812はアンダーグロー用のLEDの型番だけど両方必要なのはなぜ?
DISPLAYはOLED Dilplayですね。
あとは、左右用の corne_left.conf と corne_right.conf ですが、両方とも空でした。
メタデータファイルには、キーボードについてのハイレベルの情報を入れます。ツールやサイトのハードウェアリストの作成などに使われるようです。
このツールにはファームのビルドツールも含まれると思われるのでちゃんと書かないと動くものができなさそう。
ファイル名は、corne.zmk.yml
file_format: "1"
id: corne
name: Ypsilon Corne
type: shield
url: <my github site>
requires: [pro_micro]
exposes: [i2c_oled]
features:
- keys
- display
- underglow
siblings:
- ycorne_left
- ycorne_right
corneのソースにはbuildディレクトリがあって、そこにファイルが入っています。
ファイル名は、nice_nano.overlay なので、nice!nano用の追加設定ファイルだと思われます。 spiにLEDストリップが付いているということの設定のように見えます。
このあたりはソースを見てみないとわからないかも。
結局以下のファイルが必要になります。
# Kconfigファイル2つ
Kconfig.defconfig
Kconfig.shield
# dtsiファイルと左右個別ovarlay
corne.dtsi
corne_left.overlay
corne_right.overlay
# 設定ファイル
corne.conf
corne_left.conf
corne_right.conf
# メタデータファイル
corne.zmk.yml
# デフォルトのキーマップファイル
corne.keymap
# 不明: 追加の設定?
boards
- nice_nano.overlay
ファイルの準備ができたので、ビルドしてみます。 ファイルの内容はオリジナルのcorneと同じものですが、名前をycorneに変更します。
ドキュメントにはコマンドのことについては書いていないので、とりあえず、いま使っているコマンドを使ってみます。
シールドに ycorne を指定することで、今用意したファイルが使われているかどうかわかるはずです。
west build -d build/ycorne-left -b nice_nano_v2 -- -DZMK_CO
NFIG="<path-to>/zmk-config/config" -DSHIELD=ycorne_left
west build -d build/ycorne-right -b nice_nano_v2 -- -DZMK_CO
NFIG="<path-to>/zmk-config/config" -DSHIELD=ycorne_right
なんと両方とも問題なくビルドが終了。
キーボードに入れてみると、バインドし直さなくてもPCに接続できます。 metadataの中のidをcorneのままにしてあるからではないこと思います。
デバイス名の方は、ちゃんとで設定した「Ypsilon corne」になっています。
これで、キーマトリクスの異なるキーボードをZMKに対応させることはできるようになったと思います。
コードはconfig側にしか入れていませんが、ZMKに入れ込もうと思えば作ったものを適切な場所に入れればよさそうです。
バックライトとかアンダーグローとかについては、そもそもの設定であまり追っていないこともあり、調査は不十分です。 本格的にやるのであれば、そのあたりを詰めていく必要はありますね。
OLEDについては、そもそもZMKのサポートが不十分な状況ですが、ここのところ進んでいるようなので、進展すれば詰めていきたいところ。 今の表示は寂しいし、corneには見栄えが悪いですから。