見出し画像

Keyball61のLED制御をRGBMatrixに変更してTypingHeatmapを実現する

Keyball61

ずっと欲しかったけど、人気すぎて毎回入荷後秒で売り切れてしまうkeyball61がやっと買えた。

猫背で肩こりがひどいので、分割キーボードにして姿勢良くしたい、キーボード・マウス間の移動を減らしたい、というのがメインの購入理由なんだけど、キースイッチやキーキャップを好きなものにしたり、キー割り当てをブラウザから変更できたりとカスタマイズ性がすこぶる高いのも大きなメリット。ファームウェアのソースも公開されているのでやろうと思えば(&プログラムの知識が多少あれば)さらに細かい部分もカスタマイズが効きます。

モチベーション

かみだいさん公開のファームウェアではトラックボールを動かすと自動的にマウスレイヤーに移動(j,lキーがクリック操作になる)、一定時間操作がないとデフォルトのレイヤー(通常の入力)に戻るという非常に便利な機能が入っています。

しかし、その機能追加とトレードオフでLED関連の機能がオミットされています(マウスレイヤーの間は光りますが、Remapで本来設定できるLightingの機能は動きません)。
というのも、ProMicroの容量制限がわずか28KBとあまりに小さく、標準のファームウェアでギリギリな状況のため、何かを追加しようとすると何かを犠牲にしなければいけない状況だからです。
とはいえ、せっかく実装したLEDをもうちょっと活用できないかと思ってQMK(Keyballでも使われているオープンソースのキーボードファームウェア)を調べることにしました。

出来上がったもの

下の動画の通り、キーを押した回数によってヒートマップ的にLEDの色が変化します。
また、単純に押したキーが光るモードもあります。
両方とも押したキーのLEDの周りのLEDも変化します。

作り方

QMKのRGBMatrix

keyballではQMKのRGB Lightingという仕組みを使ってLEDを光らせています。一方、QMKのドキュメントにはRGB Matrix Lightingという仕組みもあります。RGBLightingの方が新しく、RGBMatrixで必要だったLED配置の定義をしなくても(=回路構成の違いを意識せず)LEDを制御できるようにしたということのようです。
しかし、違いはそれだけでなくLEDエフェクトの種類がだいぶ違います。
今回はこのRGBMatrixで用意されているTyping HeatmapとSOLID_REACTIVE_MULTIWIDEエフェクトを使います。
前述の通り、RGBMatrixではLED配置の定義をしなくてはならないので、Key数やLED数、配置(トラックボールの左右)によってコードが変わってきます。以下ではkeyball61右トラックボールでのコードになります(コード変更が必要な箇所はそれぞれ説明します)

rules.mk

RGBMatrixを利用するために、rules.mkでRGB_MATRIX_ENABLE = yes を追加して、容量削減のためRGBLIGHT_ENABLEをnoに変更します

RGBLIGHT_ENABLE = no

OLED_ENABLE = yes

VIA_ENABLE = yes

RGB_MATRIX_ENABLE = yes

config.h

以下のコードを追加します。

#define DRIVER_LED_TOTAL 74
#define RGB_MATRIX_MAXIMUM_BRIGHTNESS 125 // limits maximum brightness of LEDs to 200 out of 255. If not defined maximum brightness is set to 255
#define RGB_MATRIX_DEFAULT_HUE 125 // Sets the default hue value, if none has been set
#define RGB_MATRIX_DEFAULT_SAT 255 // Sets the default saturation value, if none has been set
#define RGB_MATRIX_DEFAULT_VAL 125 // Sets the default brightness value, if none has been set

#define RGB_MATRIX_FRAMEBUFFER_EFFECTS
#define ENABLE_RGB_MATRIX_TYPING_HEATMAP
#define RGB_MATRIX_TYPING_HEATMAP_SPREAD 32
#define RGB_MATRIX_TYPING_HEATMAP_AREA_LIMIT 16
#define RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MS 25

#define RGB_MATRIX_KEYPRESSES // reacts to keypresses
#define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE

#define RGB_MATRIX_DEFAULT_MODE RGB_MATRIX_TYPING_HEATMAP// Sets the default mode, if none has been set

