راه اندازی میکرو اس دی با ESP32

به آموزش راه اندازی میکرو اس دی با ESP32خوش اومدید.

تو این آموزش نحوه راه اندازی میکرو اس دی با ESP32، روش نوشتن و خوندن فایل ها از میکرو اس دی  رو در نرم افزار ARDUINO IDE یاد می گیرید. برای راه اندازی میکرو اس دی با ESP32 نیاز به یک ماژول راه انداز مثل ماژولی که تو شکل زیر میبینیم داریم. این ماژول با پروتکل SPI کار می کنه.

برای اتصال این ماژول به ESP32 دونستن اطلاعات پین های خروجی پیش فرض SPI کفایت می کنه.

چه قطعاتی نیاز داریم ؟

  • ESP32 DOIT DEVKIT V1 Board
  • میکرو اس دس
  • ماژول میکرو اس دی
  • بِردبُرد
  • سیم جامپر

شماتیک مدار

اتصالات ماژول میکرو اس دی به  ESP32 رو مثل شکل زیر وصل کنید.

خب قبل از شروع کار به یک سری نکات باید توجه کنید. میکرو اس دی رو با فرمت FAT32 طبق مراحل زیر فرمت کنید.

انتقال اطلاعات از میکرو اس دی به  ESP32

برای انتقال اطلاعات دو تا کتابخونه SD library و  SDD_MMC.h library برای  ESP32 وجود داره. کتابخونه SD library از کنترلر SPI و کتابخونه SDD_MMC.h  از کنترلر ESP32 SD/SDIO/MMC استفاده می کنه.

آپلود کردن کد

کد زیر رو تو نرم افزار ARDUINO IDE کپی کنید.

/*
  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(){

}

کارهایی که تو این انجام شده شامل این موارده :

  • لیست کردن مسیر پوشه فایل ها
  • درست کردن مسیر پوشه فایل ها
  • پاک کردن مسیر پوشه فایل ها
  • خوندن محتوای فایل
  • نوشتن در فایل
  • افزودن (الصاق) محتوا به فایل
  • تغییر دادن اسم فایل
  • پاک کردن فایل
  • مقدار دهی اولیه به میکرو اس دی مربوط به پروتکل ارتباطی
  • گرفتن اطلاعات نوع میکرو اس دی
  • گرفتن اطلاعات سایز میکرو اس دی

کد برنامه چطور کار می کنه ؟

خب تو خط اول کد کتابخونه ها معرفی میشه. کتابخونه FS.h برای سر و کار داشتن با فایل ها ، کتابخونه SD.h برای ارتباط با میکرو اس دی و کتابخونه SPI.h برای استفاده از پروتکل SPI نوشته شدن.

در بخش اول دستور ()listDir مسیر پوشه فایل ها رو تو میکرو اس دی درست می کنه. ورودی های این دستور شامل فایل های سیستم ، نام مسیر فایل ها و سطح دسترسی به مسیره . تو مثال زیر / یعنی مسیر روت اصلی میکرو اس دی  .

listDir(SD, "/", 0);

در بخش دوم درست کردن مسیر پوشه فایل ها با دستور ()creatDir انجام میشه . ورودی های این دستور فایل و اسم مسیرش هست. در مثال زیر تو مسیر روت میکرو اس دی مسیری به اسم mydir ایجاد می کنه .

createDir(SD, "/mydir");

در بخش سوم با دستور ()removeDir یک مسیری رو تو میکرو اس دی میشه پاک کرد. ورودی های این دستور فایل و اسم مسیرش هست. مثال زیر تو مسیر روت میکرو اس دی مسیری به اسم mydir رو پاک می کنه .

removeDir(SD, "/mydir");

در بخش بعدی با دستور ()readFile محتوای فایل رو خونده و در مانیتور نمایش میده و ورودی های این دستور فایل و اسم مسیرش هست. مثال زیر محتوای فایلی به اسم hello.txt رو می خونه .

readFile(SD, "/hello.txt")

در بخش بعدی با دستور ()writeFile محتوایی رو داخل فایل میشه نوشت و ورودی های این دستور فایل و اسم مسیرش هست. مثال زیر کلمه HELLO رو در فایلی به اسم hello.txt رو می نویسه .

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

در بخش بعدی با دستور ()appendFile محتوایی رو میشه به فایل اضافه کرد و ورودی های این دستور فایل و اسم مسیرش هست. مثال زیر کلمه ” world!\n ” رو در فایلی به اسم hello.txt رو می نویسه . \n یعنی محتوای جدید در خط بعدی محتوای قبلی فایل اضافه میشه.

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

در بخش بعدی با دستور ()renameFile میشه اسم فایل رو تغییر داد و ورودی های این دستور فایل و اسم قبلی و اسم جدیدش هست. مثال زیر فایلی به اسم hello.txt رو به foo.txt تغییر نام میده .

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

در بخش بعدی با دستور ()deleteFile میشه فایلی رو پاک کرد و ورودی های این دستور فایل و اسم مسیرش هست . مثال زیر فایل foo.txt رو از میکرو اس دی پاک می کنه .

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

در بخش بعدی با دستور ()testFile میشه زمان خوندن اطلاعات فایل رو حساب کرد و ورودی های این دستور فایل و اسم مسیرش هست . مثال زیر زمان خوندن اطلاعات فایل test.txt رو از میکرو اس دی محاسبه می کنه.

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

در بخش ()setup با دستور ()SD.begin به میکرو اس دی مقداردهی اولیه میشه. اگر هیچ ورودی برای begin() اعمال نشه به طور پیش فرض ارتباط SPI و میکرو اس دی رو با پین CS برقرار میکنه. اما اگر پین ورودی دیگه ای رو در ورودی دستور ()begin اعمال کنید از طریق اون پین این ارتباط شکل می گیره. مثلا اگر GPIO17 رو به عنوان پین CS تعریف کنید کد برنامه به شکل زیر میشه.

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

برای استفاده از پین های پیش فرض SPI در بخش های بعدی بیشتر توضیح می دیم.

در بخش بعدی با کدهای زیر میشه نوع میکرو اس دی رو مشخص کرد.

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 میشه اندازه میکرو اس دی رو مشخص کرد.

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

بعد از اینکه کدها رو در ESP32 آپلود کردید کلید RST روی برد بزنید اگر اتصالات به درستی انجام شده باشه شبیه پیغام زیر رو در مانیتور می بینید.

ارتباط میکرو اس دی با پین های پیش فرض  SPI

کتابخونه های SD.H و SDD_MMC.h به طور پیش فرض از پین های شماره (۲۳،۱۹،۱۸،۵) که اصطلاحا پین های VSPI نامیده میشن استفاده می کنه اما میشه از پین های دیگه ای هم جایگزین کرد. مثلا به جای VSPI از HSPIها استفاده کنیم.

برای این کار کافیه تغییراتی که در ادامه توضیح می دیم رو در کد اعمال کنید. تو مرحله اول باید پین های دلخواه رو معرفی کنید. مثلا :

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

تو ()setup یک کلاس جدید برای VSPI و یا HSPI درست کنید.

SPIClass spi = SPIClass(VSPI);

حالا باید این پین ها رو تو پروتکل معرفی کنید.

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

تو مرحله آخر هم میکرو اس دی رو در دستور ()begin مقداردهی میکنید.CS ، پروتکل SPI ای که تو محله قبل معرفی شد و فرکانس رو وارد کنید.

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

مثال اول : ثبت اطلاعات ESP32 در میکرو اس دی

یکی از کاربردهای مهم استفاده میکرو اس دی در ESP32 ثبت اطلاعاته . مثلا طبق شکل زیر میشه با سنسورهای مختلف مثلا BME280 و پروتکل های زمانی مثل NTP اطلاعات ESP32 رو طبق زمانبندی ذخیره کرد.

خب برای این کار هم باید کتابخونه های مربوط به سنسورها رو در کد لحاظ کنیم. برای این مثال دو کتابخونه Adafruit BME280 Library و Adafruit Unified Sensor Driver رو در نرم افزار ARDUINO IDE از قسمتSketch ،  include library ، manage libraries اضافه می کنیم.

شماتیک مدار این مثال

اطلاعات پین های ماژول ها رو تو شکل های زیر می بینیم.

کد برنامه

کد زیر رو تو نرم افزار ARDUINO IDE کپی کنید . این کد اطلاعات ماژول BME280 رو شامل دما ، رطوبت ، فشار رو میخونه و هر ۳۰ ثانیه تو میکرو اس دی ذخیره می کنه .

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

برای اینکه کد درست کار کنه لازمه تنظیمات مربوط به شبکه (SSID و پسورد) رو مقداردهی کنید.

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

کد رو در ESP32 آپلود کنید و پیغامی مثل شکل های زیر تو مانیتور می بینید.

بعد از چند دقیقه  اجرای پروژه محتوای میکرو اس دی رو تو کامیپیوتر بخونید . اطلاعات ذخیره شده تو فایل به اسم data.txt به شکل زیر دریافت میشه.

مثال دوم : راه اندازی وب سرور ESP32 با فایلی از میکرو اس دی

یک مثال دیگه رو با هم بررسی کنیم . خب اگر فایل هایی که مربوط به راه اندازی وب سرور مثل HTML، CSS ، JavaScript و فایل های مربوط به تصاویر بزرگ باشن بهتره که این فایل ها به جای خوندن از ESP32 ، از میکرو اس دی خونده بشن.

برای مثال وب سرور یک صفحه وب رو با استفاده از فایل های HTML ،CSS و یک فایل تصویر PNG راه اندازی می کنیم. این سه فایل  رو تو میکرو اس دی قرار بدید و بعد کد زیر رو تو نرم افزار آردوینو آپلود کنید.

/*
  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() {
  
}

مجددا تنظیمات شبکه رو مربوط به SSID و پسورد رو اعمال کنید.

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

بعد از آپلود کد و اعمال تنظیمات بادریت مانیتور (۱۱۵۲۰۰) و زدن کلید RST برد، آدرس IP مربوط به ESP32 در مانیتور نمایش داده میشه.

مرورگرتون رو باز کنید و آدرس آی پی ESP32 رو تایپ کنید. صفحه وب مورد نظری که با فایل های ذخیره شده تو میکرو اس دی ساخته شد در دسترسه!

پایان آموزش راه اندازی میکرو اس دی با ESP32.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

قبلا حساب کاربری ایجاد کرده اید؟
گذرواژه خود را فراموش کرده اید؟
Loading...