ESP32学习,驱动LED点阵屏,SD卡播放GIF动图Webserver上传 调试完成



文章目录

  • ESP32学习,驱动LED点阵屏,SD卡播放GIF动图Webserver上传 调试完成
  • 前言
  • 水平有限,慢慢学习,勉强整了一个小程序 功能比较简单,Led屏循环播放Gif动图,Webserver管理动图,循环播放;需要连接WIFI。新WIFI用手机连接192.168.4.1 配置一下即可。如果不需要随时联网,做一个超时即可;也可以直接用softAP随时访问。 ![在这里插入图片描述](https://s2.51cto.com/images/blog/202406/26122924_667b99246626330944.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 电路板是手工简单焊接的
  • 一、VScode环境platformIO
  • 二、使用步骤
  • 1.main.cpp
  • 2.sdcard_functions.hpp
  • 2.gif_functions.hpp
  • 总结



前言

ESP32与SD卡接线实物图 esp32驱动sd卡_#define


ESP32与SD卡接线实物图 esp32驱动sd卡_ESP32与SD卡接线实物图_02


ESP32与SD卡接线实物图 esp32驱动sd卡_#include_03


ESP32与SD卡接线实物图 esp32驱动sd卡_ESP32与SD卡接线实物图_04

水平有限,慢慢学习,勉强整了一个小程序

功能比较简单,Led屏循环播放Gif动图,Webserver管理动图,循环播放;需要连接WIFI。新WIFI用手机连接192.168.4.1 配置一下即可。如果不需要随时联网,做一个超时即可;也可以直接用softAP随时访问。

ESP32与SD卡接线实物图 esp32驱动sd卡_vscode_05

电路板是手工简单焊接的

一、VScode环境platformIO

VScode的代码,直接可用

二、使用步骤

1.main.cpp

代码如下(示例):

#include <Arduino.h>

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h> 
#include <AnimatedGIF.h>
#include <WiFi.h>
#include <WiFiManager.h>
//#include <ESPAsyncWebServer.h>
#include <WebServer.h>
#include <U8g2_for_Adafruit_GFX.h>


/*
 * Connect the SD card to the following pins:
 *
 * SD Card | ESP32
 *    D2       -
 *    D3       SS
 *    CMD      MOSI
 *    VSS      GND
 *    VDD      3.3V
 *    CLK      SCK
 *    VSS      GND
 *    D0       MISO
 *    D1       -
 */

/**** SD Card GPIO mappings ****/
#define SS_PIN          5
//#define MOSI_PIN        23
//#define MISO_PIN        19
//#define CLK_PIN         18

/**** HUB75 GPIO mapping ****/
// GPIO 34+ are on the ESP32 are input only!!
// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/

#define A_PIN   33  // remap esp32 library default from 23 to 33
#define B_PIN   32  // remap esp32 library default from 19 to 32
#define C_PIN   22   // remap esp32 library defaultfrom 5 to 22

#define E_PIN   21 //-1 // IMPORTANT: Change to a valid pin if using a 64x64px panel.
            

#define PANEL_RES_X 64      // Number of pixels wide of each INDIVIDUAL panel module. 
#define PANEL_RES_Y 64     // Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 2      // Total number of panels chained one to another

/**************************************************************/
//AsyncWebServer server(80);
WebServer server(80);
AnimatedGIF gif;
MatrixPanel_I2S_DMA *dma_display = nullptr;

U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;

static int totalFiles = 0; // GIF files count

static File FSGifFile; // temp gif file holder
static File GifRootFolder; // directory listing

std::vector<std::string> GifFiles; // GIF files path
String GifFileName[20];
bool GifPlayFlag=true;
const int maxGifDuration    = 30000; // ms, max GIF duration
unsigned long NowTime=0;
int RunTimes=0;

#include "gif_functions.hpp"
#include "sdcard_functions.hpp"

/**************************************************************/
void draw_test_patterns();
int gifPlay( const char* gifPath )
{ // 0=infinite

  if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) {
    log_n("Could not open gif %s", gifPath );
    //gif.close();
    return 6;
  }

  //Serial.print("Playing: "); Serial.println(gifPath);

  int frameDelay = 0; // store delay for the last frame
  int then = 0; // store overall delay

  while (gif.playFrame(true, &frameDelay)) {

    then += frameDelay;
    if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's
      //log_w("Broke the GIF loop, max duration exceeded");
      break;
    }
  }
    //Serial.println("Play Gif frames : " +  then );
    gif.close();
    
    return then;
}

void initSD_Gif(){
    // **************************** Setup Sketch ****************************
    Serial.println("Starting AnimatedGIFs Sketch");
    // SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>...
    File root = SD.open("/gifs");
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    GifFiles.clear() ;totalFiles=0;
    File file = root.openNextFile();
    while(file){
        if(!file.isDirectory())
        {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
            std::string filename = "/gifs/" + std::string(file.name());
            Serial.println(filename.c_str());
            GifFileName[totalFiles]=String(filename.c_str());
            Serial.println("Adding to gif list: "+String(totalFiles)+": " + GifFileName[totalFiles]);
            GifFiles.push_back( filename );
        
            //Serial.println("Adding to gif list:" + String(filename));
            totalFiles++;
        }
        file = root.openNextFile();
    }
    file.close();
    Serial.printf("Found %d GIFs to play. \n", totalFiles);
    //totalFiles = getGifInventory("/gifs");

    // This is important - Set the right endianness.
    gif.begin(LITTLE_ENDIAN_PIXELS);   
    GifPlayFlag=true; 
}


String listDir2Web(fs::FS &fs, const char * dirname, uint8_t levels)
{
    Serial.printf("Listing directory: %s\n", dirname);
  String message="";
  message=String(dirname)+"<br/>";
  //dirname=message.c_str();
  File root = fs.open(dirname);
    if(!root){
        message += "Failed to open directory <br />";
        return message;
    }
    if(!root.isDirectory()){
        message += "Not a directory <br />";
        return message;
    }
    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
           message +="   DIR : ";
           message += String(file.name())+String("<br />");
            if(levels){
                message += listDir2Web(fs, file.name(), levels -1);
            }
        } else {
            message += String("---FILE: ");
            message += String(file.name())+String(" ")+String("<a href='/deletefile?filename="+String(file.name())+"'><button> Del</button></a>");
            message += String("  SIZE: ");
            message += formatBytes(file.size())+String("<br />");
        }
        file = root.openNextFile();
    }
    return message;
}
//网页查看目录
void handleListDir(){ //AsyncWebServerRequest *request) {
    String header = "<html><body>";
    String message= header + "<h2>List the file in the SD Card:</h2><br><hr><br>";
    message += listDir2Web(SD,"/gifs",0);
    message += "<br><hr><br>";
    message += "<a href='/'>GIF List Success !Back!</a>";
    server.send(200,"text/html",message);
}
//单个删除gif文件
void handleDeleteFile(String filename){// AsyncWebServerRequest *request,String filename) {
    gif.close();
    GifPlayFlag=false;
    filename="/gifs/"+filename;
    deleteFile(SD, filename.c_str());
    Serial.println(filename);
    server.send(200,"text/html","<a href='/'>"+ filename+String(" deleted...Click Back!</a>"));
    initSD_Gif();
}
//批量删除gif文件
void handleDelete(){//AsyncWebServerRequest *request) { //AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final
    GifPlayFlag=false;
    gif.close();
    Serial.println("GIFs to delete... ");
    int i=0;

    if(totalFiles>=3){
    for(i=1;i<totalFiles-1;i++)
    {
 
        deleteFile(SD, GifFileName[i].c_str()); 
        Serial.println("Found GIFs to delete... "+ GifFileName[i]);
        delay(500);
  
    }
    }
    for(i=0;i<totalFiles;i++){
        Serial.println("List File ... "+ GifFileName[i]);
    }
      server.send(200, "text/html", "<a href='/'>GIF Delete Success !Back!</a>");
      initSD_Gif();
    //GifPlayFlag=true;
}
//上传gif文件
void handleUpload(){ //AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  static File uploadFile;
  
  GifPlayFlag=false;

    gif.close();
  HTTPUpload& upload = server.upload();
  String fileName = upload.filename;
  if (upload.status == UPLOAD_FILE_START) {
    if (SD.exists((char *)upload.filename.c_str())) {
      SD.remove((char *)upload.filename.c_str());
    }
    fileName = "/gifs/"+upload.filename.substring(upload.filename.lastIndexOf('\\')+1);
    //Serial.print("fileName1: ");Serial.println(fileName);
    uploadFile = SD.open(fileName, FILE_WRITE);
    //if(uploadFile!=NULL) Serial.println("File exsist!");
    Serial.print("Upload: START, filename: "); Serial.println(upload.filename);
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    if (uploadFile) {
      if(uploadFile.write(upload.buf, upload.currentSize))
        //Serial.print("write ");
        Serial.print("write  Bytes: "); Serial.println(upload.currentSize);
    }
    
  } else if (upload.status == UPLOAD_FILE_END) {
      uploadFile.close();
        server.send(200, "text/html", "<a href='/'>GIF Upload Success !Back!</a>");
        Serial.print("Upload: END! Size: "); Serial.println(upload.totalSize);
        initSD_Gif();
  }else{
    if (uploadFile) {
      if(uploadFile.write(upload.buf, upload.currentSize))
        Serial.print("Upload else, Bytes: "); Serial.println(upload.currentSize);
    }
    
    Serial.println("Upload:Donot Know ??? "); //Serial.println(upload.totalSize);

        uploadFile.close();
        server.send(200, "text/html", "<a href='/'>GIF Upload Success !Back!</a>");
        Serial.print("Upload: END! Size: "); Serial.println(upload.totalSize);
        initSD_Gif();
  
  }

}
void updateServer() {
    //开启web文件服务
    GifPlayFlag=true;
    server.on("/", HTTP_GET, [](){//AsyncWebServerRequest *request){
        GifPlayFlag=false;
        server.send(200, "text/html;charset=utf-8", "<head><title>上传GIF文件</title></head><br><br><a >1.选择要上传的文件   2.点击Upload进行上传</a><br><br><form method='POST' action='/upload' enctype='multipart/form-data'><input type='file' name='image'><input type='submit' value='Upload'></form><br><form method='GET' action='/delete'><a>3.删除已上传GIF文件  </a><input type='submit' value='Delete'></form><br><hr><br><br><form method='POST' action='/listwebdir'><a>查看文件列表...</a><input type='submit' value='List GIF'></form>"); //<a href='/delete'>  按钮重复 <button>List GIF</button></a>
    });

    server.on("/delete", HTTP_GET, handleDelete);
    server.on("/deletefile", HTTP_GET,[] (){//AsyncWebServerRequest *request){
        String inputMessage1;
    if (server.hasArg("filename")) {
      inputMessage1 = server.arg("filename");//->value();
      Serial.println(inputMessage1);
    }
    else {
      inputMessage1 = "No message sent";
      Serial.println(inputMessage1);
    }
        handleDeleteFile(inputMessage1);
        server.send(200, "text/html", "<a href='/'>Delete Success!Back!</a>");
    });
   
    server.on("/listwebdir", HTTP_POST, handleListDir);
    server.on("/upload", HTTP_POST, [](){ //AsyncWebServerRequest *request){
        //AsyncWebServerResponse *response = request->beginResponse(200, "text/html", "<a href='/'>Upload click! Back!</a>");
        String msg= "<a href='/'>Upload click! Back!</a>";

        server.send(200, "text/html",msg);
    }, handleUpload);

    server.begin();

    Serial.println("Web Server ON !");
}
void setup()
{
    Serial.begin(115200);
    pinMode(2,OUTPUT);
    delay(3000);

    WiFiManager wifiManager;
    // 自动连接WiFi。以下语句的参数是连接ESP8266时的WiFi名称
    wifiManager.autoConnect("AutoConnectAP");
    // 如果您希望该WiFi添加密码,可以使用以下语句:// wifiManager.autoConnect("AutoConnectAP", "12345678");// 以上语句中的12345678是连接AutoConnectAP的密码
    delay(1000);  // WiFi连接成功后将通过串口监视器输出连接成功信息 
    Serial.println(""); 
    Serial.print("ESP32 Connected to ");
    Serial.println(WiFi.SSID());              // WiFi名称
    Serial.print("IP address:\t");
    Serial.println(WiFi.localIP());   

    // **************************** Setup SD Card access via SPI ****************************
    if(!SD.begin(SS_PIN)){
        //  bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint="/sd", uint8_t max_files=5, bool format_if_empty=false);      
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }

    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);

    listDir(SD, "/", 0, false);
    Serial.println("-----------------------------------------------------");
    listDir(SD,"/gifs",0,false);
    Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
    Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));

//开启web文件服务
    server.on("/", HTTP_GET, [](){//AsyncWebServerRequest *request){
        GifPlayFlag=false;
        server.send(200, "text/html;charset=utf-8", "<head><title>上传GIF文件</title></head><br><br><a >1.选择要上传的文件   2.点击Upload进行上传</a><br><br><form method='POST' action='/upload' enctype='multipart/form-data'><input type='file' name='image'><input type='submit' value='Upload'></form><br><form method='GET' action='/delete'><a>3.删除已上传GIF文件  </a><input type='submit' value='Delete'></form><br><hr><br><br><form method='POST' action='/listwebdir'><a>查看文件列表...</a><input type='submit' value='List GIF'></form>"); //<a href='/delete'>  按钮重复 <button>List GIF</button></a>
    });

    server.on("/delete", HTTP_GET, handleDelete);
    server.on("/deletefile", HTTP_GET,[] (){//AsyncWebServerRequest *request){
        String inputMessage1;
    if (server.hasArg("filename")) {
      inputMessage1 = server.arg("filename");//->value();
      Serial.println(inputMessage1);
    }
    else {
      inputMessage1 = "No message sent";
      Serial.println(inputMessage1);
    }
        handleDeleteFile(inputMessage1);
        server.send(200, "text/html", "<a href='/'>Delete Success!Back!</a>");
    });



    server.on("/listwebdir", HTTP_POST, handleListDir);
    server.on("/upload", HTTP_POST, [](){ //AsyncWebServerRequest *request){

        String msg= "<a href='/'>Upload click! Back!</a>";

        server.send(200, "text/html",msg);
    }, handleUpload);


    server.begin();

    Serial.println("Web Server ON !");

    HUB75_I2S_CFG mxconfig(
      PANEL_RES_X,   // module width
      PANEL_RES_Y,   // module height
      PANEL_CHAIN    // Chain length
    );

    mxconfig.gpio.a = A_PIN;
    mxconfig.gpio.b = B_PIN;
    mxconfig.gpio.c = C_PIN;
 //   mxconfig.gpio.d = D_PIN;    
    mxconfig.gpio.e = E_PIN;
    //mxconfig.clkphase = false;
    mxconfig.driver = HUB75_I2S_CFG::FM6126A;

    // Display Setup
    dma_display = new MatrixPanel_I2S_DMA(mxconfig);

    // Allocate memory and start DMA display
    if( not dma_display->begin() )
        Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********");
 
    dma_display->setBrightness8(128); //0-255
    dma_display->clearScreen();


    // **************************** Setup Sketch ****************************
    Serial.println("Starting AnimatedGIFs Sketch");

    // SD CARD STOPS WORKING WITH DMA DISPLAY ENABLED>...

    File root = SD.open("/gifs");
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(!file.isDirectory())
        {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());

            std::string filename = "/gifs/" + std::string(file.name());
            Serial.println(filename.c_str());
            //存入到文件数组
            GifFileName[totalFiles]=String(filename.c_str());
            Serial.println("Adding to gif list: "+String(totalFiles)+": " + GifFileName[totalFiles]);            
            
            GifFiles.push_back( filename );
         //   Serial.println("Adding to gif list:" + String(filename));
            totalFiles++;
    
        }
        file = root.openNextFile();
    }

    file.close();
    Serial.printf("Found %d GIFs to play. \n", totalFiles);
    //totalFiles = getGifInventory("/gifs");
    delay(1000);


  // This is important - Set the right endianness.
    gif.begin(LITTLE_ENDIAN_PIXELS);

    Serial.print("IP address:\t");//再发送一次ip地址
    Serial.println(WiFi.localIP());   

    dma_display->clearScreen();
    u8g2Fonts.begin(*dma_display);
    u8g2Fonts.setFontMode(1);
    u8g2Fonts.setFontDirection(0); 
    u8g2Fonts.setFont(u8g2_font_wqy12_t_gb2312); //u8g2_font_wqy16_t_gb2312);  //u8g2_font_wqy16_t_gb2312
    u8g2Fonts.setForegroundColor(dma_display->color565(255, random(100), random(100)));
    u8g2Fonts.drawUTF8(1,16,"Found  GIFs to play.");
    delay(1000);
    u8g2Fonts.drawUTF8(1,32,(WiFi.SSID()).c_str());
    delay(1000);
    u8g2Fonts.drawUTF8(1,48,(String(WiFi.localIP())).c_str());
    delay(5000);


    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }
 
    cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);

    Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
    Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));

}

