Infinity ErgoDox の LCD でアニメーション

Infinity ErgoDox の LCD でアニメーションを表示して遊ぶ方法です。

ソースコードはこちら

https://github.com/koktoh/qmk_firmware_koktoh/tree/dev/ergodox_infinity_lcd_animation_pacman/keyboards/ergodox_infinity

※もろもろの事情で動作確認は行っていませんが、たぶん動くと思います。

# Infinity ErgoDox とは

[Input Club 社が販売している ErgoDox][ergo_inf] です。
LCD が標準で基板に搭載されており、同社提供の [Configurator][ergo_inf_configurator] で簡単にキーマップを変更できます。

出荷状態では独自のファームウェアが載っていますが、 [QMK Firmware][qmk] にも[プロジェクト][qmk_ergo_inf]があり、 QMK Firmware に書き換えることもできます。

今回は QMK Firmware に書き換えることを前提とします。
公式のファームウェアに戻すことも可能なので、ご安心ください。

# Infinity ErgoDox の LCD でアニメーションしてみる

## LCD に表示するデータを用意する

何はともあれ、表示するためのデータがないと始まりません。
LCD の表示領域は 128 x 32 なので、それに合わせて作りましょう。
今回は、みんな大好き（？）パックマンのアニメーションを作っていきます。

イメージとしてはこんな感じです。

![pacman.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553901/e98d3fcf-285b-496d-e7d7-1d33734be28f.gif)

雑に自作したところ、画像数は28枚となりました。
これらの画像を LCD で表示するためのデータへと変換します。

### LCD に表示するためのデータ形式

まずは、 LCD に表示するためのデータ形式について簡単に説明します。
アニメーションに必要なのは主に2つです。

* 画像のバイナリ配列
* 画像を表示するためのキーフレーム

#### 画像のバイナリ配列

これは、表示したい画像自体のデータです。
例として、 QMK Firmware のロゴのデータを見てみましょう。（ `quantum\visualizer\resources\lcd_logo.c` にあります）
そのままだと見づらいので少し整形しています。

```cpp
__attribute__((weak)) const uint8_t resource_lcd_logo[512] = {
    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, 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, 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, 0xf8, 0xfe, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x06, 0x29, 0x41, 0x24, 0x52, 0x24, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x42, 0xaa, 0xaa, 0xaa, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x82, 0x28, 0xaa, 0xae, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x09, 0x55, 0x43, 0x28, 0xaa, 0xaa, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x0a, 0x55, 0x42, 0x28, 0xaa, 0xaa, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x38, 0x38, 0x05, 0x45, 0x42, 0x28, 0x89, 0x4a, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x18, 0x38, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x1c, 0x38, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x0e, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x03, 0xff, 0x80, 0x04, 0x45, 0x14, 0xa4, 0x92, 0x83, 0x52, 0x22, 0x22, 0x36, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x0a, 0xaa, 0xaa, 0xaa, 0xba, 0x84, 0x55, 0x55, 0x57, 0x45, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x08, 0xaa, 0xaa, 0xaa, 0x92, 0xb2, 0x55, 0x55, 0x42, 0x65, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x08, 0xaa, 0xaa, 0xaa, 0x92, 0x81, 0x56, 0x65, 0x42, 0x45, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x0a, 0xaa, 0xaa, 0xaa, 0x92, 0x81, 0x54, 0x45, 0x42, 0x45, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x04, 0x48, 0xa2, 0x4a, 0x89, 0x06, 0x24, 0x42, 0x41, 0x36, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
```

8ビットのデータが16列32行並んでいます。バイナリとして考えると、 `(8 x 16) x 32 = 128 x 32` ビットと、 LCD の表示サイズと同じになっていることがわかります。
LCD はビットが `1` のピクセルのみ点灯します。
左上から128ビット分描画して、次の行に移ってまた左から128ビット分描画して……、を32行分繰り返しているわけです。
そして、8ビットをひとまとまりとして HEX 値で保持しているわけです。直観的でわかりやすいかと思います。

画像データの保持の仕方がわかったところで、先ほどの画像をすべてこの形式に変換しましょう。
[ツール][bmp_converter]や自分でスクリプト等を組んで行ってもいいです。

例えば、以下のように28枚分の配列を作り、1つのファイルにまとめてしまいます。

```cpp
const uint8_t pacman_bmp_00[512] = {
    0x00, 0x00, ...
    .
    .
    .
};

const uint8_t pacman_bmp_01[512] = {
    0x00, 0x00, ...
    .
    .
    .
};

.
.
.

const uint8_t pacman_bmp_27[512] = {
    0x00, 0x00, ...
    .
    .
    .
};
```

#### キーフレーム

次に、画像を表示するためのキーフレームを作成します。
画像1枚に対して1つキーフレームを定義します。

```cpp
bool lcd_keyframe_draw_pacman_00(keyframe_animation_t* animation, visualizer_state_t* state) {
    (void)state;
    gdispClear(White);

    gdispGBlitArea(GDISP, 0, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, LCD_WIDTH, (pixel_t*)pacman_bmp_00);

    return false;
};
```

