ESP32: Руководство по модулю MicroSD-карты с использованием Arduino IDE

Это руководство показывает, как использовать microSD-карту с ESP32: вы узнаете, как читать и записывать файлы на microSD-карту. Для подключения microSD-карты к плате ESP32 мы будем использовать модуль microSD-карты (протокол связи SPI). Использование microSD-карты с ESP32 особенно полезно для логирования данных или хранения файлов, которые не помещаются в файловую систему (SPIFFS). ESP32 будет программироваться с использованием ядра Arduino.

ESP32 Руководство по модулю MicroSD-карты с Arduino IDE

В этом руководстве мы рассмотрим следующие темы:

Знакомство с модулем MicroSD-карты и его выводами

Существуют различные модули microSD-карт, совместимые с ESP32. Мы используем модуль microSD-карты, показанный на следующем рисунке – он обменивается данными по протоколу SPI. Вы можете использовать любой другой модуль microSD-карты с интерфейсом SPI.

Модуль MicroSD-карты для ESP32 ESP8266 Arduino SPI

Этот модуль microSD-карты также совместим с другими микроконтроллерами, такими как Arduino и платы ESP8266 NodeMCU. Чтобы узнать, как использовать модуль microSD-карты с Arduino, вы можете следовать следующему руководству:

Где купить?

Вы можете нажать на ссылку ниже, чтобы проверить различные магазины, где можно приобрести модуль microSD-карты:

Модуль MicroSD-карты для ESP32 ESP8266 Arduino

Распиновка модуля MicroSD-карты – SPI

Модуль microSD-карты обменивается данными по протоколу SPI. Вы можете подключить его к ESP32, используя стандартные SPI-пины.

Модуль MicroSD-карты

ESP32

3V3

3.3V

CS

GPIO 5

MOSI

GPIO 23

CLK

GPIO 18

MISO

GPIO 19

GND

GND

Необходимые компоненты

Модуль microSD-карты ESP32 подключение на макетной плате

Для этого руководства вам понадобятся следующие компоненты:

Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

Подключение ESP32 к модулю microSD-карты

Чтобы подключить модуль microSD-карты к плате ESP32, вы можете следовать следующей схеме подключения (для стандартных SPI-пинов ESP32):

Схема подключения модуля microSD-карты к ESP32

Рекомендуемое чтение: ESP32 Pinout Reference: Which GPIO pins should you use?

Подготовка microSD-карты

Перед тем как продолжить работу с руководством, убедитесь, что вы отформатировали microSD-карту в FAT32. Следуйте инструкциям ниже для форматирования microSD-карты или используйте программу, например SD Card Formatter (совместима с Windows и Mac OS).

1. Вставьте microSD-карту в компьютер. Перейдите в Мой компьютер и нажмите правой кнопкой мыши на SD-карту. Выберите Форматировать, как показано на рисунке ниже.

Форматирование MicroSD-карты

2. Появится новое окно. Выберите FAT32, нажмите Начать, чтобы запустить процесс форматирования, и следуйте инструкциям на экране.

Форматирование MicroSD-карты FAT32

Подготовка Arduino IDE

Мы будем программировать плату ESP32 с помощью Arduino IDE. Поэтому убедитесь, что у вас установлено дополнение ESP32. Следуйте следующему руководству:

Если вы предпочитаете использовать VSCode + PlatformIO, следуйте следующему руководству:

Тестирование модуля microSD-карты: чтение, запись и работа с файлами

Для ESP32 существуют две различные библиотеки (входящие в ядро Arduino для ESP32): библиотека SD и библиотека SDD_MMC.h.

Если вы используете библиотеку SD, вы используете SPI-контроллер. Если вы используете библиотеку SDD_MMC, вы используете контроллер SD/SDIO/MMC ESP32. Подробнее о драйвере ESP32 SD/SDIO/MMC.

ESP32 работа с файлами на microSD-карте: чтение и запись

