کار با کارت حافظه MicroSD با برد ESP32 (آموزش جامع)

آیا تا به حال پروژهای با ESP32 آغاز کردهاید و با کمبود فضای ذخیرهسازی دادهها مواجه شدهاید؟ چه در حال ساخت یک ایستگاه هواشناسی باشید که نیاز به ثبت سالها داده دارد، یک دستگاه خانه هوشمند که به یک فایل پیکربندی بزرگ نیاز دارد، یا حتی یک موزیک پلیر کوچک که باید آهنگهای مورد علاقهتان را ذخیره کند، حافظه داخلی ESP32 میتواند به سرعت به یک محدودیت تبدیل شود.
در اینجا، کارت حافظه SD به کمک شما میآید! این کارت راهحل ایدهآلی برای افزایش ظرفیت ذخیرهسازی پروژه بدون صرف هزینه زیاد است.
در این آموزش، شما یاد خواهید گرفت چگونه یک ماژول کارت میکرو SD را به ESP32 متصل کنید و چگونه از فایلهای ذخیره شده روی کارت بخوانید و روی آن بنویسید. همچنین نحوه مدیریت پوشهها، ایجاد و حذف فایلها، اضافه کردن دادهها و حتی بررسی اندازه و سرعت کارت را خواهید آموخت.
بیایید شروع کنیم!
ماژول کارت حافظه میکرو SD
چند نوع ماژول کارت میکرو SD موجود است، اما در این آموزش ما از ماژولی استفاده میکنیم که در تصویر نشان داده شده است.

این ماژول خاص کارت میکرو SD انتخاب خوبی است زیرا در ولتاژ 3.3 ولت کار میکند. این همان ولتاژ مورد استفاده توسط خود کارت میکرو SD و برد ESP32 است، که باعث میشود همه چیز بدون نیاز به قطعات اضافی برای تغییر سطح ولتاژ به خوبی کار کند.
این ماژول از پروتکل ارتباطی SPI برای اتصال به ESP32 استفاده میکند. در این ماژول، خطوط داده SPI دارای مقاومتهای pull-up 10K متصل به 3.3 ولت هستند که به تضمین انتقال داده پایدار کمک میکند.
مهم است بدانید میزان جریان مصرفی کارت میکرو SD بسته به فعالیت آن متفاوت است:
- وقتی کارت استفاده نمیشود، معمولاً حدود 500 µA جریان مصرف میکند.
- هنگام خواندن دادهها از کارت، جریان مصرفی بین 15 تا 30 mA است.
- نوشتن دادهها نیاز به توان بیشتری دارد و برخی کارتهای میکرو SD ممکن است تا 100 mA جریان مصرف کنند.
به همین دلیل، ضروری است اطمینان حاصل کنید که منبع تغذیه شما قادر به تأمین جریان کافی باشد. خروجی 3.3 ولت ESP32 میتواند تا 500 mA جریان ارائه دهد که معمولاً برای خواندن و نوشتن به کارت کافی است. با این حال، اگر هنگام تلاش برای خواندن یا نوشتن دادهها با مشکل مواجه شدید، مسائل مربوط به تغذیه یکی از اولین مواردی است که باید بررسی شود.
پین ها
ماژول کارت میکرو SD دارای شش پین است و عملکرد هر کدام به شرح زیر است:

- 3V3: ورودی برق؛ به پین 3.3 ولت ESP32 متصل شود.
- CS (Chip Select): پین کنترلی برای فعال کردن ماژول در باس SPI که اجازه ارتباط در زمان لازم را میدهد.
- MOSI (Master Out Slave In): پین ورودی SPI ماژول کارت میکرو SD که دادهها را از ESP32 دریافت میکند.
- CLK (Serial Clock): پین دریافت پالسهای زمانی از دستگاه اصلی (ESP32) برای همگامسازی انتقال داده.
- MISO (Master In Slave Out): پین خروجی SPI ماژول که دادهها را به ESP32 ارسال میکند.
- GND: پین زمین.
اتصال ماژول کارت میکرو SD به ESP32
ابتدا پین 3V3 ماژول کارت میکرو SD را به پین 3.3 ولت ESP32 و پین GND ماژول را به یکی از پینهای GND ESP32 متصل کنید.

