Hướng dẫn dùng module thẻ nhớ Micro SD với ESP32
Hướng dẫn nối module thẻ nhớ Micro SD với ESP32 DevKit V1 qua SPI, định dạng thẻ FAT32, chạy code Arduino đọc ghi file, ghi dữ liệu định kỳ ra CSV và xử lý lỗi Card Mount Failed.
Module thẻ nhớ giao tiếp SPI giúp ESP32 lưu số đo cảm biến, nhật ký hoạt động hay file cấu hình ra thẻ Micro SD hoặc thẻ SD; dữ liệu không mất khi tắt nguồn và mở được trên máy tính. Bài viết hướng dẫn nối module thẻ nhớ của PYWORLD với ESP32 DevKit V1, định dạng thẻ FAT32, chạy chương trình đọc – ghi file bằng Arduino IDE và ghi dữ liệu định kỳ ra file CSV.
1. Tìm hiểu module thẻ nhớ
Module thẻ nhớ có hai phiên bản: bản Micro SD dùng thẻ Micro SD và bản SD card dùng thẻ SD cỡ lớn. Cả hai đều giao tiếp SPI và dùng chung chương trình trong bài.
| Thông số | Giá trị |
|---|---|
| Giao tiếp | SPI, 4 dây tín hiệu: MISO, MOSI, SCK, CS |
| Chân bản Micro SD | 6 chân một hàng: GND, VCC, MISO, MOSI, SCK, CS |
| Chân bản SD card | 2 hàng × 8 chân cùng tín hiệu: GND, +3.3, +5, CS, MOSI, SCK, MISO, GND |
| Điện áp của thẻ nhớ | 3,3 V (cho phép 2,7–3,6 V) |
| Nguồn cấp cho module | Bản Micro SD: 5 V vào chân VCC, qua IC ổn áp 3,3 V trên mạch. Bản SD card: 3,3 V vào chân +3.3 hoặc 5 V vào chân +5 (xem bên dưới) |
Cấp nguồn 3,3 V hay 5 V?
Thẻ nhớ chỉ làm việc ở 3,3 V và có thể hỏng nếu nhận trực tiếp 5 V. Cách cấp nguồn phụ thuộc linh kiện trên mạch, vì vậy hãy nhìn mạch trước khi cấp nguồn:
- Có IC ổn áp 3 chân (bản Micro SD trong ảnh; thân IC thường in AMS1117 3.3): VCC đi qua IC này để hạ xuống 3,3 V. IC cần điện áp vào cao hơn điện áp ra khoảng 1 V, nên cấp VCC bằng 5 V từ chân VIN của ESP32. Nếu chỉ cấp 3,3 V, thẻ nhận điện áp thấp hơn mức tối thiểu 2,7 V và dễ báo lỗi.
- Có chân ghi +3.3 hoặc 3V3 (bản SD card có cả +3.3 và +5): chân +3.3 nối thẳng tới thẻ nên chỉ cấp 3,3 V; chân +5 đi qua IC ổn áp. Chỉ cấp nguồn vào một trong hai chân.
- Không có IC ổn áp: chỉ cấp 3,3 V từ chân 3V3, không cấp 5 V.
ESP32 dùng mức logic 3,3 V, cùng mức với thẻ nhớ, nên không cần mạch chuyển mức. Trên bản Micro SD, IC đệm 14 chân (thường là 74LVC125A) lấy nguồn sau IC ổn áp nên tín hiệu MISO vẫn ở mức 3,3 V dù VCC là 5 V. Nếu mạch khác ảnh hoặc không đọc được chữ trên IC, hãy cấp nguồn khi chưa cắm thẻ và đo điện áp giữa GND với chân ra của IC ổn áp (với AMS1117 là chân giữa): kết quả phải xấp xỉ 3,3 V.
2. Linh kiện cần chuẩn bị
- ESP32 DevKit V1 30 chân và cáp USB có truyền dữ liệu.
- Module thẻ nhớ, bản Micro SD hoặc bản SD card (module không kèm thẻ).
- Thẻ nhớ đúng loại với module, dung lượng không quá 32 GB, và đầu đọc thẻ để định dạng trên máy tính.
- 6 dây jumper cái – cái. Nếu cắm ESP32 lên breadboard hoặc đế ra chân ESP32 thì chọn loại dây tương ứng.
- Máy tính đã cài Arduino IDE 2.x.
3. Sơ đồ nối module thẻ nhớ với ESP32
Bài dùng bộ chân SPI mặc định (bus VSPI) của ESP32 DevKit V1: GPIO23, GPIO19, GPIO18 và GPIO5. Bốn chân này nằm cùng hàng với chân 3V3; chân VIN nằm ở cuối hàng chân đối diện. Bảng dưới đây dùng cho bản Micro SD:
| Module thẻ nhớ | ESP32 DevKit V1 | Ghi chú |
|---|---|---|
| GND | GND | Mass chung. |
| VCC | VIN (5 V) | Module có IC ổn áp 3,3 V. Chân VIN có khoảng 5 V khi ESP32 được cấp nguồn qua cổng USB. |
| MISO | D19 / GPIO19 | Dữ liệu từ thẻ về ESP32. |
| MOSI | D23 / GPIO23 | Dữ liệu từ ESP32 tới thẻ. |
| SCK | D18 / GPIO18 | Xung nhịp SPI. |
| CS | D5 / GPIO5 | Chọn thẻ nhớ. |
Một số mạch in DO thay cho MISO, DI thay cho MOSI, CLK thay cho SCK; luôn nối theo tên chân in trên mạch. Dùng dây ngắn, cắm chặt và cắm thẻ trước khi cấp nguồn. Khi cần đổi chân, khai báo lại bằng SPI.begin(SCK, MISO, MOSI, CS); không chọn GPIO34–39 vì các chân này chỉ là đầu vào.
Nối bản SD card
Bản SD card có hàng chân đôi, hai hàng mang cùng tín hiệu nên chỉ cần nối một hàng. GND, CS, MOSI, SCK, MISO nối như bảng trên. Về nguồn, nối +3.3 với 3V3 và để trống +5 (hoặc nối +5 với VIN và để trống +3.3). Bản này không có IC đệm, tín hiệu đi thẳng tới thẻ ở mức 3,3 V nên phù hợp với ESP32.
4. Định dạng thẻ nhớ FAT32
Thư viện SD của ESP32 đọc được thẻ FAT16 và FAT32, không đọc được exFAT. Thẻ từ 64 GB trở lên thường định dạng sẵn exFAT, và hộp thoại Format của Windows không cho chọn FAT32 với ổ lớn hơn 32 GB, vì vậy nên dùng thẻ không quá 32 GB:
- Cắm thẻ vào đầu đọc thẻ. Sao lưu dữ liệu cần giữ vì định dạng sẽ xóa toàn bộ thẻ.
- Mở File Explorer, bấm chuột phải vào ổ thẻ nhớ, chọn Format.
- Ở File system chọn FAT32, chọn Quick Format rồi bấm Start.
- Chọn Eject, rút thẻ và cắm vào module.
5. Cài đặt Arduino IDE
- Mở Tools > Board > Boards Manager, tìm esp32 và cài gói esp32 by Espressif Systems.
- Chọn Tools > Board > esp32 > DOIT ESP32 DEVKIT V1 (hoặc ESP32 Dev Module) và chọn đúng cổng COM ở Tools > Port.
Ba thư viện dùng trong bài là SD, SPI và FS đều có sẵn trong gói esp32, không cần cài thêm trong Library Manager. Bài dùng gói esp32 phiên bản 3.3.10.
6. Chương trình kiểm tra thẻ nhớ
Chương trình gắn (mount) thẻ nhớ, in loại thẻ và dung lượng, ghi file /pyworld.txt, ghi nối thêm một dòng rồi đọc lại toàn bộ file:
/*
PYWORLD.VN - ESP32 DevKit V1 + module thẻ nhớ Micro SD (giao tiếp SPI)
CS = GPIO5, SCK = GPIO18, MISO = GPIO19, MOSI = GPIO23 (bus VSPI mặc định)
Thư viện FS, SD, SPI có sẵn trong gói esp32 by Espressif Systems
*/
#include <FS.h>
#include <SD.h>
#include <SPI.h>
// ====== Chân SPI nối với module thẻ nhớ ======
#define SD_CS 5
#define SD_SCK 18
#define SD_MISO 19
#define SD_MOSI 23
#define SD_FREQ 4000000 // Tốc độ SPI 4 MHz, phù hợp khi nối bằng dây jumper
const char TEST_FILE[] = "/pyworld.txt"; // Đường dẫn phải bắt đầu bằng dấu /
// In loại thẻ và dung lượng thẻ
void printCardInfo() {
uint8_t cardType = SD.cardType();
Serial.print("Loai the: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC/SDXC");
} else {
Serial.println("Khong xac dinh");
}
uint32_t sizeMB = SD.cardSize() / (1024ULL * 1024ULL);
Serial.print("Dung luong: ");
Serial.print(sizeMB);
Serial.println(" MB");
}
// Ghi đè: tạo file mới, nếu file đã có thì xóa nội dung cũ rồi ghi
void writeFile(const char *path, const char *text) {
File file = SD.open(path, FILE_WRITE);
if (!file) {
Serial.println("Khong mo duoc file de ghi.");
return;
}
if (file.print(text)) {
Serial.println("Ghi file: OK");
} else {
Serial.println("Ghi file: LOI");
}
file.close();
}
// Ghi nối thêm vào cuối file, giữ nguyên nội dung đã có
void appendFile(const char *path, const char *text) {
File file = SD.open(path, FILE_APPEND);
if (!file) {
Serial.println("Khong mo duoc file de ghi noi them.");
return;
}
if (file.print(text)) {
Serial.println("Ghi noi them: OK");
} else {
Serial.println("Ghi noi them: LOI");
}
file.close();
}
// Đọc toàn bộ file và in ra Serial Monitor
void readFile(const char *path) {
File file = SD.open(path, FILE_READ);
if (!file) {
Serial.println("Khong mo duoc file de doc.");
return;
}
Serial.println("Noi dung file:");
while (file.available()) {
Serial.write(file.read());
}
file.close();
}
void setup() {
Serial.begin(115200);
delay(500);
Serial.println();
Serial.println("=== KIEM TRA THE NHO ===");
// Khởi tạo bus SPI, sau đó gắn (mount) thẻ nhớ
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, SPI, SD_FREQ)) {
Serial.println("Card Mount Failed - khong gan duoc the nho.");
Serial.println("Kiem tra the, day noi, nguon cap va dinh dang FAT32.");
return;
}
Serial.println("Gan the nho thanh cong.");
printCardInfo();
writeFile(TEST_FILE, "PYWORLD.VN - ESP32 ghi the nho\n");
appendFile(TEST_FILE, "Dong nay duoc ghi noi them\n");
readFile(TEST_FILE);
Serial.println("=== KET THUC ===");
}
void loop() {
// Chương trình chỉ chạy một lần trong setup()
}
Nạp chương trình, mở Serial Monitor ở 115200 baud rồi nhấn nút EN trên ESP32. Nếu nối dây đúng và thẻ đã định dạng FAT32, kết quả mong đợi có dạng như sau (loại thẻ và dung lượng tùy thẻ đang dùng):
=== KIEM TRA THE NHO ===
Gan the nho thanh cong.
Loai the: SDHC/SDXC
Dung luong: 7580 MB
Ghi file: OK
Ghi noi them: OK
Noi dung file:
PYWORLD.VN - ESP32 ghi the nho
Dong nay duoc ghi noi them
=== KET THUC ===
Dung lượng tính với 1 MB = 1024 × 1024 byte nên nhỏ hơn con số in trên thẻ. Cắm thẻ vào máy tính sẽ thấy file pyworld.txt chứa hai dòng trên. Mỗi lần nhấn EN, file được ghi lại từ đầu vì chương trình mở file bằng FILE_WRITE.
7. Giải thích các lệnh chính
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS): khởi tạo bus SPI với bốn chân đã khai báo, cũng là các chân mặc định của bo DOIT ESP32 DEVKIT V1.SD.begin(SD_CS, SPI, SD_FREQ): khởi tạo thẻ và gắn hệ thống file. Hàm trả vềfalsekhi không giao tiếp được với thẻ hoặc thẻ không có phân vùng FAT hợp lệ. Tham số thứ ba là tốc độ SPI: mặc định 4 MHz, tối đa 25 MHz.SD.cardType()trả vềCARD_MMC,CARD_SD(thẻ SDSC, đến 2 GB) hoặcCARD_SDHC(SDHC/SDXC);SD.cardSize()trả về dung lượng tính bằng byte.SD.open(path, FILE_WRITE): mở file để ghi và xóa nội dung cũ.FILE_APPENDghi nối vào cuối file,FILE_READchỉ đọc. Đường dẫn phải bắt đầu bằng dấu/.file.print(),file.println()ghi chữ và số như khi in ra Serial;file.available()vàfile.read()đọc từng byte cho đến hết file.file.close(): dữ liệu ghi nằm trong bộ đệm và chỉ chắc chắn xuống thẻ sau khi đóng file, nên luôn đóng file sau mỗi lần ghi.- Hàm khác hay dùng:
SD.exists()kiểm tra file có tồn tại,SD.remove()xóa file,SD.mkdir()tạo thư mục,SD.end()ngắt thẻ nhớ.
8. Ghi dữ liệu định kỳ ra file CSV
Chương trình dưới đây cứ 5 giây ghi nối thêm một dòng gồm số thứ tự và giá trị millis() vào file /datalog.csv. Nếu chưa gắn được thẻ hoặc lần ghi trước bị lỗi, chương trình tự gắn lại thẻ ở chu kỳ sau:
/*
PYWORLD.VN - ESP32 DevKit V1 + module thẻ nhớ: ghi dữ liệu định kỳ ra file CSV
Cứ 5 giây ghi nối thêm một dòng "stt,millis_ms" vào file /datalog.csv
CS = GPIO5, SCK = GPIO18, MISO = GPIO19, MOSI = GPIO23
*/
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#define SD_CS 5
#define SD_SCK 18
#define SD_MISO 19
#define SD_MOSI 23
#define SD_FREQ 4000000
const char LOG_FILE[] = "/datalog.csv";
const unsigned long LOG_INTERVAL_MS = 5000; // Chu kỳ ghi: 5 giây
bool sdReady = false;
unsigned long lastLogMs = 0;
unsigned long lineNumber = 0;
// Gắn thẻ nhớ; nếu chưa có file log thì tạo file và ghi dòng tiêu đề
bool startSD() {
if (!SD.begin(SD_CS, SPI, SD_FREQ)) {
Serial.println("Card Mount Failed - chua gan duoc the nho.");
return false;
}
if (!SD.exists(LOG_FILE)) {
File file = SD.open(LOG_FILE, FILE_WRITE);
if (!file) {
Serial.println("Khong tao duoc file log.");
SD.end();
return false;
}
file.println("stt,millis_ms");
file.close();
}
Serial.println("The nho san sang, bat dau ghi du lieu.");
return true;
}
// Ghi nối thêm một dòng vào cuối file log
bool appendLogLine() {
File file = SD.open(LOG_FILE, FILE_APPEND);
if (!file) {
return false;
}
lineNumber++;
file.print(lineNumber);
file.print(',');
file.println(millis());
file.close(); // Đóng file để dữ liệu được ghi hẳn xuống thẻ
return true;
}
void setup() {
Serial.begin(115200);
delay(500);
Serial.println();
Serial.println("=== GHI DU LIEU RA THE NHO ===");
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
sdReady = startSD();
}
void loop() {
if (millis() - lastLogMs < LOG_INTERVAL_MS) {
return;
}
lastLogMs = millis();
// Chưa gắn được thẻ hoặc lần ghi trước bị lỗi: thử gắn lại
if (!sdReady) {
sdReady = startSD();
if (!sdReady) {
return;
}
}
if (appendLogLine()) {
Serial.print("Da ghi dong ");
Serial.println(lineNumber);
} else {
Serial.println("Ghi that bai, se gan lai the o lan ghi sau.");
SD.end();
sdReady = false;
}
}
Sau vài chu kỳ, file datalog.csv có dạng như sau (số liệu minh họa) và mở được bằng Excel:
stt,millis_ms
1,5004
2,10005
3,15004
Để ghi số đo cảm biến, thêm giá trị vào dòng ghi trong hàm appendLogLine(), các cột cách nhau bằng dấu phẩy. millis() đếm từ lúc ESP32 khởi động và trở về 0 sau mỗi lần reset; cần ngày giờ thực thì dùng thêm module thời gian thực (RTC) hoặc lấy giờ qua WiFi. Không rút thẻ hay ngắt nguồn đúng lúc đang ghi.
9. Xử lý lỗi thường gặp
- Báo "Card Mount Failed": kiểm tra lần lượt: thẻ đã cắm hết khe; nguồn đúng như mục 1 (module có IC ổn áp thì VCC nối VIN, không nối 3V3); MISO và MOSI không bị đảo; CS nối đúng chân khai báo ở
SD_CS; thẻ đã định dạng FAT32. Sửa xong nhấn EN để chạy lại. - Muốn biết lý do cụ thể: chọn Tools > Core Debug Level > Error rồi nạp lại. Thư viện in thêm dòng lỗi, ví dụ
f_mount failed: (3) The physical drive cannot workkhi không giao tiếp được với thẻ, hoặc(13) There is no valid FAT volumekhi thẻ chưa định dạng FAT32. - Lúc nhận thẻ lúc không, ESP32 tự khởi động lại khi ghi: thường do nguồn yếu hoặc dây dài. Đổi cáp, đổi cổng USB, rút ngắn dây và thử giảm
SD_FREQxuống1000000(1 MHz). - Thẻ 64 GB trở lên không nhận: thẻ đang ở định dạng exFAT; dùng thẻ không quá 32 GB định dạng FAT32.
- Không mở được file: đường dẫn phải bắt đầu bằng dấu
/, ví dụ/datalog.csv. - Mất dữ liệu cũ, file rỗng hoặc thiếu dòng cuối:
FILE_WRITExóa nội dung cũ, hãy dùngFILE_APPEND; luôn gọifile.close()và không rút thẻ khi đang ghi. - Rút thẻ rồi cắm lại thì không ghi được: nhấn EN, hoặc gọi
SD.end()rồiSD.begin()như chương trình ở mục 8. - Không nạp được chương trình: dùng cáp USB có truyền dữ liệu, chọn đúng cổng COM và cài driver USB-UART đúng chip trên mạch (CP2102 hoặc CH340). Nếu IDE dừng ở dòng Connecting..., giữ nút BOOT cho đến khi bắt đầu nạp.
- Serial Monitor hiện ký tự lạ: chọn đúng tốc độ 115200 baud.
Xem thêm: ESP32 điều khiển module relay 4 kênh, nạp file .bin vào ESP32 bằng Flash Download Tool và HX711 với load cell và LCD 1602. Mạch dùng trong bài có hai loại chip USB: ESP32 DevKit V1 30 chân CP2102 và ESP32 DevKit V1 30 chân CH340.
Xem thêm