В Arduino IDE есть несколько примеров, показывающих, как работать с файлами на microSD-карте с помощью ESP32. В Arduino IDE перейдите в File > Examples > SD(esp32) > SD_Test или скопируйте следующий код.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-microsd-card-arduino/

  This sketch can be found at: Examples > SD(esp32) > SD_Test
*/

#include "FS.h"
#include "SD.h"
#include "SPI.h"

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  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.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

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

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

void setup(){
  Serial.begin(115200);
  if(!SD.begin(5)){
    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);
  createDir(SD, "/mydir");
  listDir(SD, "/", 0);
  removeDir(SD, "/mydir");
  listDir(SD, "/", 2);
  writeFile(SD, "/hello.txt", "Hello ");
  appendFile(SD, "/hello.txt", "World!\n");
  readFile(SD, "/hello.txt");
  deleteFile(SD, "/foo.txt");
  renameFile(SD, "/hello.txt", "/foo.txt");
  readFile(SD, "/foo.txt");
  testFileIO(SD, "/test.txt");
  Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void loop(){

}

Просмотр исходного кода

Этот пример показывает, как выполнить практически любую задачу, которая может вам понадобиться при работе с microSD-картой:

В качестве альтернативы вы можете использовать примеры SD_MMC – они аналогичны примерам SD, но используют драйвер SDMMC. Для драйвера SDMMC вам нужен совместимый модуль microSD-карты. Модуль, который мы используем в этом руководстве, не поддерживает SDMMC.

Как работает код

Сначала вам нужно подключить следующие библиотеки: FS.h для работы с файлами, SD.h для взаимодействия с microSD-картой и SPI.h для использования протокола связи SPI.

#include "FS.h"
#include "SD.h"
#include "SPI.h"

Пример предоставляет несколько функций для работы с файлами на microSD-карте.

Вывод списка каталогов

Функция listDir() выводит список каталогов на SD-карте. Эта функция принимает в качестве аргументов файловую систему (SD), имя основного каталога и количество уровней вложенности для обхода.

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  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.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
     file = root.openNextFile();
  }
}

Вот пример вызова этой функции. Символ / соответствует корневому каталогу microSD-карты.

listDir(SD, "/", 0);

Создание каталога

Функция createDir() создаёт новый каталог. Передайте в качестве аргумента файловую систему SD и путь к имени каталога.

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

Например, следующая команда создаёт новый каталог в корне с именем mydir.

createDir(SD, "/mydir");

Удаление каталога

Для удаления каталога с microSD-карты используйте функцию removeDir() и передайте в качестве аргумента файловую систему SD и путь к каталогу.

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

Вот пример:

removeDir(SD, "/mydir");

Чтение содержимого файла

Функция readFile() читает содержимое файла и выводит его в Serial Monitor. Как и в предыдущих функциях, передайте в качестве аргумента файловую систему SD и путь к файлу.

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

Например, следующая строка читает содержимое файла hello.txt.

readFile(SD, "/hello.txt")

Запись содержимого в файл

Для записи содержимого в файл вы можете использовать функцию writeFile(). Передайте в качестве аргумента файловую систему SD, путь к файлу и сообщение.

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

Следующая строка записывает Hello в файл hello.txt.

writeFile(SD, "/hello.txt", "Hello ");

Добавление содержимого в файл

Аналогично, вы можете добавить содержимое в файл (без перезаписи предыдущего содержимого), используя функцию appendFile().

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

Следующая строка добавляет сообщение World!\n в файл hello.txt. Символ \n означает, что в следующий раз, когда вы что-то запишете в файл, это будет записано с новой строки.

appendFile(SD, "/hello.txt", "World!\n");

Переименование файла

Вы можете переименовать файл с помощью функции renameFile(). Передайте в качестве аргументов файловую систему SD, исходное имя файла и новое имя файла.

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

Следующая строка переименовывает файл hello.txt в foo.txt.

renameFile(SD, "/hello.txt", "/foo.txt");

Удаление файла

