Skip to content

Instantly share code, notes, and snippets.

@ypsilon-takai
Last active May 5, 2022 13:03
Show Gist options
  • Save ypsilon-takai/8bd301e2b11f828b7f3ce34cbbd7957f to your computer and use it in GitHub Desktop.
Save ypsilon-takai/8bd301e2b11f828b7f3ce34cbbd7957f to your computer and use it in GitHub Desktop.
新しいキーボードを作るにはどうしたら

ZMKの使いかた:独自キーボードをZMK対応にする

自分で設計したキーボードをZMK対応にするにはどうすりゃあいいんだろうという話です。 僕もErgoDoxの改造基板でキーボードを作ったことがありますが、そういうときの話です。

今回は、手持ちの Corne を自設計のキーボードということにして環境作ってみます。

情報

ZMKのドキュメントはよく作られていてここにあります。

自分のキーボードをZMK対応にしようとすると、キーマップを変える以上のことが必要になりますが、それについても、ドキュメントに書いてあります。

ドキュメントには全部一通り目を通しておくべきですが、自作ボードを対応させるのであれば、 ZMKのキーボードの論理構成について あたりはとくに読んでおく必要があります。

独自キーボードをZMKに対応させる方法については以下の2つの項目に書いてあります。

New Keyboard Shield

Hadware Metadata Files

ファイルの構成

まずは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

ファイルを作っていきます。

Kconfigファイル

■ 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

Devicetreeファイル

■ メインファイル キーボードにファームウェア載せるには、そのキーボード特有の構成などを記述するファイルが必要です。

.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>
                ;
};

.confファイル

設定ファイルですが、分離型キーボードの場合は、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

不明のboradディレクトリ

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には見栄えが悪いですから。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment