matuconとマイコン

ITガジェットが好物です

自作キーボード「Keyball61」:おれのファームまとめ前編

はじめに

前回作ったKeyball61 ですが、使い始めてからファームウェアをいろいろカスタムしてみたので変更内容をまとめてみます。

対象読者

  • Keyball のファームをカスタムしたい人
  • QMK Firmware でKeyball ファームのビルド、書き込みができる人

環境構築やビルド方法は記事がたくさん出ているので、今回はカスタム内容にフォーカスします。

ベースにしたリソース

Keyball 開発者のヨーキースさんリポジトリをフォークさせていただきました。

github.com

本記事のソースは私のリポジトリの「blog_firm1」ブランチに上げました。

github.com

カスタム内容

最初に「default」キーマップをコピーしておれおれキーマップを作成しました。

└─qmk_firmware
    └─keyboards
        └─keyball
            ├─keyball61
            │  └─keymaps
            │      ├─default
            │      ├─develop
            │      ├─matucon # default フォルダをコピー
            │      ├─test
            │      └─via

基本的には新しく作ったフォルダ内のファイルを編集します。

設定変更系

config.h を編集して設定変更します。

LEDエフェクト無効化

カスタムするとProMicroの容量が不足するのですべて除外しました。定義をコメントアウトすると無効化されます。作りたての頃は光らせてenjoyしてたんですけどね。

#ifdef RGBLIGHT_ENABLE
// #    define RGBLIGHT_EFFECT_BREATHING
// #    define RGBLIGHT_EFFECT_RAINBOW_MOOD
// #    define RGBLIGHT_EFFECT_RAINBOW_SWIRL
// #    define RGBLIGHT_EFFECT_SNAKE
// #    define RGBLIGHT_EFFECT_KNIGHT
// #    define RGBLIGHT_EFFECT_CHRISTMAS
// #    define RGBLIGHT_EFFECT_STATIC_GRADIENT
// #    define RGBLIGHT_EFFECT_RGB_TEST
// #    define RGBLIGHT_EFFECT_ALTERNATING
// #    define RGBLIGHT_EFFECT_TWINKLE
#endif
OLED自動消灯時間

OLEDのデフォルトタイムアウトは60秒ですが、30秒で消灯するようにしました。以下を追記します。

#define OLED_TIMEOUT 30000 // ミリ秒
キー長押し時間

レイヤー切り替えキーなどの長押しまでの判定時間を変更しました。

#define TAPPING_TERM 180 // ミリ秒
レイヤ―追加

デフォルトでは4レイヤーですが1レイヤ―追加しました。以下を追記して総レイヤー数を指定します。keymap.c のkeymaps配列に追加分のキーマップを設定します。

#define DYNAMIC_KEYMAP_LAYER_COUNT 5 
マウス、スクロール速度デフォルト値

CPIとスクロール速度のデフォルト値を常用する値に変更しました。

#define KEYBALL_CPI_DEFAULT 1100 // マウス速度 (default: 500)
#define KEYBALL_SCROLL_DIV_DEFAULT 5 // スクロール速度 (default: 4)
CPI増減値変更

KeyballでCPIを変更するキーとして±100と±1000が用意されていますが、あまり大きく動かすことはなくて±1000は使いにくいので500単位で変更できるように修正しました。 これはlib配下にあるソース keyball.c を編集します。

└─qmk_firmware
    └─keyboards
        └─keyball
            └─lib
               └─keyball
                   └─keyball.c

ファイル末尾にあるswitch文(本稿執筆時のコードの553行目あたり)に手を加えます。add_cpi()関数の引数を「10」から「5」に書き換えました。

case CPI_I1K:
    add_cpi(5);
    break;
case CPI_D1K:
    add_cpi(-5);
    break;

新しいキーコードを用意して実装することもできそうですが、容量を消費するので今回はlibを直接修正しました。

コンボキー

複数のキーを同時押しすることで任意のキーコードを割り当てる機能です。QMK Firmware 側で実装されている機能で、有効化の設定と組み合わせキーの定義をするだけで使えるようになります。ここでは「Y」と「H」の同時押しでバックスペースを割り当てる設定を追加してみます。

基本設定

まず rules.mk に以下を追記しコンボキーを有効化します。

COMBO_ENABLE = yes

keymap.c に組み合わせたいキーと送りたいキーコードを定義します。

#ifdef COMBO_ENABLE
const uint16_t PROGMEM my_bs[] = {KC_Y, KC_H, COMBO_END};

combo_t key_combos[] = {
    COMBO(my_bs, KC_BSPC),
};
#endif

