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;
}