Используйте функцию deleteFile() для удаления файла. Передайте в качестве аргумента файловую систему SD и путь к файлу, который вы хотите удалить.

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

Следующая строка удаляет файл foo.txt с microSD-карты.

deleteFile(SD, "/foo.txt");

Тестирование файла

Функция testFileIO() показывает, сколько времени занимает чтение содержимого файла.

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

Следующая функция тестирует файл test.txt.

testFileIO(SD, "/test.txt");

Инициализация microSD-карты

В setup() следующие строки инициализируют microSD-карту с помощью SD.begin().

Serial.begin(115200);
if(!SD.begin()){
  Serial.println("Card Mount Failed");
  return;
}
uint8_t cardType = SD.cardType();

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

Если вы не передаёте никакого аргумента в функцию begin(), она попытается инициализировать SPI-соединение с microSD-картой на стандартном пине выбора чипа (CS). Если вы хотите использовать другой CS-пин, вы можете передать его в качестве аргумента функции begin(). Например, если вы хотите использовать GPIO 17 в качестве CS-пина, вам следует использовать следующие строки кода:

Serial.begin(115200);
if(!SD.begin(17)){
  Serial.println("Card Mount Failed");
  return;
}
uint8_t cardType = SD.cardType();

Если вы хотите использовать пользовательские SPI-пины с microSD-картой, перейдите к этому разделу.

Получение типа microSD-карты

Следующие строки выводят тип microSD-карты в Serial Monitor.

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

Получение размера microSD-карты

Вы можете получить размер microSD-карты, вызвав метод cardSize():

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

Тестирование функций MicroSD-карты

Следующие строки вызывают функции, которые мы рассмотрели ранее.

listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));

Демонстрация

Загрузите предыдущий скетч на плату ESP32. После этого откройте Serial Monitor и нажмите встроенную кнопку RST на ESP32. Если инициализация пройдёт успешно, вы увидите похожие сообщения в Serial Monitor.

ESP32 тестирование модуля MicroSD-карты: чтение, запись, удаление ESP32 тестирование модуля MicroSD-карты: чтение, запись, удаление

Использование пользовательских SPI-пинов с microSD-картой

Библиотеки SD.h и SD_MMC.h по умолчанию используют SPI-пины VSPI (23, 19, 18, 5). Вы можете назначить другие пины в качестве SPI-пинов. ESP32 имеет два SPI-интерфейса: HSPI и VSPI на следующих пинах:

SPI

MOSI

MISO

CLK

CS

VSPI

GPIO 23

GPIO 19

GPIO 18

GPIO 5

HSPI

GPIO 13

GPIO 12

GPIO 14

GPIO 15

Рекомендуемое чтение: ESP32 Pinout Reference: Which GPIO pins should you use?

Чтобы использовать другие SPI-пины, вы можете поступить следующим образом:

В начале вашего кода объявите пины, которые вы хотите использовать, например:

#define SCK  17
#define MISO  19
#define MOSI  23
#define CS  5

Создайте новый SPI-класс на HSPI или VSPI. Мы используем VSPI. Оба варианта будут работать нормально.

SPIClass spi = SPIClass(VSPI);

В setup() инициализируйте протокол SPI на ранее определённых пинах:

spi.begin(SCK, MISO, MOSI, CS);

Наконец, инициализируйте microSD-карту с помощью метода begin(). Передайте в качестве аргумента CS-пин, экземпляр SPI, который вы хотите использовать, и частоту шины.

if (!SD.begin(CS,spi,80000000)) {
  Serial.println("Card Mount Failed");
  return;
}

Вот пример кода, модифицированного для использования пользовательских SPI-пинов:

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-microsd-card-arduino/

  This sketch was mofidied from: Examples > SD(esp32) > SD_Test
*/

#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCK  17
#define MISO  19
#define MOSI  23
#define CS  5

SPIClass spi = SPIClass(VSPI);

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  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.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

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

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

