完成品
githubリポジトリはこちらになります。
はじめに
keyballをカスタマイズするのは沼です。キーキャップ,キースイッチ,ケーブル,テンティング,キー配列と様々な沼がありますが,今回はその中のOLED沼に浸かっていこうと思います。
OLEDで何をするかを考えたときに最初に思いついたのは,RunCatでした。やはり全人類猫を見ながら生活したいということで,今回はkeyballのOLEDで猫を走らせて行こうと思います。
ベースにしているのはかみだいさんのファームウェアです。
事前準備
- かみだいさんのファームウェアをforkしてcloneする
- QMK firmware環境構築
画像の用意
今回,画像はRunCatのgithubリポジトリから拝借します。
RunCat_for_windows/RunCat/resources/cat/に猫ちゃんのicoファイルが白黒それぞれ5枚ずつあるのでそれをダウンロードしてきます。(今回は白の5枚を使います)
しかし,この猫ちゃんは0~255のモノクロ画像なので,いい感じのスクリプトを書いて0 or 255のモノクロ画像に変換します。スクリプトもgithubにおいてあります。
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')
これで,画像の準備ができました。
画像を変換
次に画像をファームウェアに書きこめるように,コンバーターでbytesに変換します。
画像を選択した後,↑の図のように設定を変更します。
- 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の編集
コンバーターで生成したコードをそれぞれ定数として持たせます。
#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の値によって,表示する猫の画像を切り替えています。
# 何枚目の猫の画像を表示しているかを管理する変数
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を変更する処理を書きます。
...
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も修正します。
...
void keyball_oled_render_cat(void); // <- これを追加
keymap.cの編集
keymap.cにすでにあるoledkit_render_info_userを修正します。
また,keyballのOLEDは縦向きで,猫を横に走らせたいので,OLEDを縦表示にするためにoled_init_user関数を配置しています。(master側のみ回転しているのは,keyballはデフォルトで,スレイブ側のOLEDにkeyballのロゴを表示するようになっているので,そのレイアウトを崩さないようにするためです)
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ライフを!