見出し画像

QMK入門: meishi Trackball Moduleを自作ファームウェアで動かす

作成したファームウェアはこちらのリポジトリでコードを上げています。不明点あればこちらでご確認ください。

ちなみにヘッダー画像はDALL-Eが描いた、なんか列が多くてガタガタして草が生えるキーボードです。

背景

キーボードから手を離さずマウスを移動させるのに使えるのでは?と思って買ってみた、名刺サイズのトラックボールです。スイッチ2つとロータリーエンコーダーが付いており、マウスのように使えます。名前の通り、トラックボール部分だけを切り出して使うこともできる優れもの。

マイコン(Pro Micro)を逆にはんだ付けして泣きながら剥がしたり、USB接続部分がもげて再度買うことになったりと、色々ありながら組み立て完了しました。制作者による丁寧なガイドがあるのですが、それでも向きを間違えるので、自らの愚かさを存分に味わうことができます。

トラックボールの角度問題

さて、無事に組み立てられ、トラックボールや各種スイッチが動くのは確認できましたが、なぜかトラックボールの角度が微妙に変わりません。`ROT_R15`、`ROT_L15`というキーコードを発行するたびに15°ずつ変わるはずなのですが、どうやら90°くらいでリセットされているようです。

ありがたいことにファームウェアのコードは公開されています。読んでみると回転可能な角度が決め打ちで、トータル120°になっている模様です。意図的なものでしたが、見る限りコードをいじらないと全方位に設定できなさそうでした。

# define COCOT_ROTATION_ANGLE { -60, -45, -30, -15, 0, 15, 30, 45, 60 }

https://github.com/aki27kbd/qmk_firmware/blob/067112151a0634b7a1d01604041e5347179431e0/keyboards/aki27/trackball_module/trackball_module.c#L54

QMKとコンパイル通らない問題

公開されているファームウェアをちょこっといじれば解決できそうです。このファームウェアはQMKが使われています。

QMKは自作キーボードでよく使われるファームウェアライブラリです。これに従うと簡単に実装できるだけでなく、ブラウザ上でキーリマップするなどの便利機能に簡単に対応できるという凄いやつです。

そして精力的に開発されているため、破壊的変更もちょくちょくあります。meishi Trackball Moduleの公開されているコードもQMK 0.16という少し昔のバージョンで作られているため、QMK 0.22ではコンパイルエラーになります。

……ならどうするか。折角なので、新しいバージョンに対応したものを再実装してしまおう、と100人居れば1000人が思うことでしょう。ご多分に漏れず、自分も10000人の内の一人だったので、100000回目の車輪の再発明をすることにしました。

ファームウェアを書く

QMKは非常にドキュメントが充実しているので、大体公式を見ると解決します。

そのため、ここからは躓いたポイントだけ記載してゆきます。

今回実装する範囲

キーが押せて、ロータリーエンコーダーでスクロールできて、トラックボールが動けばOKとします。元々のファームウェアはもっと高機能ですが、ここでは最低限の用途に絞ります。

各機能を実装した時点で焼き込んで動作確認すると、進捗が感じられて楽しいです。

便利なツール

  • QMK Toolbox

    • ファームウェアの焼き込みをしてくれます。ブラウザから焼くこともできますが、コンソール出力を見るために入れておくと安心です。

  • REMAP

    • ファームウェアのリポジトリサイトです。とても便利。公式のmeishi Trackball Moduleのファームウェアもここから焼いたりDLできます。Chromeで開くのが安心。今回はもっぱら動作確認のために使います。

各種HW設定値

HWの型番、ピン設定など。元のコードから引っ張ってきました。困った時に参照する用です。

  • MCU: atmega32u4

  • bootloader: atmel-dfu

  • トラックボールのセンサー: adns5050

  • diode_direction: COL2ROW

  • ピン

    • B6: キー1

    • B3: キー2 (ロータリーエンコーダーのクリック?)

    • B2: キー3

    • D2: ロータリーエンコーダー回転1

    • D3: ロータリーエンコーダー回転2

セットアップ

