概要

SD 卡作为一种通用的大容量存储介质,在嵌入式系统中被广泛应用于数据日志、固件存储、多媒体文件管理等场景。多数 SD 卡模块支持 SPI 模式通信,通过简单的 4 线接口即可与 STM32、Arduino 等主控芯片实现高速数据交互,相比 SDIO 模式具有硬件设计简单、兼容性强的优势。

一、SD 卡 SPI 模式硬件架构

1. 基本连接方式

SD 卡与主控芯片的 SPI 模式连接仅需 4 根信号线,典型电路如下:
在这里插入图片描述

关键连接注意事项:

SD 卡工作电压为 3.3V,需使用电平转换电路(如 74LVC245)连接 5V 主控芯片
信号线建议串联 100Ω 限流电阻,保护 SD 卡接口
电源端需添加 100nF 和 10μF 电容滤波,减少电源噪声
布线应短且直,尤其在高速通信时(>10MHz)

2. 支持的 SD 卡类型

SPI 模式兼容多种 SD 卡标准,但需注意不同类型的差异:

SD 卡:容量≤2GB,支持 SPI 模式
SDHC 卡:容量 2GB~32GB,采用 FAT32 文件系统,支持 SPI 模式
SDXC 卡:容量 32GB~2TB,采用 exFAT 文件系统,部分模块可能不支持
microSD 卡:通过适配器可与标准 SD 卡接口兼容,是嵌入式系统首选

二、SD 卡 SPI 模式通信协议

1. 通信基础

SD 卡 SPI 模式采用主从同步通信方式,具有以下特点:

由主控芯片产生 SCK 时钟信号,频率可在 100kHz~25MHz 范围内调整(初始化阶段需≤400kHz)
数据在 SCK 的上升沿从主控发送到 SD 卡,在 SCK 的下降沿从 SD 卡接收数据
所有命令和数据传输均以 CS 线拉低为前提,CS 线拉高时 SD 卡忽略 SPI 总线上的所有信号

2. 命令格式

SD 卡通过特定命令集进行操作,SPI 模式下的命令格式如下:
在这里插入图片描述
常用基础命令:

CMD0:复位卡,进入 IDLE 状态(0x40 00 00 00 00 95)
CMD1:初始化卡,检查卡是否准备好(0x41 00 00 00 00 FF)
CMD8:发送接口条件,用于 SDHC 卡识别(0x48 00 00 01 AA 87)
CMD16:设置块长度(默认 512 字节)(0x50 00 00 02 00 FF)
CMD17:读单块数据(0x51 + 32 位地址 + CRC)
CMD24:写单块数据(0x58 + 32 位地址 + CRC)
CMD55:应用命令前缀(0x77 00 00 00 00 FF)
ACMD41:SD 卡初始化(需先发送 CMD55)

3. 初始化流程

SD 卡在使用前必须经过初始化流程,特别是 SPI 模式:

三、数据读写操作实现

1. 单块读取操作

读取 SD 卡数据以块(默认 512 字节)为单位,流程如下:

// 从SD卡指定地址读取一个块(512字节)
// addr: 块地址(SDHC为扇区地址,单位512字节)
// buf: 接收缓冲区(需至少512字节)
// 返回值: 0=成功,非0=失败
uint8_t sd_read_block(uint32_t addr, uint8_t *buf) {
    uint8_t response;
    uint16_t timeout;
    
    // 1. 拉低CS,选中SD卡
    SD_CS_LOW();
    
    // 2. 发送读块命令CMD17 (0x51)
    spi_send_byte(0x51);                     // 命令字节
    spi_send_byte((addr >> 24) & 0xFF);      // 地址高8位
    spi_send_byte((addr >> 16) & 0xFF);      // 地址中8位
    spi_send_byte((addr >> 8) & 0xFF);       // 地址低8位
    spi_send_byte(addr & 0xFF);
    spi_send_byte(0xFF);                     // CRC(SPI模式可忽略)
    
    // 3. 等待响应(0x00表示成功)
    timeout = 0;
    do {
        response = spi_receive_byte();
        if (timeout++ > 0xFFFF) {
            SD_CS_HIGH();
            return 1; // 超时错误
        }
    } while (response != 0x00);
    
    // 4. 等待数据起始令牌(0xFE)
    timeout = 0;
    do {
        response = spi_receive_byte();
        if (timeout++ > 0xFFFF) {
            SD_CS_HIGH();
            return 2; // 无数据令牌错误
        }
    } while (response != 0xFE);
    
    // 5. 读取512字节数据
    for (uint16_t i = 0; i < 512; i++) {
        buf[i] = spi_receive_byte();
    }
    
    // 6. 读取2字节CRC(SPI模式可忽略)
    spi_receive_byte();
    spi_receive_byte();
    
    // 7. 拉高CS,结束读取
    SD_CS_HIGH();
    
    return 0; // 成功
}

2. 单块写入操作

写入操作同样以块为单位,且需要严格遵循时序:

