ESP32-S3 + 2.8 吋 TFT LCD 顯示JPG圖檔(LovyanGFX)

發揮 ESP32-S3 N16R8 的硬體優勢,免外接 SD 卡!透過 LittleFS 將 JPG 圖片直接存入 Flash,並利用 8MB PSRAM 快取,搭配 LovyanGFX 在螢幕上快速讀取圖片並輪播。

ESP32-S3 + 2.8 吋 TFT LCD 顯示JPG圖檔(LovyanGFX)

上一篇教學我們成功讓 ESP32-S3 使用 LovyanGFX 在 2.8 吋螢幕上顯示了繁體中文。許多朋友馬上接著問傑森:「如果我想在畫面上放自己的專案 Logo 或是全彩照片,該怎麼做?」

ESP32-S3 + 2.8 吋 TFT LCD 顯示繁體中文(LovyanGFX)
使用 LovyanGFX 函式庫,在 ESP32-S3 開發板上驅動 2.8 吋 TFT LCD,內建 efontTW 字型直接顯示繁體中文,從接線、設定到顯示圖形與中文字,一步步帶你完成。
ESP32-S3 N16R8 開發板 (16MB Flash / 8MB PSRAM)
傑森創工,專注於Arduino、ESP32、樹莓派(Raspberry Pi)、物聯網、創客(Maker)相關商品的研究,專業銷售Arduino材料、Arduino教材、各種電子材料、開發板、Arduino套件、感測器模組,以及各類工具。更提供許多獨家的專題套件,供大學或高中職學生製作Arduino、ESP32專題。最專業的Arduino、ESP32供應商。
2.8吋SPI LCD 240*320 TFT模組 ST7789
2.8吋彩屏,支援16BIT RGB 65K色顯示,顯示色彩豐富 320X240解析度,可選觸摸功能 採用SPI序列匯流排,只需幾個IO即可點亮顯示 帶SD卡槽方便擴展實驗 提供豐富的示例程序 軍工級工藝標準,長期穩定工作 提供底層驅動技術支援

方法一:將照片轉成 C 語言陣列(寫死在程式碼裡)

這是創客界最常拿來測試、也是最不需要更動硬體的方法。做法是透過轉檔軟體(例如 Image2LCD 或是網頁版轉換器),把 JPG 或 PNG 照片直接翻譯成成千上萬個代表顏色的 16 進位色碼(例如 0xFFFF 代表白色),變成一個 .h 標頭檔。

  • 優點:
    • 最簡單、免接線: 不需要增加任何額外模組,程式碼燒進去照片就出來了。
    • 顯示速度極快: 因為資料已經解碼並直接存在晶片的 Flash 記憶體裡,LovyanGFX 只要用一行 display.pushImage() 就能瞬間把畫面刷出來。
  • 缺點:
    • 極度消耗記憶體: 一張 240x320 解析度的照片,大約會吃掉 150KB 的程式空間。ESP32 的空間通常只有 4MB 到 16MB,放沒幾張高畫質照片,程式庫就爆滿了。
    • 更新麻煩: 每次想換照片,都要重新轉檔、重新編譯燒錄程式。

方法二:存放在 ESP32 內部檔案系統 (SPIFFS / LittleFS)

ESP32 的 Flash 記憶體其實可以切出一塊空間,當成「虛擬的隨身碟」來用。你可以把真正的 .jpg.png 檔案,透過 Arduino IDE 的專屬外掛工具,直接上傳到 ESP32 的肚子裡。

  • 優點:
    • 免接外掛模組: 一樣不需要額外買 SD 卡模組,硬體保持最精簡。
    • 管理直覺: 你面對的是真正的圖檔,而不是一堆看不懂的十六進位代碼。LovyanGFX 支援直接讀取檔案,例如使用 display.drawJpgFile()。而且jpg圖檔很省空間,240x320 解析度的照片,一張30-50k,ESP32你可以放一堆圖檔了!
  • 缺點:
    • 需要安裝上傳工具: Arduino IDE 原本不支援直接上傳檔案到 ESP32 內部,你必須先安裝「ESP32 Sketch Data Upload」或 LittleFS 的外掛。
    • 空間依然受限: 通常能切出來當檔案系統的空間還是有限制,適合放幾張介面 UI 圖片或圖示,無法做大型電子相框。但ESP32-S3 N16R8就不同了,有了16M的大容量,可以放不少jpg圖囉!

方法三:讀取外部 SD 卡(外接 MicroSD 模組)

這就是真正的「電子相框」做法。你需要另外準備一個 MicroSD 卡模組,接上 ESP32,然後把電腦裡的照片全部複製到 SD 卡裡面。

  • 優點:
    • 容量幾乎無限: 隨便一張 8GB 或 16GB 的 SD 卡,可以放幾千、幾萬張照片。
    • 更新超方便: 想換照片時,只要把 SD 卡拔下來插進電腦複製貼上就好,完全不用動到 ESP32 的程式碼。
  • 缺點:
    • 硬體變複雜: 你需要額外買 SD 卡模組,而且它也是走 SPI 通訊,代表你又要多接 4 到 6 根線(CS, MOSI, MISO, SCK)。這意味著它必須跟你的螢幕、觸控晶片一起「三方共用」匯流排,硬體除錯難度會再稍微提升。
    • 顯示速度稍慢: ESP32 必須先從 SD 卡把 JPG 檔案讀出來,經過 CPU 運算解碼,再丟給螢幕,所以圖片太大的話,畫面載入會有一點點「由上往下刷」的感覺。

總結來說: 「方法一」是網路最多人用的,但每次要轉檔,傑森是蠻不喜歡的啦!從SD卡也是麻煩,還要自己再焊線,而且讀取慢半拍。所以如果你想做數位照片輪播,或是有其它快速顯示的需求,那絕對要選「方法三」。

所以大家猜到了,這篇進階教學傑森要帶大家徹底發揮 ESP32-S3 N16R8 的強大硬體實力,透過 LittleFS 將圖片存入大容量 Flash,再利用 8MB PSRAM 達成極速讀取與顯示。

核心觀念:為什麼需要 LittleFS 與 PSRAM?

  • LittleFS (Flash 儲存): ESP32-S3 斷電後記憶體資料會消失,所以我們必須先用外掛工具,把 JPG 圖片像存進硬碟一樣,燒錄到 ESP32-S3 內建的 Flash 空間中。
  • PSRAM (高速快取): 如果每次畫圖都從 Flash 讀取,速度會比較慢。N16R8 版本最棒的就是有 8MB 的超大 PSRAM。我們可以在開機時,把圖片從 Flash 讀出來並「常駐」在 PSRAM 裡,之後要顯示或切換圖片時,速度就會快到飛起來!

準備工作

1. 安裝 LittleFS Data Uploader 工具

這個工具不會內建在 Arduino IDE 中,需要額外安裝。

  • Arduino IDE 2.x 版: 請至 GitHub 搜尋 arduino-littlefs-upload 下載 .vsix 檔,並放入 磁碟/使用者/(名稱)/.arduinoIDE/plugins 資料夾中。安裝後可按 Ctrl + Shift + P 呼叫指令區使用。
  • 如果沒有plugins資料夾,就自行建立。
Releases · earlephilhower/arduino-littlefs-upload
Build and uploads LittleFS filesystems for the Arduino-Pico RP2040, RP2350, ESP8266, and ESP32 cores under Arduino IDE 2.2.1 or higher - earlephilhower/arduino-littlefs-upload
  • 準備圖片: 在你的 Arduino 專案資料夾下,建立一個名為 data 的資料夾。把準備好的圖片(例如 image1.jpgimage2.jpg,建議尺寸 240x320)放進去。

2. 最關鍵的一步:自訂分區表 (Partition Scheme)

ESP32-S3 N16R8 有高達 16MB 的空間,但預設的分割方式可能沒有留空間給 LittleFS,直接上傳會遇到 Partition entry not found in csv file! 的錯誤。

傑森的終極解法: 在專案資料夾中(與 .ino 檔同層),建立一個名為 partitions.csv 的純文字檔,貼上以下內容:

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x300000,
app1,     app,  ota_1,   0x310000,0x300000,
spiffs,   data, spiffs,  0x610000,0x9F0000,

(這段設定保留了 OTA 更新功能,並給了 app 3MB,剩下的近 10MB 全部分配給 spiffs 也就是你的 LittleFS。如果你要做其它的分區,簡單,叫Gemini幫你寫新的語法就行了^^)

