สอนใช้งาน ESP32 อ่าน — เขียน SD Card exFAT บน Arduino IDE
สำหรับการใช้งาน ESP32 คู่กับ SD Card นั้น นิยมอย่างมากในงาน IoT และ Data Logger ไม่ว่าจะเป็นการเก็บค่า Sensor, Log การทำงานของระบบ หรือบันทึกข้อมูลไฟล์ใหญ่ ๆ ที่ FAT32 ไม่รองรับ (เกิน 4GB) การใช้ exFAT จึงเป็นทางออกที่ดี บทความนี้จะสอนวิธีเชื่อมต่อ SD Card, ตั้งค่า Arduino IDE และเขียนโค้ดตัวอย่างเพื่อ อ่าน–เขียนไฟล์บน SD Card exFAT อย่างละเอียด

- exFAT vs FAT32 Format ไหนดี
มีวิธีการเลือกใช้งานดังนี้
ถ้าต้องเก็บไฟล์ใหญ่ ๆ (วีดิโอ/ข้อมูลยาว ๆ/Log ต่อไฟล์เกิน 4GB) ให้ใช้ exFAT
เปรียบเทียบ exFAT vs FAT32 สำหรับ SD Card
| หัวข้อ | FAT32 | exFAT |
|---|---|---|
| ขนาดไฟล์สูงสุด | ~4GB ต่อไฟล์ | ใหญ่กว่า 4GB ได้ไม่จำกัด |
| ขนาดพาร์ทิชัน | ใช้ได้ถึงระดับ TB (แต่ Windows GUI ฟอร์แมตได้ไม่เกิน 32GB) | รองรับการ์ดความจุสูง TB ได้จริง |
| โครงสร้าง Allocate | FAT table เดินตาม Cluster → เขียน/อ่านไฟล์ใหญ่ช้าลง | ใช้ Allocation Bitmap ลด I/O เหมาะกับแฟลช |
| ความเข้ากันได้ (Compatibility) | สูงมาก อุปกรณ์เก่าอ่านได้ | ใหม่กว่า บางอุปกรณ์รุ่นเก่าอาจไม่รองรับ |
| งานที่เหมาะ | ไฟล์เล็ก–กลาง, อุปกรณ์ทั่วไป | ไฟล์ใหญ่, Data log ระยะยาว, Multimedia |
| หากไฟดับ/ถูกดึงการ์ด | ข้อมูลสูญหาย/ไฟล์เสียหายง่าย ต้อง flush() และ close() ให้เรียบร้อย | เหมือนกัน แต่โครงสร้าง Bitmap มักเสียหายเฉพาะไฟล์ ไม่ลามทั้ง table |
Notes
- แนะนำ ฟอร์แมตการ์ดเป็น exFAT บน PC ด้วยค่า Default allocation unit ก่อนใช้งาน
- เวลาบันทึกไฟล์ยาว ๆ ให้เขียนเป็น Buffer block (512B–1KB) แล้วเรียก
flush()เป็นระยะ และclose()ทุกครั้งก่อนถอดการ์ด/รีเซ็ต - ถ้าไฟมักตกบ่อย ควรใช้ A/B file strategy (เขียนไฟล์สลับ 2 ชุด) เพื่อลดโอกาสคอร์รัปต์
การเชื่อมต่อ Hardware
ทำการเชื่อมต่อโมดูลอ่าน SD card กับ ESP32 ผ่าน
โมดูล SPI → ESP32 pin
MOSI → 23
MISO → 19
SCK → 18
CS → 5
VCC → 5V
GND → GND
เมื่อเตรียม Hardware เสร็จสิ้น ให้มาเตรียม SD card โดยทำการเปิดบนคอมพิวเตอร์และ Format ก่อนใช้งาน 1 ครั้ง เพื่อเคลียร์ข้อมูลเก่าทิ้ง

เมื่อคลิก Format ให้ทำการตรวจสอบดังภาพด้านล่างในส่วนของ File system ให้เลือกเป็น exFAT และทำการคลิก Start เพื่อเริ่ม Format

เมื่อเสร็จสิ้น ให้เข้าไปตรวจสอบ File system ใน properties จะเห็นว่าเป็น exFAT เป็นอันเสร็จสิ้นในขั้นตอนการเตรียม Hardware

ตอนนี้เราพร้อมเขียนโปรแกรมทดสอบกันแล้ว แต่เพื่อความมั่นใจ ขอให้ทุกท่านตรวจสอบการเชื่อมต่อสายโมดูลกับบอร์ด ESP32 และที่สำคัญอย่าลืมเสียบ SD card เข้าไปที่โมดูลด้วยครับ
การตั้งค่า Arduino IDE
เมื่อเตรียม Hardware เสร็จ เรามาเตรียมโปรแกรม Arduino กันครับ โดยให้ทุกท่านติดตั้ง Library ชื่อว่า SdFat by Bill Greiman ในรูปผมใช้ V2.3.0

ตัวอย่างโค้ด Arduino
#include <SPI.h>
#include <SdFat.h>
// ---- SPI pin ----
#define PIN_MOSI 23
#define PIN_MISO 19
#define PIN_SCK 18
#define PIN_CS 5
// ใช้ SdExFat สำหรับ exFAT volumes
SdExFat sd;
ExFatFile file;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
Serial.println("\n=== ESP32 + SdFat exFAT Test for SD Card 64GB ===");
// กำหนด SPI pins
SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_CS);
// เริ่ม SD ด้วย DEDICATED SPI ที่ 10 MHz (สำหรับความเสถียร)
SdSpiConfig cfg(PIN_CS, DEDICATED_SPI, SD_SCK_MHZ(10));
Serial.print("กำลังเชื่อมต่อ exFAT volume ... ");
if (!sd.begin(cfg)) {
Serial.println("ล้มเหลว!");
Serial.println("คำแนะนำ:");
Serial.println("- ตรวจสอบให้แน่ใจว่าการ์ดถูกจัดรูปแบบเป็น exFAT");
Serial.println("- ตรวจสอบขา CS และการต่อสาย (ใช้ 3.3V เท่านั้น)");
Serial.println("- ลองใช้ SPI clock ต่ำกว่า เช่น 4-8 MHz");
Serial.println("- ตรวจสอบว่าการ์ดใส่อย่างแน่นหนา");
return;
}
Serial.println("สำเร็จ!");
// ตรวจสอบประเภทไฟล์ระบบ (64 หมายถึง exFAT ใน SdFat)
uint8_t vtype = sd.fatType();
Serial.printf("ประเภท Volume: %u (คาดหวัง 64 สำหรับ exFAT)\n", vtype);
if (vtype != 64) {
Serial.println("คำเตือน: ไม่ใช่ exFAT filesystem!");
}
// แสดงข้อมูลการ์ด
printCardInfo();
// แสดงรายการไฟล์ในโฟลเดอร์หลัก
Serial.println("\n=== รายการไฟล์ในโฟลเดอร์หลัก ===");
listFiles();
// ทดสอบการเขียนและอ่านไฟล์
testFileOperations();
}
void loop() {
delay(5000);
}
void printCardInfo() {
Serial.println("\n=== ข้อมูลการ์ด ===");
// ขนาดการ์ด
uint64_t cardSize = sd.card()->sectorCount() * 512ULL;
Serial.printf("ขนาดการ์ด: %.2f GB\n", cardSize / 1000000000.0);
// พื้นที่ว่าง
uint64_t freeSpace = sd.freeClusterCount() * sd.bytesPerCluster();
Serial.printf("พื้นที่ว่าง: %.2f GB\n", freeSpace / 1000000000.0);
// พื้นที่ที่ใช้แล้ว
uint64_t usedSpace = cardSize - freeSpace;
Serial.printf("พื้นที่ที่ใช้: %.2f GB\n", usedSpace / 1000000000.0);
}
void listFiles() {
ExFatFile root;
if (!root.open("/")) {
Serial.println("ไม่สามารถเปิดโฟลเดอร์หลักได้");
return;
}
ExFatFile entry;
char name[256];
int fileCount = 0;
while (entry.openNext(&root, O_RDONLY)) {
entry.getName(name, sizeof(name));
Serial.printf("%3d. %-30s", ++fileCount, name);
if (entry.isDir()) {
Serial.print(" [โฟลเดอร์]");
} else {
uint64_t size = entry.fileSize();
if (size < 1024) {
Serial.printf(" %llu B", size);
} else if (size < 1024 * 1024) {
Serial.printf(" %.1f KB", size / 1024.0);
} else if (size < 1024 * 1024 * 1024) {
Serial.printf(" %.1f MB", size / (1024.0 * 1024.0));
} else {
Serial.printf(" %.1f GB", size / (1024.0 * 1024.0 * 1024.0));
}
}
Serial.println();
entry.close();
// จำกัดจำนวนไฟล์ที่แสดง
if (fileCount >= 50) {
Serial.println("... (แสดงเพียง 50 รายการแรก)");
break;
}
}
root.close();
Serial.printf("พบไฟล์/โฟลเดอร์ทั้งหมด: %d รายการ\n", fileCount);
}
void testFileOperations() {
Serial.println("\n=== ทดสอบการเขียนและอ่านไฟล์ ===");
// เขียนไฟล์ทดสอบ
if (file.open("test_esp32.txt", O_CREAT | O_WRITE)) {
Serial.print("กำลังเขียนไฟล์ทดสอบ... ");
// ใช้ write() สำหรับเขียนข้อมูล
const char* testText1 = "=== ESP32 SD Card Test ===\n";
file.write(testText1, strlen(testText1));
String timeStr = "เวลาที่สร้างไฟล์: " + String(millis()) + " ms\n";
file.write(timeStr.c_str(), timeStr.length());
const char* testText2 = "ขนาดการ์ด: 64GB\n";
file.write(testText2, strlen(testText2));
const char* testText3 = "ระบบไฟล์: exFAT\n";
file.write(testText3, strlen(testText3));
const char* testText4 = "ESP32 สามารถเขียนไฟล์ภาษาไทยได้\n";
file.write(testText4, strlen(testText4));
// เขียนข้อมูลแบบไบนารี่
uint8_t testData[] = {0x01, 0x02, 0x03, 0x04, 0x05};
file.write(testData, sizeof(testData));
file.close();
Serial.println("สำเร็จ!");
} else {
Serial.println("ล้มเหลวในการเขียนไฟล์!");
return;
}
// อ่านไฟล์ทดสอบ
if (file.open("test_esp32.txt", O_READ)) {
Serial.println("เนื้อหาไฟล์ที่อ่านได้:");
Serial.println("------------------------");
// อ่านไฟล์ทีละไบต์
while (file.available()) {
int c = file.read();
if (c >= 0) {
Serial.print((char)c);
}
}
file.close();
Serial.println("\n------------------------");
Serial.println("อ่านไฟล์สำเร็จ!");
} else {
Serial.println("ล้มเหลวในการอ่านไฟล์!");
}
}
void readSpecificFile(const char* filename) {
Serial.printf("\n=== อ่านไฟล์: %s ===\n", filename);
if (file.open(filename, O_READ)) {
Serial.printf("ขนาดไฟล์: %llu bytes\n", file.fileSize());
// อ่านไฟล์ทีละบรรทัด
int lineCount = 0;
String line = "";
while (file.available() && lineCount < 10) { // จำกัด 10 บรรทัดแรก
int c = file.read();
if (c >= 0) {
char ch = (char)c;
if (ch == '\n') {
Serial.printf("บรรทัด %d: %s\n", ++lineCount, line.c_str());
line = "";
} else if (ch != '\r') {
line += ch;
}
}
}
// แสดงบรรทัดสุดท้ายถ้ามี
if (line.length() > 0) {
Serial.printf("บรรทัด %d: %s\n", ++lineCount, line.c_str());
}
if (file.available()) {
Serial.println("... (มีข้อมูลเพิ่มเติม)");
}
file.close();
} else {
Serial.printf("ไม่สามารถเปิดไฟล์ %s ได้\n", filename);
}
}
// ฟังก์ชันสำหรับการจัดการข้อผิดพลาด
void handleSDError() {
Serial.println("\nเกิดข้อผิดพลาดกับ SD Card");
Serial.println("กำลังลองเชื่อมต่อใหม่...");
// ลองเชื่อมต่อใหม่
SdSpiConfig cfg(PIN_CS, DEDICATED_SPI, SD_SCK_MHZ(8)); // ใช้ความเร็วต่ำกว่า
if (sd.begin(cfg)) {
Serial.println("เชื่อมต่อใหม่สำเร็จ!");
} else {
Serial.println("ยังไม่สามารถเชื่อมต่อได้");
}
}
// ฟังก์ชันเสริมสำหรับอ่านไฟล์ขนาดใหญ่แบบแบ่งส่วน
void readLargeFileInChunks(const char* filename, size_t chunkSize = 512) {
Serial.printf("\n=== อ่านไฟล์ขนาดใหญ่: %s ===\n", filename);
if (file.open(filename, O_READ)) {
uint64_t totalSize = file.fileSize();
Serial.printf("ขนาดไฟล์: %llu bytes\n", totalSize);
uint8_t buffer[chunkSize];
uint64_t bytesRead = 0;
while (file.available() && bytesRead < 2048) { // อ่านแค่ 2KB แรก
size_t bytesToRead = min(chunkSize, (size_t)(totalSize - bytesRead));
bytesToRead = min(bytesToRead, (size_t)2048 - (size_t)bytesRead);
int actualRead = file.read(buffer, bytesToRead);
if (actualRead <= 0) break;
Serial.printf("Chunk %d (%d bytes): ", (int)(bytesRead/chunkSize) + 1, actualRead);
for (int i = 0; i < actualRead && i < 50; i++) { // แสดงแค่ 50 ไบต์แรกของแต่ละ chunk
if (buffer[i] >= 32 && buffer[i] <= 126) {
Serial.print((char)buffer[i]);
} else {
Serial.printf("[%02X]", buffer[i]);
}
}
if (actualRead > 50) Serial.print("...");
Serial.println();
bytesRead += actualRead;
}
file.close();
Serial.printf("อ่านทั้งหมด: %llu bytes\n", bytesRead);
} else {
Serial.printf("ไม่สามารถเปิดไฟล์ %s ได้\n", filename);
}
}
// ฟังก์ชันสำหรับสร้างไฟล์ทดสอบขนาดใหญ่
void createLargeTestFile(const char* filename, size_t sizeKB) {
Serial.printf("\n=== สร้างไฟล์ทดสอบขนาดใหญ่: %s (%zu KB) ===\n", filename, sizeKB);
if (file.open(filename, O_CREAT | O_WRITE)) {
const char* testPattern = "0123456789ABCDEF";
size_t patternLen = strlen(testPattern);
size_t totalBytes = sizeKB * 1024;
size_t bytesWritten = 0;
Serial.print("กำลังเขียน");
while (bytesWritten < totalBytes) {
size_t bytesToWrite = min(patternLen, totalBytes - bytesWritten);
int written = file.write(testPattern, bytesToWrite);
if (written <= 0) break;
bytesWritten += written;
// แสดงความคืบหน้า
if (bytesWritten % (sizeKB * 102) == 0) { // ทุกๆ 10%
Serial.print(".");
}
}
file.close();
Serial.printf("\nเขียนไฟล์สำเร็จ: %zu bytes\n", bytesWritten);
} else {
Serial.printf("ไม่สามารถสร้างไฟล์ %s ได้\n", filename);
}
}
// ฟังก์ชันแสดงข้อมูลรายละเอียดของไฟล์
void showFileDetails(const char* filename) {
Serial.printf("\n=== รายละเอียดไฟล์: %s ===\n", filename);
ExFatFile testFile;
if (testFile.open(filename, O_READ)) {
Serial.printf("ขนาด: %llu bytes\n", testFile.fileSize());
Serial.printf("ตำแหน่ง: %llu\n", testFile.curPosition());
Serial.printf("Available: %d\n", testFile.available());
// แสดงข้อมูล hex ของ 32 bytes แรก
Serial.print("เนื้อหา 32 bytes แรก (HEX): ");
for (int i = 0; i < 32 && testFile.available(); i++) {
int b = testFile.read();
if (b >= 0) {
Serial.printf("%02X ", b);
}
}
Serial.println();
testFile.close();
} else {
Serial.printf("ไม่สามารถเปิดไฟล์ %s ได้\n", filename);
}
}
หรือสามารถ Fork หรือ Clone จาก GitHub ผมได้ที่
https://github.com/yuttapichai/SDcard_exFAT_ESP32
เมื่อทุกท่าน Upload Code ตามตัวอย่างของผมไปแล้วนั้น เปิด Serial monitor Speed 115200 จะได้ข้อความแสดงตามรูปด้านล่างเลยครับ

