0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LILYGO e-Paper でも電子ペーパーの名札ソリューション

0
Posted at

先日、次のような記事を投稿しました。M5Stack 社製の電子ペーパー端末を名札として使うために、SD カードに入れた画像を表示するアプリを作った話です。今回はその続きで、同じものを LILYGO の電子ペーパーへ移植します。

移植したのは LILYGO T5 4.7 inch e-Paper(ESP32-S3)です。2024 年 12 月に Shigezone で購入しました。4.7 インチは M5Paper より一回り大きく、名札としてちょうどよさそうです。

実は購入直後に実装着手したのですが、実装方法が分からない・うまくいかないで挫折していますた。今回 M5Paper 向けの改修にあたり、Claude Code に相談したらできるのでは?と思い、約二年間ほど埃に被っていた中から出してきました。

機能と操作は M5Paper 版に揃えています。ただし、M5Unified / M5GFX が使えないので、描画まわりは作り直しになりました。この記事では、その違いを中心に書きます。

作ったものは GitHub に置いてあります。詳細はそちらをご確認ください。

対応機種

型番 画面 操作
H716 960 × 540 / モノクロ タッチ・ボタン
H578 / H580 960 × 540 / モノクロ ボタンのみ

同じ製品の中で、タッチパネルの有無だけが違う版が売られています。中身はどれも ESP32-S3-WROOM-1-N16R8(16MB Flash / 8MB PSRAM)で、パネルも同じ ED047TC1 です。私が所有しているのは H716 です。

タッチの有無は起動時に I2C を叩いて判定するようにしました。GT911 が見つかればタッチあり、見つからなければボタンだけで動きます。PlatformIO の env は 1 つだけで、同じバイナリがどの版でも動きます。M5Paper 版のように機種ごとの分岐を用意する必要はありませんでした。

画面が回らない

M5GFX には setRotation() がありますが、LILYGO のドライバ(LilyGo-EPD47)にはありません。epd_draw_grayscale_image() はフレームバッファをそのまま転送するだけです。

パネルは 960 × 540 の横長固定です。縦向きの写真をそのまま出すと、細い帯になってしまいます。そこで、画面ではなく書き込む座標の方を回すことにしました。

// M5GFX ならこれで済む
M5.Display.setRotation(1);

// EPD47 には無いので、論理座標をパネル座標へ変換して書く
void Screen::putGray(int x, int y, uint8_t gray)
{
    int px, py;
    toPanel(x, y, px, py);   // 回転をここで吸収する
    epd_draw_pixel(px, py, gray, buffer);
}

ただし、文字だけはこの方法が使えません。ライブラリの writeln() がフレームバッファへ直接書き込むためです。なので、文字を含む画面(一覧や QR)は回転 0 のまま組み、回すのは画像だけにしました。

画像のデコードも自前でやる

M5GFX の drawJpg() / drawPng() にあたるものもありません。JPEGDEC と PNGdec を使って、デコード・縮小・回転・減色を自分で書きました。

デコーダはブロック単位・行単位でしか画素を渡してこないので、「このブロックが受け持つ描画先はどこか」を毎回引く形になります。このとき、描画先から元画像を引く向きで範囲を決めるのがコツでした。逆向きにすると、拡大したときに描画先へ穴が空きます。

減色は 4 × 4 の組織的ディザ(Bayer)にしました。16 階調しかないので、8 bit をそのまま切り捨てると空や肌にはっきりした縞が出ます。誤差拡散も考えましたが、行を跨いで誤差を持ち回る必要があり、渡ってくる順番が保証されないこの作りでは使えませんでした。位置だけで決まる組織的ディザなら、順番に関係なく同じ結果となります。

遅さの正体は「白へ振る回数」

前回の記事で、カラーの電子ペーパーの描画回数に苦戦したと書きました。今回も似た話にぶつかりましたが、原因は少し違いました。

一覧のカーソルを 1 つ動かすたびに、体感できるほど待たされます。範囲は 1 行ぶんしかないのに、なぜ遅いのか。ライブラリの中を追うと、こうなっていました。

void epd_clear_area(Rect_t area)
{
    epd_clear_area_cycles(area, 4, 50);   // 4 サイクル × (黒 4 + 白 4) = 32 パス
}

