koktoh の雑記帳

気ままに書いていきます

今更ながらマジョカアイリスハックした

今更ながら、マジョカアイリスハックに挑戦しました
どういうものかは Twitter#マジョカアイリスハック などで検索したら出てくると思います

雑に言うと、マジョカアイリスというおもちゃをハックして遊ぶことです
特に、 640 x 48 という珍しい液晶が対象になっているようです

www.amazon.co.jp

今回は、 元のデータから JPEG を抽出し、抽出した JPEG を SD カード に保存し、それを ESP32 で読み込んでムービーを再生するようにします

ハックする

分解や解析はだいぶ進んでおり、検索すればたいていの情報は出てくると思います
ここに詳しい解析結果がまとまっています

github.com

JPEG を抽出する

抽出にはこちらを使わせていただきました

github.com

一部抽出できないものもありましたが、バイナリを直接いじって、(おそらく)すべての JPEG を抽出することができました

JPEG を変換する

JPEG には、大きく分けてベースラインとプログレッシブという2つのフォーマットがあるそうです
今回抽出した JPEGプログレッシブのようでした

プログラムの都合上、ベースラインである必要があるので、変換します
変換には jpegtran を使用しました

jpegclub.org

jpegtran -copy none input.jpg output.jpg

のように使います

使用機器

ESP32 DevKit v1 互換品

www.amazon.co.jp

SD カードモジュール

www.amazon.co.jp

FFC 変換基板

www.aitendo.com

プログラムを書く

今回のプログラムは、こちらを大いに参考にさせていただきました
というか、ほぼコピペと言っても差し支えないですね

github.com

画像の表示は、サンプルコードと同じく LovyanGFX を使用させていただきました

github.com

また、ツイッターにて LovyanGFX の作者である らびやん(@lovyan03) 氏に多くのアドバイスをいただきました
ここに、感謝申し上げます

#include <WiFi.h>
#include <SD.h>
#include <LovyanGFX.hpp> // SD.h より後に include する

// SD
#define SD_FREQ 8000000

// Image
int counter = 0;
#define IMAGE_DIR "/image"

struct LGFX_Config {
  static constexpr int gpio_wr  =  4; //to LCD WR(14)
  static constexpr int gpio_rd  =  2; //to LCD RD(13)
  static constexpr int gpio_rs  = 15; //to LCD DC(16)
  static constexpr int gpio_d0  = 12; //to LCD D0(4)
  static constexpr int gpio_d1  = 13; //to LCD D1(5)
  static constexpr int gpio_d2  = 26; //to LCD D2(6)
  static constexpr int gpio_d3  = 25; //to LCD D3(7)
  static constexpr int gpio_d4  = 17; //to LCD D4(8)
  static constexpr int gpio_d5  = 16; //to LCD D5(9)
  static constexpr int gpio_d6  = 27; //to LCD D6(10)
  static constexpr int gpio_d7  = 14; //to LCD D7(11)
};

static lgfx::LGFX_PARALLEL<LGFX_Config> lcd;

static lgfx::Panel_ILI9342 panel;

static lgfx::LGFX_Sprite buf; // [640 * 48] Buffer

constexpr int32_t panel_width = 640;

void setup() {
  // Wifi off
  WiFi.mode(WIFI_OFF);

  // SD mount
  SD.begin(SS, SPI, SD_FREQ);

  // 設定できるクロックは20MHz~6.7MHzです。また、80MHzを整数で割った値に調整されます。
  // 20MHz, 16MHz, 13.4MHz, 11.5MHz, 10MHz, 88.9MHz, 8MHz, 7.3MHz, 6.7MHz
  panel.freq_write = 16000000;
  panel.len_dummy_read_pixel = 8;

  panel.spi_cs = 33;  //to LCD CS(15)
  panel.spi_dc = -1;
  panel.gpio_rst = 32;  //to LCD RST(2)

  panel.gpio_bl  = -1;
  panel.pwm_ch_bl = -1;
  panel.backlight_level = true;

  panel.memory_width  = 320;
  panel.memory_height = 240;

  panel.panel_width  = 320;
  panel.panel_height = 96;

  panel.offset_x = 0;
  panel.offset_y = 144;

  panel.rotation = 0;

  panel.offset_rotation = 0;

  lcd.setPanel(&panel);

  lcd.init();
  lcd.setColorDepth(16);

  lcd.setRotation(0); // 0 or 2

  buf.setColorDepth(16);
  buf.createSprite(640, 48);
  buf.setSwapBytes(true);
}

// ===== MAIN LOOP ===== //
void loop() {
  char fileName[128];
  sprintf(fileName, "%s/%03d.jpg", IMAGE_DIR, counter);

  if (buf.drawJpgFile(SD, fileName)) {
    lcd_buffer_write();
    counter++;
  } else {
    counter = 0;
  }
}

// ===== 640*48px Buffer to 320*96px LCD ===== //
inline void lcd_buffer_write() {
  lcd.setAddrWindow(0, 0, 320, 96);
  lcd.pushPixels((lgfx::swap565_t*)buf.getBuffer(), 640 * 48);
}

バッファに JPEG を読み込んで LCD に送る を繰り返しています

注意点としては、 LovyanGFX.hppSD.h より後に include すること です
この順番で include することで、 LovyanGFX の SD 用の機能が有効になる、そうです
順番を守らないと buf.drawJpgFile(SD, fileName)コンパイル時に「そんな関数ねーぞ」と怒られます

また、 LovyanGFX では現時点でベースラインの JPEG のみに対応しているため、変換を行ったのでした

配線する

以下のように配線します

f:id:koktoh:20210423230051p:plain

わかりにくいかもしれないので、回路図も用意しました(わかりやすいとは言っていない)

f:id:koktoh:20210423230058p:plain

なお、 VIN から SD カードモジュールの電源を取っているため、 USB 接続が必要です
9V電池は液晶のバックライトのみに使用しています

再生する

実際に再生したものがこちらです

途中白飛びしてわかりにくいですが、レインボーダイヤモンドを実行したときのムービーが流れています
だいたい 10fps くらいかと思います
もともともそれくらいだったと思うので、あまり違和感はありません

まとめ

以上、マジョカアイリスの液晶にムービーを表示してみる、でした

実は、液晶を使うのはこれが初めてでした
なかなか攻めたことしてる気がします

今後は、もう少し fps を上げたいところです
SD との通信周波数を上げればいいと思ったのですが、どうも 8MHz を超えるとマウントに失敗してしまうようです
SD カードとかモジュールの問題なのか、よくわかりません
あとは、一気に JPEG を読み込んでしまうとか、 MJPEG を使ってみるとか、いろいろ考えています

最終的にはキーボードに付けるとかやってみたいですね
いつになるかわかりませんが……

ここまでお読みいただき、ありがとうございました