📑 เนื้อหาในบทความ
📊 ภาพรวม Filesystem บน ESP32
ESP32 มีความสามารถในการจัดเก็บข้อมูลถาวรได้หลายวิธี ซึ่งเหมาะสำหรับโปรเจกต์ที่ต้องการบันทึกข้อมูล sensor, logs หรือการตั้งค่า ในบทความนี้เราจะเรียนรู้ 3 วิธีหลักๆ:
- 💾SD Card: ใช้กับ Micro SD Card Module ความจุสูง (GB) เหมาะกับ data logging จำนวนมาก
- 📁SPIFFS: Flash filesystem ในตัว ESP32 เอง ความจุเล็ก (KB-MB) เหมาะกับไฟล์ config
- 🗂️LittleFS: รุ่นถัดไปของ SPIFFS เร็วกว่า มีประสิทธิภาพดีกว่า แนะนำให้ใช้
🔧 อุปกรณ์ที่ต้องใช้
Hardware
- ✅ ESP32 Development Board (ทุกรุ่น)
- ✅ Micro SD Card Module (SPI Interface)
- ✅ Micro SD Card (แนะนำ 4GB-32GB, Class 10)
- ✅ Jumper wires (Female-to-Female หรือ Female-to-Male)
- ✅ Breadboard (ถ้าจำเป็น)
Software
- ✅ Arduino IDE หรือ PlatformIO
- ✅ Library: SD, FS, LittleFS
🔌 การต่อวงจร SD Card Module
SD Card Module ส่วนใหญ่ใช้ SPI Protocol ในการสื่อสาร ต่อกับ ESP32 ดังนี้:
| SD Card Module | ESP32 Pin |
|---|---|
| CS (Chip Select) | GPIO 5 |
| SCK (Clock) | GPIO 18 |
| MOSI (Master Out Slave In) | GPIO 23 |
| MISO (Master In Slave Out) | GPIO 19 |
| VCC | 3.3V |
| GND | GND |
⚠️ ข้อควรระวัง: บาง SD Card Module ต้องการ 5V สำหรับ VCC ให้ตรวจสอบ spec ของ module ที่ซื้อก่อนต่อวงจร
💾 การใช้งาน SD Card กับ ESP32
ตัวอย่างที่ 1: เขียนและอ่านไฟล์พื้นฐาน
โค้ดนี้สาธิตการเขียนและอ่านข้อมูลจาก SD Card เบื้องต้น:
#include "SD.h"
#include "SPI.h"
// กำหนดขา CS สำหรับ SD Card
#define SD_CS 5
void setup() {
Serial.begin(115200);
// เริ่มต้นใช้งาน SD Card
if (!SD.begin(SD_CS)) {
Serial.println("ไม่สามารถเริ่มต้น SD Card ได้!");
return;
}
Serial.println("SD Card พร้อมใช้งาน!");
// ตรวจสอบประเภทของ Card
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("ไม่พบ SD Card");
return;
}
Serial.print("ประเภท SD Card: ");
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");
// แสดงข้อมูลขนาดของ SD Card
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %llu MB\n", cardSize);
// เขียนข้อมูลลงไฟล์
File file = SD.open("/test.txt", FILE_WRITE);
if (file) {
file.println("Hello, CynoIoT! SD Card ทำงานได้แล้ว");
file.println("วันที่: 10/03/2026");
file.close();
Serial.println("เขียนข้อมูลลง test.txt สำเร็จ!");
} else {
Serial.println("ไม่สามารถเปิดไฟล์เพื่อเขียนได้");
}
// อ่านข้อมูลจากไฟล์
file = SD.open("/test.txt");
if (file) {
Serial.println("\n--- ข้อมูลใน test.txt ---");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
// แสดงพื้นที่ว่างที่เหลือ
uint64_t usedBytes = SD.usedBytes() / (1024 * 1024);
uint64_t totalBytes = SD.totalBytes() / (1024 * 1024);
Serial.printf("\nพื้นที่ใช้ไป: %llu MB / %llu MB\n", usedBytes, totalBytes);
}
void loop() {
// ไม่ต้องทำอะไรใน loop
}ตัวอย่างที่ 2: Data Logging สำหรับ Sensor
โค้ดนี้สาธิตการบันทึกข้อมูล sensor เป็นระยะ พร้อม timestamp:
#include "SD.h"
#include "SPI.h"
#define SD_CS 5
#define LED_PIN 2
// ชื่อไฟล์สำหรับ logging
const char* LOG_FILE = "/sensor_log.csv";
// ตัวแปรสำหรับจับเวลา
unsigned long lastLogTime = 0;
const unsigned long LOG_INTERVAL = 5000; // บันทึกทุก 5 วินาที
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// เริ่มต้น SD Card
if (!SD.begin(SD_CS)) {
Serial.println("SD Card initialization failed!");
while (1) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(100);
}
}
Serial.println("SD Card พร้อมใช้งาน!");
// สร้างไฟล์ CSV พร้อม header
File file = SD.open(LOG_FILE, FILE_WRITE);
if (file) {
// เขียน header เฉพาะกรณีไฟล์ใหม่ (ไฟล์ว่าง)
if (file.size() == 0) {
file.println("Timestamp,Temperature,Humidity,Light");
Serial.println("สร้างไฟล์ logging พร้อม header");
}
file.close();
}
}
void loop() {
unsigned long currentTime = millis();
// บันทึกข้อมูลทุก 5 วินาที
if (currentTime - lastLogTime >= LOG_INTERVAL) {
lastLogTime = currentTime;
// จำลองค่า sensor (ในการใช้งานจริง ให้อ่านจาก sensor จริง)
float temperature = random(200, 350) / 10.0; // 20.0 - 35.0 C
float humidity = random(400, 800) / 10.0; // 40.0 - 80.0 %
int light = random(100, 1000); // Lux
// สร้าง timestamp ง่ายๆ
char timestamp[20];
sprintf(timestamp, "%02d:%02d:%02d",
(int)(currentTime / 3600000) % 24,
(int)(currentTime / 60000) % 60,
(int)(currentTime / 1000) % 60);
// เปิดไฟล์เพื่อเขียน
File file = SD.open(LOG_FILE, FILE_APPEND);
if (file) {
// เขียนข้อมูลในรูปแบบ CSV
file.printf("%s,%.1f,%.1f,%d\n", timestamp, temperature, humidity, light);
file.close();
// กระพริบ LED แสดงว่าบันทึกสำเร็จ
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
Serial.printf("[%s] บันทึก: %.1f°C, %.1f%%, %d lux\n",
timestamp, temperature, humidity, light);
} else {
Serial.println("ไม่สามารถเปิดไฟล์เพื่อเขียนได้!");
}
}
}📁 SPIFFS vs LittleFS
นอกจาก SD Card แล้ว ESP32 ยังมี Flash filesystem ภายในสำหรับเก็บไฟล์ขนาดเล็ก เช่น ไฟล์ config, HTML สำหรับ web server หรือ certificates:
เปรียบเทียบ SPIFFS และ LittleFS
| คุณสมบัติ | SPIFFS | LittleFS |
|---|---|---|
| ความเร็ว | ปานกลาง | เร็วกว่า ✅ |
| ประสิทธิภาพพื้นที่ | ไม่ดี (มี waste) | ดีกว่า ✅ |
| ความเสถียร | ดี | ดีกว่า ✅ |
| ความยาวไฟล์สูงสุด | ~2GB | ~2GB |
💡 คำแนะนำ: สำหรับโปรเจกต์ใหม่ ให้ใช้ LittleFS เพราะมีประสิทธิภาพดีกว่า และ Espressif แนะนำให้ใช้แทน SPIFFS
ตัวอย่าง: ใช้งาน LittleFS
#include "LittleFS.h"
// ชื่อไฟล์ config
const char* CONFIG_FILE = "/config.json";
void setup() {
Serial.begin(115200);
// เริ่มต้น LittleFS
if (!LittleFS.begin(true)) { // true = format ถ้ายังไม่ได้ mount
Serial.println("LittleFS ล้มเหลว!");
return;
}
Serial.println("LittleFS พร้อมใช้งาน!");
// เขียนไฟล์ config
writeFile(LittleFS, CONFIG_FILE, "{\"wifi_ssid\":\"MyWiFi\",\"interval\":5000}");
// อ่านไฟล์ config
String config = readFile(LittleFS, CONFIG_FILE);
Serial.println("Config: " + config);
// แสดงข้อมูล filesystem
Serial.printf("Total: %d bytes\n", LittleFS.totalBytes());
Serial.printf("Used: %d bytes\n", LittleFS.usedBytes());
}
void loop() {
// ไม่ต้องทำอะไรใน loop
}
// ฟังก์ชันเขียนไฟล์
void writeFile(fs::FS &fs, const char *path, const char *message) {
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("ไม่สามารถเปิดไฟล์เพื่อเขียน");
return;
}
if (file.print(message)) {
Serial.println("เขียนไฟล์สำเร็จ");
} else {
Serial.println("เขียนไฟล์ล้มเหลว");
}
file.close();
}
// ฟังก์ชันอ่านไฟล์
String readFile(fs::FS &fs, const char *path) {
File file = fs.open(path);
if (!file || file.isDirectory()) {
Serial.println("ไม่สามารถเปิดไฟล์เพื่ออ่าน");
return String();
}
String content = file.readString();
file.close();
return content;
}📊 โปรเจกต์จริง: Data Logger สำหรับ Smart Farm
นี่คือตัวอย่างโค้ดสมบูรณ์สำหรับโปรเจกต์ data logging จริง ที่รวม SD Card + DHT Sensor + Deep Sleep:
#include "SD.h"
#include "SPI.h"
#include "DHT.h"
#include "WiFi.h"
// กำหนดขาและค่าคงที่
#define SD_CS 5
#define DHTPIN 4
#define DHTTYPE DHT22
#define LED_PIN 2
// ตั้งค่า Deep Sleep (เวลานอน)
#define uS_TO_S_FACTOR 1000000 // แปลง microsecond เป็น second
#define TIME_TO_SLEEP 300 // นอน 5 นาที (300 วินาที)
DHT dht(DHTPIN, DHTTYPE);
const char* LOG_FILE = "/smartfarm_log.csv";
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
dht.begin();
// แสดงสถานะ wake up
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("ตื่นจาก Deep Sleep เพื่อบันทึกข้อมูล");
} else {
Serial.println("เริ่มต้นระบบ Smart Farm Data Logger");
}
// เริ่มต้น SD Card
if (!SD.begin(SD_CS)) {
Serial.println("SD Card initialization failed!");
goToDeepSleep();
}
// อ่านค่า sensor
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
// ตรวจสอบว่าอ่านค่าได้หรือไม่
if (isnan(humidity) || isnan(temperature)) {
Serial.println("อ่านค่าจาก DHT ล้มเหลว!");
goToDeepSleep();
}
// สร้าง timestamp
char timestamp[25];
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
// ถ้าไม่มี NTP ให้ใช้ millis()
sprintf(timestamp, "%lu", millis() / 1000);
} else {
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &timeinfo);
}
// บันทึกข้อมูล
logData(timestamp, temperature, humidity);
// แสดงสถานะบน Serial Monitor
Serial.printf("[%s] Temp: %.1f°C, Humidity: %.1f%%\n",
timestamp, temperature, humidity);
// กระพริบ LED
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
// เข้าสู่โหมด Deep Sleep
Serial.println("เข้าสู่ Deep Sleep 5 นาที...");
goToDeepSleep();
}
void loop() {
// ไม่ต้องทำอะไรใน loop เพราะใช้ Deep Sleep
}
void logData(const char* timestamp, float temp, float humidity) {
File file = SD.open(LOG_FILE, FILE_APPEND);
if (!file) {
Serial.println("ไม่สามารถเปิดไฟล์ logging");
return;
}
// สร้าง header ถ้าไฟล์ว่าง
if (file.size() == 0) {
file.println("Timestamp,Temperature,Humidity,Battery");
}
// อ่านแรงดันแบตเตอรี่ (ถ้ามีวงจรวัดแรงดัน)
float battery = readBattery();
// เขียนข้อมูล
file.printf("%s,%.1f,%.1f,%.2f\n", timestamp, temp, humidity, battery);
file.close();
Serial.println("บันทึกข้อมูลลง SD Card สำเร็จ");
}
float readBattery() {
// วงจรแบตเตอรี่ divider: 100k + 100k
// ADC range: 0-4095, Reference: 3.3V
int adcValue = analogRead(34); // GPIO34 สำหรับอ่านแรงดัน
float voltage = (adcValue / 4095.0) * 3.3 * 2; // x2 เพราะ voltage divider
return voltage;
}
void goToDeepSleep() {
Serial.println("กำลังเข้าสู่ Deep Sleep...");
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}🔧 แก้ปัญหาที่พบบ่อย
ปัญหา: SD Card initialization failed
อาการ: ขึ้นข้อความ "SD Card initialization failed!" บน Serial Monitor
สาเหตุ:
- ต่อสายผิดขา
- SD Card เสียหรือ format ไม่ถูกต้อง
- ใช้แรงดันไฟผิด (บาง module ต้องการ 5V)
- ขา SPI ไม่ตรงกับโค้ด
วิธีแก้:
- ตรวจสอบการต่อสายทุกขา
- ลอง format SD Card เป็น FAT32
- เปลี่ยน SD Card ใบใหม่
- ตรวจสอบว่า module ต้องการ 3.3V หรือ 5V
ปัญหา: เขียนไฟล์ไม่ได้หรือข้อมูลเสียหาย
สาเหตุ:
- ปิดไฟล์ไม่ครบก่อน ESP32 รีเซ็ต
- ถอด SD Card ขณะกำลังเขียนข้อมูล
- พื้นที่เต็ม
วิธีแก้:
- เรียก
file.close()หลังเขียนทุกครั้ง - ตรวจสอบพื้นที่ว่างด้วย
SD.usedBytes() - ใช้
FILE_APPENDแทนFILE_WRITEถ้าต่อท้าย
ปัญหา: LittleFS ไม่ mount ได้
สาเหตุ: ยังไม่เคย format หรือ filesystem เสียหาย
วิธีแก้:
- ใช้
LittleFS.begin(true)เพื่อ format ครั้งแรก - ระวัง: true จะลบข้อมูลทั้งหมด!
✅ สรุป
ในบทความนี้เราได้เรียนรู้:
- ✓การต่อ SD Card Module กับ ESP32 ผ่าน SPI
- ✓การอ่านและเขียนไฟล์พื้นฐานบน SD Card
- ✓สร้าง Data Logger สำหรับบันทึกข้อมูล sensor เป็น CSV
- ✓เปรียบเทียบ SPIFFS และ LittleFS สำหรับ flash filesystem
- ✓วิธีแก้ปัญหาที่พบบ่อยกับ SD Card