つまり、消す処理が 32 パス。そのあとの階調の転送(epd_draw_grayscale_image)は 15 パスなので、消す方が倍以上重いのです。しかも動く 2 行を別々に更新していたので、カーソル 1 つで合計 94 パス走っていました。

やったことは 2 つだけです。

// 動く 2 行はまとめて 1 回で出す(ボタン操作なら隣り合う行なので範囲はほぼ変わらない)
// カーソル移動のときは白へ振る回数を 2 サイクルに落とす
Screen::flushArea(0, top, 960, rows * kRowHeight, 2);

これで 94 パスが 31 パスになり、はっきり速くなりました。部分更新が遅いときは、範囲を狭めるより先にサイクル数を見直したほうがよさそうです。

使えるボタンが 1 つしかない

基板にはボタンが 3 つ付いていますが、アプリが使えるのは 1 つだけでした。

ボタン GPIO 役割
RST リセット
BOOT 0 ダウンロードモード用
ユーザーボタン 21 これだけが使える

BOOT は押してはいけません。IO0 は電子ペーパーの設定用シフトレジスタ(74HCT4094)の STR ラッチと共用で、動作中に押すと表示が乱れます。

そこで、操作は「短押しで次へ、長押しで決定」の 1 つに詰め込んでいます。

タッチが保持されていた

実機で動かして最初にぶつかった不具合です。一覧から [WiFi] を選ぶと QR コードが出るのですが、出た瞬間に一覧へ戻ってしまいます。

原因は GT911 の仕様でした。タッチの状態を、ホストが読み取るまで保持します。QR を出す全面更新は数秒かかり、その間こちらは I2C を読んでいません。更新が終わって最初に読むと、さっき [WiFi] を選んだそのタップがそのまま返ってきます。次の読み取りで「離された」と判定され、開いた画面がその場で閉じる、というわけです。

問題は、時間で待っても防げないことです。保持された状態は時間では消えないので、待ってから読んでも同じものが出てきます。

// 画面を切り替えたあとは、一度「触れていない」を読むまで新しい操作として扱わない
if (down) {
    if (waitingForRelease) { return; }
    ...
} else if (waitingForRelease) {
    waitingForRelease = false;   // ここで初めてセンサーが空になったと分かる
    return;
}

指を置いたまま画面が切り替わった場合にも同じ理屈で効きます。

PNG が表示できなかった

WiFi 経由でスマホから送った画像が cannot show this image になりました。これは PNGdec の PNG_MAX_BUFFERED_PIXELS が原因でした。

既定は (320*4 + 1)*2 = 2562 バイト、つまり幅 320px 相当までしか通りません。1 行がこれを超える PNG は open() の時点で PNG_TOO_BIG を返します。パネルは 960px あり、ブラウザは画面サイズに合わせて縮小して送ってくるので、送られてくる PNG はほぼ確実に範囲外でした。

; 幅 2048px ぶんに広げる。(最大の幅 * 4 + 1) * 2
build_flags = -DPNG_MAX_BUFFERED_PIXELS=16386

流用できたところ

大変だったところばかり書きましたが、そのまま持ってこられた部分もかなりあります。

M5Paper 版では「Arduino や M5 に依存しない計算・判定は #ifdef ARDUINO の外に置く」という方針で書いていました。EXIF の向きの解析、画像寸法の読み取り、表示する向きの決定、選択位置とページングの計算、WiFi の接続情報の組み立て。このあたりはまるごとコピーで動きました。単体テストも一緒に持ってきています。

スマホから画像を送る仕組み(本体をアクセスポイントにして QR を出し、キャプティブポータルでアップロード画面を開く)も、描画の 1 行を差し替えるだけで済みました。

リリースの仕組みも同じです。タグを打つと GitHub Actions が統合バイナリを作り、ESP Web Tools のインストーラページを GitHub Pages へ公開します。ブートローダの位置が ESP32-S3 なので 0x0 になる点だけ直しました。

まとめ

M5GFX が使えないので、なかなか腰が上がりませんでした。実際、描画まわりはほぼ書き直しました。ただ実際に作業をしたのは Claude Code で、私は眺めているだけでした。

一方で、純粋ロジックを切り離しておいたおかげで、アプリの中身そのものは驚くほどそのまま動きました。実務でも責務分離などを意識してますが、この考えは組み込みでも同じですね。

LILYGO T5 4.7 inch e-Paper をお持ちの方がいれば、ぜひご利用ください。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?