// 向SD卡指定地址写入一个块(512字节)
// addr: 块地址
// buf: 待写入数据(必须512字节)
// 返回值: 0=成功,非0=失败
uint8_t sd_write_block(uint32_t addr, const uint8_t *buf) {
    uint8_t response;
    uint16_t timeout;
    
    // 1. 拉低CS,选中SD卡
    SD_CS_LOW();
    
    // 2. 发送写块命令CMD24 (0x58)
    spi_send_byte(0x58);                     // 命令字节
    spi_send_byte((addr >> 24) & 0xFF);      // 地址高8位
    spi_send_byte((addr >> 16) & 0xFF);      // 地址中8位
    spi_send_byte((addr >> 8) & 0xFF);       // 地址低8位
    spi_send_byte(addr & 0xFF);
    spi_send_byte(0xFF);                     // CRC
    
    // 3. 等待响应(0x00表示成功)
    timeout = 0;
    do {
        response = spi_receive_byte();
        if (timeout++ > 0xFFFF) {
            SD_CS_HIGH();
            return 1; // 超时错误
        }
    } while (response != 0x00);
    
    // 4. 发送数据起始令牌(0xFE)
    spi_send_byte(0xFE);
    
    // 5. 发送512字节数据
    for (uint16_t i = 0; i < 512; i++) {
        spi_send_byte(buf[i]);
    }
    
    // 6. 发送2字节伪CRC(SPI模式可忽略)
    spi_send_byte(0xFF);
    spi_send_byte(0xFF);
    
    // 7. 检查数据响应
    response = spi_receive_byte();
    if ((response & 0x1F) != 0x05) { // 0x05表示数据接受成功
        SD_CS_HIGH();
        return 2; // 写入失败
    }
    
    // 8. 等待写入完成
    timeout = 0;
    while (spi_receive_byte() != 0xFF) {
        if (timeout++ > 0xFFFF) {
            SD_CS_HIGH();
            return 3; // 写入超时
        }
    }
    
    // 9. 拉高CS,结束写入
    SD_CS_HIGH();
    
    return 0; // 成功
}

3. 多块读写操作

对于大数据量传输,可使用多块读写命令提高效率:

多块读:使用 CMD18 命令,持续接收数据直到发送 CMD12 停止命令
多块写:使用 CMD25 命令,发送数据直到发送停止令牌 (0xFD)

四、文件系统与上层应用

SD 卡通常采用 FAT 文件系统(FAT16/FAT32/exFAT),嵌入式系统中可通过以下库简化开发:

1. 常用文件系统库

FatFs:开源的 FAT 文件系统模块,支持 SPI 模式 SD 卡,体积小、移植性好
SD 库:Arduino 平台专用,封装了底层 SPI 通信和 FatFs 文件系统
STM32Cube 库:ST 官方提供的 SD 卡驱动,支持 SPI 和 SDIO 模式

2. 文件操作示例(基于 FatFs)

#include "ff.h"
#include "diskio.h"

FATFS fs;         // 文件系统对象
FIL file;         // 文件对象
FRESULT fr;       // 操作结果
UINT bw, br;      // 读写字节数

// 初始化文件系统并创建文件
void sd_file_demo(void) {
    // 1. 挂载文件系统
    fr = f_mount(&fs, "", 1);
    if (fr != FR_OK) {
        // 挂载失败处理
        return;
    }
    
    // 2. 创建并打开文件(如果不存在则创建)
    fr = f_open(&file, "data.log", FA_WRITE | FA_CREATE_ALWAYS);
    if (fr != FR_OK) {
        // 打开失败处理
        f_mount(NULL, "", 1);
        return;
    }
    
    // 3. 向文件写入数据
    const char *text = "Hello, SD Card!";
    fr = f_write(&file, text, strlen(text), &bw);
    if (fr != FR_OK || bw != strlen(text)) {
        // 写入失败处理
    }
    
    // 4. 关闭文件
    f_close(&file);
    
    // 5. 打开文件读取内容
    fr = f_open(&file, "data.log", FA_READ);
    if (fr == FR_OK) {
        char buffer[100];
        fr = f_read(&file, buffer, sizeof(buffer)-1, &br);
        if (fr == FR_OK) {
            buffer[br] = '\0'; // 添加字符串结束符
            // 处理读取到的数据
        }
        f_close(&file);
    }
    
    // 6. 卸载文件系统
    f_mount(NULL, "", 1);
}

五、性能优化与可靠性设计

1. 速率优化

初始化完成后可提高 SPI 时钟频率(最高 25MHz),提高传输速度
使用 DMA 方式传输数据,减少 CPU 占用(尤其在 STM32 等高端 MCU 上)
采用多块读写命令,减少命令交互开销

2. 可靠性保障

电源管理:确保 3.3V 电源稳定,波动不超过 ±0.3V
数据校验:关键数据可添加校验和,检测传输错误
异常处理:实现命令超时、卡拔出检测和自动重连机制
磨损均衡:避免频繁写入同一区块,延长 SD 卡使用寿命

3. 低功耗设计

空闲时通过 CMD50 命令使 SD 卡进入休眠模式
降低 SPI 时钟频率或关闭 SPI 外设,减少功耗
采用中断方式而非轮询,减少 CPU 活动时间

六、常见问题与解决方案

在这里插入图片描述

总结

SD 卡的 SPI 模式通信为嵌入式系统提供了便捷的大容量存储解决方案,其核心优势在于:

硬件接口简单,仅需 4 根线即可实现通信
兼容性强,支持多种 SD 卡类型和主控芯片
传输速度适中,满足大多数嵌入式应用需求
配合文件系统库,可实现复杂的文件管理功能

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