キーコードは以下のドキュメントを参考にしました。

github.com

変数名のmy_bsは適当な名前でOKです。key_combos配列はちょっとルールがあります。

  • 必ずkey_combosという名前で定義する
  • 「COMBO_ENABLE = yes」を定義したら必ず定義する(存在しないとコンパイルエラー)
ON/OFFキー

コンボキーの機能をON/OFFするためのキーコードが用意されていました。一時的にコンボキーを無効にしたい場合は便利そうです。以下をキーマップに追加するだけで利用可能です。

キーコード 内容
CM_TOGG コンボキーのON/OFF切り替え
CM_ON コンボキーをON
CM_OFF コンボキーをOFF
レイヤー固定化

レイヤーが切り替わっても指定した1つのレイヤーのみをコンボキーとして利用できる仕組みがあるようです。 以下を config.h に加えると、例えばレイヤー3に切り替えてる状態でもレイヤー0のコンボが反映されるようになります。

#define COMBO_ONLY_FROM_LAYER 0
同時押し判定時間設定

同時押しを判定する時間は config.h に以下を追記することで調整可能です。

#define COMBO_TERM 80 // ミリ秒

あまり長くすると入力ミスが増えるので、自分のタイピング速度に合わせてチューニングするとよいかと思います。

同時押し判定をするのにコンボタイマーという仕組みがあるようで、最初のキー入力で開始し時間内に次のキーを押すとリセットされるようです。この挙動を変えるためのオプションが2つ用意されていましたが、今回は未検証です。

オプション 内容
#define COMBO_NO_TIMER 最初のキー押下時のみタイマー開始
#define COMBO_STRICT_TIMER タイマー無効化
その他

今回は試しませんでしたが他にも便利機能があるようです。

  • コンボキーを放すタイミングで処理を入れる
  • 大量のコンボ定義を管理しやすくするDictionary Management
  • コンボキーを押す順序を厳密化

コンボキーに関するドキュメントはこちらです。

github.com

Automatic Mouse Layer

マウスを動かしてる時だけマウス用のレイヤーに自動切換えしてくれる機能です。しばらく使って結構便利だったので常用してます。こちらもQMK Firmwareで実装されてる機能です。

config.h で機能有効化と設定を行います。

#define POINTING_DEVICE_AUTO_MOUSE_ENABLE // 有効化
#define AUTO_MOUSE_DEFAULT_LAYER 2 // 切り替えるマウスレイヤー番号を指定
#define AUTO_MOUSE_TIME 500 // マウスが止まってから元のレイヤーに戻るまでの時間(ms)

keymap.c には以下を追記します。

#ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
void pointing_device_init_user(void) {
    set_auto_mouse_enable(true);
}
#endif

これでAutolayerが動くようになりますが、レイヤー1ではAutolayerしたくないので除外する処理を追加しました。既に存在するlayer_state_set_user() 関数にswitch文を追加します。

layer_state_t layer_state_set_user(layer_state_t state) {
    // Auto enable scroll mode when the highest layer is 3
    keyball_set_scroll_mode(get_highest_layer(state) == 3);

    #ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE
    switch(get_highest_layer(remove_auto_mouse_layer(state, true))) {
        case 1:
            state = remove_auto_mouse_layer(state, false);
            set_auto_mouse_enable(false);
            break;
        default:
            set_auto_mouse_enable(true);
            break;
    }
    #endif
    
    return state;
}

ドキュメントはこちらです。

github.com

レイヤー毎にLED色変更する機能を実装

レイヤーを切り替えたタイミングでLED色が変わるようにしてみました。以下のような機能です。

  • レイヤー0は消灯
  • レイヤー1以降は指定した色で点灯
  • 機能のON/OFFを切り替えられるキーコード作成
  • 色はとりあえずハードコーディング

新しいファイルを作成します。

└─qmk_firmware
    └─keyboards
        └─keyball
            └─keyball61
                └─keymaps
                    └─matucon 
                        └─layer_led.c // 新ファイル

layer_led.c

#ifdef LAYER_LED_ENABLE

#include QMK_KEYBOARD_H

static const uint8_t my_layer_colors[] = {234, 17,170, 85}; // ピンク、黄、青、緑

static uint8_t my_latest_val = 0;
static uint8_t my_latest_hue = 0;
static bool    layer_led     = false;

// レイヤーごとにLED色変更
void change_layer_led_color(uint8_t layer_no) {
    if (!layer_led) {
        return;
    }

    if (layer_no == 0) {
        my_latest_val = rgblight_get_val();
        rgblight_sethsv(rgblight_get_hue(), rgblight_get_sat(), 0); 
    } else {
        rgblight_sethsv(my_layer_colors[layer_no-1], rgblight_get_sat(), my_latest_val);
    }
}

