STM32 读写 SD 卡源码(SPI 模式 + FATFS 文件系统)

一、硬件连接(SPI 模式)

SD卡引脚 STM32F103 引脚 说明
DAT3 (CS) PA4 (SPI1_NSS) 片选
CMD (DI) PA7 (SPI1_MOSI) 主机输出
GND GND
VDD 3.3V 电源
CLK (SCK) PA5 (SPI1_SCK) 时钟
DAT0 (DO) PA6 (SPI1_MISO) 主机输入

二、wenjian结构

复制代码
SDCard_Project/
├── User/
│   ├── main.c
│   ├── sd_spi.c / sd_spi.h
│   ├── fatfs.c / fatfs.h
│   └── ffconf.h
├── Drivers/
│   ├── SPI/
│   │   ├── spi.c / spi.h
│   │   └── spi_lowlevel.c
│   └── GPIO/
│       └── gpio.c
├── FatFs/
│   ├── src/
│   │   ├── diskio.c
│   │   ├── ff.c
│   │   └── ff.h
│   └── option/
│       └── cc936.c
└── CMSIS/
    └── core_cm3.h

三、核心源码实现

3.1 SD卡 SPI 驱动 (sd_spi.h)

c 复制代码
#ifndef __SD_SPI_H
#define __SD_SPI_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>

// SD卡类型定义
#define SD_TYPE_ERR     0x00
#define SD_TYPE_MMC     0x01
#define SD_TYPE_V1      0x02
#define SD_TYPE_V2      0x04
#define SD_TYPE_V2HC    0x06

// SD卡命令定义
#define CMD0    0   // 复位
#define CMD1    1   // 激活
#define CMD8    8   // 发送接口条件
#define CMD9    9   // 读取CSD
#define CMD10   10  // 读取CID
#define CMD12   12  // 停止传输
#define CMD16   16  // 设置块大小
#define CMD17   17  // 读取单块
#define CMD18   18  // 读取多块
#define CMD24   24  // 写入单块
#define CMD25   25  // 写入多块
#define CMD55   55  // 应用特定命令
#define CMD58   58  // 读取OCR
#define ACMD41  41  // 发送操作条件

// SD卡响应类型
typedef enum {
    SD_RESPONSE_NO_ERROR = 0x00,
    SD_RESPONSE_TIMEOUT  = 0xFF,
    SD_RESPONSE_CRC_ERROR = 0xFE
} SD_Response_t;

// SD卡信息结构体
typedef struct {
    uint8_t  cardType;          // 卡类型
    uint32_t blockCount;        // 块数量
    uint32_t blockSize;         // 块大小
    uint32_t capacity;          // 容量(MB)
    uint8_t  cid[16];           // CID寄存器
    uint8_t  csd[16];           // CSD寄存器
} SD_CardInfo_t;

// 函数声明
bool SD_Init(void);
bool SD_ReadBlock(uint8_t *buffer, uint32_t blockAddr);
bool SD_ReadBlocks(uint8_t *buffer, uint32_t blockAddr, uint32_t blockNum);
bool SD_WriteBlock(uint8_t *buffer, uint32_t blockAddr);
bool SD_WriteBlocks(uint8_t *buffer, uint32_t blockAddr, uint32_t blockNum);
bool SD_GetCardInfo(SD_CardInfo_t *cardInfo);
void SD_CS_Enable(void);
void SD_CS_Disable(void);

#endif /* __SD_SPI_H */

3.2 SD卡 SPI 驱动实现 (sd_spi.c)

c 复制代码
#include "sd_spi.h"
#include "spi.h"
#include "delay.h"

static SD_CardInfo_t sd_card_info;

// 发送命令
static uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc) {
    uint8_t response;
    uint8_t retry = 0;
    
    SD_CS_Enable();
    
    // 发送命令
    SPI_ReadWriteByte(0x40 | cmd);
    SPI_ReadWriteByte(arg >> 24);
    SPI_ReadWriteByte(arg >> 16);
    SPI_ReadWriteByte(arg >> 8);
    SPI_ReadWriteByte(arg);
    SPI_ReadWriteByte(crc);
    
    // 等待响应
    do {
        response = SPI_ReadWriteByte(0xFF);
        retry++;
    } while ((response & 0x80) && retry < 200);
    
    return response;
}

// 初始化SD卡
bool SD_Init(void) {
    uint8_t response;
    uint16_t retry = 0;
    uint8_t buf[4];
    
    // 初始化SPI
    SPI_Init();
    
    // 发送至少74个时钟脉冲
    SD_CS_Disable();
    for (retry = 0; retry < 10; retry++) {
        SPI_ReadWriteByte(0xFF);
    }
    
    // 发送CMD0复位
    retry = 0;
    do {
        response = SD_SendCmd(CMD0, 0, 0x95);
        retry++;
        if (retry > 200) {
            return false;  // 复位失败
        }
    } while (response != 0x01);
    
    // 发送CMD8检查版本
    response = SD_SendCmd(CMD8, 0x1AA, 0x87);
    if (response == 0x01) {
        // SD v2.0
        SPI_ReadWriteByte(0xFF);  // 读取R7响应
        SPI_ReadWriteByte(0xFF);
        SPI_ReadWriteByte(0xFF);
        SPI_ReadWriteByte(0xFF);
        
        // 发送ACMD41
        retry = 0;
        do {
            SD_SendCmd(CMD55, 0, 0x01);
            response = SD_SendCmd(ACMD41, 0x40000000, 0x01);
            retry++;
        } while ((response != 0x00) && retry < 1000);
        
        if (response != 0x00) {
            return false;  // 初始化失败
        }
        
        // 读取OCR检查是否为SDHC
        response = SD_SendCmd(CMD58, 0, 0x01);
        if (response == 0x00) {
            buf[0] = SPI_ReadWriteByte(0xFF);
            buf[1] = SPI_ReadWriteByte(0xFF);
            buf[2] = SPI_ReadWriteByte(0xFF);
            buf[3] = SPI_ReadWriteByte(0xFF);
            
            if (buf[0] & 0x40) {
                sd_card_info.cardType = SD_TYPE_V2HC;
            } else {
                sd_card_info.cardType = SD_TYPE_V2;
            }
        }
    } else {
        // SD v1.x or MMC
        SD_SendCmd(CMD55, 0, 0x65);
        response = SD_SendCmd(ACMD41, 0, 0x65);
        
        if (response <= 0x01) {
            sd_card_info.cardType = SD_TYPE_V1;
            retry = 0;
            do {
                SD_SendCmd(CMD55, 0, 0x01);
                response = SD_SendCmd(ACMD41, 0, 0x01);
                retry++;
            } while ((response != 0x00) && retry < 1000);
        } else {
            sd_card_info.cardType = SD_TYPE_MMC;
            retry = 0;
            do {
                response = SD_SendCmd(CMD1, 0, 0x01);
                retry++;
            } while ((response != 0x00) && retry < 1000);
        }
        
        if (response != 0x00) {
            return false;
        }
    }
    
    // 设置块大小为512字节
    response = SD_SendCmd(CMD16, 512, 0x01);
    if (response != 0x00) {
        return false;
    }
    
    SD_CS_Disable();
    SPI_ReadWriteByte(0xFF);
    
    return true;
}