void loop(){
    //draw_test_patterns();

      RunTimes++;
      if(RunTimes%10==0){Serial.print("RunTimes: "); Serial.println(RunTimes); }
      if(RunTimes>300){ 
        Serial.print("RunTimes: "); Serial.println(RunTimes); 
        RunTimes=0;
        server.stop();
        updateServer();
        }
    server.handleClient();
    if(GifPlayFlag==true){
        int t_times=0;
        for(auto & elem : GifFiles){
            if(GifPlayFlag==false) break; // return;
            gifPlay( elem.c_str() );
            gif.reset();
            Serial.printf("Found %d GIFs to play. \n", elem); //.c_str());
            t_times++;
            Serial.print("已播放文件: ");Serial.println(t_times);
            digitalWrite(2,!digitalRead(2));
            server.handleClient();    
            delay(200);
            RunTimes++;
        }
    }
    delay(200);
    //listDir(SD, "/", 1, false);
    //Serial.println("---------------------------------");
    //listDir(SD,"/gifs",0,false);
    //Serial.println("---------------------------------");
    digitalWrite(2,!digitalRead(2));
}

void draw_test_patterns()
{
 // fix the screen with green
  dma_display->fillRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(0, 15, 0));
  delay(500);

  // draw a box in yellow
  dma_display->drawRect(0, 0, dma_display->width(), dma_display->height(), dma_display->color444(15, 15, 0));
  delay(500);

  // draw an 'X' in red
  dma_display->drawLine(0, 0, dma_display->width()-1, dma_display->height()-1, dma_display->color444(15, 0, 0));
  dma_display->drawLine(dma_display->width()-1, 0, 0, dma_display->height()-1, dma_display->color444(15, 0, 0));
  delay(500);

  // draw a blue circle
  dma_display->drawCircle(10, 10, 10, dma_display->color444(0, 0, 15));
  delay(500);

  // fill a violet circle
  dma_display->fillCircle(40, 21, 10, dma_display->color444(15, 0, 15));
  delay(500);
  delay(1000);

}

