# STM32F103 SD卡读写程序
下面是一个基于STM32F103系列微控制器的SD卡读写完整程序,使用标准外设库(StdPeriph)和FatFs文件系统。
硬件准备
- STM32F103C8T6开发板(或其他F103系列)
- SD卡模块(SPI接口)
- 连接线缆
硬件连接
SD卡模块 STM32F103
CS -> PA4 (SPI1_NSS)
SCK -> PA5 (SPI1_SCK)
MISO -> PA6 (SPI1_MISO)
MOSI -> PA7 (SPI1_MOSI)
VCC -> 3.3V
GND -> GND
完整代码
1. 主程序 (main.c)
#include "stm32f10x.h"
#include "ff.h"
#include "diskio.h"
#include "spi_sd.h"
#include <stdio.h>
#include <string.h>FATFS fs; /* FatFs文件系统对象 */
FIL fil; /* 文件对象 */
FRESULT res; /* FatFs函数返回结果 */
UINT bw; /* 读写字节数 */void RCC_Configuration(void);
void GPIO_Configuration(void);
void USART1_Init(void);int main(void)
{char buffer[128];/* 系统时钟配置 */RCC_Configuration();/* GPIO配置 */GPIO_Configuration();/* USART1初始化 */USART1_Init();printf("STM32F103 SD Card Test\r\n");/* 挂载文件系统 */if(f_mount(&fs, "0:", 1) != FR_OK){printf("Mount SD Card Failed!\r\n");while(1);}printf("SD Card Mounted Successfully!\r\n");/* 打开/创建文件 */res = f_open(&fil, "0:test.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ);if(res != FR_OK){printf("Open File Failed!\r\n");while(1);}/* 写入数据 */f_lseek(&fil, 0);f_printf(&fil, "Hello, STM32 SD Card!\r\n");f_printf(&fil, "This is a test file.\r\n");/* 读取数据 */f_lseek(&fil, 0);while(f_gets(buffer, sizeof(buffer), &fil)){printf("%s", buffer);}/* 关闭文件 */f_close(&fil);/* 卸载文件系统 */f_mount(NULL, "0:", 1);while(1){}
}void RCC_Configuration(void)
{/* 复位RCC时钟配置 */RCC_DeInit();/* 使能外部高速晶振 */RCC_HSEConfig(RCC_HSE_ON);/* 等待HSE就绪 */if(RCC_WaitForHSEStartUp() == SUCCESS){/* 设置HCLK = SYSCLK */RCC_HCLKConfig(RCC_SYSCLK_Div1);/* 设置PCLK2 = HCLK */RCC_PCLK2Config(RCC_HCLK_Div1);/* 设置PCLK1 = HCLK/2 */RCC_PCLK1Config(RCC_HCLK_Div2);/* 设置FLASH延时 */FLASH_SetLatency(FLASH_Latency_2);FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);/* 设置PLL时钟源为HSE, 9倍频 */RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);/* 使能PLL */RCC_PLLCmd(ENABLE);/* 等待PLL就绪 */while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);/* 设置PLL为系统时钟源 */RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);/* 等待系统时钟源切换完成 */while(RCC_GetSYSCLKSource() != 0x08);}/* 使能SPI1时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);/* 使能GPIOA时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/* 使能USART1时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
}void GPIO_Configuration(void)
{GPIO_InitTypeDef GPIO_InitStructure;/* 配置SPI1引脚: SCK, MISO, MOSI */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);/* 配置SPI1 CS引脚 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS高电平/* 配置USART1 TX引脚 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);/* 配置USART1 RX引脚 */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);
}void USART1_Init(void)
{USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1, ENABLE);
}/* 重定向printf到USART1 */
int fputc(int ch, FILE *f)
{USART_SendData(USART1, (uint8_t)ch);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);return ch;
}
2. SPI和SD卡驱动 (spi_sd.c)
#include "spi_sd.h"
#include "stm32f10x.h"
#include "delay.h"/* SPI1读写一个字节 */
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{/* 等待发送缓冲区空 */while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);/* 发送数据 */SPI_I2S_SendData(SPI1, TxData);/* 等待接收缓冲区非空 */while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);/* 返回接收到的数据 */return SPI_I2S_ReceiveData(SPI1);
}/* SPI1初始化 */
void SPI1_Init(void)
{SPI_InitTypeDef SPI_InitStructure;/* SPI1配置 */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);/* CS高电平 */SD_CS_HIGH();
}/* SD卡初始化 */
uint8_t SD_Initialize(void)
{uint8_t retry = 0;uint8_t i;uint8_t buf[4];uint16_t count = 0;/* 初始化SPI */SPI1_Init();/* 延时至少74个时钟周期 */SD_CS_HIGH();for(i = 0; i < 10; i++){SPI1_ReadWriteByte(0xFF);}/* 发送CMD0进入空闲状态 */while(SD_SendCmd(CMD0, 0, 0x95) != 0x01){retry++;if(retry > 100){return 1; // 超时}}/* 发送CMD8检查SD卡版本 */if(SD_SendCmd(CMD8, 0x1AA, 0x87) == 0x01){/* SD卡v2.0 */for(i = 0; i < 4; i++){buf[i] = SPI1_ReadWriteByte(0xFF);}/* 初始化SD卡 */while(SD_SendCmd(ACMD41, 0x40000000, 0x01) != 0x00){count++;if(count > 0xFFFE){return 2; // 初始化失败}}/* 读取OCR寄存器 */if(SD_SendCmd(CMD58, 0, 0x01) != 0x00){return 3;}for(i = 0; i < 4; i++){buf[i] = SPI1_ReadWriteByte(0xFF);}/* 检查CCS位 */if(buf[0] & 0x40){CardType = SD_TYPE_V2HC; // SDHC/SDXC}else{CardType = SD_TYPE_V2; // SDv2}}else{/* SD卡v1.x或MMC */if(SD_SendCmd(ACMD41, 0, 0x01) <= 1){/* SD卡v1.x */CardType = SD_TYPE_V1;count = 0;/* 等待初始化完成 */while(SD_SendCmd(ACMD41, 0, 0x01) != 0x00){count++;if(count > 0xFFFE){return 4; // 初始化失败}}}else{/* MMC卡 */CardType = SD_TYPE_MMC;count = 0;/* 等待初始化完成 */while(SD_SendCmd(CMD1, 0, 0x01) != 0x00){count++;if(count > 0xFFFE){return 5; // 初始化失败}}}}/* 设置SPI时钟为高速模式 */SPI1_SetSpeed(SPI_BaudRatePrescaler_4);return 0; // 初始化成功
}/* 发送SD卡命令 */
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{uint8_t res;/* 等待SD卡准备就绪 */SD_WaitReady();/* 发送命令 */SD_CS_LOW();SPI1_ReadWriteByte(cmd | 0x40);SPI1_ReadWriteByte(arg >> 24);SPI1_ReadWriteByte(arg >> 16);SPI1_ReadWriteByte(arg >> 8);SPI1_ReadWriteByte(arg);SPI1_ReadWriteByte(crc);/* 等待响应 */if(cmd == CMD12){SPI1_ReadWriteByte(0xFF); // 跳过停止命令后的一个字节}/* 等待响应 */uint8_t retry = 0;do{res = SPI1_ReadWriteByte(0xFF);retry++;} while((res & 0x80) && (retry < 200));return res;
}/* 等待SD卡准备就绪 */
uint8_t SD_WaitReady(void)
{uint32_t timeout = 0;do{if(SPI1_ReadWriteByte(0xFF) == 0xFF){return 0;}timeout++;} while(timeout < 0xFFFFFF);return 1;
}/* 读取SD卡一个扇区 */
uint8_t SD_ReadDisk(uint8_t *buf, uint32_t sector, uint8_t cnt)
{uint8_t res;if(CardType == SD_TYPE_V2HC){sector *= 512; // SDHC/SDXC卡使用字节地址}if(cnt == 1){/* 读取单个块 */res = SD_SendCmd(CMD17, sector, 0x01);if(res == 0){/* 等待数据开始标记 */if(!SD_RecvData(buf, 512)){res = 1;}}}else{/* 读取多个块 */res = SD_SendCmd(CMD18, sector, 0x01);do{if(!SD_RecvData(buf, 512)){res = 1;break;}buf += 512;} while(--cnt);/* 发送停止传输命令 */SD_SendCmd(CMD12, 0, 0x01);}SD_CS_HIGH();SPI1_ReadWriteByte(0xFF);return res;
}/* 接收数据 */
uint8_t SD_RecvData(uint8_t *buf, uint16_t len)
{uint16_t i;uint8_t res;/* 等待数据开始标记 */uint16_t retry = 0;do{res = SPI1_ReadWriteByte(0xFF);retry++;} while((res == 0xFF) && (retry < 0xFFFF));if(res != 0xFE){return 1; // 数据开始标记错误}/* 接收数据 */for(i = 0; i < len; i++){buf[i] = SPI1_ReadWriteByte(0xFF);}/* 接收CRC */SPI1_ReadWriteByte(0xFF);SPI1_ReadWriteByte(0xFF);return 0;
}/* 写入SD卡一个扇区 */
uint8_t SD_WriteDisk(const uint8_t *buf, uint32_t sector, uint8_t cnt)
{uint8_t res;if(CardType == SD_TYPE_V2HC){sector *= 512; // SDHC/SDXC卡使用字节地址}if(cnt == 1){/* 写入单个块 */res = SD_SendCmd(CMD24, sector, 0x01);if(res == 0){/* 发送数据开始标记 */if(!SD_SendData(buf, 0xFE)){res = 1;}}}else{/* 写入多个块 */if(CardType != SD_TYPE_MMC){SD_SendCmd(CMD55, 0, 0x01);SD_SendCmd(CMD23, cnt, 0x01);}res = SD_SendCmd(CMD25, sector, 0x01);if(res == 0){do{if(!SD_SendData(buf, 0xFC)){res = 1;break;}buf += 512;} while(--cnt);/* 发送停止传输标记 */if(!SD_SendData(0, 0xFD)){res = 1;}}}SD_CS_HIGH();SPI1_ReadWriteByte(0xFF);return res;
}/* 发送数据 */
uint8_t SD_SendData(const uint8_t *buf, uint8_t token)
{uint16_t i;uint8_t res;/* 等待SD卡准备就绪 */SD_WaitReady();/* 发送数据开始标记 */SPI1_ReadWriteByte(token);/* 发送数据 */if(token != 0xFD){for(i = 0; i < 512; i++){SPI1_ReadWriteByte(buf[i]);}/* 发送CRC */SPI1_ReadWriteByte(0xFF);SPI1_ReadWriteByte(0xFF);/* 获取数据响应 */res = SPI1_ReadWriteByte(0xFF);if((res & 0x1F) != 0x05){return 1;}}/* 等待SD卡完成写入 */SD_WaitReady();return 0;
}/* 设置SPI速度 */
void SPI1_SetSpeed(uint16_t Speed)
{SPI_Cmd(SPI1, DISABLE);SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = Speed;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);
}
3. 头文件 (spi_sd.h)
#ifndef __SPI_SD_H
#define __SPI_SD_H#include "stm32f10x.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 // 复位SD卡
#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 CMD23 23 // 设置预擦除块数
#define CMD24 24 // 写入单个块
#define CMD25 25 // 写入多个块
#define CMD41 41 // 发送主机容量支持信息
#define CMD55 55 // 应用特定命令
#define CMD58 58 // 读取OCR寄存器
#define CMD59 59 // 设置CRC开/关
#define ACMD41 0x41 // ACMD41/* SD卡片选控制 */
#define SD_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define SD_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)/* 全局变量 */
extern uint8_t CardType;/* 函数声明 */
uint8_t SD_Initialize(void);
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc);
uint8_t SD_WaitReady(void);
uint8_t SD_ReadDisk(uint8_t *buf, uint32_t sector, uint8_t cnt);
uint8_t SD_WriteDisk(const uint8_t *buf, uint32_t sector, uint8_t cnt);
uint8_t SD_RecvData(uint8_t *buf, uint16_t len);
uint8_t SD_SendData(const uint8_t *buf, uint8_t token);
void SPI1_Init(void);
uint8_t SPI1_ReadWriteByte(uint8_t TxData);
void SPI1_SetSpeed(uint16_t Speed);#endif
4. FatFs磁盘接口 (diskio.c)
#include "diskio.h"
#include "spi_sd.h"/* 获取磁盘状态 */
DSTATUS disk_status(BYTE pdrv)
{return 0;
}/* 初始化磁盘 */
DSTATUS disk_initialize(BYTE pdrv)
{if(SD_Initialize() == 0){return 0;}return STA_NOINIT;
}/* 读取扇区 */
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{if(SD_ReadDisk(buff, sector, count) == 0){return RES_OK;}return RES_ERROR;
}/* 写入扇区 */
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{if(SD_WriteDisk(buff, sector, count) == 0){return RES_OK;}return RES_ERROR;
}/* 其他控制命令 */
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{switch(cmd){case CTRL_SYNC:return RES_OK;case GET_SECTOR_COUNT:*(DWORD*)buff = 1024*1024; // 假设SD卡容量为512MBreturn RES_OK;case GET_SECTOR_SIZE:*(WORD*)buff = 512;return RES_OK;case GET_BLOCK_SIZE:*(DWORD*)buff = 8;return RES_OK;default:return RES_PARERR;}
}
5. 头文件 (diskio.h)
#ifndef _DISKIO
#define _DISKIO#include "integer.h"/* 状态码 */
#define STA_NOINIT 0x01 /* 驱动器未初始化 */
#define STA_NODISK 0x02 /* 无介质 */
#define STA_PROTECT 0x04 /* 写保护 *//* 命令码 */
#define CTRL_SYNC 0 /* 完成挂起的写入过程 */
#define GET_SECTOR_COUNT 1 /* 获取扇区数 */
#define GET_SECTOR_SIZE 2 /* 获取扇区大小 */
#define GET_BLOCK_SIZE 3 /* 获取擦除块大小 */
#define CTRL_POWER 4 /* 控制电源状态 */
#define CTRL_LOCK 5 /* 锁定/解锁介质移除 */
#define CTRL_EJECT 6 /* 弹出介质 */
#define MMC_GET_TYPE 10 /* 获取卡类型 */
#define MMC_GET_CSD 11 /* 读取CSD */
#define MMC_GET_CID 12 /* 读取CID */
#define MMC_GET_OCR 13 /* 读取OCR */
#define MMC_GET_SDSTAT 14 /* 读取SD状态 *//* 结果码 */
typedef enum {RES_OK = 0, /* 成功 */RES_ERROR, /* 读写错误 */RES_WRPRT, /* 写保护 */RES_NOTRDY, /* 设备未就绪 */RES_PARERR /* 无效参数 */
} DRESULT;/* 函数声明 */
DSTATUS disk_initialize(BYTE pdrv);
DSTATUS disk_status(BYTE pdrv);
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff);#endif
使用说明
- 将上述代码分别保存到对应的文件中
- 创建一个新的STM32工程,添加这些文件
- 需要包含FatFs文件系统(可以从官网http://elm-chan.org/fsw/ff/00index_e.html下载)
- 编译并下载到STM32F103开发板
- 连接SD卡模块
- 通过串口调试工具(如Putty)查看输出结果
功能说明
这个程序实现了以下功能:
- 初始化SD卡(支持SDv1, SDv2和SDHC卡)
- 挂载FAT文件系统
- 创建/打开文件"test.txt"
- 向文件写入两行文本
- 从文件读取内容并通过串口输出
- 关闭文件并卸载文件系统
注意事项
- 确保SD卡已格式化为FAT16或FAT32文件系统
- 如果使用不同的SPI引脚,需要修改GPIO配置
- SD卡工作电压为3.3V,不要使用5V电压
- 如果读写失败,可以尝试降低SPI时钟速度
希望这个完整的SD卡读写程序对你有帮助!