自分はM1 Macなので、 `brew install qmk/qmk/qmk` で放置すれば完了しました。ただし、めちゃくちゃ時間がかかります。数時間必要なので、コードを書く気になる前にやっておかないとやる気が霧散します。

また、Cのプロジェクトかつ各キーボードで設定がモジュール化されているため、セットアップが難しいです。公式の手順通りにやってもIDEの補完が効かない所がちょこちょこあります。VS Codeくんの顔も真っ赤ですが、気にせず行きたいと思います。コンパイルさえ通ればOKです。

顔真っ赤なVS Codeくん

`qmk setup -H ./qmk_firmware` などで、QMKのリポジトリをクローンしてきます。これをそのまま使わず、v0.22.0のタグをチェックアウトして使うと良さそうです。2024/02時点で最新は0.23なのですが、REMAPの対応状況を見ると0.22.0を使うのが無難かと思います。

info.jsonに書くこと

重要なファイルですが、何を設定したら良いか分からん所でもあります。同じような設定は `config.h` にマクロで書くこともできますが、可能な限りこちらで設定する方が分かりやすそうです。

多くの自作キーボードはピン配置がmatrix(行列の組み合わせで押されたスイッチを判定する)になっているのですが、今回は `matrix_pins.direct` という設定ができるという点が分からず詰んでいました。この場合でも、 `layouts` の指定はmatrixな場合とほぼ変わらずです。

また、USBのベンダーIDなどは、元のファームウェアの値を使わず、まだREMAPに登録が無い適当な値にしましょう。さもないとREMAPが元々あるキーボード定義と一致させようとするのかして沈黙し、原因追及に一日潰すことになります。

なお、printfによるデバッグ情報をconsoleに出力したい場合、features.consoleをtrueにするだけでOKです。

{
    "manufacturer": "aaaa",
    "keyboard_name": "test_trackball",
    "maintainer": "aaaa",
    "bootloader": "atmel-dfu",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "command": false,
        "console": false,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "processor": "atmega32u4",
    "url": "",

    "matrix_pins": {
        "direct": [["B6", "B3", "B2"]]
    },

    "usb": {
        "device_version": "1.0.0",
        "pid": "0x000A",
        "vid": "0x1720"
    },

    "layouts": {
        "LAYOUT": {
            "layout": [
                { "matrix": [0, 0], "label": "0,0", "x": 0, "y": 0 },
                { "matrix": [0, 1], "label": "0,1", "x": 1, "y": 0 },
                { "matrix": [0, 2], "label": "0,2", "x": 2, "y": 0 }
            ]
        }
    }
}

キー入力を受け取る

`keymaps/default/keymap.c` に `matrix_pins` や `layouts` で設定したピンを受け取った際の処理を書きます。順番が大事です。`info.json` で `layouts` 配下で指定した名前を使うことで、コンパイル時に数が違うなども検出できます。マクロ天国ですね。

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {[0] = LAYOUT(KC_MS_BTN1, KC_MS_BTN2, KC_MS_BTN3)};

ロータリーエンコーダーを動かす

ここに記載の通りです。

`rules.mk` に `ENCODER_ENABLE = yes` を記載すると有効になります。この回転は `config.h` にピンを指定します。ここは `info.json` で指定する方法は見つかりませんでした。

#define ENCODERS_PAD_A \
    { D2 }
#define ENCODERS_PAD_B \
    { D3 }
#define ENCODER_RESOLUTION 4

キーマップは、 `keymaps/default/rules.mk` に `ENCODER_MAP_ENABLE = yes` を指定し、 `keymaps/default/keymap.c` には実際に発行するキーコードを指定します。

#if defined(ENCODER_MAP_ENABLE)
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
    [0] = {ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN)},
};
#endif

トラックボール対応

ようやく本来変えたい所に入りました。これもドキュメントを見ると大体書いてあります。

まずは `rules.mk` で有効にします。センサーのドライバを指定するのが大事です。

POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = adns5050

`config.h` にはピンを指定します。元々のファームウェアに書かれているのがそのまま使えます。

#define ADNS5050_SCLK_PIN F7
#define ADNS5050_SDIO_PIN F6
#define ADNS5050_CS_PIN B1