\=== ESP32 + SdFat exFAT Test for SD Card 64GB ===
กำลังเชื่อมต่อ exFAT volume ... สำเร็จ!
ประเภท Volume: 64 (คาดหวัง 64 สำหรับ exFAT)
=== ข้อมูลการ์ด ===
ขนาดการ์ด: 63.86 GB
พื้นที่ว่าง: 3.70 GB
พื้นที่ที่ใช้: 60.16 GB
=== รายการไฟล์ในโฟลเดอร์หลัก ===
1. System Volume Information \[โฟลเดอร์]
2. test_esp32.txt 237 B
พบไฟล์/โฟลเดอร์ทั้งหมด: 2 รายการ
=== ทดสอบการเขียนและอ่านไฟล์ ===
กำลังเขียนไฟล์ทดสอบ... สำเร็จ!
เนื้อหาไฟล์ที่อ่านได้:
------------------------
=== ESP32 SD Card Test ===
เวลาที่สร้างไฟล์: 191 ms
ขนาดการ์ด: 64GB
ระบบไฟล์: exFAT
ESP32 สามารถเขียนไฟล์ภาษาไทยได้
------------------------
อ่านไฟล์สำเร็จ!
และนอกจากนี้สามารถดึง SD card ไปเปิดดูใน PC ได้ โดยทุกท่านจะพบกับไฟล์ test_esp32.txt เมื่อเปิดไฟล์จะพบกับข้อความตามในรูปเลยครับ

ข้อแนะนำ
ตัวอย่างโค้ดผมสามารถทดลองปรับความเร็วในการอ่านได้ที่บรรทัดนี้
SdSpiConfig cfg(PIN_CS, DEDICATED_SPI, SD_SCK_MHZ(10));
//เปลี่ยนความเร็วที่ SD_SCK_MHZ(10)
SdSpiConfig cfg(PIN_CS, DEDICATED_SPI, SD_SCK_MHZ(40));
ข้อควรระวังเมื่อการอ่าน/เขียน ไม่เสถียร ให้ลดความเร็วลงมา (หากระยะทางไกล อาจทำให้การอ่าน/เขียน ที่ความเร็วสูง ๆ นั้นไม่เสถียร)
หวังว่าจะเป็นประโยชน์ และช่วยทุกท่านแก้ไขปัญหาที่พบได้นะครับ ฝากกดดาวเพื่อเป็นกำลังใจในการทำบทความต่อไปด้วยครับ ขอบคุณมากครับ