void setup(){
  Serial.begin(115200);
  spi.begin(SCK, MISO, MOSI, CS);

  if (!SD.begin(CS,spi,80000000)) {
    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);
  createDir(SD, "/mydir");
  listDir(SD, "/", 0);
  removeDir(SD, "/mydir");
  listDir(SD, "/", 2);
  writeFile(SD, "/hello.txt", "Hello ");
  appendFile(SD, "/hello.txt", "World!\n");
  readFile(SD, "/hello.txt");
  deleteFile(SD, "/foo.txt");
  renameFile(SD, "/hello.txt", "/foo.txt");
  readFile(SD, "/foo.txt");
  testFileIO(SD, "/test.txt");
  Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void loop(){

}

Просмотр исходного кода


Пример: Логирование данных ESP32 на microSD-карту

Использование microSD-карты особенно полезно для проектов логирования данных. В качестве примера мы покажем вам, как сохранять показания датчика BME280 с метками времени (epoch time).

ESP32 логирование данных BME280 на microSD-карту - обзор проекта

Предварительные требования

Для этого примера убедитесь, что у вас установлены следующие библиотеки:

Вы можете установить эти библиотеки с помощью менеджера библиотек Arduino. В Arduino IDE перейдите в Sketch > Include Library > Manage Libraries… Затем найдите названия библиотек и установите их.

Если вы используете VS Code с PlatformIO, скопируйте следующие строки в файл platformio.ini, чтобы включить все необходимые библиотеки.

lib_deps = adafruit/Adafruit BME280 Library @ ^2.1.0
  adafruit/Adafruit Unified Sensor @ ^1.1.4

Схема подключения

Для этого примера вам нужно подключить модуль microSD-карты и датчик BME280 к ESP32. Вот список необходимых компонентов:

Подключите схему, следуя приведённой ниже диаграмме.

Схема подключения ESP32 microSD-карты и BME280

Вы также можете посмотреть следующие таблицы:

BME280

ESP32

VIN

3V3

GND

GND

SCL

GPIO 22

SDA

GPIO 21

Модуль microSD-карты

ESP32

3V3

3.3V

CS

GPIO 5

MOSI

GPIO 23

CLK

GPIO 18

MISO

GPIO 19

GND

GND

Код

Скопируйте следующий код в Arduino IDE. Этот скетч получает показания датчика BME280 (температура, влажность и давление) и записывает их в файл на microSD-карте каждые 30 секунд. Он также записывает метку времени (epoch time, запрошенное у NTP-сервера).

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-microsd-card-arduino/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

// Libraries for SD card
#include "FS.h"
#include "SD.h"
#include <SPI.h>

//Libraries for BME280 sensor
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Libraries to get time from NTP Server
#include <WiFi.h>
#include "time.h"

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;

// BME280 I2C
Adafruit_BME280 bme;

// Variables to hold sensor readings
float temp;
float hum;
float pres;
String dataMessage;

// NTP server to request epoch time
const char* ntpServer = "pool.ntp.org";

// Variable to save current epoch time
unsigned long epochTime;

// Function that gets current epoch time
unsigned long getTime() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    //Serial.println("Failed to obtain time");
    return(0);
  }
  time(&now);
  return now;
}

// Initialize WiFi
void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

// Init BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

// Initialize SD card
void initSDCard(){
   if (!SD.begin()) {
    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);
}

// Write to the SD card
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();
}

// Append data to the SD card
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 setup() {
  Serial.begin(115200);

  initWiFi();
  initBME();
  initSDCard();
  configTime(0, 0, ntpServer);

  // If the data.txt file doesn't exist
  // Create a file on the SD card and write the data labels
  File file = SD.open("/data.txt");
  if(!file) {
    Serial.println("File doesn't exist");
    Serial.println("Creating file...");
    writeFile(SD, "/data.txt", "Epoch Time, Temperature, Humidity, Pressure \r\n");
  }
  else {
    Serial.println("File already exists");
  }
  file.close();
}