接著回到 Arduino IDE:

  1. 工具 (Tools) -> Partition Scheme -> 選擇 Custom 的選項(IDE 會優先讀取你剛建立的 csv 檔)。
  2. 然後編譯(或是直接上傳)一次,讓設定生效。
  3. 執行 LittleFS Data Uploader,按住組合鍵 Ctrl+Shift+p,在輸入框內打upload,就能找到LittleFS Data Uploader,點它就能成功把圖片上傳到開發板了!

範例一:載入單張 JPG 到 PSRAM 並顯示

圖片上傳成功後,我們來寫程式。別忘了在 IDE 的 工具 -> PSRAM 中選擇 OPI PSRAM

這個範例會示範如何配置 PSRAM 記憶體,並把圖片畫在螢幕上。

#include <LovyanGFX.hpp>
#include <LittleFS.h>

// ===== 這裡請貼上與上一篇完全相同的 class LGFX 腳位設定 =====
// (包含 SPI2_HOST, SCK=8, MOSI=18, CS=15 等設定,為了版面簡潔此處省略)
// ==========================================================

LGFX display;

// 宣告指標與大小,用來存放 PSRAM 的位址與圖片大小
uint8_t* img_buffer = NULL;
size_t img_size = 0;

void setup() {
  Serial.begin(115200);
  delay(500);
  
  display.init();
  display.setRotation(1); // 橫向顯示
  display.fillScreen(TFT_BLACK);

  // 1. 初始化 LittleFS
  if(!LittleFS.begin(true)){
    Serial.println("LittleFS 掛載失敗");
    return;
  }

  // 2. 檢查 PSRAM
  if (!psramFound()) {
    Serial.println("警告:未找到 PSRAM!");
    return;
  }

  // 3. 開啟圖片檔案
  File file = LittleFS.open("/image1.jpg", "r");
  if(!file){
    Serial.println("開啟圖片失敗");
    return;
  }

  img_size = file.size();
  
  // 4. 在 PSRAM 中要一塊記憶體空間
  img_buffer = (uint8_t*)ps_malloc(img_size);

  if (img_buffer != NULL) {
    // 5. 將圖片資料讀取進 PSRAM
    file.read(img_buffer, img_size);
    Serial.println("圖片成功載入 PSRAM!");
  }
  file.close();

  // 6. 畫出 PSRAM 中的圖片 (指標, 大小, X, Y)
  if (img_buffer != NULL) {
    display.drawJpg(img_buffer, img_size, 0, 0);
  }
}

void loop() { }

如果你要做一個數位相框或 UI 介面,我們可以在開機時把 3 張圖片全部塞進大容量的 PSRAM 裡,之後在 loop() 中就能無延遲地切換!

#include <LovyanGFX.hpp>
#include <LittleFS.h>

// ===== 請貼上完整的 class LGFX 腳位設定 =====
// 警告:如果你複製程式碼時漏掉腳位設定,螢幕會變成全白哦!

LGFX display;

const int IMG_COUNT = 3;
uint8_t* img_buffers[IMG_COUNT];
size_t img_sizes[IMG_COUNT];
String file_names[IMG_COUNT] = {"/image1.jpg", "/image2.jpg", "/image3.jpg"};

void setup() {
  Serial.begin(115200);
  display.init();
  display.setRotation(1); 
  display.fillScreen(TFT_BLACK);

  LittleFS.begin(true);

  // 用迴圈將 3 張圖片全部載入 PSRAM
  for (int i = 0; i < IMG_COUNT; i++) {
    File file = LittleFS.open(file_names[i], "r");
    if(file){
      img_sizes[i] = file.size();
      img_buffers[i] = (uint8_t*)ps_malloc(img_sizes[i]);
      if (img_buffers[i] != NULL) {
        file.read(img_buffers[i], img_sizes[i]);
        Serial.printf("成功載入 %s 至 PSRAM\n", file_names[i].c_str());
      }
      file.close();
    }
  }
}

int current_img = 0;

void loop() {
  if (img_buffers[current_img] != NULL) {
    display.drawJpg(img_buffers[current_img], img_sizes[current_img], 0, 0);
  }
  
  current_img++;
  if (current_img >= IMG_COUNT) {
    current_img = 0; 
  }
  
  delay(3000); // 每 3 秒切換一張
}

學會了顯示圖片,你的 ESP32-S3 專案視覺效果就能大幅提升啦!如果有任何問題,歡迎到我們粉絲團留言,我們下篇教學見!

https://www.facebook.com/geeksfans