ここまででトラックボールが動くようになりました。今回はY軸を変えられるようにしたいので、`<keyboard>.c` に処理を実装します(`<keyboard>`は指定したキーボード名と同じ)。座標値を加工するコールバック関数が用意されているので、それを上書きして処理します。

`pointing_device_task_kb` は `mouse_report` という変数に移動量が入ってくるので、元のファームウェアを参考に加工します。ここでは、 `angle` は0から360/15の値を取るようにし、それをボタンを押すごとに1ずつ増減させる想定です。

#define ANGLE_UNIT 15
#define ANGLE_MAX (360 / ANGLE_UNIT)
uint8_t angle = 0;

report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
    double rad     = ANGLE_UNIT * angle * (M_PI / 180);
    int8_t x_rev   = +mouse_report.x * cos(rad) - mouse_report.y * sin(rad);
    int8_t y_rev   = +mouse_report.x * sin(rad) + mouse_report.y * cos(rad);
    mouse_report.x = x_rev;
    mouse_report.y = y_rev;
    return pointing_device_task_user(mouse_report);
}

カスタムキーの設定

ここまでで、`angle`の値に応じて移動方向を変更する処理ができました。最後に、`ROT_R15`、`ROT_L15` のカスタムキーで `angle` が変わるようにします。なお、printfをしておくとデバッグが簡単です。

`QK_KB_0 = 0x7E00` が最近のQMKで使われる、キーボード側で設定するカスタムキー定数の模様です。ユーザー側は他のキーとバッティングしない `QK_USER_0 = SAFE_RANGE = 0x7E40` という定数があり、これが使えます。 `QK_KB_MAX = 0x7E3F` が最大なので、64個ほど設定できます。

enum my_keycodes { ROT_R15 = QK_KB_0, ROT_L15 };

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
#    ifdef CONSOLE_ENABLE
    uprintf("KL: kc: 0x%04X, col: %2u, row: %2u, pressed: %u, time: %5u, int: %u, count: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed, record->event.time, record->tap.interrupted, record->tap.count);
    uprintf("%04X\n", ROT_R15);
#    endif

    switch (keycode) {
        case ROT_R15:
            angle = (angle + 1) % ANGLE_MAX;
            return false;
        case ROT_L15:
            angle = (angle + ANGLE_MAX - 1) % ANGLE_MAX;
            return false;
        default:
            return true;
    }
}

VIA対応

キーをブラウザで設定するには、VIA対応が必要です。REMAPを使う場合も同様です。

定義ファイルは適当な名前を付け、次のように指定します。 `vendorId`、`productId`は同じものを指定します。 `customKeycodes` に指定したもののキーコードは、順番に `QK_KB_0`、`QK_KB_1` に対応します。

{
    "name": "test_trackball",
    "vendorId": "0x1720",
    "productId": "0x000A",
    "lighting": "none",
    "menus": [],
    "keycodes": [],
    "matrix": { "cols": 5, "rows": 1 },
    "customKeycodes": [
        {
            "name": "ROT_R15",
            "title": "Rotate sensor Y-axis by 15 degrees clockwise",
            "shortName": "ROT_R15"
        },
        {
            "name": "ROT_L15",
            "title": "Rotate sensor Y-axis by 15 degrees counterclockwise",
            "shortName": "ROT_L15"
        }
    ],
    "layouts": {
        "keymap": [["0,0", "0,2\n\n\n\n\n\n\n\n\ne0", "0,1"]]
    }
}

完成!

適当な方法で焼き込み、REMAPでカスタムキーをアサインして動かすことができれば完成です!

細かい箇所の説明は省いているので、サンプルコードの方を確認ください。また、コールバック部分はprintfで確認しつつデバッグすると楽です。

あとがき

QMKは非常に整っており便利でした。自作が捗るのも頷けます。

meishi Trackball Moduleはdocやコードが公開されており、スイッチ/ロータリーエンコーダー/トラックボールの3つをまとめて実験できる優れものでした。自作入門に良さそうです。

これでQMKを使ったファームウェアをカンゼンニリカイシタので、次はキーボード分離や基板の設計を確認してゆきたいと思います。

この記事が気に入ったらサポートをしてみませんか?