`gdispGBlitArea` 関数で描画範囲や描画するデータを指定しています。ここでは、画像データに先ほど作った `pacman_bmp_00` を指定しています。

これも先ほどと同様に28枚分作ってファイルにまとめます。

## LCD に表示するようにする

これで準備が整いました。 LCD でアニメーションするようにしていきましょう。

なお、 Infinity ErgoDox では一般的な OLED ドライバ API を使いません。
キーボードプロジェクト配下にある、 `visualizer.c` で制御します。

各関数についてはコメントで説明がついているので、そんなに迷うことはないと思います。

今回いじるのは以下の関数です。

```cpp
static keyframe_animation_t lcd_bitmap_animation = {
    .num_frames = 1,
    .loop = false,
    .frame_lengths = {gfxMillisecondsToTicks(0)},
    .frame_functions = {lcd_keyframe_display_layer_bitmap},
};
```

他にも同様の関数がいくつか定義されていますが、これと同じように編集すれば対応できます。

それぞれの設定は以下のようになっています。

|項目|説明|
|:--|:--|
|`.num_frames`|アニメーションのフレーム数|
|`.loop`|ループするか|
|`.frame_lengths`|各キーフレームの表示時間の配列|
|`.frame_functions`|キーフレームの配列|

今回は28個のキーフレームを 200ms ずつ表示して無限ループさせるようにします。

```cpp
static keyframe_animation_t lcd_bitmap_animation = {
    .num_frames = 28,
    .loop = true,
    .frame_lengths = { gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200), gfxMillisecondsToTicks(200) },
    .frame_functions = { lcd_keyframe_draw_pacman_00, lcd_keyframe_draw_pacman_01, lcd_keyframe_draw_pacman_02, lcd_keyframe_draw_pacman_03, lcd_keyframe_draw_pacman_04, lcd_keyframe_draw_pacman_05, lcd_keyframe_draw_pacman_06, lcd_keyframe_draw_pacman_07, lcd_keyframe_draw_pacman_08, lcd_keyframe_draw_pacman_09, lcd_keyframe_draw_pacman_10, lcd_keyframe_draw_pacman_11, lcd_keyframe_draw_pacman_12, lcd_keyframe_draw_pacman_13, lcd_keyframe_draw_pacman_14, lcd_keyframe_draw_pacman_15, lcd_keyframe_draw_pacman_16, lcd_keyframe_draw_pacman_17, lcd_keyframe_draw_pacman_18, lcd_keyframe_draw_pacman_19, lcd_keyframe_draw_pacman_20, lcd_keyframe_draw_pacman_21, lcd_keyframe_draw_pacman_22, lcd_keyframe_draw_pacman_23, lcd_keyframe_draw_pacman_24, lcd_keyframe_draw_pacman_25, lcd_keyframe_draw_pacman_26, lcd_keyframe_draw_pacman_27 },
};
```

これで LCD でアニメーションがされるようになりました。
なお、今回の変更ではレイヤーを変更すると別のアニメーションが実行され、また最初から再生されます。
それを避ける方法もありますが、今回は割愛します。

さて、実は、これではコンパイル時にエラーが発生します。
次はそれを解決しましょう。

## 最大キーフレーム数を拡張する

実は、 `quantum\visualizer\visualizer.h` で最大キーフレーム数が定義されているのですが、16個までとなっています。
今回はキーフレーム数が28あるので最大値を超えてしまい、コンパイルが通りません。

なので、定義を変えてしまいましょう。

```cpp
// #define MAX_VISUALIZER_KEY_FRAMES 16
#define MAX_VISUALIZER_KEY_FRAMES 32
```

雑に `32` に変更しました。
これでコンパイルが通るようになります。

# LCD でアニメーションを楽しもう！

あとはコンパイルしてファームウェアを焼けば LCD でアニメーションが表示されます。（焼き方は readme を読んでください）
なお、今回作成したコードのヘッダーファイルの設定やインクルード宣言など、基本的なことは省略していますので、適宜追加してください。

ちなみに、私はこんなことをしています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Infinity ErgoDox の LCD で響ちゃんのスクロールアニメーション <a href="https://t.co/xzL0HFnLFL">pic.twitter.com/xzL0HFnLFL</a></p>&mdash; アーサー㌠ (@KokToH_kuro) <a href="https://twitter.com/KokToH_kuro/status/1220965207951831040?ref_src=twsrc%5Etfw">January 25, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<font color="#00a3af">**響ちゃんかわいい……**</font>

参考になったかはわかりませんが、こんな感じで Infinity ErgoDox の LCD で遊べるよ、というお話でした。
皆さんも、好きなキャラを LCD に表示してみたり、好き勝手アニメーションさせてみたり、遊んでみてください。

[ergo_inf]:https://input.club/devices/infinity-ergodox/
[ergo_inf_configurator]:https://input.club/configurator-ergodox/
[qmk]:https://github.com/qmk/qmk_firmware
[qmk_ergo_inf]:https://github.com/qmk/qmk_firmware/tree/master/keyboards/ergodox_infinity
[bmp_converter]:http://www.riuson.com/lcd-image-converter/