Numlock
CapsLock
ErgoDoxEZ
QMK
LEDLighting

ErgoDoxEZによるCapsLockを検知してLEDを光らせる(一部未検証)

基本的に、キー押下の結果は、テキストエディタなどに文字が表示されない限り分からない。
しかし、ErgoDoxEZはそれを否定できる。
キー押下の結果をLED点灯で表現すれば実現可能だ(特定の条件のみ)。

注意するべきは、既存のキーコードを書き換える作業が発生すると言うこと。
(当たり前)

上手く使いこなせていないが、キャップスロックキーのみに限定して言えば、成功した。

接尾辞付きの関数

以下の接尾辞付きの関数が今回の対象になる。
(使い分けが分からない)

  • _kb
  • _user

以下も範囲内だろう。

  • _quantum

キーコードの仕込み

過去ではenumのcustom_keycodesを使ったが、今回の例ではmy_keycodesと言う名前になっている。
統一して欲しいね。

既存キーコードの書き換え

以下2種類の関数を用いる。

  • process_record_kb()
  • process_record_user()

キーイベント発生前に、QMKによってキー処理が先に呼び出される(trueを返すのが必須だが)。
そして、この関数によってキー処理の内容を書き換えた場合、書き換えると言うより処理内容が拡張されたことになる(だから何?)。

この関数の特徴は、キー押下やキー押上の度に呼び出される(確実に実行されると言うこと)。
しかし、この関数がfalseを返却した場合は、拡張処理を無視し、通常のキーコードのみが使われる。

例(process_record_user() Implementation

なぜか例では、switch関数でFOOを処理させるようになっているが、処理の中身がなく、falseを返すようにしている。

keymap.c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case FOO:
            if (record->event.pressed) {
                // Do something when pressed
            } else {
                // Do something else when release
            }
            return false; // Skip all further processing of this key
        case KC_ENTER:
            // Play a tone when enter is pressed
            if (record->event.pressed) {
                PLAY_NOTE_ARRAY(tone_qwerty);
            }
            return true; // Let QMK send the enter press/release events
        default:
            return true; // Process all other keycodes normally
    }
}

この関数を使うのは、レイヤー移動などで使った過去があるため、特に難しいことは無いだろう。

process_record_*

これ何?

上記で列挙した関数2種類を使うようだ。

  • bool process_record_kb(uint16_t keycode, keyrecord_t *record)
  • bool process_record_user(uint16_t keycode, keyrecord_t *record)

キーボード用の関数:process_record_kb
キーマップ用の関数:process_record_user

接尾辞の_kbと_userの違いがよく分からない。
なぜに説明がないのだろうか。

エンターキー押下でLED点灯

今回試しに実装したのだが、なぜか光らなかった。
その理由は、レイヤー変更(LT関数)を利用したときに、LED点灯設定を組み込んでいたため、競合が発生し、無灯化したのだろう。
それらの変更を変えず、エンターキー単体を用意し、押下したときは点灯を確認した。

keymap.c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
    case KC_LCTRL:  // ←コントロールキー押下で、黄色に光る。
        if (record->event.pressed) {
            rgblight_sethsv_noeeprom_yellow();
        }
        else {
            rgblight_init();
        }
        return true;

    case KC_ENTER:  // ←エンターキー押下で、緑に光る。
        if (record->event.pressed) {
            rgblight_sethsv_noeeprom_green();
        }
        else {
            rgblight_init();
        }
        return true;
  }
  return true;
}

LED点灯処理だけに限らないのだが、record->event.pressed処理は、キー押下時の処理であり、キー押上の場合はelse文内で処理させる必要がある。
そのため、キー押下で点灯させ、キー押上で無灯にする。
それをしなければ、何も触っていない状態で光り続けてしまう。

今回無理矢理光らせたのだが、以下の説明では、もっと手軽に出来る!? もっとよく分からない状態になった。

特有のキーコードはLED制御可能

以下5種類が特有のキーコードになる。

  • USB_LED_NUM_LOCK
  • USB_LED_CAPS_LOCK
  • USB_LED_SCROLL_LOCK
  • USB_LED_COMPOSE
  • USB_LED_KANA

