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 tin cơ bản của module thẻ nhớ
Thông sốGiá trị
Giao tiếpSPI, 4 dây tín hiệu: MISO, MOSI, SCK, CS
Chân bản Micro SD6 chân một hàng: GND, VCC, MISO, MOSI, SCK, CS
Chân bản SD card2 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 moduleBả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)
Ảnh thật module thẻ nhớ Micro SD mặt linh kiện và mặt sau, có chú thích hàng chân, IC ổn áp, IC đệm và khe cắm thẻ
Ảnh thật module thẻ nhớ bản Micro SD của shop, có thêm số chú thích: hàng chân, IC ổn áp, IC đệm tín hiệu và khe cắm thẻ.

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:

Đấu nối module thẻ nhớ bản Micro SD với ESP32 DevKit V1
Module thẻ nhớESP32 DevKit V1Ghi chú
GNDGNDMass chung.
VCCVIN (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.
MISOD19 / GPIO19Dữ liệu từ thẻ về ESP32.
MOSID23 / GPIO23Dữ liệu từ ESP32 tới thẻ.
SCKD18 / GPIO18Xung nhịp SPI.
CSD5 / GPIO5Chọn thẻ nhớ.
Sơ đồ nối ESP32 DevKit V1 với module thẻ nhớ Micro SD qua SPI: GND, VCC nối VIN, MISO D19, MOSI D23, SCK D18, CS D5
Sơ đồ nối 6 dây giữa ESP32 DevKit V1 và module thẻ nhớ bản Micro SD (hình vẽ minh họa).

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.

Hàng chân module thẻ nhớ bản Micro SD và bản SD card cùng chân ESP32 DevKit V1 tương ứng
Hàng chân của hai phiên bản module thẻ nhớ và chân ESP32 DevKit V1 tương ứng (hình vẽ minh họa).

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:

  1. 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ẻ.
  2. Mở File Explorer, bấm chuột phải vào ổ thẻ nhớ, chọn Format.
  3. Ở File system chọn FAT32, chọn Quick Format rồi bấm Start.
  4. Chọn Eject, rút thẻ và cắm vào module.

5. Cài đặt Arduino IDE

  1. Mở Tools > Board > Boards Manager, tìm esp32 và cài gói esp32 by Espressif Systems.
  2. 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ề false khi 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ặc CARD_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_APPEND ghi nối vào cuối file, FILE_READ chỉ đọ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 work khi không giao tiếp được với thẻ, hoặc (13) There is no valid FAT volume khi 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_FREQ xuống 1000000 (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_WRITE xóa nội dung cũ, hãy dùng FILE_APPEND; luôn gọi file.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ồi SD.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.


Tin tức liên quan

Hướng dẫn bắt đầu với Wemos D1 R2 (ESP8266) trên Arduino IDE
Hướng dẫn bắt đầu với Wemos D1 R2 (ESP8266) trên Arduino IDE

4 Lượt xem

Hướng dẫn dùng Wemos D1 R2 (ESP8266 khung Arduino UNO) với Arduino IDE 2.x: sơ đồ chân, cài driver CH340 và gói board ESP8266, nạp code nháy LED, kết nối WiFi và xử lý lỗi thường gặp.

Hướng dẫn dùng mạch tạo trễ NE555 có relay: đấu dây và chỉnh thời gian trễ
Hướng dẫn dùng mạch tạo trễ NE555 có relay: đấu dây và chỉnh thời gian trễ

3 Lượt xem

Hướng dẫn dùng mạch tạo trễ NE555 có relay bản 5 V và 12 V: nguyên lý tạo trễ, ý nghĩa các cọc VCC, GND, COM, CK, CB, cách chỉnh thời gian trễ, sơ đồ đấu dây và xử lý lỗi thường gặp.

Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32
Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32

5 Lượt xem

So sánh mạch hạ áp LM2596 và MP1584, cách chỉnh điện áp ra bằng đồng hồ vạn năng trước khi nối tải, cấp nguồn 5 V cho ESP32 DevKit V1 từ nguồn 12 V và xử lý lỗi thường gặp.

Hướng dẫn ESP32 làm web server điều khiển relay 4 kênh qua WiFi
Hướng dẫn ESP32 làm web server điều khiển relay 4 kênh qua WiFi

6 Lượt xem

Hướng dẫn dùng ESP32 DevKit V1 làm web server trong mạng WiFi nội bộ, mở trang trên điện thoại để bật, tắt module relay 4 kênh; có sơ đồ nối dây, code Arduino IDE và lưu ý an toàn.

Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32
Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32

5 Lượt xem

Hướng dẫn nối màn hình OLED 1.3 inch SH1106 với ESP32 DevKit V1 qua I2C, cài thư viện Adafruit SH110X, chạy code hiển thị chữ và xử lý lỗi thường gặp.

Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny
Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny

4 Lượt xem

Hướng dẫn cài driver, cài Thonny và nạp firmware MicroPython cho mạch ESP32 Lite, dùng REPL, nháy LED, lưu main.py, đọc nút nhấn, quét I2C và xử lý lỗi thường gặp.

Hướng dẫn dùng màn hình LCD 2004 I2C với ESP32
Hướng dẫn dùng màn hình LCD 2004 I2C với ESP32

5 Lượt xem

Hướng dẫn nối màn hình LCD 2004 I2C (mạch PCF8574) với ESP32 DevKit V1, quét địa chỉ I2C, hiển thị 4 dòng chữ, tạo ký tự riêng, làm bộ đếm thời gian và đổi sang LCD 1602.

Hướng dẫn dùng thanh cảm biến dò line 5 kênh TCRT5000 với ESP32
Hướng dẫn dùng thanh cảm biến dò line 5 kênh TCRT5000 với ESP32

4 Lượt xem

Hướng dẫn nối thanh dò line 5 kênh TCRT5000 (BFD-1000) với ESP32 DevKit V1, đọc trạng thái 5 kênh trên Serial Monitor, tính vị trí vạch và gợi ý hướng lái cho xe dò line.


Bình luận
Đã thêm vào giỏ hàng