懐かしのドット絵アニメをLEDマトリクスパネルに表示させてみた

# はじめに

LEDマトリクスパネルを買ってみたので、動作テストとしてドット絵アニメに挑戦してみました。
最終的に出来たのはこれ。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LEDマトリクスパネルを使ってドット絵アニメーション。FF4フィールドキャラ絵かわいい。 <a href="https://twitter.com/hashtag/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C?src=hash&amp;ref_src=twsrc%5Etfw">#電子工作</a> <a href="https://twitter.com/hashtag/Arduino?src=hash&amp;ref_src=twsrc%5Etfw">#Arduino</a> <a href="https://twitter.com/hashtag/ESP32?src=hash&amp;ref_src=twsrc%5Etfw">#ESP32</a> <a href="https://t.co/97uv1oPQFP">pic.twitter.com/97uv1oPQFP</a></p>&mdash; すかこ (@dimeman_sumika) <a href="https://twitter.com/dimeman_sumika/status/1383295851649327105?ref_src=twsrc%5Etfw">April 17, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

![ff4_pic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/02324f3c-04ec-8d4d-ace2-f787e8ec7141.jpeg)

# LEDマトリクスパネル

本来は店舗等でたくさん並べて巨大なディスプレイを構成するものですが、１枚単位でも使えます。
１枚だと16x16～64x64ピクセル程度の表示能力しかありません。
LEDのピッチは2mm～10mmくらいまでいろいろあるようです。

自分は4mmピッチ(P4)の64x32ピクセルのパネルをAliexpressから購入。
信号用のフラットケーブルと電源コネクタも付属して、2000円弱で買えます。
（特に電源コネクタは）別に手に入れるのが大変そうなので、購入前に確認を。

https://www.aliexpress.com/item/32754106669.html

海外通販に抵抗がある場合は、高いですが国内でも入手できそうです。

https://www.akiba-led.jp/product-list/169

これらのパネルはどうやらHUB75というプロトコルで動いているようです。最終的にはライブラリに任せてしまうのですが、原理は一通り見ておきます。

参考記事

https://qiita.com/mml/items/c08946c91053ef23c441

https://bikerglen.com/projects/lighting/led-panel-1up/

# ハードウェア編

## ドライバ基板制作

ドライバとしてESP32開発ボード(devkit)を使用してユニバーサル基板で組みました。
GPIO(出力で使えるピン)とパネルに接続するための16ピンコネクタをひたすら繋ぎます。

方眼紙に配線イメージを書いてから作業開始
![lo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/fae52e8f-d7cb-764a-1013-cd2bf03c2735.jpeg)

パネルの電源は5VなのでdevkitのUSBから取っても良いのですが、表示内容によっては数アンペア必要だそうです。今回は12V ACアダプターからDCDC(縦になっている小基板)で5Vに落としてdevkitおよびパネルの電源としています。

基板（表）
![kiban1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/b57d1d93-17b6-09d3-ef07-3c173290dd65.jpeg)

基板（裏）
![IMG_20210417_132259.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/42455fcc-fa4a-0810-d0dc-b7663ce7e11e.jpeg)

## "ガワ"制作

ホームセンターで買った木材を塗装してパネルに枠を付けました。
おしゃれガジェットっぽくなったぞ。

![IMG_20210417_132857.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/dd97a01b-5bce-c27c-bf8f-fe6d8be17955.jpeg)

のちのち動作モードを変更したくなるので、プッシュスイッチも２つ程付けました。

![IMG_20210417_132905.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/ca1d15ee-f093-884b-aeed-45ce986553aa.jpeg)

# ソフトウェア編

## ライブラリインストール

HUB75の制御は以下のライブラリに任せます。

https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA

Arduinoのリポジトリに登録されているようなので、ライブラリマネージャからインストールしておきます。

![lib_dl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/b290b85b-b42f-fdee-5395-32b3ea7d95ea.png)