例(led_set_user() Implementation

led_set_user関数を用いる(標準では利用されていない関数)。

keymap.c
void led_set_user(uint8_t usb_led) {
    if (usb_led & (1<<USB_LED_NUM_LOCK)) {
        PORTB |= (1<<0);
    } else {
        PORTB &= ~(1<<0);
    }
    if (usb_led & (1<<USB_LED_CAPS_LOCK)) {
        PORTB |= (1<<1);
    } else {
        PORTB &= ~(1<<1);
    }
    if (usb_led & (1<<USB_LED_SCROLL_LOCK)) {
        PORTB |= (1<<2);
    } else {
        PORTB &= ~(1<<2);
    }
    if (usb_led & (1<<USB_LED_COMPOSE_LOCK)) {
        PORTB |= (1<<3);
    } else {
        PORTB &= ~(1<<3);
    }
    if (usb_led & (1<<USB_LED_KANA_LOCK)) {
        PORTB |= (1<<4);
    } else {
        PORTB &= ~(1<<4);
    }
}

ヘッダファイルの読み込み

上記は、そのままでは使えない。
led.hファイルをインクルードし、デファイン値で定義している値を使う。

その他(揚げ足とり)

箇条書きと例のdefine値が異なる・・・う〜ん。

custom_quantum_functions.md
USB_LED_COMPOSE_LOCK -> USB_LED_COMPOSE
USB_LED_KANA_LOCK -> USB_LED_KANA

修正依頼を出してみた。

led_set_*

これ何?

今回も関数2種類を使うようだ。

  • void led_set_kb(uint8_t usb_led)
  • void led_set_user(uint8_t usb_led)

キーボード用の関数:led_set_kb
キーマップ用の関数:led_set_user

キャップスロックキー押下でLED点灯

結論を言えば、点灯した。
しかし、止めた方が良い。
他のLED点灯に悪影響を与えるようだ(私の知識不足)。

keymap.c
#include "led.h"

void led_set_user(uint8_t usb_led) {
    if (usb_led & (1<<USB_LED_NUM_LOCK)) {
        PORTB |= (1<<0);
            rgblight_sethsv_noeeprom_yellow();
    } else {
        PORTB &= ~(1<<0);
            rgblight_init();
    }
    if (usb_led & (1<<USB_LED_CAPS_LOCK)) {
        PORTB |= (1<<1);
            rgblight_sethsv_noeeprom_green();
    } else {
        PORTB &= ~(1<<1);
            rgblight_init();
    }
}

ナムロックは、瞬間だけLEDが光り、あとは沈黙した。
本来光るべきLED(レイヤー変更時にLED点灯設定有り)も瞬間だけ光り、あとは沈黙し、使い物にならない。
なぜか、キャップスロックキーが有効になっているときはREDが点灯し、無効にしたとき無灯火する(挙動が異なる)。

関数の違いがわからない

led_set_user関数がダメならled_set_kb関数だろうと思い両方試したが、どちらもナムロックは一瞬の点滅で終わった。

点灯というのは

前面の3つのLEDは「我関せず」と言う感じで光りは一切無し。
ここで言う点灯しているというのは、背面のLEDについて。

だったらPORTB |= (1<<0);は、何をする処理になる?
これだけなのは間違っているように思うが・・・。

結論

この関数で、LED点灯は難しいだろう。
何の説明も無いため、使いこなせる人を尊敬する。ぜひ、使い方を教えて欲しい。

杞憂

私が背面LEDを設定しているのは、EEPROMを使わない状態でのLED点灯だ。
そのため、もしかしたらEEPROMを使えば、共演(?)できるようになるかもね。
(検証するつもりはない)

ナムロックとキャップスロックで挙動が異なるのは理解できないが・・・。

初期化コード (過去に検証済み)

キーボードの初期化が必要なようだ。

例(matrix_init_user() Implementation

B1・B2・B3に関するLEDに関する説明をしているが、けっきょくどうすればいい!?
前面LEDについて例を出しているのか!?

keymap.c
void matrix_init_user(void) {
  // Call the keymap level matrix init.

  // Set our LED pins as output
  DDRB |= (1<<1);
  DDRB |= (1<<2);
  DDRB |= (1<<3);
}

これをどこかに定義しておけば初期化される!?

matrix_init_*

これ何?

今回も関数2種類を使うようだ。

  • void matrix_init_kb(void)
  • void matrix_init_user(void)

キーボード用の関数:matrix_init_kb
キーマップ用の関数:matrix_init_user

これも過去に使った関数だ

私はこの関数に、キーボードを接続したとき、レイヤーを0にする処理を入れている。

スキャンコード(利用注意) 未検証

process_record_*()関数によってキーボードのキーコードが送信されるだけでなく、キーボード本体に悪影響を及ぼさないようにもしているのだが、ごく稀に影響することがあるため気をつけなければならない。
そして、更に気をつけなければならないのは、パフォーマンス低下も引き起こすこと。
最低でも毎秒10回もの呼び出しが発生する(ほぼ無限ループじゃないか)。

例はない

十分に理解して使わなければ問題を引き起こすだろう。
ゆえに、正しい知識があり、十分に使いこなせる理解度があるならば、例を付ける必要は無いとのこと。
やはり、勉強より経験を優先することは、犯罪行為に手を染める(損をする)と言うことであり、害にしかならないと言うことか。

matrix_scan_*

これ何?

今回も関数2種類を使うようだ。

  • void matrix_scan_kb(void)
  • void matrix_scan_user(void)

キーボード用の関数:matrix_scan_kb
キーマップ用の関数:matrix_scan_user

定期実行するときにもこの関数が有効なようだ。

知識がない

ゆくゆくは検証対象にしたい。
しかし、今ではないのは確かだ。

一時停止や起動のコード 未検証

パソコン本体が一時停止した場合、キーボードも一時停止に出来る。
それが以下の2種類の関数だ。

  • suspend_power_down_*
  • suspend_wakeup_init_*

例(suspend_power_down_user() and suspend_wakeup_init_user() Implementation

使い道で、特別難しいことはないはず。

keymap.c
void suspend_power_down_user(void)
{
    rgb_matrix_set_suspend_state(true);
}

void suspend_wakeup_init_user(void)
{
    rgb_matrix_set_suspend_state(false);
}

これで動くようだが、何か分かっていない。

keyboard_init_*

これ何?

今回も関数2種類を使うようだ。

  • void suspend_power_down_kb(void)
  • void suspend_wakeup_init_user(void)

キーボード用の関数:suspend_power_down_kb・suspend_wakeup_init_user
キーマップ用の関数:suspend_power_down_kb・suspend_wakeup_init_user

未検証

キーボードをスリープさせた場合、キーボードからのコンピュータ復帰が出来ないはずなので、今後も使うことはないだろう。
タダの勘違い?

レイヤー変更コード 過去に検証済み

レイヤー変更の度に実行されるコードになる。
独自のレイヤー変更に有効だろう。

例(layer_state_set_* Implementation

アンダーグロウRGB点灯例

keymap.c
uint32_t layer_state_set_user(uint32_t state) {
    switch (biton32(state)) {
    case _RAISE:
        rgblight_setrgb (0x00,  0x00, 0xFF);
        break;
    case _LOWER:
        rgblight_setrgb (0xFF,  0x00, 0x00);
        break;
    case _PLOVER:
        rgblight_setrgb (0x00,  0xFF, 0x00);
        break;
    case _ADJUST:
        rgblight_setrgb (0x7A,  0x00, 0xFF);
        break;
    default: //  for any other layers, or the default layer
        rgblight_setrgb (0x00,  0xFF, 0xFF);
        break;
    }
  return state;
}

layer_state_set_*

これ何?

今回も関数2種類を使うようだ。

  • void uint32_t layer_state_set_kb(uint32_t state)
  • uint32_t layer_state_set_user(uint32_t state)

キーボード用の関数:layer_state_set_kb
キーマップ用の関数:layer_state_set_user

EEPROMにかき込むことで永続性を保つ 未検証

設定の読み込みはeeconfig_read_kbeeconfig_read_user関数から利用ができる。
設定への書き込みは、eeconfig_update_kbeeconfig_update_user関数を使う。

デフォルト

eeconfig_init_kbeeconfig_init_user関数で設定する。
しかし、4バイトしかないため、たいしたことは出来ないだろう(合っているのか?)。

注意点

EEPROMへの書き込み回数は(高い上限ではあるが)回数が限られている。
そして、頻繁な書き込みはMCUの寿命を短くする(MCUって何?)。
そのため、理解していない状態で書き込むのは控えた方が良い。

例(Implementation

複雑な関数だが、ソースファイルの先頭に記述する必要がある。

keymap.c
typedef union {
  uint32_t raw;
  struct {
    bool     rgb_layer_change :1;
  };
} user_config_t;

user_config_t user_config;

見て分かるとおりuint32_tは、32ビットを表現する変数宣言だ。
他に8ビットや16ビットが存在する。それらを混在させることも可能だが、我々利用者が混乱するため止めた方が良い。

以下のソースコードが今回の処理本体になる。
matrix_init_user関数で定義する。

keymap.c
void matrix_init_user(void) {
  // Call the keymap level matrix init.

  // Read the user config from EEPROM
  user_config.raw = eeconfig_read_user();

  // Set default layer, if enabled
  if (user_config.rgb_layer_change) {
    rgblight_enable_noeeprom();
    rgblight_sethsv_noeeprom_cyan(); 
    rgblight_mode_noeeprom(1);
  }
}

この例では、eepromとnoeepromを使い分けているが、どのように使いこなせば良いのだろうか。

この処理にRGB点灯が有効になる。
しかし、その一方で通常のRGB処理は無効になるため、今回の処理を無効にするかなど切り替える必要がある。

keymap.c
uint32_t layer_state_set_user(uint32_t state) {
    switch (biton32(state)) {
    case _RAISE:
        if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_magenta(); rgblight_mode_noeeprom(1); }
        break;
    case _LOWER:
        if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_red(); rgblight_mode_noeeprom(1); }
        break;
    case _PLOVER:
        if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_green(); rgblight_mode_noeeprom(1); }
        break;
    case _ADJUST:
        if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_white(); rgblight_mode_noeeprom(1); }
        break;
    default: //  for any other layers, or the default layer
        if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_cyan(); rgblight_mode_noeeprom(1); }
        break;
    }
  return state;
}
keymap.c
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
    case FOO:
      if (record->event.pressed) {
        // Do something when pressed
      } else {
        // Do something else when release
      }
      return false; // Skip all further processing of this key
    case KC_ENTER:
        // Play a tone when enter is pressed
        if (record->event.pressed) {
            PLAY_NOTE_ARRAY(tone_qwerty);
        }
        return true; // Let QMK send the enter press/release events
    case EPRM:
        if (record->event.pressed) {
            eeconfig_init(); // resets the EEPROM to default
        }
        return false;
    case RGB_LYR:  // This allows me to use underglow as layer indication, or as normal
        if (record->event.pressed) { 
            user_config.rgb_layer_change ^= 1; // Toggles the status
            eeconfig_update_user(user_config.raw); // Writes the new status to EEPROM
            if (user_config.rgb_layer_change) { // if layer state indication is enabled, 
                layer_state_set(layer_state);   // then immediately update the layer color
            }
        }
        return false; break;
    case RGB_MODE_FORWARD ... RGB_MODE_GRADIENT: // For any of the RGB codes (see quantum_keycodes.h, L400 for reference)
        if (record->event.pressed) { //This disables layer indication, as it's assumed that if you're changing this ... you want that disabled
            if (user_config.rgb_layer_change) {        // only if this is enabled 
                user_config.rgb_layer_change = false;  // disable it, and 
                eeconfig_update_user(user_config.raw); // write the setings to EEPROM
            }
        }
        return true; break;
    default:
      return true; // Process all other keycodes normally
  }
}
keymap.c
void eeconfig_init_user(void) {  // EEPROM is getting reset! 
  user_config.rgb_layer_change = true; // We want this enabled by default
  eeconfig_update_user(user_config.raw); // Write default value to EEPROM now

  // use the non noeeprom versions, to write these values to EEPROM too
  rgblight_enable(); // Enable RGB by default
  rgblight_sethsv_cyan();  // Set it to CYAN by default
  rgblight_mode(1); // set to solid by default
}

理解する気をそがれるぐらい難しそうだ。

EECONFIG関数説明

これ何?

今回は関数6種類を使うようだ。

  • void eeconfig_init_kb(void)

  • uint32_t eeconfig_read_kb(void)

  • void eeconfig_update_kb(uint32_t val)

  • void eeconfig_init_user(void)

  • uint32_t eeconfig_read_user(void)

  • void eeconfig_update_user(uint32_t val)

キーボード用の関数:eeconfig_init_kb・eeconfig_read_kb・eeconfig_update_kb
キーマップ用の関数:eeconfig_init_user・eeconfig_read_user・eeconfig_update_user

備忘

今回の処理をする以前の問題として、コンパイル方法を忘れてはあほくさいので、それをメモする。

コンパイル方法

QMKディレクトリに移動して、必要なキーボードのキーマップを指定して、コンパイルを行う。

プロンプト
cd クローンディレクトリ\github_repository\qmk_firmware
make clean | make keyboard=ergodox_ez keymap=testChesscommands

コンパイル結果

qmk_firmwareディレクトリ配下に、Hexファイルが作られる。

ergodox_ez_testChesscommands.hex

キーボードに反映

teensy.exeを使い、上記のHexファイルをキーボードに読み込ませる。
キーボードのリセットボタン押下で読み込みが始まる。

感想

タイトルは一部だが、大分未検証部分が多いのは、私がチキンだからだ。
キーボードが壊れるような説明があれば、躊躇する。
どうせ、自分になじむキーボードは、ErgoDoxEZではないため、壊れたところで問題はない・・・問題は無い。
問題は無い。
しかし、数万円したからには少しでも長生きさせたい。

3年間の保証期間があるため、壊すなら今だが・・・。

LED点灯を欲する

LED点灯の記事を探すが、ほぼない。
なぜに無いのか不思議でならないぐらい投稿を見つけられない。
誰も興味ないのだろうか。

以上だ。