سپس پینهای مورد استفاده برای ارتباط SPI را تنظیم میکنیم. از آنجا که کارتهای میکرو SD به انتقال سریع داده نیاز دارند، بهترین عملکرد وقتی است که به پینهای SPI سختافزاری ESP32 متصل شوند. پینهای SPI پیشفرض در ESP32 به شرح زیر هستند: GPIO 18 (CLK)، GPIO 19 (MISO)، GPIO 23 (MOSI) و GPIO 5 (CS).
جدول مرجع سریع برای اتصالات پینها:
| ماژول کارت میکرو SD | ESP32 |
|---|---|
| 3V3 | 3.3V |
| CS | 5 |
| MOSI | 23 |
| CLK | 18 |
| MISO | 19 |
| GND | GND |
این اتصالها تضمین میکنند که همه چیز به درستی به هم متصل شده و آماده استفاده است.
آمادهسازی کارت میکرو SD
قبل از استفاده از کارت میکرو SD در پروژه خود، مهم است که مطمئن شوید کارت بهدرستی با سیستم فایل مناسب—FAT16 یا FAT32 فرمت شده است. این کار به ESP32 کمک میکند تا بدون مشکل فایلها را بخواند و بنویسد.
اگر از یک کارت SD کاملاً جدید استفاده میکنید، احتمالاً قبلاً با سیستم فایل FAT فرمت شده است. با این حال، فرمت کارخانهای ممکن است کامل نباشد و ممکن است با مشکلاتی مواجه شوید. اگر از کارت قدیمیتری استفاده میکنید که قبلاً مورد استفاده قرار گرفته، قطعاً نیاز به فرمت مجدد دارد. در هر صورت، بهتر است قبل از استفاده از کارت در پروژه، آن را فرمت کنید.
دو روش برای فرمت کردن کارت میکرو SD وجود دارد:
روش 1
ابتدا کارت میکرو SD را در کامپیوتر خود قرار دهید. سپس درایو کارت SD را پیدا کرده و روی آن راستکلیک کنید. از منو گزینه Format را انتخاب کنید. یک پنجره باز میشود—سیستم فایل FAT32 را انتخاب کنید و سپس روی Start کلیک کنید تا فرمت آغاز شود. دستورالعملهای روی صفحه را تا پایان دنبال کنید.

روش 2
برای نتایج بهتر و کاهش خطاها، توصیه میشود از نرمافزار رسمی فرمت کارت SD که توسط SD Association ارائه شده، استفاده کنید. این ابزار از فرمتور پایهای کامپیوتر قابل اعتمادتر است. میتوانید آن را از وبسایت SD Association دانلود کنید. پس از نصب، برنامه را اجرا کرده، کارت SD خود را از فهرست درایوها انتخاب کرده و روی دکمه Format کلیک کنید. این ابزار ویژه به جلوگیری از مشکلات رایج ناشی از فرمت ناقص یا خراب کمک میکند و میتواند وقت زیادی از شما در عیبیابی صرفهجویی کند.

راهاندازی Arduino IDE
ما از Arduino IDE برای برنامهنویسی ESP32 استفاده خواهیم کرد، بنابراین قبل از ادامه مطمئن شوید که افزونه ESP32 روی IDE نصب شده است.
کد نمونه
Arduino IDE شامل چند مثال آماده است که با هسته Arduino برای ESP32 ارائه میشوند و نشان میدهند چگونه با فایلها روی کارت میکرو SD کار کنید.
برای دسترسی به این مثالها، Arduino IDE را باز کرده و به مسیر File > Examples > SD بروید. دو اسکچ نمونه مشاهده خواهید کرد. میتوانید هر کدام را بارگذاری کنید؛ در اینجا اسکچ SD_Test را انتخاب میکنیم.
این اسکچ تقریباً همه کارهای پایهای که ممکن است با یک کارت میکرو SD بخواهید انجام دهید، پوشش میدهد. این مثال نحوه فهرست کردن محتوای یک پوشه (دایرکتوری)، ایجاد و حذف دایرکتوری، خواندن محتوای فایل، نوشتن داده جدید و اضافه کردن محتوا به فایل موجود را نشان میدهد. همچنین شامل تغییر نام فایل و حذف فایل از کارت است.
علاوه بر عملیات فایل، اسکچ نحوه مقداردهی اولیه کارت میکرو SD، بررسی نوع کارت متصل شده و تعیین اندازه کارت را نیز نشان میدهد.
قبل از وارد شدن به جزئیات، این اسکچ را اجرا کنید.

