前幾天解決了 TFT_eSPI-master 庫 圖片取模問題,但儘管是ESP32的 flash 也無法存儲太多圖片的數組,因此我找到了ESP32從SD卡讀取圖片並顯示在LCD屏幕上的方法,SD卡可以輕鬆的存儲大量圖片,後期可以做一個電子相冊,甚至播放個視頻都是可以的。
- 所用到的Arduino庫:
1.JPEGDecoder
2.TFT_eSPI
- 硬件連線方式:
1.SD卡(採用SPI方式連接,SD卡內的圖片請改爲240*135像素,這樣在1.14寸屏幕上顯示會更加出色)
#define SD_MISO 13
#define SD_MOSI 15
#define SD_SCLK 17
#define SD_CS 14
2.1.14寸IPS彩屏(ST7789驅動芯片)
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 23
#define TFT_BL -1 //燈光控制引腳不接
- 程序代碼:
/*
*@功能:ESP32讀取SD卡圖片顯示在1.14IPS屏幕上
*@作者:劉澤文
*@時間:2020/3/27
*/
//引用相關庫
#include <SD.h>
#include <FS.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <JPEGDecoder.h>
#define DEBUG
#ifdef DEBUG
#define DebugPrintln(message) Serial.println(message)
#else
#define DebugPrintln(message)
#endif
#ifdef DEBUG
#define DebugPrint(message) Serial.print(message)
#else
#define DebugPrint(message)
#endif
TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library
SPIClass sdSPI(VSPI);
#define SD_MISO 13
#define SD_MOSI 15
#define SD_SCLK 17
#define SD_CS 14
void drawSdJpeg(const char *filename, int xpos, int ypos);
void jpegRender(int xpos, int ypos);
void jpegInfo();
void showTime(uint32_t msTime);
void SD_read_Time(uint32_t msTime);
void setup()
{
Serial.begin(9600);
DebugPrintln();
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_WHITE);
tft.setTextSize(1);
tft.setTextColor(TFT_MAGENTA);
tft.setCursor(0, 0);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(1);
tft.setSwapBytes(true);
delay(500);
if (TFT_BL > 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h
pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. TFT_BACKLIGHT_ON has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h
}
//掛載文件系統
sdSPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, sdSPI))
{
DebugPrintln("存儲卡掛載失敗");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE)
{
DebugPrintln("未連接存儲卡");
return;
}
else if (cardType == CARD_MMC)
{
DebugPrintln("掛載了MMC卡");
}
else if (cardType == CARD_SD)
{
DebugPrintln("掛載了SDSC卡");
}
else if (cardType == CARD_SDHC)
{
DebugPrintln("掛載了SDHC卡");
}
else
{
DebugPrintln("掛載了未知存儲卡");
}
//打印存儲卡信息
Serial.printf("存儲卡總大小是: %lluMB \n", SD.cardSize() / (1024 * 1024)); // "/ (1024 * 1024)"可以換成">> 20"
Serial.printf("文件系統總大小是: %lluB \n", SD.totalBytes());
Serial.printf("文件系統已用大小是: %lluB \n", SD.usedBytes());
}
void loop() {
//測試壁紙
for(int image_num = 1;image_num<=6;image_num++){
char FileName[10];
sprintf(FileName,"/Data/%d.jpg",image_num);
drawSdJpeg(FileName, 0, 0); // This draws a jpeg pulled off the SD Card
delay(500);
}
//播放badapple,共6540幀,每秒30幀
for(int image_num = 1;image_num<=(6540-3);image_num+=2){
char FileName[10];
sprintf(FileName,"/apple/%d.jpg",image_num);
drawSdJpeg(FileName, 0, 0); // This draws a jpeg pulled off the SD Card
}
}
void drawSdJpeg(const char *filename, int xpos, int ypos) {
uint32_t readTime = millis();
// Open the named file (the Jpeg decoder library will close it)
File jpegFile = SD.open( filename, FILE_READ); // or, file handle reference for SD library
if ( !jpegFile ) {
DebugPrint("ERROR: File \"");
DebugPrint(filename);
DebugPrintln ("\" not found!");
return;
}
DebugPrintln("===========================");
DebugPrint("Drawing file: "); DebugPrintln(filename);
DebugPrintln("===========================");
// Use one of the following methods to initialise the decoder:
boolean decoded = JpegDec.decodeSdFile(jpegFile); // Pass the SD file handle to the decoder,
//boolean decoded = JpegDec.decodeSdFile(filename); // or pass the filename (String or character array)
SD_read_Time(millis() - readTime);
if (decoded) {
// print information about the image to the serial port
jpegInfo();
// render the image onto the screen at given coordinates
jpegRender(xpos, ypos);
}
else {
DebugPrintln("Jpeg file format not supported!");
}
}
//####################################################################################################
// Draw a JPEG on the TFT, images will be cropped on the right/bottom sides if they do not fit
//####################################################################################################
// This function assumes xpos,ypos is a valid screen coordinate. For convenience images that do not
// fit totally on the screen are cropped to the nearest MCU size and may leave right/bottom borders.
void jpegRender(int xpos, int ypos) {
// record the current time so we can measure how long it takes to draw an image
uint32_t drawTime = millis();
//jpegInfo(); // Print information from the JPEG file (could comment this line out)
uint16_t *pImg;
uint16_t mcu_w = JpegDec.MCUWidth;
uint16_t mcu_h = JpegDec.MCUHeight;
uint32_t max_x = JpegDec.width;
uint32_t max_y = JpegDec.height;
bool swapBytes = tft.getSwapBytes();
tft.setSwapBytes(true);
// Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
// Typically these MCUs are 16x16 pixel blocks
// Determine the width and height of the right and bottom edge image blocks
uint32_t min_w = (mcu_w<(max_x % mcu_w)?mcu_w:(max_x % mcu_w));
uint32_t min_h = (mcu_h<(max_y % mcu_h)?mcu_h:(max_y % mcu_h));
// save the current image block size
uint32_t win_w = mcu_w;
uint32_t win_h = mcu_h;
// save the coordinate of the right and bottom edges to assist image cropping
// to the screen size
max_x += xpos;
max_y += ypos;
// Fetch data from the file, decode and display
while (JpegDec.read()) { // While there is more data in the file
pImg = JpegDec.pImage ; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block)
// Calculate coordinates of top left corner of current MCU
int mcu_x = JpegDec.MCUx * mcu_w + xpos;
int mcu_y = JpegDec.MCUy * mcu_h + ypos;
// check if the image block size needs to be changed for the right edge
if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
else win_w = min_w;
// check if the image block size needs to be changed for the bottom edge
if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
else win_h = min_h;
// copy pixels into a contiguous block
if (win_w != mcu_w)
{
uint16_t *cImg;
int p = 0;
cImg = pImg + win_w;
for (int h = 1; h < win_h; h++)
{
p += mcu_w;
for (int w = 0; w < win_w; w++)
{
*cImg = *(pImg + w + p);
cImg++;
}
}
}
// calculate how many pixels must be drawn
uint32_t mcu_pixels = win_w * win_h;
// draw image MCU block only if it will fit on the screen
if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height())
tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
else if ( (mcu_y + win_h) >= tft.height())
JpegDec.abort(); // Image has run off bottom of screen so abort decoding
}
tft.setSwapBytes(swapBytes);
showTime(millis() - drawTime); //將圖片顯示到屏幕所用的時間(ms)
}
void jpegInfo() {
DebugPrintln("JPEG image info");
DebugPrintln("===============");
DebugPrint("Width :");
DebugPrintln(JpegDec.width);
DebugPrint("Height :");
DebugPrintln(JpegDec.height);
DebugPrint("Components :");
DebugPrintln(JpegDec.comps);
DebugPrint("MCU / row :");
DebugPrintln(JpegDec.MCUSPerRow);
DebugPrint("MCU / col :");
DebugPrintln(JpegDec.MCUSPerCol);
DebugPrint("Scan type :");
DebugPrintln(JpegDec.scanType);
DebugPrint("MCU width :");
DebugPrintln(JpegDec.MCUWidth);
DebugPrint("MCU height :");
DebugPrintln(JpegDec.MCUHeight);
DebugPrintln("===============");
DebugPrintln("");
}
void showTime(uint32_t msTime) {
DebugPrint(F(" JPEG drawn in "));
DebugPrint(msTime);
DebugPrintln(F(" ms "));
}
void SD_read_Time(uint32_t msTime) {
Serial.print(F(" SD JPEG read in "));
Serial.print(msTime);
Serial.println(F(" ms "));
}
- 程序效果:
- 程序中嘗試播放了badapple,也是將視頻轉爲圖片,具體參照另一篇博文:《MATLAB》應用 之 用 MATLAB 將 badapple 視頻轉換爲128*64分辨率圖片
- 存在的問題:在播放badapple時,只有10幀,無法做到樹莓派那樣的幀率,SD(10年前的256M內存卡)卡和SPI速度可能不夠,有解決方案的小夥伴歡迎下方留言!