void loop() {
  if ((millis() - lastTime) > timerDelay) {
    //Get epoch time
    epochTime = getTime();

    //Get sensor readings
    temp = bme.readTemperature();
    //temp = 1.8*bme.readTemperature() + 32;
    hum = bme.readHumidity();
    pres = bme.readPressure()/100.0F;

    //Concatenate all info separated by commas
    dataMessage = String(epochTime) + "," + String(temp) + "," + String(hum) + "," + String(pres)+ "\r\n";
    Serial.print("Saving data: ");
    Serial.println(dataMessage);

    //Append the data to file
    appendFile(SD, "/data.txt", dataMessage.c_str());

    lastTime = millis();
  }
}

Просмотр исходного кода

Вставьте ваши сетевые учётные данные в следующие переменные, и код будет работать сразу:

const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Этот пример использует функции, которые мы рассмотрели ранее, для записи и добавления данных на microSD-карту (функции writeFile() и appendFile()).

Для лучшего понимания работы этого примера рекомендуем ознакомиться со следующими руководствами:

Демонстрация

Загрузите код на вашу плату. Вы можете проверить в Serial Monitor, что всё работает как ожидается.

ESP32 BME280 логирование данных на microSD-карту Serial Monitor

Дайте проекту поработать некоторое время, чтобы накопить показания. Затем вставьте microSD-карту в компьютер, и вы должны увидеть файл data.txt с показаниями датчиков.

ESP32 BME280 логирование данных в файл на microSD-карте

Пример: Веб-сервер ESP32 с файлами с microSD-карты

Вы можете сохранить файлы для создания веб-сервера с ESP32 на microSD-карту (HTML, CSS, JavaScript и файлы изображений). Это может быть полезно, если файлы слишком велики для размещения в файловой системе ESP32, или это может быть более удобно в зависимости от вашего проекта.

Веб-сервер ESP32 с файлами с microSD-карты - как это работает

Чтобы показать вам, как это сделать, мы создадим простой веб-сервер, который обслуживает HTML-файл, CSS-файл и PNG-файл для создания веб-страницы и отображения favicon.

Переместите следующие файлы на вашу microSD-карту (нажмите на ссылки, чтобы скачать файлы):

Примечание: переместите только файлы на microSD-карту, а не папки.

Затем загрузите следующий код в Arduino IDE.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-web-server-microsd-card/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

void initSDCard(){
  if(!SD.begin()){
    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);
}

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  initWiFi();
  initSDCard();

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SD, "/index.html", "text/html");
  });

  server.serveStatic("/", SD, "/");

  server.begin();
}

void loop() {

}

Просмотр исходного кода

Вставьте ваши сетевые учётные данные в следующие переменные, и код должен работать сразу:

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Демонстрация

После загрузки файлов на microSD-карту и скетча на плату ESP32 откройте Serial Monitor на скорости 115200 бод. Нажмите встроенную кнопку RST на ESP32. IP-адрес ESP32 будет отображён в Serial Monitor.

Веб-сервер ESP32 с файлами с microSD-карты - Serial Monitor демонстрация

В вашей локальной сети откройте веб-браузер и введите IP-адрес ESP32. Вы должны получить доступ к следующей веб-странице, построенной с использованием файлов, хранящихся на microSD-карте.

Веб-сервер ESP32 с файлами с microSD-карты

Для подробного объяснения этого проекта обратитесь к следующему руководству:

Заключение

В этом руководстве вы узнали, как подключить microSD-карту к ESP32 и читать и записывать файлы. Вы также узнали, как использовать её для проектов логирования данных или хранения файлов для обслуживания в ваших проектах веб-серверов.

Для более глубоких проектов с microSD-картой рекомендуем ознакомиться со следующими руководствами:

Мы надеемся, что это руководство было для вас полезным. Узнайте больше об ESP32 с помощью наших ресурсов:


Источник: ESP32: Guide for MicroSD Card Module using Arduino IDE