IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series 37 Module Cảm Biến – SD Card Module SPI: FAT32, Ghi Log CSV Cảm Biến & Đọc Config File Không Hardcode

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ếpSPI (4 dây: MOSI, MISO, SCK, CS)
Điện áp VCC module5V (có LDO onboard) hoặc 3.3V (phiên bản khác)
Điện áp logic SD3.3V
Thẻ hỗ trợMicro SD (SDHC tối đa 32GB FAT32)
Tốc độ SPI400kHz (init) → 25MHz (max)
Thư việnSD.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ânMô tả
CS (SS)Chip Select — chọn SD card (GPIO tùy chọn)
MOSIMaster Out Slave In — data từ MCU xuống SD
MISOMaster In Slave Out — data từ SD lên MCU
SCKSPI Clock
VCC3.3V hoặc 5V (xem module)
GNDMass

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:

  1. Format thẻ SD FAT32 (trên Windows: chuột phải → Format → FAT32; hoặc dùng SD Card Formatter)
  2. Thẻ tối đa 32GB với FAT32
  3. 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ụngMô tả
Trạm đo khí hậuLog nhiệt độ + độ ẩm 15 phút/lần trong nhiều ngày
Black box xeGhi 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 serverPhục vụ file HTML/CSS từ SD card
Config không hardcodeWiFi 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.

Bài tiếp theo: