LoginSignup
9
3

keyballのOLEDで猫ちゃんを走らせる

Last updated at Posted at 2023-06-17

完成品

無題の動画 ‐ Clipchampで作成.gif

githubリポジトリはこちらになります。

はじめに

keyballをカスタマイズするのは沼です。キーキャップ,キースイッチ,ケーブル,テンティング,キー配列と様々な沼がありますが,今回はその中のOLED沼に浸かっていこうと思います。

OLEDで何をするかを考えたときに最初に思いついたのは,RunCatでした。やはり全人類猫を見ながら生活したいということで,今回はkeyballのOLEDで猫を走らせて行こうと思います。

ベースにしているのはかみだいさんのファームウェアです。

事前準備

  • かみだいさんのファームウェアをforkしてcloneする

  • QMK firmware環境構築

画像の用意

今回,画像はRunCatのgithubリポジトリから拝借します。

RunCat_for_windows/RunCat/resources/cat/に猫ちゃんのicoファイルが白黒それぞれ5枚ずつあるのでそれをダウンロードしてきます。(今回は白の5枚を使います)

image.png

しかし,この猫ちゃんは0~255のモノクロ画像なので,いい感じのスクリプトを書いて0 or 255のモノクロ画像に変換します。スクリプトもgithubにおいてあります。

conver.ipynb
for i in range(5):
    im = Image.open(f'img/{i}.png')
    im.point(lambda x: 255 if x > 115 else 0).save(f'converted_img/{i}.png')

image.png

これで,画像の準備ができました。

画像を変換

次に画像をファームウェアに書きこめるように,コンバーターでbytesに変換します。

image.png

image.png

画像を選択した後,↑の図のように設定を変更します。

  • Background color: Black
  • Code output format: Plain bytes
  • Draw mode: Vertical - 1 bit per pixel

なお設定に関しては,ご自身の環境や図によってよしなにいじってみてください。

Generate codeで出力されたコードをメモっておきます。

ファームウェアの編集

今回は,キーが押されるごとに猫の画像を切り替えていき,パラパラ漫画の要領で猫を走らせます。

詳しいコードの編集は,githubのcommitの差分を参照してみてください。

また今回編集するのは以下のファイルたちです。

keyball/qmk_firmware/keyboards/keyball/keyball61/keymaps/cat/keymap.c (new)
keyball/qmk_firmware/keyboards/keyball/lib/keyball/keyball.c
keyball/qmk_firmware/keyboards/keyball/lib/keyball/keyball.h

かみだいさんのファームウェアのkeymapを複製,名前変更する

上書きしてしまってもいいのですが,わかりにくくなるため自分のkeymapを作成します。

$ cp -r qmk_firmware/keyboards/keyball/keyball61/keymaps/kamidai/ qmk_firmware/keyboards/keyball/keyball61/keymaps/cat

これで,

keyball/qmk_firmware/keyboards/keyball/keyball61/keymaps/cat/
    ├── config.h
    ├── keymap.c
    ├── readme.md
    └── rules.mk

が生成されていればOKです。

keyball.cの編集

コンバーターで生成したコードをそれぞれ定数として持たせます。