最初のDRIVER_LED_TOTALはLEDの数です。実際は keyball61に実装できるLEDは71ですが、keyball61/config.hにRGBLED_NUMが74と定義されているので同様に74にしています。
keyball61以外の場合は同様にkeyballXX/config.h(Keyball39ならkeyball39/config.h)の値と合わせると良いと思います。
RGB_MATRIX_MAXIMUM_BRIGHTNESSはLEDの最大輝度です。keyball61/confug.hではRGBLIGHT_LIMIT_VALが150と定義されていますが、ここでは125にしています。これは電力不足でLEDがちらつく場合があり、125にしたらちらつきがなくなったということがKeyballオフィシャルのGitHubでディスカッションされていたからです。

LED輝度についてもconfig.h内のRGBLIGHT_LIMIT_VALの値を150から125に下げることでMAX輝度でもちらつきを無くすことができました。

https://github.com/Yowkees/keyball/discussions/194

というか、この定義をせずに動かしたらLED光った瞬間フリーズしました(笑)
その次の3行はデフォルトのLEDカラーのHSVでの各値です。HSVでの色指定は馴れませんね。。。Vの値がRGB_MATRIX_MAXIMUM_BRIGHTNESSの値を超えないようにしてください。

2ブロック目はTYPING_HEATMAPを利用するための定義です。RGB_MATRIX_TYPING_HEATMAP_SPREADは押したキーから周囲のキーに影響が広がる距離の定義ですが、このあとのkeymap.cで説明するキー間の距離が縦16,横14なので押したキーを中心に3×3に影響が広がるように32としています。
RGB_MATRIX_TYPING_HEATMAP_AREA_LIMITは熱の伝わりやすさ(だと思う)で、デフォルト値のままです。
RGB_MATRIX_TYPING_HEATMAP_DECREASE_DELAY_MSは冷めやすさ(温度を少し下げる間隔)でデフォルト値の半分にしています。

3ブロック目は押したキーが光るモード(以下SOLID_REACTIVE_MULTIWIDE)のための定義です。

最後の行でデフォルトのエフェクトをTyping Heatmapに設定しています。今回の修正では、RGB_MATRIX_TYPING_HEATMAPの他、
SOLID_REACTIVE_MULTIWIDEとデフォルトで使用できるRGB_MATRIX_SOLID_COLOR(単色の全面発光)が使えるのでどれを指定しても構いません。

keymap.c

LEDの定義を追加しますが、前述の通りここはkeyballの種類・トラックボールの左右によって異なります。
まず、keyballXX/keyballXX.hに

#define LAYOUT_right_ball( \
    L00, L01, L02, L03, L04, L05,              R05, R04, R03, R02, R01, R00, \
    L10, L11, L12, L13, L14, L15,              R15, R14, R13, R12, R11, R10, \
    L20, L21, L22, L23, L24, L25,              R25, R24, R23, R22, R21, R20, \
    L30, L31, L32, L33, L34, L35, L36,    R36, R35, R34, R33, R32, R31, R30, \
    L40, L41, L42, L43, L44, L45, L46,    R46, R45,                R41, R40  \
    ) \
    { \
        {   L00,   L01,   L02, KC_NO,   L03,   L04,   L05, KC_NO }, \
        {   L10,   L11,   L12, KC_NO,   L13,   L14,   L15, KC_NO }, \
        {   L20,   L21,   L22, KC_NO,   L23,   L24,   L25, KC_NO }, \
        {   L30,   L31,   L32, KC_NO,   L33,   L34,   L35,   L36 }, \
        {   L40,   L41,   L42, KC_NO,   L43,   L44,   L45,   L46 }, \
        {   R00,   R01,   R02, KC_NO,   R03,   R04,   R05, KC_NO }, \
        {   R10,   R11,   R12, KC_NO,   R13,   R14,   R15, KC_NO }, \
        {   R20,   R21,   R22, KC_NO,   R23,   R24,   R25, KC_NO }, \
        {   R30,   R31,   R32, KC_NO,   R33,   R34,   R35,   R36 }, \
        {   R40,   R41, KC_NO, KC_NO, KC_NO, KC_NO,   R45,   R46 }  \
    }

