SD Card module cho phép vi điều khiển lưu trữ dữ liệu lâu dài — ghi log cảm biến, lưu file cấu hình, đọc firmware. Bài này giải thích giao tiếp SPI 4 dây, định dạng FAT32, và cách viết data logger CSV thực tế và đọc config file để tránh hardcode thông tin nhạy cảm.
Nguyên Lý Hoạt Động
1. Giao Tiếp SPI — 4 Dây Đồng Bộ
SD card giao tiếp qua SPI (Serial Peripheral Interface):
- MOSI (Master Out Slave In): vi điều khiển gửi data xuống SD
- MISO (Master In Slave Out): SD gửi data lên vi điều khiển
- SCK (Serial Clock): xung nhịp từ master
- CS/SS (Chip Select): chọn chip active (kéo LOW để chọn)
SPI Transaction — Ghi 1 byte:
Master SD Card
│ │
│── CS LOW → │ ← Chọn SD card
│ │
│ ─SCK─────→│ ← 8 xung clock
│ ─MOSI────→│ ← Gửi 8 bit data
│ ←MISO─────│ ← Nhận 8 bit phản hồi đồng thời
│ │
│── CS HIGH→ │ ← Bỏ chọn SD card
2. Tại Sao Cần Module (Không Cắm Trực Tiếp)?
SD card dùng logic 3.3V, trong khi Arduino Uno dùng 5V:
- Cắm thẳng 5V vào SD card → SD card hỏng ngay
- Module SD Card có: – Bộ chuyển mức (level shifter): 5V ↔ 3.3V cho các dây SPI – Bộ ổn áp (voltage regulator): 5V → 3.3V cấp cho SD card – Socket thẻ micro SD
Module SD Card — cấu trúc bên trong:
VCC(5V) → [LDO 3.3V regulator] → VCC SD card (3.3V)
MOSI → [Level shifter 5V→3.3V] → SD MOSI
MISO ← [Level shifter 3.3V→5V] ← SD MISO
SCK → [Level shifter 5V→3.3V] → SD SCK
CS → [Level shifter 5V→3.3V] → SD CS
GND ────────────────────────── → SD GND
3. File System FAT32
SD card dùng FAT (File Allocation Table):
- FAT16: <= 2GB
- FAT32: <= 32GB (Arduino SD library hỗ trợ)
- exFAT: > 32GB (cần SDFat library)
- Thẻ SD phải format FAT32 trước khi dùng với Arduino SD library
4. Cách Hoạt Động Của SD.h
Thư viện SD.h của Arduino gói gọn thao tác SPI và FAT32:
Chuỗi hoạt động:
SD.begin(CS) → Initialize SPI + Mount FAT32
SD.open() → Mở file handle trong bảng FAT
file.write() → Ghi vào sector buffer (chưa flush lên thẻ!)
file.close() → Flush buffer → cập nhật FAT → đóng file handle
QUAN TRỌNG: Nếu mất điện sau file.write() mà chưa file.close() → data bị mất, file có thể corrupt.
Thông Số Kỹ Thuật Module
| Thông số | Giá trị |
|---|---|
| Giao tiếp | SPI (4 dây: MOSI, MISO, SCK, CS) |
| Điện áp VCC module | 5V (có LDO onboard) hoặc 3.3V (phiên bản khác) |
| Điện áp logic SD | 3.3V |
| Thẻ hỗ trợ | Micro SD (SDHC tối đa 32GB FAT32) |
| Tốc độ SPI | 400kHz (init) → 25MHz (max) |
| Thư viện | SD.h (built-in) hoặc SDFat |
Sơ Đồ Chân (Pinout)
Module SD Card — 6 chân:
┌─────────────────────┐
│ CS MOSI GND VCC │
│ SCK MISO │
└─────────────────────┘
Hoặc thứ tự: GND VCC MISO MOSI SCK CS
(đọc nhãn trên PCB của module bạn đang dùng)
| Chân | Mô tả |
|---|---|
| CS (SS) | Chip Select — chọn SD card (GPIO tùy chọn) |
| MOSI | Master Out Slave In — data từ MCU xuống SD |
| MISO | Master In Slave Out — data từ SD lên MCU |
| SCK | SPI Clock |
| VCC | 3.3V hoặc 5V (xem module) |
| GND | Mass |
Kết Nối Phần Cứng
SD Card Module với Arduino Uno
Arduino Uno SD Card Module
───────────────── ──────────────────────────
5V ───────────────→ VCC
GND ───────────────→ GND
Pin 10 (SS) ───────→ CS ← Chip Select (tùy chọn GPIO)
Pin 11 (MOSI) ─────→ MOSI ← SPI MOSI cố định
Pin 12 (MISO) ←────── MISO ← SPI MISO cố định
Pin 13 (SCK) ──────→ SCK ← SPI SCK cố định
SPI hardware pins Arduino Uno: MOSI=11, MISO=12, SCK=13 — KHÔNG thể thay đổi. Chỉ CS (Pin 10) có thể dùng pin khác.
SD Card Module với ESP32 DevKit V1
ESP32 DevKit V1 SD Card Module
───────────────── ──────────────────────────
3.3V ──────────────→ VCC ← Nếu module không có LDO riêng
GND ───────────────→ GND
GPIO5 ────────────→ CS ← Chip Select
GPIO23 (MOSI) ─────→ MOSI ← SPI2 (VSPI) MOSI
GPIO19 (MISO) ←────── MISO ← SPI2 (VSPI) MISO
GPIO18 (SCK) ──────→ SCK ← SPI2 (VSPI) SCK
ESP32 VSPI hardware pins: MOSI=23, MISO=19, SCK=18 (mặc định). CS có thể dùng GPIO bất kỳ.
Chuẩn Bị Thẻ SD
Trước khi dùng:
- Format thẻ SD FAT32 (trên Windows: chuột phải → Format → FAT32; hoặc dùng SD Card Formatter)
- Thẻ tối đa 32GB với FAT32
- Thẻ 64GB+ cần SDFat library với exFAT
Code Arduino IDE
Code Kiểm Tra SD Card — Arduino Uno
/*
* SD Card — Kiểm tra kết nối và in thông tin thẻ
* Board: Arduino Uno
* Kết nối: VCC→5V, GND→GND, CS→10, MOSI→11, MISO→12, SCK→13
*/
#include <SPI.h>
#include <SD.h>
const int CS_PIN = 10; // Chip Select pin
void setup() {
Serial.begin(9600);
Serial.println("Khởi động SD card...");
// Khởi tạo SD — truyền vào pin CS
if (!SD.begin(CS_PIN)) {
Serial.println("Lỗi: Không tìm thấy SD card!");
Serial.println("- Kiểm tra kết nối dây");
Serial.println("- Kiểm tra format FAT32");
while (true); // Dừng chương trình
}
Serial.println("SD card OK!");
// Liệt kê file trong thư mục root
File root = SD.open("/");
Serial.println("--- Danh sách file ---");
while (true) {
File entry = root.openNextFile();
if (!entry) break; // Hết file
Serial.print(entry.name());
if (entry.isDirectory()) {
Serial.println("/");
} else {
Serial.print(" ");
Serial.print(entry.size());
Serial.println(" bytes");
}
entry.close();
}
root.close();
}
void loop() {}
Code Ghi Log CSV Cảm Biến — Arduino Uno
/*
* SD Card — Data logger ghi nhiệt độ (LM35) dạng CSV mỗi 5 giây
* Board: Arduino Uno
* Kết nối SD: CS→10, MOSI→11, MISO→12, SCK→13
* Kết nối LM35: VOUT→A0, VCC→5V, GND→GND
*
* Format CSV: timestamp_ms,temperature_C
* File: DATALOG.CSV (tự tạo nếu chưa có)
*/
#include <SPI.h>
#include <SD.h>
const int CS_PIN = 10;
const int LM35_PIN = A0;
const char LOG_FILE[] = "DATALOG.CSV"; // Tên file tối đa 8.3 (FAT format)
const unsigned long LOG_INTERVAL = 5000; // 5 giây/lần
unsigned long lastLog = 0;
unsigned long logCount = 0;
float readTempC() {
long sum = 0;
for (int i = 0; i < 10; i++) { sum += analogRead(LM35_PIN); delay(5); }
return (float)sum / 10.0 * 5000.0 / 1023.0 / 10.0; // 10mV/°C, 5V ref
}
void setup() {
Serial.begin(9600);
if (!SD.begin(CS_PIN)) {
Serial.println("SD ERROR — Dừng lại");
while (true);
}
// Tạo header nếu file chưa tồn tại
if (!SD.exists(LOG_FILE)) {
File f = SD.open(LOG_FILE, FILE_WRITE);
if (f) {
f.println("timestamp_ms,temperature_C"); // Header CSV
f.close(); // LUÔN close sau khi ghi
Serial.println("Tạo file log mới: " + String(LOG_FILE));
}
} else {
Serial.println("Tiếp tục ghi vào file: " + String(LOG_FILE));
}
Serial.println("Bắt đầu ghi log...");
}
void loop() {
if (millis() - lastLog >= LOG_INTERVAL) {
lastLog = millis();
float temp = readTempC();
unsigned long ts = millis();
// Mở file ở chế độ APPEND (FILE_WRITE mở ở cuối file nếu đã tồn tại)
File dataFile = SD.open(LOG_FILE, FILE_WRITE);
if (dataFile) {
dataFile.print(ts); // timestamp milliseconds
dataFile.print(",");
dataFile.println(temp, 2); // nhiệt độ, 2 chữ số thập phân
dataFile.close(); // *** QUAN TRỌNG: close để flush xuống thẻ ***
logCount++;
Serial.print("Log #"); Serial.print(logCount);
Serial.print(" | "); Serial.print(ts);
Serial.print("ms | "); Serial.print(temp, 2); Serial.println("°C");
} else {
Serial.println("Lỗi: Không mở được file log!");
}
}
}
Code Đọc Config File — Arduino Uno
/*
* SD Card — Đọc file cấu hình key=value
* Board: Arduino Uno
* Kết nối: CS→10, MOSI→11, MISO→12, SCK→13
*
* File CONFIG.TXT trên thẻ SD chứa:
* ssid=IoTLabs_WiFi
* password=matkhaumanh
* log_interval=10
*
* Không hardcode WiFi password trong code!
*/
#include <SPI.h>
#include <SD.h>
const int CS_PIN = 10;
const char CONFIG_FILE[] = "CONFIG.TXT";
// Lưu các giá trị config
String wifiSSID = "";
String wifiPassword = "";
int logInterval = 5;
// Đọc file config và parse key=value
bool loadConfig() {
if (!SD.exists(CONFIG_FILE)) {
Serial.println("Config file không tồn tại!");
return false;
}
File f = SD.open(CONFIG_FILE);
if (!f) return false;
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim(); // Xóa khoảng trắng và \r
if (line.length() == 0 || line.startsWith("#")) continue; // Bỏ qua dòng trống và comment
int eqPos = line.indexOf('=');
if (eqPos < 0) continue; // Bỏ qua dòng không có dấu =
String key = line.substring(0, eqPos);
String value = line.substring(eqPos + 1);
key.trim();
value.trim();
// Map key → biến
if (key == "ssid") wifiSSID = value;
else if (key == "password") wifiPassword = value;
else if (key == "log_interval") logInterval = value.toInt();
}
f.close();
return true;
}
void setup() {
Serial.begin(9600);
if (!SD.begin(CS_PIN)) {
Serial.println("SD ERROR"); while (true);
}
if (loadConfig()) {
Serial.println("Config đọc thành công:");
Serial.print(" SSID: "); Serial.println(wifiSSID);
Serial.print(" Password: "); Serial.println("***"); // Không in mật khẩu ra serial!
Serial.print(" Log interval: "); Serial.print(logInterval); Serial.println("s");
} else {
Serial.println("Dùng giá trị mặc định");
}
}
void loop() {}
Code ESP32 — Log Dữ Liệu Với Timestamp Uptime
/*
* SD Card — ESP32, Data logger với uptime timestamp
* Board: ESP32 DevKit V1
* Kết nối: VCC→3.3V, GND→GND, CS→GPIO5,
* MOSI→GPIO23, MISO→GPIO19, SCK→GPIO18
*/
#include <SPI.h>
#include <SD.h>
const int CS_PIN = 5; // Chip Select ESP32
const char LOG_FILE[] = "ESPLOG.CSV";
const unsigned long INTERVAL = 10000; // 10 giây
unsigned long lastLog = 0;
unsigned long lineCount = 0;
float readFakeTemp() {
return 25.0 + (random(-30, 31) / 10.0); // Giả lập 22.0°C - 28.0°C
}
String uptimeStr(unsigned long ms) {
unsigned long s = ms / 1000;
unsigned long m = s / 60;
unsigned long h = m / 60;
char buf[12];
sprintf(buf, "%02lu:%02lu:%02lu", h, m % 60, s % 60);
return String(buf);
}
void setup() {
Serial.begin(115200);
randomSeed(esp_random());
// Khởi tạo SPI với đúng pin ESP32
SPI.begin(18, 19, 23, CS_PIN); // SCK, MISO, MOSI, CS
if (!SD.begin(CS_PIN)) {
Serial.println("SD card không tìm thấy!");
while (true);
}
// Tạo header
if (!SD.exists(LOG_FILE)) {
File f = SD.open(LOG_FILE, FILE_WRITE);
f.println("uptime,temp_C");
f.close();
}
Serial.println("ESP32 Data Logger — sẵn sàng");
Serial.printf("Ghi log vào: %s mỗi %lus\n", LOG_FILE, INTERVAL/1000);
}
void loop() {
if (millis() - lastLog >= INTERVAL) {
lastLog = millis();
float temp = readFakeTemp();
String uptime = uptimeStr(millis());
File f = SD.open(LOG_FILE, FILE_WRITE);
if (f) {
f.print(uptime);
f.print(",");
f.println(temp, 1);
f.close();
lineCount++;
Serial.printf("[%s] Temp: %.1f°C | Dòng #%lu\n", uptime.c_str(), temp, lineCount);
} else {
Serial.println("Lỗi ghi file!");
}
}
}
Kết Quả Mong Đợi
Serial Monitor:
SD card OK!
--- Danh sách file ---
DATALOG.CSV 1240 bytes
CONFIG.TXT 56 bytes
Bắt đầu ghi log...
Log #1 | 5001ms | 26.50°C
Log #2 | 10002ms | 26.75°C
...
File DATALOG.CSV trên thẻ (mở bằng Excel):
timestamp_ms,temperature_C
5001,26.50
10002,26.75
15003,27.00
Ứng Dụng Thực Tế
| Ứng dụng | Mô tả |
|---|---|
| Trạm đo khí hậu | Log nhiệt độ + độ ẩm 15 phút/lần trong nhiều ngày |
| Black box xe | Ghi tốc độ + GPS mỗi giây khi động cơ chạy |
| Firmware OTA | Đọc file .bin từ SD → cập nhật firmware ESP32 |
| Web server | Phục vụ file HTML/CSS từ SD card |
| Config không hardcode | WiFi credentials, API keys trong file riêng |
Lưu Ý Khi Sử Dụng
1. Luôn gọi file.close() sau khi ghi
Không close → data trong buffer RAM chưa được flush xuống NAND flash → mất điện = mất data, file corrupt. Với data logger thực tế: close sau mỗi lần ghi, mở lại lần tiếp theo (chậm hơn nhưng an toàn).
2. Tên file phải theo quy tắc 8.3 (FAT32)
Tên file tối đa 8 ký tự + 3 ký tự extension. DATALOG.CSV = OK. sensordata2026.csv = KHÔNG OK → bị cắt.
3. Thẻ SD phải format FAT32 trước
Thẻ SDXC mới mua thường format exFAT → SD.begin() fail. Dùng SD Card Formatter (công cụ chính thức của SD Association) format lại FAT32.
4. Dòng tiêu thụ cao khi ghi
SD card khi ghi dữ liệu: 100-200mA. Nguồn yếu (USB hub, pin nhỏ) → điện áp sụt → SD không hoạt động ổn định → data corrupt. Dùng tụ lọc 100-220μF trên đường VCC SD module.