keyball.c
#ifdef OLED_ENABLE
// clang-format off
    const char PROGMEM cat_0[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xe0, 0xe0, 
        0xe0, 0xe0, 0xe0, 0xf0, 0xfc, 0xff, 0xfd, 0xe7, 0xe7, 0xff, 0xe7, 0x70, 0x30, 0x00, 0x00, 0x00, 
        0x06, 0x06, 0x06, 0x06, 0x06, 0x03, 0x03, 0x01, 0x01, 0x00, 0x01, 0x7f, 0xff, 0xff, 0x3f, 0x7f, 
        0xff, 0xdf, 0x0f, 0x0f, 0x0f, 0x7f, 0x3f, 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    };
    const char PROGMEM cat_1[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0x00, 0x00, 0x00, 
        0x00, 0x80, 0xc0, 0xc0, 0xc0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0xe0, 0xc0, 0xe0, 0xe0, 0xf0, 
        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf8, 0xfc, 0xff, 0xfe, 0xff, 0xf3, 0xff, 0x7f, 0x32, 0x1c, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x38, 0x1c, 0xde, 0xfe, 0x7f, 0x3f, 0x1f, 0x0f, 0x0f, 
        0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x03, 0x03, 0x07, 0x07, 0x0f, 0x0f, 0x06, 0x04, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    };
    const char PROGMEM cat_2[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x04, 0x0c, 0x06, 0x06, 0x06, 0x06, 0x06, 0x0c, 0x8c, 0x9c, 0xf8, 0xf0, 0xf0, 0xf0, 
        0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 0xf0, 0xfc, 0xfe, 0xfb, 0xce, 0xfe, 0xff, 0xcb, 0x60, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0x0e, 0x0f, 0x27, 0x3f, 0x3f, 0x1f, 0x0f, 0x07, 
        0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x0f, 0x0f, 0x1f, 0x3f, 0x3f, 0x3e, 0x04, 0x04, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    const char PROGMEM cat_3[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x06, 0x0c, 0xdc, 0xf8, 0xe0, 0xf0, 
        0xf0, 0xe0, 0xe0, 0xc0, 0x80, 0x80, 0x80, 0xc0, 0xe0, 0xf0, 0x68, 0x7c, 0xf0, 0xf8, 0x78, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x1f, 0x0f, 
        0x0f, 0x0f, 0x0f, 0x1f, 0x1f, 0x1f, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0x7f, 0x67, 0x66, 0x06, 0x01, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 
    };
    const char PROGMEM cat_4[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf0, 
        0xf0, 0xf0, 0xe0, 0xe0, 0xf0, 0xf8, 0xfc, 0xf6, 0x9e, 0xfc, 0xfe, 0xbe, 0xc0, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x3f, 0x3f, 0x3f, 
        0xff, 0xff, 0x7f, 0x3f, 0xff, 0xff, 0xff, 0x07, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
// clang-format on
#endif

次に,実際に猫をOLEDに描画する部分の処理を追加します。

cat_stateの値によって,表示する猫の画像を切り替えています。

keyball.c
# 何枚目の猫の画像を表示しているかを管理する変数
uint8_t cat_state = 0;

void keyball_oled_render_cat(void)
{
#ifdef OLED_ENABLE
    switch (cat_state) {
        case 0:
            oled_write_raw_P(cat_0, sizeof(cat_0));
            break;
        case 1:
            oled_write_raw_P(cat_1, sizeof(cat_1));
            break;
        case 2:
            oled_write_raw_P(cat_2, sizeof(cat_2));
            break;
        case 3:
            oled_write_raw_P(cat_3, sizeof(cat_3));
            break;
        case 4:
            oled_write_raw_P(cat_4, sizeof(cat_4));
            break;
    }
#endif
}

最後に,keyball.cにもともとあるprocess_record_kbという関数(キーが押されたときに発火するやつ)の中に,cat_stateを変更する処理を書きます。

keyball.c
...
bool process_record_kb(uint16_t keycode, keyrecord_t *record) {
    cat_state = (cat_state + 1) % 5; // <- これを追加
    // store last keycode, row, and col for OLED
    keyball.last_kc  = keycode;
    keyball.last_pos = record->event.key;
    ...

mod 5を取ってあげることで,cat_stateの値は0 ~ 4の間を無限にループします。

keyball.hの編集

keyball.cに関数を追加したためヘッダーファイルのkeyball.hも修正します。

keyball.h
...
void keyball_oled_render_cat(void); // <- これを追加

keymap.cの編集

keymap.cにすでにあるoledkit_render_info_userを修正します。

また,keyballのOLEDは縦向きで,猫を横に走らせたいので,OLEDを縦表示にするためにoled_init_user関数を配置しています。(master側のみ回転しているのは,keyballはデフォルトで,スレイブ側のOLEDにkeyballのロゴを表示するようになっているので,そのレイアウトを崩さないようにするためです)

keymap.c
void oledkit_render_info_user(void) {
    // 猫を中央に持ってくるために空行を入れる
    oled_write_ln_P(PSTR(" "), false);
    oled_write_ln_P(PSTR(" "), false);
    oled_write_ln_P(PSTR(" "), false);
    oled_write_ln_P(PSTR(" "), false);
    oled_write_ln_P(PSTR(" "), false);
    keyball_oled_render_cat();
}

// 猫を表示する側のOLEDを縦表示にする
oled_rotation_t oled_init_user(oled_rotation_t rotation) {
    return !is_keyboard_master() ? OLED_ROTATION_180 : OLED_ROTATION_270;
}

あとは,ファームウェアをビルドして,keyballに書き込めば完成です!

おわりに

筆者はC言語なんもわからん人間なので,もし変な実装があったらぜひ教えていただけるとありがたいです!

それでは,良きkeyballライフを!

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3