ESP32与SD卡接线实物图 esp32驱动sd卡_vscode_06


文件操作比较简单,3删除文件会保留原始两个,用于演示

ESP32与SD卡接线实物图 esp32驱动sd卡_学习_07


文件列表可以单独删除文件,相对方便点。

2.sdcard_functions.hpp

库里的辅助文件,稍作了改动:

/************************ SD Card Code ************************/
// As per: https://github.com/espressif/arduino-esp32/tree/master/libraries/SD/examples/SD_Test

//format bytes
String formatBytes(size_t bytes) {
  if (bytes < 1024) {
    return String(bytes) + "B";
  } else if (bytes < (1024 * 1024)) {
    return String(bytes / 1024.0) + "KB";
  } else if (bytes < (1024 * 1024 * 1024)) {
    return String(bytes / 1024.0 / 1024.0) + "MB";
  } else {
    return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
  }
}
 
void createDir(fs::FS &fs, const char * path){
    Serial.printf("Creating Dir: %s\n", path);
    if(fs.mkdir(path)){
        Serial.println("Dir created");
    } else {
        Serial.println("mkdir failed");
    }
}
 
void removeDir(fs::FS &fs, const char * path){
    Serial.printf("Removing Dir: %s\n", path);
    if(fs.rmdir(path)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
}
 
void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);
 
    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}
 