といったキー配置に関する記述があるので自分のKeyballのトラックボール配置にあったものを選んでコピーします。
そしてまず、実際の左手側Keyballの裏面 のLED番号を確認しながら、キー配置に合わせる形でL00のようなキー番号をLED番号に書き換えます(上段がキー配置イメージ、下段が制御用の情報になっているので一つのキーで2箇所書き換えます)。アンダーグロー用のLEDの番号は飛ばして構いませんが、後でわかるようにコメントで残しておいた方が良いでしょう。(親指キー部分のLEDは一番近いアンダーグロー用のLEDで代用するのが良いと思います)
右手側は左手側からの通番となるので、右手側のLED数+LED番号となります。
最後にKC_NOをNO_LEDに書換えます。
Keyball61の場合以下のようになります。

#define LAYOUT_right_ball( \
       24, 19, 14,  9,  5,  1,            66, 62, 58, 54, 49, 44, \
    25, 20, 15, 10,  6,  2,            67, 63, 59, 55, 50, 45, \
       26, 21, 16, 11,  7,  3,            68, 64, 60, 56, 51, 46, \
       27, 22, 17, 12,  8,  4,  0,    70, 69, 65, 61, 57, 52, 47, \
       28, 23, 18, 13, 34, 35, 36,    37, 38,                        53, 48  \ 
       // {29, 30, 31 ,32, 33,               39, 40, 41, 42, 43}  //underglows 
    ) \
    { \
        {  24, 19,     14, NO_LED,      9,      5,  1, NO_LED }, \
        {  25, 20,     15, NO_LED,     10,      6,  2, NO_LED }, \
        {  26, 21,     16, NO_LED,     11,      7,  3, NO_LED }, \
        {  27, 22,     17, NO_LED,     12,      8,  4,      0 }, \
        {  28, 23,     18, NO_LED,     13,     34, 35,     36 }, \
        {  44, 49,     54, NO_LED,     58,     62, 66, NO_LED }, \
        {  45, 50,     55, NO_LED,     59,     63, 67, NO_LED }, \
        {  46, 51,     56, NO_LED,     60,     64, 68, NO_LED }, \
        {  47, 52,     57, NO_LED,     61,     65, 69,     70 }, \
        {  48, 53, NO_LED, NO_LED, NO_LED, NO_LED, 38,     37 } \
    }

次に各LEDの位置情報を作成します。位置座標を{x,y}とすると、
x = 224 / (列数 - 1) * 列番号
y = 64 / (行数 - 1) * 行番号
で計算できます。(小数点以下切上げか切り捨てで整数とします)
ただ、分割キーボードなので、ヒートマップの影響が左右をまたいで伝搬しないように、中央に2列架空の列があるとして計算するとよいです。
(補足:SPLITキーボードをしめすための定義があるのですが、定義しても、マスター側からの影響はスレーブ側に出ないが、スレーブ側の影響がマスター側に影響するといった状態になったためこのような対応にしています)
上記のLED番号の定義に出てこなかった、アンダーグロー用LEDとDRIVER_LED_TOTALに定義した数に足りない部分は位置情報は{0,0}として、LED番号順に位置座標をリスト化します。また、同様に上記のLED番号の定義をしたものをLED_FLAG_KEYLIGHT、それ以外(アンダーグロー用、DRIVER_LED_TOTALでの定義数に足りないもの)をLED_FLAG_NONEとして、LED番号順にリスト化します。
補足; 座標計算やこれらのリスト作成は1個1個手計算・手打ちすると大変なので表計算ソフトをうまく使うと楽ができます

これらの定義を以下の形でkeymap.cに追加します。