## ドット絵データ作成

画像からマイコンに読ませる配列データを作成するために、LCD image converterを利用します。

https://lcd-image-converter.riuson.com/en/about/

画面上をポチポチクリックして絵を描くこともできますし、Image -> Importで読み込みも可能。

![lic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/90f66f1a-f2e3-97e6-3b36-8fb3d65f46a1.png)

変換設定はこんな感じで。1ピクセルあたり16bitの情報で扱うので、R5G6B5を選んでBlock sizeを16bitにしておきます。

![lic_c0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/44e68545-aa9d-1f04-f995-69b988479f5c.png)
![lic_c1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/51ca16ac-fb44-f142-1136-07d5908cc6c1.png)

設定が終わったら、File -> Convertで書き出します。

書き出したファイルを開いてみると、配列データと構造体が定義されています。
このデータをそのまま使うとSRAMに展開されてしまうので、"PROGMEM"属性を追加してFlashから直接読むようにしておきます。

```cpp
typedef struct {
   const uint16_t *data;
   uint16_t width;
   uint16_t height;
   uint8_t dataSize;
} tImage;

static const uint16_t image_data_img0[1024] PROGMEM = {
    0x0000, 0x0000, 0x0000, 0x0000,
    ～略～
};
const tImage img0 = { image_data_img0, 32, 32,
    16 };
```
アニメーションさせたい場合は複数の画像が必要なので、ここまでの手順を繰り返してデータを全て作っておきます。

## 描画プログラムの作成

```cpp
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include "sprites.h"

MatrixPanel_I2S_DMA *d = nullptr;

void drawRGBBitmap (int x, int y, const uint16_t bitmap[], int w, int h) {
  for(int j=0; j<h; j++) {
    for(int i=0; i<w; i++) {
      d->drawPixel(x+i, y+j, pgm_read_word(&bitmap[j*w+i]));
    }
  }
}

void setup() {
  // LED Matrix
  //                               R1, G1, B1, R2, G2, B2, A,  B,  C,  D,  E, LAT, OE, CLK};
  HUB75_I2S_CFG::i2s_pins _pins = {25, 26, 27, 21, 22, 23, 12, 16, 17, 18, -1, 32, 33, 15};
  HUB75_I2S_CFG mxconfig(
                          64,  // width
                          32,  // height
                           1,  // chain length
                       _pins   // pin mapping
  );
  d = new MatrixPanel_I2S_DMA(mxconfig);
  d->begin();
}

void loop() {
  // 1枚目
  d->flipDMABuffer();
  d->fillScreen(d->color444(0, 0, 0));  
  drawRGBBitmap(0, 0, img0.data, img0.width, img0.height);
  d->showDMABuffer();
  delay(500);

  // 2枚目
  d->flipDMABuffer();
  d->fillScreen(d->color444(0, 0, 0));  
  drawRGBBitmap(0, 0, img1.data, img1.width, img1.height);
  d->showDMABuffer();
  delay(500);

  // ここは好きなように変更
}
```

まずLCD image converterで作った画像データをincludeしておきます。
drawRGBBitmapは画像データを順次読み出して1ピクセル毎に描画する関数。
setup()内では基板に配線した通りに端子を定義してインスタンス生成。
loop()内ではdrawRGBBitmap関数を使って描画。
ダブルバッファになっているので、対象バッファ切り替え→描画→バッファ有効化のループにします。

# おわりに

アニメーション表示はかわいいのですが、実用性は全くありません。今回使ったライブラリの描画バックエンドは界隈ではおなじみのAdafruits GFXのため、テキストや図形も自由自在に表示出来ます。とりあえず普段は時計と環境センサーの情報を表示させています。

![lp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558348/6d06e3f4-6ffb-5b62-5822-f3ac291f949a.jpeg)

予算的にも3000~4000円で作れるので、一家に一台いかがでしょうか？