void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\n", path);
 
    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}
 
void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("File renamed");
    } else {
        Serial.println("Rename failed");
    }
}
 
void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

bool loadFromSdCard(String path) {
  String dataType = "text/plain";
  if (path.endsWith("/")) {
    path += "index.htm";
  }
 
  if (path.endsWith(".src")) {
    path = path.substring(0, path.lastIndexOf("."));
  } else if (path.endsWith(".htm")) {
    dataType = "text/html";
  } else if (path.endsWith(".css")) {
    dataType = "text/css";
  } else if (path.endsWith(".js")) {
    dataType = "application/javascript";
  } else if (path.endsWith(".png")) {
    dataType = "image/png";
  } else if (path.endsWith(".gif")) {
    dataType = "image/gif";
  } else if (path.endsWith(".jpg")) {
    dataType = "image/jpeg";
  } else if (path.endsWith(".ico")) {
    dataType = "image/x-icon";
  } else if (path.endsWith(".xml")) {
    dataType = "text/xml";
  } else if (path.endsWith(".pdf")) {
    dataType = "application/pdf";
  } else if (path.endsWith(".zip")) {
    dataType = "application/zip";
  }
 
  File dataFile = SD.open(path.c_str());
  if (dataFile.isDirectory()) {
    path += "/index.htm";
    dataType = "text/html";
    dataFile = SD.open(path.c_str());
  }
 
  if (!dataFile) {
    return false;
  }
 
  //if (server.hasArg("download")) {
    //dataType = "application/octet-stream";
 // }
 
  //if (server.streamFile(dataFile, dataType) != dataFile.size()) {
   // Serial.println("Sent less data than expected!");
 //}
 
  dataFile.close();
  return true;
}  



//上面内容增加针对sd卡文件操作
void listDir(fs::FS &fs, const char * dirname, uint8_t levels, bool add_to_gif_list = false){
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.path(), levels -1, false);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());

            if (add_to_gif_list && levels == 0) 
            {
              GifFiles.push_back( std::string(dirname) +  file.name() );
              Serial.println("Adding to gif list:" + String(dirname) +"/" + file.name());
              totalFiles++;
            }
        }
        file = root.openNextFile();
    }

    file.close();
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.print("Read from file: ");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void testFileIO(fs::FS &fs, const char * path){
    File file = fs.open(path);
    static uint8_t buf[512];
    size_t len = 0;
    uint32_t start = millis();
    uint32_t end = start;
    if(file){
        len = file.size();
        size_t flen = len;
        start = millis();
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            len -= toRead;
        }
        end = millis() - start;
        Serial.printf("%u bytes read for %u ms\n", flen, end);
        file.close();
    } else {
        Serial.println("Failed to open file for reading");
    }


    file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }

    size_t i;
    start = millis();
    for(i=0; i<2048; i++){
        file.write(buf, 512);
    }
    end = millis() - start;
    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
    file.close();
}