led_config_t g_led_config = { {
  // Key Matrix to LED Index
  /*
  {24, 19, 14,  9,  5,  1,            66,     62,     58,     54, 49, 44}, \
  {25, 20, 15, 10,  6,  2,            67,     63,     59,     55, 50, 45}, \
  {26, 21, 16, 11,  7,  3,            68,     64,     60,     56, 51, 46}, \
  {27, 22, 17, 12,  8,  4,  0,    70, 69,     65,     61,     57, 52, 47}, \
  {28, 23, 18, 13, 34, 35, 36,    37, 38, NO_LED, NO_LED, NO_LED, 53, 48} \ 
  // {29, 30, 31 ,32, 33,                    39, 40, 41, 42, 43}   //underglows 
  */
  {24, 19,     14, NO_LED,      9,      5,  1, NO_LED}, \
  {25, 20,     15, NO_LED,     10,      6,  2, NO_LED}, \
  {26, 21,     16, NO_LED,     11,      7,  3, NO_LED}, \
  {27, 22,     17, NO_LED,     12,      8,  4,      0}, \
  {28, 23,     18, NO_LED,     13,     34, 35,     36}, \
  {44, 49,     54, NO_LED,     58,     62, 66, NO_LED}, \
  {45, 50,     55, NO_LED,     59,     63, 67, NO_LED}, \
  {46, 51,     56, NO_LED,     60,     64, 68, NO_LED}, \
  {47, 52,     57, NO_LED,     61,     65, 69,     70}, \
  {48, 53, NO_LED, NO_LED, NO_LED, NO_LED, 38,     37} \
}, {
  // LED Index to Physical Position
  // {x,y}: x = 224 / (NUMBER_OF_COLS - 1) * COL_POSITION , y =  64 / (NUMBER_OF_ROWS - 1) * ROW_POSITION
{84,48}	, //0
{70,0}	, //1
{70,16}	, //2
{70,32}	, //3
{70,48}	, //4
{56,0}	, //5
{56,16}	, //6
{56,32}	, //7
{56,48}	, //8
{42,0}	, //9
{42,16}	, //10
{42,32}	, //11
{42,48}	, //12
{42,64}	, //13
{28,0}	, //14
{28,16}	, //15
{28,32}	, //16
{28,48}	, //17
{28,64}	, //18
{14,0}	, //19
{14,16}	, //20
{14,32}	, //21
{14,48}	, //22
{14,64}	, //23
{0,0}	, //24
{0,16}	, //25
{0,32}	, //26
{0,48}	, //27
{0,64}	, //28
{0,0}	, //29
{0,0}	, //30
{0,0}	, //31
{0,0}	, //32
{0,0}	, //33
{56,64}	, //34
{70,64}	, //35
{84,64}	, //36
{126,64}	, //37
{140,64}	, //38
{0,0}	, //39
{0,0}	, //40
{0,0}	, //41
{0,0}	, //42
{0,0}	, //43
{210,0}	, //44
{210,16}	, //45
{210,32}	, //46
{210,48}	, //47
{210,64}	, //48
{196,0}	, //49
{196,16}	, //50
{196,32}	, //51
{196,48}	, //52
{196,64}	, //53
{182,0}	, //54
{182,16}	, //55
{182,32}	, //56
{182,48}	, //57
{168,0}	, //58
{168,16}	, //59
{168,32}	, //60
{168,48}	, //61
{154,0}	, //62
{154,16}	, //63
{154,32}	, //64
{154,48}	, //65
{140,0}	, //66
{140,16}	, //67
{140,32}	, //68
{140,48}	, //69
{126,48}	, //70
{0,0}	, //71
{0,0}	, //72
{0,0}	, //73
}, {
  // LED Index to Flag
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT	,
LED_FLAG_KEYLIGHT ,
LED_FLAG_NONE	,
LED_FLAG_NONE	,
LED_FLAG_NONE		
} };

さらに、Keyball接続時に前回使用したエフェクトモードで起動したり、マウスレイヤーから戻った際に使用していたエフェクトモードに戻るための追記・修正を行います。
まず、起動時用に以下を追加。

void keyboard_post_init_user(void) {
  // Call the post init code.
  rgb_matrix_reload_from_eeprom();
}

さらに、layer_state_set_user関数を以下のように修正します。(rgblight_sethsvで始まる行をコメントアウト、rgb_matrix_ではじまる行を追加)

layer_state_t layer_state_set_user(layer_state_t state)
{
  // レイヤーが1または3の場合、スクロールモードが有効になる
  keyball_set_scroll_mode(get_highest_layer(state) == 3);
  // keyball_set_scroll_mode(get_highest_layer(state) == 1 || get_highest_layer(state) == 3);

  // レイヤーとLEDを連動させる
  uint8_t layer = biton32(state);
  switch (layer)
  {
  case 4:
      //rgblight_sethsv(HSV_WHITE);
      rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR);
    break;

  default:
    //rgblight_sethsv(HSV_OFF);
    rgb_matrix_reload_from_eeprom();
  }
  return state;
}

