IoTLabs

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

ESP32-C3 đọc/ghi dữ liệu microSD: Bài 1 -Health Check module thẻ nhớ

Mục tiêu

  • Kết nối ESP32-C3 SuperMini với module microSD SPI.
  • Viết khung code theo style Feature-based: setupFeatureSD(), runFeatureSD().
  • Kiểm tra module thẻ nhớ hoạt động (card type, size, read/write file test).
  • Liệt kê thư mục, tạo cấu trúc thư mục mẫu.

Ứng dụng thực tế

  • Ghi log/cấu hình vào thẻ nhớ.
  • Lưu dữ liệu cảm biến dạng CSV.
  • Làm nền tảng cho Part 2 (đọc ảnh BMP) và Part 3 (WiFi gallery).

Yêu cầu phần cứng

  • ESP32-C3 SuperMini (Tenstar Robot)
  • Module microSD SPI (loại đã có ổn áp AMS1117-3.3, sẽ ấp nguồn 5v)
  • Thẻ microSD 8–32GB, format FAT32
  • Dây nối ngắn, nguồn 3.3V ổn định

Sơ đồ nối dây (SPI)

Bạn có thể đổi GPIO theo board bạn đang dùng. Dưới đây là mapping gợi ý (phổ biến) — chỉ cần đúng 4 dây SPI + CS là được.

  • CS → GPIO7
  • SCK → GPIO4
  • MOSI → GPIO6
  • MISO → GPIO5
  • VCC → 3V3
  • GND → GND

Lưu ý quan trọng:

  • Ưu tiên cấp 3.3V cho module SD để ổn định.
  • Nếu SD.begin() fail: giảm tần số SPI (10MHz → 4MHz), dây ngắn lại, kiểm tra chân CS.

Chuẩn bị thẻ nhớ

  • Format FAT32.
  • Tạo sẵn thư mục: /images (để Part 2/3 dùng).

Code convention (áp dụng cho cả series)

  • Feature init: setupFeatureX() trả về bool.
  • Feature loop: runFeatureX().
  • Hằng số pin: PIN_*.
  • Log thống nhất: LOGI(), LOGE().
  • Tránh delay() dài trong loop(); nếu cần thì dùng millis().

Code Part 1 (SD health check)

/*
  Project: Test hoạt động module microSD SPI
  Target : ESP32-C3 SuperMini (Tenstar Robot) + microSD SPI module

  Hardware notes:
  - Module SD trong ảnh có AMS1117-3.3 => có thể cấp VCC = 5V (ổn áp xuống 3.3V).
  - ESP32-C3 I/O là 3.3V. SPI signals vẫn 3.3V là OK.
  - Nếu bạn dùng TFT chung SPI: CS phải tách riêng (SD_CS != TFT_CS).

  Wiring (ví dụ - đổi theo board bạn):
  - SD_CS-> GPIO10
  - SCK  -> GPIO4
  - MISO -> GPIO5
  - MOSI -> GPIO6
  - VCC  -> 5V
  - GND  -> GND
*/
#include <SPI.h>
#include <SD.h>

static const int PIN_SD_CS = 10; // sửa theo dây CS của bạn