2.gif_functions.hpp

gif辅助文件:

// Code copied from AnimatedGIF examples

#ifndef M5STACK_SD
 // for custom ESP32 builds
 #define M5STACK_SD SD
#endif


static void * GIFOpenFile(const char *fname, int32_t *pSize)
{
  //log_d("GIFOpenFile( %s )\n", fname );
  FSGifFile = M5STACK_SD.open(fname);
  if (FSGifFile) {
    *pSize = FSGifFile.size();
    return (void *)&FSGifFile;
  }
  return NULL;
}


static void GIFCloseFile(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  if (f != NULL)
     f->close();
}


static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
  int32_t iBytesRead;
  iBytesRead = iLen;
  File *f = static_cast<File *>(pFile->fHandle);
  // Note: If you read a file all the way to the last byte, seek() stops working
  if ((pFile->iSize - pFile->iPos) < iLen)
      iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
  if (iBytesRead <= 0)
      return 0;
  iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
  pFile->iPos = f->position();
  return iBytesRead;
}


static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{
  int i = micros();
  File *f = static_cast<File *>(pFile->fHandle);
  f->seek(iPosition);
  pFile->iPos = (int32_t)f->position();
  i = micros() - i;
  //log_d("Seek time = %d us\n", i);
  return pFile->iPos;
}


// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{
  uint8_t *s;
  uint16_t *d, *usPalette, usTemp[320];
  int x, y, iWidth;

  iWidth = pDraw->iWidth;
  //if (iWidth > PANEL_RES_X*PANEL_CHAIN)
      //iWidth = PANEL_RES_X*PANEL_CHAIN;
  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line

  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) {// restore to background color
    for (x=0; x<iWidth; x++) {
      if (s[x] == pDraw->ucTransparent)
          s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }
  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) { // if transparency used
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    int x, iCount;
    pEnd = s + iWidth;
    x = 0;
    iCount = 0; // count non-transparent pixels
    while(x < iWidth) {
      c = ucTransparent-1;
      d = usTemp;
      while (c != ucTransparent && s < pEnd) {
        c = *s++;
        if (c == ucTransparent) { // done, stop
          s--; // back up to treat it like transparent
        } else { // opaque
            *d++ = usPalette[c];
            iCount++;
        }
      } // while looking for opaque pixels
      if (iCount) { // any opaque pixels?
          for(int xOffset = 0; xOffset < iCount; xOffset++ ){
            dma_display->drawPixel(x + xOffset, y, usTemp[xOffset]); // 565 Color Format
          }
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd) {
        c = *s++;
        if (c == ucTransparent)
            iCount++;
        else
            s--;
      }
      if (iCount) {
        x += iCount; // skip these
        iCount = 0;
      }
    }
  } else {
    s = pDraw->pPixels;
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    for (x=0; x<iWidth; x++)
      dma_display->drawPixel(x, y, usPalette[*s++]); // color 565
      /*
      usTemp[x] = usPalette[*s++];

      for (x=0; x<pDraw->iWidth; x++) {
        dma_display->drawPixel(x, y, usTemp[*s++]); // color 565
      } */     

  }
} /* GIFDraw() */

ESP32与SD卡接线实物图 esp32驱动sd卡_#include_08


ESP32与SD卡接线实物图 esp32驱动sd卡_ESP32与SD卡接线实物图_09


ESP32与SD卡接线实物图 esp32驱动sd卡_学习_10


ESP32与SD卡接线实物图 esp32驱动sd卡_#include_11


总结

比较简单,高手请批评指正。