// 读取单块数据
bool SD_ReadBlock(uint8_t *buffer, uint32_t blockAddr) {
    uint8_t response;
    uint16_t retry = 0;
    
    // 转换地址(SDHC使用块地址,SDSC使用字节地址)
    if (sd_card_info.cardType != SD_TYPE_V2HC) {
        blockAddr *= 512;
    }
    
    // 发送读命令
    response = SD_SendCmd(CMD17, blockAddr, 0x01);
    if (response != 0x00) {
        return false;
    }
    
    SD_CS_Enable();
    
    // 等待数据起始令牌
    retry = 0;
    do {
        response = SPI_ReadWriteByte(0xFF);
        retry++;
    } while ((response != 0xFE) && retry < 65535);
    
    if (retry >= 65535) {
        SD_CS_Disable();
        return false;
    }
    
    // 读取512字节数据
    for (uint16_t i = 0; i < 512; i++) {
        buffer[i] = SPI_ReadWriteByte(0xFF);
    }
    
    // 读取CRC
    SPI_ReadWriteByte(0xFF);
    SPI_ReadWriteByte(0xFF);
    
    SD_CS_Disable();
    SPI_ReadWriteByte(0xFF);
    
    return true;
}

// 写入单块数据
bool SD_WriteBlock(uint8_t *buffer, uint32_t blockAddr) {
    uint8_t response;
    uint16_t retry = 0;
    
    // 转换地址
    if (sd_card_info.cardType != SD_TYPE_V2HC) {
        blockAddr *= 512;
    }
    
    // 发送写命令
    response = SD_SendCmd(CMD24, blockAddr, 0x01);
    if (response != 0x00) {
        return false;
    }
    
    SD_CS_Enable();
    
    // 发送数据起始令牌
    SPI_ReadWriteByte(0xFE);
    
    // 写入512字节数据
    for (uint16_t i = 0; i < 512; i++) {
        SPI_ReadWriteByte(buffer[i]);
    }
    
    // 写入CRC
    SPI_ReadWriteByte(0xFF);
    SPI_ReadWriteByte(0xFF);
    
    // 等待写入完成
    response = SPI_ReadWriteByte(0xFF);
    while ((response & 0x1F) != 0x05) {
        response = SPI_ReadWriteByte(0xFF);
        retry++;
        if (retry > 65535) {
            SD_CS_Disable();
            return false;
        }
    }
    
    SD_CS_Disable();
    SPI_ReadWriteByte(0xFF);
    
    return true;
}

// 读取多块数据
bool SD_ReadBlocks(uint8_t *buffer, uint32_t blockAddr, uint32_t blockNum) {
    for (uint32_t i = 0; i < blockNum; i++) {
        if (!SD_ReadBlock(buffer + i * 512, blockAddr + i)) {
            return false;
        }
    }
    return true;
}

// 写入多块数据
bool SD_WriteBlocks(uint8_t *buffer, uint32_t blockAddr, uint32_t blockNum) {
    for (uint32_t i = 0; i < blockNum; i++) {
        if (!SD_WriteBlock(buffer + i * 512, blockAddr + i)) {
            return false;
        }
    }
    return true;
}

// 片选控制
void SD_CS_Enable(void) {
    GPIO_ResetBits(GPIOA, GPIO_Pin_4);
}

void SD_CS_Disable(void) {
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
}

3.3 SPI 底层驱动 (spi.c)

c 复制代码
#include "spi.h"
#include "gpio.h"

SPI_HandleTypeDef hspi1;

void SPI_Init(void) {
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 低速初始化
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;
    
    if (HAL_SPI_Init(&hspi1) != HAL_OK) {
        Error_Handler();
    }
    
    // 提高SPI速度
    __HAL_SPI_DISABLE(&hspi1);
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 18MHz
    __HAL_SPI_ENABLE(&hspi1);
}

uint8_t SPI_ReadWriteByte(uint8_t data) {
    uint8_t rxData;
    HAL_SPI_TransmitReceive(&hspi1, &data, &rxData, 1, 100);
    return rxData;
}

3.4 FatFs 文件系统移植 (diskio.c)

c 复制代码
#include "diskio.h"
#include "sd_spi.h"

// FatFs磁盘状态
static volatile DSTATUS Stat = STA_NOINIT;

// 初始化磁盘
DSTATUS disk_initialize(BYTE pdrv) {
    if (pdrv != 0) return STA_NOINIT;
    
    if (SD_Init()) {
        Stat &= ~STA_NOINIT;
        return RES_OK;
    }
    
    return STA_NOINIT;
}

