一、硬件连接
1. 引脚映射(以STM32F103为例)
| STM32引脚 | 功能 | SD卡引脚 |
|---|---|---|
| PA4 | CS片选 | CS |
| PA5 | SCK时钟 | CLK |
| PA7 | MOSI主出从入 | DI |
| PA6 | MISO主入从出 | DO |
| 3.3V | 电源 | VDD |
| GND | 地 | GND |
2. 电路设计要点
- 电平匹配:STM32的GPIO为3.3V电平,需确保SD卡模块支持3.3V逻辑电平
- 上拉电阻:MOSI和SCK线需接10kΩ上拉电阻
- 滤波电容:在VDD和GND间并联10μF电解电容+0.1μF陶瓷电容
二、SPI时序模拟实现
1. GPIO初始化
// 硬件SPI引脚配置(HAL库)
void SPI_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用GPIO时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// SCK/MOSI配置(推挽输出)
GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// MISO配置(浮空输入)
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// CS引脚配置(推挽输出)
GPIO_InitStruct.Pin = GPIO_PIN_4;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
2. SPI时序控制函数
// 发送1字节数据(含时钟生成)
uint8_t SPI_WriteByte(uint8_t data) {
uint8_t rx_data = 0;
for(int i=0; i<8; i++) {
// 发送位
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_Delay(1); // 保持时间>30ns
// 产生时钟上升沿
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
HAL_Delay(1); // 时钟高电平维持>30ns
// 采样MISO数据
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET) {
rx_data |= (1 << (7 - i));
}
// 时钟下降沿
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_Delay(1);
data <<= 1;
}
return rx_data;
}
三、SD卡驱动核心代码
1. 初始化流程
#define CMD0 0x40 // 复位命令
#define CMD8 0x48 // 电压检测
#define ACMD41 0x60 // 初始化命令
uint8_t SD_Init(void) {
uint8_t r1;
uint16_t retry;
// 发送74+时钟脉冲
for(retry=0; retry<10; retry++) {
SPI_WriteByte(0xFF);
}
// 进入SPI模式
r1 = SD_SendCommand(CMD0, 0, 0x95);
if(r1 != 0x01) return 1; // 复位失败
// 检测SD卡版本
r1 = SD_SendCommand(CMD8, 0x1AA, 0x87);
if(r1 != 0x01) return 2; // 不支持CMD8
// 初始化SD卡
for(retry=0; retry<200; retry++) {
r1 = SD_SendCommand(ACMD41, 0x40000000, 0x01);
if(r1 == 0x00) break;
HAL_Delay(1);
}
if(retry >= 200) return 3; // 初始化超时
return 0; // 成功
}
2. 命令发送函数
uint8_t SD_SendCommand(uint8_t cmd, uint32_t arg, uint8_t crc) {
uint8_t r1;
// 取消片选
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
// 发送命令
SPI_WriteByte(cmd | 0x40); // 添加起始位
SPI_WriteByte((arg >> 24) & 0xFF);
SPI_WriteByte((arg >> 16) & 0xFF);
SPI_WriteByte((arg >> 8) & 0xFF);
SPI_WriteByte(arg & 0xFF);
SPI_WriteByte(crc);
// 等待响应
for(int i=0; i<1000; i++) {
r1 = SPI_WriteByte(0xFF);
if((r1 & 0x80) == 0) break;
}
return r1;
}
四、数据读写实现
1. 单块读取(CMD17)
uint8_t SD_ReadBlock(uint32_t addr, uint8_t* buffer) {
uint8_t r1;
// 发送读块命令
r1 = SD_SendCommand(CMD17, addr, 0xFF);
if(r1 != 0x00) return 1;
// 等待数据起始令牌
for(int i=0; i<1000; i++) {
r1 = SPI_WriteByte(0xFF);
if(r1 == 0xFE) break;
}
// 读取512字节数据
for(int i=0; i<512; i++) {
buffer[i] = SPI_WriteByte(0xFF);
}
// 读取CRC
SPI_WriteByte(0xFF);
SPI_WriteByte(0xFF);
return 0;
}
2. 单块写入(CMD24)
uint8_t SD_WriteBlock(uint32_t addr, uint8_t* buffer) {
uint8_t r1;
// 发送写块命令
r1 = SD_SendCommand(CMD24, addr, 0xFF);
if(r1 != 0x00) return 1;
// 发送数据起始令牌
SPI_WriteByte(0xFE);
// 写入512字节数据
for(int i=0; i<512; i++) {
SPI_WriteByte(buffer[i]);
}
// 接收CRC
SPI_WriteByte(0xFF);
SPI_WriteByte(0xFF);
return 0;
}
参考代码 stm32单片机,硬件SPI模拟spi协议驱动sd卡 www.youwenfan.com/contentcsj/69803.html
五、调试技巧
1. 逻辑分析仪验证时序
- 捕获SCK、MOSI、MISO信号,检查: 时钟频率:初始化阶段≤400kHz,正常模式≤25MHz 数据有效性:MOSI在时钟上升沿前稳定,MISO在下降沿后采样
2. 常见问题处理
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 初始化超时 | 电源不稳定 | 检查电源滤波,增加去耦电容 |
| 命令无响应 | 片选信号异常 | 检查CS引脚电平,确保低电平有效 |
| 数据校验失败 | 时钟频率过高 | 降低SPI时钟频率(≤10MHz) |
| 写入数据错误 | 未等待写入完成 | 增加写操作后的等待时间 |
六、性能优化
-
DMA传输
使用STM32 DMA直接传输数据,减少CPU负载:
// DMA配置示例(HAL库) DMA_HandleTypeDef hdma_spi1_tx; hdma_spi1_tx.Instance = DMA1_Channel3; hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE; HAL_DMA_Init(&hdma_spi1_tx); -
硬件CRC加速
启用STM32硬件CRC模块:
__HAL_RCC_CRC_CLK_ENABLE(); CRC_HandleTypeDef hcrc; hcrc.Instance = CRC; HAL_CRC_Init(&hcrc);
七、工程结构
SD_Driver/
├── Src/
│ ├── main.c # 主程序
│ ├── spi.c # SPI底层驱动
│ └── sdcard.c # SD卡协议实现
├── Inc/
│ ├── spi.h # SPI函数声明
│ └── sdcard.h # SD卡函数声明
└── Middlewares/
└── FATFS/ # 可选文件系统
八、扩展功能实现
-
FATFS文件系统移植
在标准SPI读写基础上添加文件操作接口:
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode); FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw); -
多卡支持 通过GPIO矩阵切换多个SD卡片选信号,实现多卡轮询访问。
684

被折叠的 条评论
为什么被折叠?