کتابخانهها
کد با اضافه کردن سه کتابخانه اصلی شروع میشود: FS.h، SD.h و SPI.h. این کتابخانهها به ESP32 اجازه میدهند تا از پروتکل SPI برای ارتباط با کارت میکرو SD استفاده کرده و عملیات سیستم فایل مانند خواندن، نوشتن و مدیریت فایلها و پوشهها را انجام دهد.
همچنین بخشی از کد وجود دارد که فعلاً غیرفعال است و به شما امکان میدهد پینهای SPI را مجدداً اختصاص دهید. اگر نمیخواهید از پینهای پیشفرض ESP32 استفاده کنید، میتوانید این بخش را فعال کرده و پینهای دلخواه خود را تنظیم کنید.
/* Uncomment and set up if you want to use custom pins for the SPI communication #define REASSIGN_PINS int sck = -1; int miso = -1; int mosi = -1; int cs = -1; */
تابع setup()
تمام عملیات اصلی در تابع setup() انجام میشود. ابتدا مانیتور سریال مقداردهی میشود تا خروجی برنامه قابل مشاهده باشد:
Serial.begin(115200);
سپس کارت SD مقداردهی اولیه میشود. کد ابتدا بررسی میکند که آیا REASSIGN_PINS تعریف شده است تا از پینهای دلخواه استفاده شود، در غیر این صورت از پینهای SPI پیشفرض ESP32 برای کارت SD استفاده میکند. اگر کارت مقداردهی نشود، پیام خطا چاپ شده و برنامه متوقف میشود:
#ifdef REASSIGN_PINS
SPI.begin(sck, miso, mosi, cs);
if (!SD.begin(cs)) {
#else
if (!SD.begin()) {
#endif
Serial.println("Card Mount Failed");
return;
}
پس از موفقیت در اتصال کارت، نوع آن (MMC، SDSC یا SDHC) بررسی شده و همراه با اندازه کل کارت بر حسب مگابایت چاپ میشود. این بررسی اولیه نمای کلی خوبی از کارت مورد استفاده به ما میدهد.
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);
عملیات سیستم فایل
بعد از بررسیهای اولیه، تابع setup() به انجام عملیات مختلف سیستم فایل روی کارت میکرو SD میپردازد. ابتدا محتویات دایرکتوری ریشه (“/“) فهرست میشود:
listDir(SD, "/", 0);
سپس یک دایرکتوری جدید به نام mydir ایجاد میشود و دوباره محتویات ریشه بررسی میشود تا ایجاد آن تأیید شود:
createDir(SD, "/mydir"); listDir(SD, "/", 0);
پس از آن همان دایرکتوری حذف شده و محتویات یک بار دیگر فهرست میشود تا نحوه ایجاد و حذف دایرکتوری نمایش داده شود:
removeDir(SD, "/mydir"); listDir(SD, "/", 2);
عملیات روی فایلها
کد یک فایل به نام hello.txt ایجاد کرده و متن Hello را در آن مینویسد:
writeFile(SD, "/hello.txt", "Hello ");
سپس متن World!\n به همان فایل اضافه میشود بدون اینکه محتوای قبلی حذف شود. سپس کل فایل خوانده شده و محتوای آن در مانیتور سریال چاپ میشود:
appendFile(SD, "/hello.txt", "World!\n"); readFile(SD, "/hello.txt");
برای نمایش نحوه مدیریت خطاها، کد تلاش میکند فایلی به نام foo.txt را حذف کند که وجود ندارد:
deleteFile(SD, "/foo.txt");
سپس فایل hello.txt به foo.txt تغییر نام داده شده و فایل جدید خوانده میشود تا تغییر تأیید شود:
renameFile(SD, "/hello.txt", "/foo.txt"); readFile(SD, "/foo.txt");
تست ورودی/خروجی فایل
برنامه سپس با استفاده از تابع testFileIO() تستی انجام میدهد که حجم زیادی از داده را در فایل test.txt میخواند و مینویسد و مدت زمان هر عملیات را گزارش میکند. این کمک میکند سرعت خواندن و نوشتن کارت SD را درک کنید:
testFileIO(SD, "/test.txt");
نمایش فضای کارت
در نهایت برنامه فضای کل و فضای استفادهشده کارت SD را چاپ میکند:
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
قسمت بعدی مقاله شامل توضیح کامل توابع سفارشی استفادهشده در اسکچ است.
فهرست کردن یک دایرکتوری
تابع listDir() برای فهرست کردن تمام فایلها و پوشهها در یک دایرکتوری مشخص روی کارت SD استفاده میشود. شما به آن سیستم فایل SD، مسیر پوشه مورد نظر و عددی که مشخص میکند تا چه عمقی در زیرپوشهها بررسی شود، میدهید. این تابع پوشه را باز میکند، اعتبار آن را بررسی میکند و سپس هر فایل یا پوشه داخل آن را مرور کرده و نام و اندازه آنها را چاپ میکند. اگر پوشه دیگری پیدا کند و سطح عمق بزرگتر از 0 باشد، وارد آن پوشه نیز میشود.
کد نمونه:
listDir(SD, "/", 0);
این دستور محتویات دایرکتوری ریشه را فهرست میکند.
ایجاد یک فولدر
تابع createDir() یک پوشه جدید روی کارت SD ایجاد میکند. شما سیستم فایل SD و نام یا مسیر پوشه را به آن میدهید. تابع تلاش میکند پوشه را ایجاد کند و نتیجه موفقیت یا شکست را چاپ میکند.
کد نمونه برای ایجاد پوشهای به نام mydir:
createDir(SD, "/mydir");
حذف یک فولدر
تابع removeDir() یک پوشه را از کارت SD حذف میکند. مانند تابع createDir()، سیستم فایل SD و مسیر پوشه را به آن میدهید و نتیجه عملیات چاپ میشود.
مثال کد برای حذف پوشه mydir:
removeDir(SD, "/mydir");
خواندن یک فایل
تابع readFile() یک فایل را باز کرده، کاراکتر به کاراکتر میخواند و محتوای آن را در Serial Monitor چاپ میکند. مانند توابع قبل، سیستم فایل SD و مسیر فایل را به آن میدهید.
کد نمونه برای خواندن محتوای فایل hello.txt:
readFile(SD, "/hello.txt");
نوشتن در یک فایل
تابع writeFile() یک فایل را در حالت نوشتن باز میکند و پیام ارائهشده را مینویسد و هر محتوای قبلی را بازنویسی میکند. شما باید سیستم فایل SD، مسیر فایل و پیام مورد نظر را به تابع بدهید.
کد نمونه برای نوشتن Hello در فایل hello.txt:
writeFile(SD, "/hello.txt", "Hello ");
اضافه کردن محتوا به یک فایل
تابع appendFile() نیز پیامی را در یک فایل مینویسد، اما برخلاف writeFile()، محتوای قبلی فایل را بازنویسی نمیکند و پیام جدید را به انتهای فایل اضافه میکند. این عمل append نامیده میشود و زمانی مفید است که میخواهید دادههای جدید را بدون حذف محتویات قبلی اضافه کنید.
کد نمونه برای اضافه کردن پیام World!\n به فایل hello.txt:
appendFile(SD, "/hello.txt", "World!\n");
توجه: \n باعث میشود دفعه بعد که دادهای به فایل اضافه میکنید، محتوا در خط جدید قرار گیرد.
تغییر نام یک فایل
تابع renameFile() نام یک فایل موجود را تغییر میدهد. شما سیستم فایل SD، نام فعلی فایل و نام جدید مورد نظر را به آن میدهید. اگر تغییر نام موفقیتآمیز باشد، پیامی مبنی بر موفقیت چاپ میشود.
کد نمونه برای تغییر نام فایل hello.txt به foo.txt:
renameFile(SD, "/hello.txt", "/foo.txt");
حذف یک فایل
تابع deleteFile() یک فایل را از کارت SD حذف میکند. شما سیستم فایل SD و مسیر فایل مورد نظر برای حذف را به آن میدهید.
کد نمونه برای حذف فایل foo.txt:
deleteFile(SD, "/foo.txt");
اندازهگیری سرعت خواندن و نوشتن
تابع testFileIO() مثال پیشرفتهتری است که سرعت خواندن و نوشتن کارت میکرو SD را اندازهگیری میکند. ابتدا یک فایل باز شده و تعداد زیادی بایت به صورت بخشبندیشده خوانده میشود تا سرعت خواندن اندازهگیری شود، سپس فایل بسته میشود. بعد همان فایل برای نوشتن باز شده و حجم زیادی از داده به صورت بخشبندیشده نوشته میشود تا سرعت نوشتن اندازهگیری شود. این تابع مفید است تا عملکرد کارت SD در عملیات دادههای بزرگ را بررسی کنید.
کد نمونه:
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();
}