// 获取磁盘状态
DSTATUS disk_status(BYTE pdrv) {
    return Stat;
}

// 读取扇区
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) {
    if (pdrv != 0 || count == 0) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    
    if (SD_ReadBlocks(buff, sector, count)) {
        return RES_OK;
    }
    
    return RES_ERROR;
}

// 写入扇区
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) {
    if (pdrv != 0 || count == 0) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    
    if (SD_WriteBlocks((uint8_t*)buff, sector, count)) {
        return RES_OK;
    }
    
    return RES_ERROR;
}

// 控制函数
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
    DRESULT res = RES_ERROR;
    SD_CardInfo_t cardInfo;
    
    if (pdrv != 0) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    
    switch (cmd) {
        case CTRL_SYNC:
            res = RES_OK;
            break;
            
        case GET_SECTOR_COUNT:
            SD_GetCardInfo(&cardInfo);
            *(DWORD*)buff = cardInfo.blockCount;
            res = RES_OK;
            break;
            
        case GET_SECTOR_SIZE:
            *(WORD*)buff = 512;
            res = RES_OK;
            break;
            
        case GET_BLOCK_SIZE:
            *(DWORD*)buff = 1;
            res = RES_OK;
            break;
            
        default:
            res = RES_PARERR;
            break;
    }
    
    return res;
}

// 获取时间(可选)
DWORD get_fattime(void) {
    return ((2024UL - 1980) << 25) | // Year
           (6UL << 21) |             // Month
           (15UL << 16) |            // Day
           (12UL << 11) |            // Hour
           (0UL << 5) |              // Minute
           0UL;                      // Second
}

3.5 FatFs 配置文件 (ffconf.h)

c 复制代码
#ifndef _FFCONF_H_
#define _FFCONF_H_

// 功能配置
#define FF_FS_READONLY    0  // 只读模式
#define FF_FS_MINIMIZE    0  // 最小化代码大小
#define FF_USE_STRFUNC    1  // 字符串函数
#define FF_USE_FIND       1  // 查找功能
#define FF_USE_MKFS       1  // 格式化功能
#define FF_USE_FASTSEEK   1  // 快速寻址
#define FF_USE_EXPAND     1  // 扩展功能
#define FF_USE_CHMOD      1  // 权限管理
#define FF_USE_LABEL      1  // 卷标功能
#define FF_USE_FORWARD    1  // 前向功能

// 命名空间和本地化
#define FF_CODE_PAGE      936  // 简体中文
#define FF_USE_LFN        1    // 长文件名支持
#define FF_MAX_LFN        255  // 最大文件名长度
#define FF_LFN_UNICODE    0    // Unicode支持

// 卷/驱动器配置
#define FF_VOLUMES        1    // 卷数量
#define FF_STR_VOLUME_ID  0    // 字符串卷ID
#define FF_VOLUME_STRS    "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"

// 系统配置
#define FF_MAX_SS         512  // 最大扇区大小
#define FF_MIN_SS         512  // 最小扇区大小
#define FF_MULTI_PARTITION 0    // 多分区支持
#define FF_PROC_DIR       1    // 进程目录

#endif /* _FFCONF_H_ */

3.6 主程序示例 (main.c)

c 复制代码
#include "stm32f10x.h"
#include "sd_spi.h"
#include "fatfs.h"
#include "usart.h"
#include "delay.h"
#include <stdio.h>
#include <string.h>

FATFS fs;           // 文件系统对象
FIL file;           // 文件对象
FRESULT res;        // 结果代码
UINT bw;            // 写入字节数

