ESP32: Руководство по модулю MicroSD-карты с использованием Arduino IDE
Это руководство показывает, как использовать microSD-карту с ESP32: вы узнаете, как читать и записывать файлы на microSD-карту. Для подключения microSD-карты к плате ESP32 мы будем использовать модуль microSD-карты (протокол связи SPI). Использование microSD-карты с ESP32 особенно полезно для логирования данных или хранения файлов, которые не помещаются в файловую систему (SPIFFS). ESP32 будет программироваться с использованием ядра Arduino.
В этом руководстве мы рассмотрим следующие темы:
Знакомство с модулем MicroSD-карты и его выводами
Существуют различные модули microSD-карт, совместимые с ESP32. Мы используем модуль microSD-карты, показанный на следующем рисунке – он обменивается данными по протоколу SPI. Вы можете использовать любой другой модуль microSD-карты с интерфейсом SPI.
Этот модуль microSD-карты также совместим с другими микроконтроллерами, такими как Arduino и платы ESP8266 NodeMCU. Чтобы узнать, как использовать модуль microSD-карты с Arduino, вы можете следовать следующему руководству:
Где купить?
Вы можете нажать на ссылку ниже, чтобы проверить различные магазины, где можно приобрести модуль microSD-карты:
Распиновка модуля 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 |
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Подключение ESP32 к модулю microSD-карты
Чтобы подключить модуль microSD-карты к плате ESP32, вы можете следовать следующей схеме подключения (для стандартных SPI-пинов ESP32):
Рекомендуемое чтение: ESP32 Pinout Reference: Which GPIO pins should you use?
Подготовка microSD-карты
Перед тем как продолжить работу с руководством, убедитесь, что вы отформатировали microSD-карту в FAT32. Следуйте инструкциям ниже для форматирования microSD-карты или используйте программу, например SD Card Formatter (совместима с Windows и Mac OS).
1. Вставьте microSD-карту в компьютер. Перейдите в Мой компьютер и нажмите правой кнопкой мыши на SD-карту. Выберите Форматировать, как показано на рисунке ниже.
2. Появится новое окно. Выберите 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.
В 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.
Использование пользовательских 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).
Предварительные требования
Для этого примера убедитесь, что у вас установлены следующие библиотеки:
Вы можете установить эти библиотеки с помощью менеджера библиотек 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. Вот список необходимых компонентов:
Подключите схему, следуя приведённой ниже диаграмме.
Вы также можете посмотреть следующие таблицы:
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, что всё работает как ожидается.
Дайте проекту поработать некоторое время, чтобы накопить показания. Затем вставьте microSD-карту в компьютер, и вы должны увидеть файл data.txt с показаниями датчиков.
Пример: Веб-сервер ESP32 с файлами с microSD-карты
Вы можете сохранить файлы для создания веб-сервера с ESP32 на microSD-карту (HTML, CSS, JavaScript и файлы изображений). Это может быть полезно, если файлы слишком велики для размещения в файловой системе ESP32, или это может быть более удобно в зависимости от вашего проекта.
Чтобы показать вам, как это сделать, мы создадим простой веб-сервер, который обслуживает 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.
В вашей локальной сети откройте веб-браузер и введите IP-адрес ESP32. Вы должны получить доступ к следующей веб-странице, построенной с использованием файлов, хранящихся на microSD-карте.
Для подробного объяснения этого проекта обратитесь к следующему руководству:
Заключение
В этом руководстве вы узнали, как подключить microSD-карту к ESP32 и читать и записывать файлы. Вы также узнали, как использовать её для проектов логирования данных или хранения файлов для обслуживания в ваших проектах веб-серверов.
Для более глубоких проектов с microSD-картой рекомендуем ознакомиться со следующими руководствами:
Мы надеемся, что это руководство было для вас полезным. Узнайте больше об ESP32 с помощью наших ресурсов:
Источник: ESP32: Guide for MicroSD Card Module using Arduino IDE