もともとマウスレイヤー時にはrgblight_sethsvで白色になり、マウスレイヤー以外ではLEDをOFFにするというコードでしたが、rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR)でEEPROMにセーブせずにRGB_MATRIX_SOLID_COLORに変更して、マウスレイヤー以外ではEEPROMに保存されている状態をロードすることによってエフェクトモードを維持することを実現しています。また、RGB_MATRIX_SOLID_COLORを使うことでRemapからLEDカラーを変更することもできるようになります。

完成

上記の修正を行なってコンパイルしてできたhexファイルをProMicroWebUpdaterなどで書き込めば動作するはずです。

  • Remapで「RGBToggle」が設定されているキーでLEDのON/OFFができます

  • Remapで「RGBMode+」「RGBMode-」が設定されているキーで、Typing Heatmap、SOLID_REACTIVE_MULTIWIDE、RGB_MATRIX_SOLID_COLORが切り替わります

  • Remapでマウスレイヤー時および、RGB_MATRIX_SOLID_COLOR、SOLID_REACTIVE_MULTIWIDE時のLED色の変更ができます。RemapのLightingの中から「Solid color」を選択して色指定しください(色指定はTyping Heatmap以外は共通で反映されます)

その他

容量に多少余裕があるので、もう一つならエフェクトを入れれると思います。また、このみによってTyping Heatmap、SOLID_REACTIVE_MULTIWIDEを他のものに変えてみるのも楽しいかと思います。
RGBMatrixで利用できるTyping Heatmap以外のエフェクトは以下の動画で確認できます。概要欄にエフェクトの名前と対応する動画位置、QMKドキュメントへのリンクがあります。

 config.hに

#define ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON

のように使いたいエフェクトを定義するだけなので簡単に試せると思います。

今後チャレンジするかもしれない話

QMKドキュメントをみると、LEDもレイヤーにできたり、オリジナルのエフェクトを定義できたりするようですし、OLEDに関しても画像を表示したりとかもできそうなので、やろうと思えば

  • レイヤーごとにLEDの光り方を変える

  • OLEDでレイヤー状態をもっとわかりやすく表示

  • テンキー部分やカーソルキー部分など、特定部分を色分けして光らせる

などができるかもしれません(空き容量との戦いになりそうですが)。
なにか面白いアイデアがあったら教えてください。
モチベと暇があったらチャレンジするかもしれません。

追記(2023/5/24)

上記で修正した各ファイルとhexファイルを置いておきます。(おまけで座標計算用Excelファイルも入れました)
hexファイルをProMicroWebUpdaterなどで書き込めばお試しできると思いますが、なんかあっても責任持てませんので自己責任でお願いします。
また、Keyball61専用ですのでご注意ください。
(keyball39,44はLED番号わからなくてコード書けないし動作確認もできないので、有志の方が作って共有してくれると非常に嬉しいです)

追記その2(2023/5/24)

カスタムキーコードが変わっていました。
私の環境(Keyball61,QMK0.18.17)では以下になります

// コード表
// 【KBC_RST: 0x5DB1】Keyball 設定のリセット
// 【KBC_SAVE: 0x5DB2】現在の Keyball 設定を EEPROM に保存します
// 【CPI_I100: 0x5DB3】CPI を 100 増加させます(最大:12000)
// 【CPI_D100: 0x5DB4】CPI を 100 減少させます(最小:100)
// 【CPI_I1K: 0x5DB5】CPI を 1000 増加させます(最大:12000)
// 【CPI_D1K: 0x5DB6】CPI を 1000 減少させます(最小:100)
// 【SCRL_TO: 0x5DB7】タップごとにスクロールモードの ON/OFF を切り替えます
// 【SCRL_MO: 0x5DB8】キーを押している間、スクロールモードになります
// 【SCRL_DVI: 0x5DB9】スクロール除数を1つ上げます(max D7 = 1/128)← 最もスクロール遅い
// 【SCRL_DVD: 0x5DBA】スクロール除数を1つ下げます(min D0 = 1/1)← 最もスクロール速い

keymap.cのコメントを上記内容に修正して再ビルドして上のダウンロードファイルも置き換えました。
カスタムキーコードが変わる理由(というかそもそもカスタムキーコードの定義の仕方)がわかっていません。誰か教えてください。。。
キーコードがどの値に値になっているかはEEPROM reset後にRemapに再接続して、レイヤー3のHJKLキー等の値を確認するのが確実かも(keymap.cにあるデフォルトキーマップ参照)


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