int main(void) {
    uint8_t buffer[512];
    char filename[] = "test.txt";
    char writeData[] = "Hello STM32 SD Card!\r\n";
    
    // 系统初始化
    System_Init();
    Delay_Init();
    USART_Init(115200);
    
    printf("STM32 SD Card Test\r\n");
    
    // 初始化SD卡
    if (SD_Init()) {
        printf("SD Card Initialized Successfully!\r\n");
        
        // 挂载文件系统
        res = f_mount(&fs, "", 1);
        if (res == FR_OK) {
            printf("File System Mounted!\r\n");
            
            // 创建/打开文件
            res = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);
            if (res == FR_OK) {
                printf("File Created: %s\r\n", filename);
                
                // 写入数据
                res = f_write(&file, writeData, strlen(writeData), &bw);
                if (res == FR_OK) {
                    printf("Data Written: %d bytes\r\n", bw);
                }
                
                // 关闭文件
                f_close(&file);
            }
            
            // 读取文件
            res = f_open(&file, filename, FA_READ);
            if (res == FR_OK) {
                printf("Reading File...\r\n");
                res = f_read(&file, buffer, sizeof(buffer), &bw);
                if (res == FR_OK) {
                    printf("File Content: %s\r\n", buffer);
                }
                f_close(&file);
            }
            
            // 卸载文件系统
            f_unmount("");
        } else {
            printf("Mount Failed! Error: %d\r\n", res);
        }
    } else {
        printf("SD Card Init Failed!\r\n");
    }
    
    while (1) {
        // 主循环
        Delay_ms(1000);
    }
}

参考代码 单片机STM32读写SD卡的程序源码 www.youwenfan.com/contentcsu/60516.html

四、编译与测试

4.1 编译环境

  • IDE: Keil MDK-ARM 或 STM32CubeIDE
  • 编译器: ARM GCC
  • 调试器: ST-Link V2

4.2 测试步骤

  1. 硬件检查: 确认SD卡座连接正确,供电稳定
  2. 格式化: 使用FAT32格式化SD卡
  3. 编译下载: 编译程序并下载到STM32
  4. 串口监控: 使用串口助手查看输出信息

4.3 常见问题解决

问题 原因 解决方案
初始化失败 SPI速度过快 降低SPI时钟频率
读写错误 供电不足 检查3.3V电源稳定性
文件无法创建 文件系统损坏 重新格式化SD卡
数据丢失 写操作未完成 确保f_close()被调用

五、优化

5.1 速度优化

c 复制代码
// 使用DMA传输
HAL_SPI_TransmitReceive_DMA(&hspi1, txBuf, rxBuf, 512);

// 多块读写
SD_ReadBlocks(buffer, startBlock, blockCount);
SD_WriteBlocks(buffer, startBlock, blockCount);

5.2 功能扩展

  • 多分区支持: 配置FF_MULTI_PARTITION
  • 长文件名: 启用FF_USE_LFN
  • 文件加密: 添加AES加密功能
  • 数据校验: 添加CRC32校验
相关推荐
Quinn271 小时前
正点原子 STM32MP257 修复异核 FreeRTOS+OpenAMP 例程里 SysTick 延时异常的问题
stm32·嵌入式硬件·正点原子·arm linux
Deitymoon1 小时前
STM32——OLED显示图片
stm32·单片机·嵌入式硬件
深圳英康仕1 小时前
龙芯2K3000嵌入式工控机的技术拆解:算力、接口与国产系统适配
嵌入式硬件·工控机·工业计算机·国产工控机·龙芯2k3000
山木嵌入式2 小时前
STM32 UART串口通信协议与3种底层驱动实现(寄存器/标准库/HAL库)
stm32·单片机·串口·uart
Heartache boy2 小时前
野火STM32_HAL库版课程笔记-I2C介绍
笔记·stm32·单片机
Deitymoon2 小时前
STM32——OLED显示汉字
stm32·单片机·嵌入式硬件
狮驼岭的小钻风2 小时前
单片机启动流程与 .s 文件详解
单片机·嵌入式硬件
金色光环2 小时前
【DSP学习笔记】 F28335中断系统理解-基于普中DSP28335开发攻略
笔记·单片机·学习·dsp开发
iCxhust2 小时前
8086/8088单板机VSCode集中环境开发编译(第二版整理)
ide·vscode·嵌入式硬件·编辑器·嵌入式·微机原理·8086最小系统