跳转至

SD 卡

库: Arduino SD(随 ESP32 Arduino 核心内置)
访问: SD 全局对象,通过 SPI 总线

M5Cardputer 内置 microSD 卡槽,通过 SPI 访问。可用于存储 WAV 录音、日志文件、配置文件、图片或任何需要持久化存储的数据。


引脚分配

信号 GPIO
SCK 40
MISO 39
MOSI 14
CS 12

这些由 M5Unified 预配置,你只需将 CS 引脚传给 SD.begin()


挂载 / 初始化

#include <SD.h>

void setup() {
    M5Cardputer.begin();

    SPI.begin(40, 39, 14, 12);   // SCK, MISO, MOSI, CS
    if (!SD.begin(12, SPI, 25000000)) {  // CS, SPI总线, 频率 (25 MHz)
        M5Cardputer.Display.println("SD 初始化失败!");
        return;
    }

    uint8_t  cardType = SD.cardType();
    uint64_t cardSize = SD.cardSize() / (1024 * 1024);  // MB

    if (cardType == CARD_NONE) {
        M5Cardputer.Display.println("未检测到 SD 卡");
    } else {
        M5Cardputer.Display.printf("SD 卡: %llu MB\n", cardSize);
    }
}

卡类型常量

常量 含义
CARD_NONE 无卡 / 未知
CARD_MMC MMC 卡
CARD_SD SD 卡
CARD_SDHC SDHC 卡(最常见)

文件操作

所有文件操作使用 fs::FS 抽象层,通过 File 类进行。

打开文件

File file = SD.open("/data.txt");              // 读取模式(默认)
File file = SD.open("/data.txt", FILE_WRITE);  // 写入模式(创建/截断)
File file = SD.open("/data.txt", FILE_APPEND); // 追加模式

读取

// 逐字节读取
while (file.available()) {
    char c = file.read();
    // ...
}

// 读取到缓冲区
uint8_t buf[256];
size_t bytesRead = file.read(buf, sizeof(buf));

// 跳到指定位置
file.seek(44);  // 跳过 WAV 文件头(44 字节)

写入

// 写入文本
file.print("你好,SD 卡!");
file.println(" 第二行");

// 写入二进制
uint8_t data[] = {0x00, 0xFF, 0x55};
file.write(data, sizeof(data));

关闭

file.close();  // 完成操作务必关闭!

文件 / 目录管理

// 检查是否存在
if (SD.exists("/config.json")) { ... }

// 删除
SD.remove("/old_file.txt");

// 重命名
SD.rename("/old.txt", "/new.txt");

// 创建目录
SD.mkdir("/logs");

// 删除目录(必须为空)
SD.rmdir("/empty_dir");

列出目录内容

File dir = SD.open("/");
while (File entry = dir.openNextFile()) {
    M5Cardputer.Display.printf("%s  %u 字节  %s\n",
        entry.name(),
        entry.size(),
        entry.isDirectory() ? "[目录]" : "");
    entry.close();
}
dir.close();

空间信息

uint64_t total = SD.totalBytes();
uint64_t used  = SD.usedBytes();
uint64_t free  = total - used;

M5Cardputer.Display.printf("总容量: %llu MB\n", total / 1024 / 1024);
M5Cardputer.Display.printf("剩余:   %llu MB\n", free / 1024 / 1024);

WAV 录音到 SD 卡

结合 麦克风扬声器 和 SD 卡使用 — 完整示例见:

examples/Basic/mic_wav_record

最小 WAV 写入示例

struct WAVHeader {
    char     riff[4]       = {'R','I','F','F'};
    uint32_t fileSize      = 0;
    char     wave[4]       = {'W','A','V','E'};
    char     fmt[4]        = {'f','m','t',' '};
    uint32_t fmtSize       = 16;
    uint16_t audioFormat   = 1;            // PCM
    uint16_t numChannels   = 1;            // 单声道
    uint32_t sampleRate    = 16000;
    uint32_t byteRate      = 16000 * 2;
    uint16_t blockAlign    = 2;
    uint16_t bitsPerSample = 16;
    char     data[4]       = {'d','a','t','a'};
    uint32_t dataSize      = 0;
};

void saveWAV(const char* path, int16_t* samples, size_t count) {
    File file = SD.open(path, FILE_WRITE);

    WAVHeader header;
    header.fileSize = 36 + count * sizeof(int16_t);
    header.dataSize = count * sizeof(int16_t);

    file.write((uint8_t*)&header, sizeof(WAVHeader));
    file.write((uint8_t*)samples, count * sizeof(int16_t));
    file.close();
}

快速示例:按键日志记录到 SD 卡

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

void setup() {
    M5Cardputer.begin();
    SPI.begin(40, 39, 14, 12);
    SD.begin(12, SPI, 25000000);

    // 创建日志表头(仅一次)
    if (!SD.exists("/log.csv")) {
        File f = SD.open("/log.csv", FILE_WRITE);
        f.println("时间戳,按键");
        f.close();
    }
}

void loop() {
    M5Cardputer.update();

    if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) {
        M5Cardputer.Keyboard.updateKeysState();
        for (char c : M5Cardputer.Keyboard.keysState().word) {
            File f = SD.open("/log.csv", FILE_APPEND);
            f.printf("%lu,%c\n", millis(), c);
            f.close();
        }
    }
}