弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 RGBマトリクス使ってますか? 私は自作キーボードで qmk_firmware (Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っています。 qmkにはフルカラーLEDをキースイッチごとに配置して流れたりヒートマップを表現するようなアニメーション処理があります。押したキーを中心にLEDの物理的な位置を考慮した光の流れは見ていて気持ちがいいものです。 ところが、ロータリーエンコーダーやその他のキーマトリクスに組み込まれていない入力を使うとLEDを発光させるきっかけがありません。 今回はロータリーエンコーダーを操作したときにLEDのアニメーションを動かそうと思います。 キー入力とLED処理 キーを押した時にLEDが光る仕組みは、quantum/keyboard.c の中でキーマトリクスのON/OFFを確認するループ matrix_task() に書かれており、スキャンが完了して各スイッチの状態変化をキューに積むループの中で switch_events() を呼び出して実現しています。これはLED側の(ソースコードの)処理かと思いきや、 keyboard.c に書かれていて LED_MATRIX と RGB_MATRIX の定義状況に応じて必要な処理をそれぞれ呼び出します。(つまり併存可能なのですね) アニメーションのきっかけになる位置を指定するのはkeymatrixではなくrgbmatrixの定義に記述されたrow,colを使っていました。キースイッチのmatrixが既に存在するのにledのmatrixでもrow,colを設定するのはとても面倒な作業ですが、プログラム的にシンプルな処理を目指す定義としては妥当なのでしょう。 このようにLEDのアニメーションを指示するための定義がキーマトリクスと別の定義になっていることで、次にご紹介する事が可能になっています。 マトリクスに無いキーを光らせる キーボードの余白などにLEDを置いた際、rgbmatrixで指定するmatrix位置はどうしたらよいでしょうか? 設定しないでおくことも可能ですが、私としてはキーマトリクスの未使用の交点を設定しておくことをおすすめします。ダミーでもrow,colを定義しておくことで、そのLEDを狙ってアニメーションを開始させることができます。 (ダミー定義:実際には0,4と0,5はスイッチをつけていませんが、LEDを設置しています) "layout": [ (略) { "flags": 1, "matrix": [0, 4], "x": 174, "y": 64 }, { "flags": 1, "matrix": [0, 5], "x": 194, "y": 64 }, 具体的にはLEDを登録する際、キーマトリクスで不使用のrow,col位置をmatrixタグに他のLEDと同じように設定するだけです。例えばロータリーエンコーダーのプッシュスイッチに反応させるときなどに使えないでしょうか。 (もっともその場合はプッシュスイッチ自体をマトリクスに組み込んでそのrow,colを指定する方がシンプルですが) LEDが無い位置をアニメーションの起点にする もう少し自由度をもとめて、ロータリーエンコーダーを回した際にスイッチの少し右側や左側がアニメーションの始点にするにはどうしたらよいでしょうか? それぞれの始点にLEDを設置することも一つの解ですが、ここでは存在しないLEDを設定する方法をご紹介します。シリアルLEDは長いデータを送ると個々のLEDが必要なデータを受け取って、それ以降の残りは次のLEDに渡すことでシリアルに処理する仕様です。 LEDの故障や配線ミスで途中までしか光らないことに遭遇した経験はないでしょうか?このように末尾のLEDは後続が居なくても正しく発光できます。この性質を利用して、実際に存在しないLEDを定義することができます。そしてその定義に設定されたマトリクスの座標を使ってqmkはアニメーションの計算を行ってくれるのです。 具体的にはLEDを正しく登録した rgb_matrix -> layout の末尾にロータリーエンコーダーの座標の左右に相当するダミーのLEDを、これまたダミーのキーマトリクスのものとして登録します。 ダミーLEDの登録は以下のようにします "layout": [ (略) { "flags": 1, "matrix": [0, 4], "x": 174, "y": 64 }, { "flags": 1, "matrix": [0, 5], "x": 194, "y": 64 }, ※ここまで存在するLEDの定義 ※以下はエンコーダー用のダミー定義(x座標を左右にずらしている) { "flags": 4, "matrix": [1, 6], "x": 168, "y": 64 }, { "flags": 4, "matrix": [0, 6], "x": 180, "y": 64 }, { "flags": 4, "matrix": [1, 7], "x": 188, "y": 64 }, { "flags": 4, "matrix": [0, 7], "x": 200, "y": 64 }, そしてロータリーエンコーダーのキー自体は以下のようにダミースイッチを登録します {"label": "1,6", "matrix": [1, 6], "x": 11, "y": 5, "w": 0.5}, {"label": "0,6", "matrix": [0, 6], "x": 11.75, "y": 5, "w": 0.5}, {"label": "1,7", "matrix": [1, 7], "x": 12.25, "y": 5, "w": 0.5}, {"label": "0,7", "matrix": [0, 7], "x": 13, "y": 5, "w": 0.5}, そして、ロータリーエンコーダーでスイッチが発生した際にLEDのアニメーションを指示するため encoder_update_user() の中で LED にアニメーションの開始を指示します。 (switch_events()定義 ※quantum/keyboard.cからの複製です) // RGBアニメーション開始処理(keypressのon/offはセットじゃなくてもよい) // この関数は quantum/keyboard.c からの複製です inline void switch_events(uint8_t row, uint8_t col, bool pressed) { #if defined(LED_MATRIX_ENABLE) led_matrix_handle_key_event(row, col, pressed); #endif #if defined(RGB_MATRIX_ENABLE) rgb_matrix_handle_key_event(row, col, pressed); #endif } // エンコーダーのキー位置を配列で定義 static const uint8_t encoder_a_rows[NUM_ENCODERS] = {1,1}; static const uint8_t encoder_a_cols[NUM_ENCODERS] = {7,6}; static const uint8_t encoder_b_rows[NUM_ENCODERS] = {0,0}; static const uint8_t encoder_b_cols[NUM_ENCODERS] = {7,6}; // エンコーダーのイベント処理(デフォルトを有効にする場合 encoder_update_userを実装してindexを読む) bool encoder_update_user(uint8_t index, bool clockwise) { int8_t row = clockwise ? encoder_a_rows[index] : encoder_b_rows[index]; int8_t col = clockwise ? encoder_a_cols[index] : encoder_b_cols[index]; tap_code_delay(matrix_to_keycode(row, col), 10); // RGBアニメーション開始(offで点灯する場合もあるので両方書いている) switch_events(row, col, true); // 点灯させる位置を知らせるためにkeyboard.jsonでダミーのLEDを登録する switch_events(row, col, false); return false; } ロータリーエンコーダーは通常のキースイッチよりスイッチ発生頻度が高いので賑やかですが、目的の回転方向によってアニメーションの起点を変えることができました。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。