LovyanGFXを使ってTFカード内のJPEG画像を表示するサンプルです。

LovyanGFXは、ESP32やArduino環境で使える高機能・高速なグラフィックライブラリです。日本の開発者によって開発され、AdafruitGFXやTFT_eSPIと互換性を持ちつつ、より柔軟で強力な描画機能を提供しています。

TFカードに画像を保存する

TFカードに480×320ピクセルのJPEG画像データをルートディレクトリに保存します。

TFカードは必ずFAT32でフォーマットします。exFatやNTFSは使えません。サイズは~2GB程度の小さいサイズのもののほうが問題が少なくなります。

ここでは例として、 img1.jpg img2.jpg img3.jpg 3枚の画像を保存したとします。

※画像データはデータサイズが大きいとエラーになります。また解像度は必ず480×320にしてください。

コードの記述

TFカードからimg1.jpg, img2.jpg, img3.jpgを読み込んで3秒間隔で表示するプログラム例です。

#define LGFX_USE_V1
#include <Arduino.h>
#include <LovyanGFX.hpp>
#include <SD.h>
#include <SPI.h>

// ------------- パネル定義 -------------------
class LGFX_ESP32_3248S035 : public lgfx::LGFX_Device {
  lgfx::Panel_ST7796  _panel;
  lgfx::Bus_SPI       _bus;
  lgfx::Light_PWM     _light;

public:
  LGFX_ESP32_3248S035(void) {
    { // TFT用 SPI(VSPI)
      auto cfg = _bus.config();
      cfg.spi_host    = VSPI_HOST;
      cfg.spi_mode    = 0;
      cfg.freq_write  = 40000000;
      cfg.freq_read   = 16000000;
      cfg.spi_3wire   = true;
      cfg.use_lock    = true;
      cfg.dma_channel = 1;

      cfg.pin_sclk = 14;  // TFT SCLK
      cfg.pin_mosi = 13;  // TFT MOSI
      cfg.pin_miso = 12;  // TFT MISO
      cfg.pin_dc   = 2;   // TFT DC
      _bus.config(cfg);
      _panel.setBus(&_bus);
    }
    { // パネル設定
      auto cfg = _panel.config();
      cfg.pin_cs   = 15;
      cfg.pin_rst  = -1;
      cfg.pin_busy = -1;

      cfg.panel_width   = 320;
      cfg.panel_height  = 480;
      cfg.memory_width  = 320;
      cfg.memory_height = 480;

      cfg.offset_x = 0; cfg.offset_y = 0; cfg.offset_rotation = 0;
      cfg.readable   = true;
      cfg.invert     = false;
      cfg.rgb_order  = false;
      cfg.dlen_16bit = false;
      cfg.bus_shared = true;
      _panel.config(cfg);
    }
    { // バックライト PWM
      auto lcfg = _light.config();
      lcfg.pin_bl      = 27;
      lcfg.invert      = false;
      lcfg.freq        = 44100;
      lcfg.pwm_channel = 7;
      _light.config(lcfg);
      _panel.setLight(&_light);
    }
    setPanel(&_panel);
  }
};

LGFX_ESP32_3248S035 tft;

// SD(VSPI 明示)
SPIClass spiSD(VSPI);

// ---------- プロトタイプ宣言 ----------
bool showJpeg(const char* path, int x = 0, int y = 0, int maxW = -1, int maxH = -1);

// 画面中央にエラーメッセージを出すだけ(停止はしない)--------
static void showError(const String& msg) {
  tft.fillScreen(TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.setFont(&fonts::Font0);
  tft.setTextSize(2);
  tft.drawString(msg, tft.width()/2, tft.height()/2);
}

// ---------- セットアップ ----------
void setup() {
  Serial.begin(115200);
  delay(100);

  // TFT 初期化
  tft.init();
  tft.setRotation(1);       // 横向き(480x320)
  tft.setBrightness(255);   // BL 0〜255
  tft.fillScreen(TFT_BLACK);

  // SD 初期化(SCLK=18, MISO=19, MOSI=23, CS=5)
  spiSD.begin(18, 19, 23, 5);
  if (!SD.begin(5, spiSD)) {
    showError("SD init failed (CS=5)");
  }
}

// ------------------------------------------

void loop() {

  const char* files[] = { "/img1.jpg", "/img2.jpg", "/img3.jpg" };
  static int idx = 0;

  tft.fillScreen(TFT_BLACK);
 
  bool ok = showJpeg(files[idx], 0, 0, tft.width(), tft.height());
 
  if (!ok) {
    showError(String("showJpeg failed:\n"));
  }

  idx = (idx + 1) % (sizeof(files)/sizeof(files[0]));

  delay(3000);
}

// ---------- JPEG表示関数(SD→メモリ→drawJpg の順で表示)----------

bool showJpeg(const char* path, int x, int y, int maxW, int maxH) {
  if (!path || path[0] == '\0') return false;
  if (!SD.begin(5, spiSD)) return false;
  if (!SD.exists(path)) return false;

  File f = SD.open(path, FILE_READ);
  if (!f) return false;

  size_t len = f.size();
  if (len == 0) { f.close(); return false; }

  // バッファ確保
  uint8_t* buf = (uint8_t*)malloc(len);
  if (!buf) { f.close(); return false; }

  size_t read_total = 0;
  while (read_total < len) {
    int n = f.read(buf + read_total, len - read_total);
    if (n <= 0) break;
    read_total += n;
  }
  f.close();
  if (read_total != len) { free(buf); return false; }

  // maxW/maxH 未指定なら画面サイズを使う
  if (maxW <= 0) maxW = tft.width();
  if (maxH <= 0) maxH = tft.height();

  // JPEG描画(LovyanGFXのメモリ入力API)
  bool ok = tft.drawJpg(buf, len, x, y, maxW, maxH);
  free(buf);
  return ok;
}