// 機能の有効・無効を切り替え
void toggle_layer_led(bool pressed) {
    if (!pressed) {
        return;
    }

    layer_led = !layer_led;
    if (layer_led) {
        my_latest_hue = rgblight_get_hue();
    } else {
        rgblight_sethsv(my_latest_hue, rgblight_get_sat(), rgblight_get_val());
    }
}

#endif

機能の有効、無効が切り替えられるようにconfig.h に定義を追加します。定義を消すと本機能がコンパイルされず容量が削減できます。

#define LAYER_LED_ENABLE

keymap.cには以下の内容を修正します。

#ifdef LAYER_LED_ENABLE
#include "layer_led.c"
#endif

enum my_keyball_keycodes {
    LAY_TOG = KEYBALL_SAFE_RANGE,
};

// キーマップの任意の場所に「LAY_TOG」を追加(機能の有効無効切り替えキー)
// 例:
//  [3] = LAYOUT_universal(
//    RGB_TOG  , LAY_TOG  , 

layer_state_t layer_state_set_user(layer_state_t state) {
    // 既存処理 ...

    change_layer_led_color(state);

    return state;
}

// 切り替え処理
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        #ifdef LAYER_LED_ENABLE
        case LAY_TOG: toggle_layer_led(record->event.pressed); return true;
        #endif
        default: break;
    }
    return true;
}

LEDが無効の状態では点灯しないので、RGB Toggleキーで有効にします。初期状態では本機能はOFF状態なので「LAY_TOG」に割り当てたキーでON状態にします。

brightnessとsaturationは本体の設定を利用するので、RGBを有効にしても点灯しなかったり色が白い場合はこの辺を調整します。

CPIのプレシジョンモード実装

一時的にCPIを下げるような機能を実装しました。以下のような機能で実装します。

  • 任意のキーを押下中のみ切り替え
  • 任意のキーでトグル切り替え
  • 下げるCPIはハードコーディング

layer_led.c と同じようにprecision.cを作成します。

#ifdef PRECISION_ENABLE

#include QMK_KEYBOARD_H

static const uint16_t PROGMEM down_cpi = PRECISION_CPI;

static uint16_t latest_cpi = 1;
static bool     cpi_state  = false;

// トグルで切り替え
void precision_toggle(bool pressed) {
    if (!pressed) {
        return;
    }
    
    uint16_t current_cpi = keyball_get_cpi();
    if (!cpi_state || down_cpi != current_cpi) {
        latest_cpi = current_cpi;
        keyball_set_cpi(down_cpi);
        cpi_state = true;
    } else {
        keyball_set_cpi(latest_cpi);
        cpi_state = false;
    }
}

// キー押下中だけ切り替え
void precision_switch(bool pressed) {
    if (pressed) {
        if(!cpi_state) {
            latest_cpi = keyball_get_cpi();
        }
        keyball_set_cpi(down_cpi);
    } else {
        keyball_set_cpi(latest_cpi);
    }
}

#endif

こちらも機能の有効、無効が切り替えられるようにconfig.h に定義を追加します。モードON時のCPIもここで定義します。

#define PRECISION_ENABLE // 有効化
#define PRECISION_CPI 4  // 下げた時のCPI (1/100の値を指定。左記ならCPI 400)

keymap.cにキーコード追加とキー押下時の処理を追加します。

enum my_keyball_keycodes {
    LAY_TOG = KEYBALL_SAFE_RANGE, // レイヤーLEDトグル
    PRC_TOG,                      // Precision モードトグル
    PRC_SW,                       // Precision モードスイッチ  
};

// キーマップの任意の場所に「PRC_TOG」、「PRC_SW」を追加 
// 例:
//  [3] = LAYOUT_universal(
//    RGB_TOG  , LAY_TOG  , PRC_TOG, PRC_SW


bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        #ifdef LAYER_LED_ENABLE
        case LAY_TOG: toggle_layer_led(record->event.pressed); return true;
        #endif
        #ifdef PRECISION_ENABLE
        case PRC_SW:  precision_switch(record->event.pressed); return false;
        case PRC_TOG: precision_toggle(record->event.pressed); return false;
        #endif
        default: break;
    }
    return true;
}

さいごに

試してみたカスタムファームの内容を紹介しました。細かいテストはやってないので使い方によっては動作しないものがあるかもしれません。後編ではこんな感じのOLEDカスタムをまとめてみます。