一、硬件连接(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 测试步骤
- 硬件检查: 确认SD卡座连接正确,供电稳定
- 格式化: 使用FAT32格式化SD卡
- 编译下载: 编译程序并下载到STM32
- 串口监控: 使用串口助手查看输出信息
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校验