void setup() {
  Serial.begin(115200);
  delay(500);

  Serial.println("SD init...");
  if (!SD.begin(PIN_SD_CS, SPI, 4000000)) {
    Serial.println("[ERR ] SD.begin failed");
    while (true) delay(1000);
  }

  Serial.println("[INFO] SD OK");
  Serial.printf("Card size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));

  File f = SD.open("/test.txt", FILE_APPEND);
  if (f) { f.println("hello"); f.close(); }
  f = SD.open("/test.txt", FILE_READ);
  if (f) { while (f.available()) Serial.write(f.read()); f.close(); }
}

void loop() {}

Cách kiểm tra module SD hoạt động

  • Mở Serial Monitor 115200
  • Kỳ vọng thấy:
    • SD OK + Card type + size

Code 2 (SD test read/write)

/*
  Project: Test hoạt động module microSD SPI
  Target : ESP32-C3 SuperMini (Tenstar Robot) + microSD SPI module

  Hardware notes:
  - Module SD trong ảnh có AMS1117-3.3 => cấp VCC = 5V (ổn áp xuống 3.3V) là hợp lý.
  - ESP32-C3 I/O là 3.3V. SPI signals vẫn 3.3V là OK.
  - Nếu dùng chung SPI với TFT: CS phải tách riêng (SD_CS != TFT_CS).

  Wiring (đang dùng):
  - SD_CS-> GPIO10
  - SCK  -> GPIO4
  - MISO -> GPIO5
  - MOSI -> GPIO6
  - VCC  -> 5V
  - GND  -> GND

  Tested baseline:
  - SD.begin(PIN_SD_CS, SPI, 4000000) OK
*/

#include <Arduino.h>
#include <SPI.h>
#include <SD.h>

#define LOGI(fmt, ...) Serial.printf("[INFO] " fmt "\n", ##__VA_ARGS__)
#define LOGE(fmt, ...) Serial.printf("[ERR ] " fmt "\n", ##__VA_ARGS__)

// ===== Config =====
static const int PIN_SD_CS = 10;
static const uint32_t SD_FREQ_HZ = 4000000;

static const char* FILE_TEXT  = "/test.txt";
static const char* FILE_BIN   = "/test.bin";
static const char* DIR_IMAGES = "/images";

// ===== Helpers =====
static const char* cardTypeToStr(uint8_t t) {
  switch (t) {
    case CARD_MMC:  return "MMC";
    case CARD_SD:   return "SDSC";
    case CARD_SDHC: return "SDHC";
    default:        return "UNKNOWN";
  }
}

uint32_t simpleChecksum(const uint8_t* data, size_t len) {
  // checksum đơn giản để verify đọc/ghi nhị phân
  uint32_t s = 0;
  for (size_t i = 0; i < len; i++) s = (s * 131) + data[i];
  return s;
}

// ===== SD functions =====
bool sdInit() {
  // Best practice: CS output + HIGH trước khi init
  pinMode(PIN_SD_CS, OUTPUT);
  digitalWrite(PIN_SD_CS, HIGH);

  LOGI("SD init... CS=%d, freq=%lu", PIN_SD_CS, (unsigned long)SD_FREQ_HZ);
  if (!SD.begin(PIN_SD_CS, SPI, SD_FREQ_HZ)) {
    LOGE("SD.begin failed. Check wiring/CS/power.");
    return false;
  }
  return true;
}

bool sdHealthCheck() {
  uint8_t type = SD.cardType();
  if (type == CARD_NONE) {
    LOGE("No SD card detected.");
    return false;
  }

  uint64_t sizeMB = SD.cardSize() / (1024ULL * 1024ULL);
  LOGI("SD OK. Type=%s, Size=%llu MB", cardTypeToStr(type), sizeMB);
  return true;
}

bool sdEnsureDir(const char* dirPath) {
  if (SD.exists(dirPath)) {
    LOGI("Dir exists: %s", dirPath);
    return true;
  }
  bool ok = SD.mkdir(dirPath);
  LOGI("mkdir %s => %s", dirPath, ok ? "OK" : "FAIL");
  return ok;
}

void sdListDir(const char* dirPath, uint8_t levels = 1) {
  File dir = SD.open(dirPath);
  if (!dir || !dir.isDirectory()) {
    LOGE("Not a directory: %s", dirPath);
    return;
  }

  LOGI("Listing: %s", dirPath);
  File f = dir.openNextFile();
  while (f) {
    if (f.isDirectory()) {
      LOGI("  DIR : %s", f.name());
      if (levels) sdListDir(f.name(), levels - 1);
    } else {
      LOGI("  FILE: %s (%lu bytes)", f.name(), (unsigned long)f.size());
    }
    f = dir.openNextFile();
  }
}

bool sdWriteText(const char* path, const char* text) {
  File f = SD.open(path, FILE_WRITE); // overwrite
  if (!f) {
    LOGE("Open write failed: %s", path);
    return false;
  }
  f.print(text);
  f.close();
  LOGI("Write text OK: %s", path);
  return true;
}

bool sdAppendLine(const char* path, const char* line) {
  File f = SD.open(path, FILE_APPEND);
  if (!f) {
    LOGE("Open append failed: %s", path);
    return false;
  }
  f.println(line);
  f.close();
  LOGI("Append line OK: %s", path);
  return true;
}

bool sdReadText(const char* path) {
  File f = SD.open(path, FILE_READ);
  if (!f) {
    LOGE("Open read failed: %s", path);
    return false;
  }

  LOGI("----- READ %s -----", path);
  while (f.available()) Serial.write(f.read());
  Serial.println();
  LOGI("--------------------");

  f.close();
  return true;
}

bool sdWriteBinary(const char* path, const uint8_t* data, size_t len) {
  File f = SD.open(path, FILE_WRITE); // overwrite
  if (!f) {
    LOGE("Open bin write failed: %s", path);
    return false;
  }
  size_t w = f.write(data, len);
  f.close();

  if (w != len) {
    LOGE("Binary write short. wrote=%u expect=%u", (unsigned)w, (unsigned)len);
    return false;
  }
  LOGI("Write binary OK: %s (%u bytes)", path, (unsigned)len);
  return true;
}

bool sdReadBinary(const char* path, uint8_t* out, size_t len) {
  File f = SD.open(path, FILE_READ);
  if (!f) {
    LOGE("Open bin read failed: %s", path);
    return false;
  }
  size_t r = f.read(out, len);
  f.close();

  if (r != len) {
    LOGE("Binary read short. read=%u expect=%u", (unsigned)r, (unsigned)len);
    return false;
  }
  LOGI("Read binary OK: %s (%u bytes)", path, (unsigned)len);
  return true;
}

// ===== Main test flow =====
void runAllTests() {
  // 1) Text tests
  sdWriteText(FILE_TEXT, "IoTLabs - Test hoạt động module microSD SPI\n");
  sdAppendLine(FILE_TEXT, "Line 1: hello");
  sdAppendLine(FILE_TEXT, "Line 2: read/write OK");
  sdReadText(FILE_TEXT);

  // 2) Directory tests
  sdEnsureDir(DIR_IMAGES);
  sdListDir("/", 2);

  // 3) Binary tests (verify checksum)
  const size_t N = 256;
  uint8_t bufW[N];
  uint8_t bufR[N];

  for (size_t i = 0; i < N; i++) bufW[i] = (uint8_t)(i ^ 0x5A);

  uint32_t c1 = simpleChecksum(bufW, N);
  LOGI("Binary checksum (write): %lu", (unsigned long)c1);

  if (sdWriteBinary(FILE_BIN, bufW, N) && sdReadBinary(FILE_BIN, bufR, N)) {
    uint32_t c2 = simpleChecksum(bufR, N);
    LOGI("Binary checksum (read) : %lu", (unsigned long)c2);

    if (c1 == c2) LOGI("Binary verify: PASS ✅");
    else          LOGE("Binary verify: FAIL ❌");
  }
}

void setup() {
  Serial.begin(115200);
  delay(300);

  LOGI("Project start: Test hoạt động module microSD SPI");

  if (!sdInit() || !sdHealthCheck()) {
    LOGE("Stop due to SD init/health failure.");
    while (true) delay(1000);
  }

  runAllTests();
  LOGI("All tests done.");
}

void loop() {
  // no loop needed for this test
}

Kiểm tra kết quả

  • Mở Serial Monitor 115200
  • Kỳ vọng thấy:
    • File /test.bin được ghi và đọc ra
    • [INFO] Project start: Test hoạt động module microSD SPI (Feature-based)
      [INFO] Init SD… CS=10, freq=4000000
      [INFO] SD OK. Type=SDSC, Size=1944 MB
      [INFO] Binary checksum (write): 4291153024
      [INFO] Text overwrite => OK
      [INFO] Text append #1 => OK
      [INFO] Text append #2 => OK
      [INFO] —– READ /test.txt —–
      IoTLabs – Test hoạt động module microSD SPI
      Line 1: hello
      Line 2: read/write OK

Troubleshooting nhanh

  • SD.begin failed:
    • Đổi chân PIN_SD_CS (hay nhầm nhất)
    • Hạ SD_FREQ xuống 4*1000*1000
    • Cấp 5V ổn định
    • Format lại thẻ FAT32
    • Dây ngắn, tránh jumper dài