news 2026/4/23 21:21:16

STM32F103VET6实战:用SPI模式驱动SD卡,手把手移植FatFs文件系统(含完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103VET6实战:用SPI模式驱动SD卡,手把手移植FatFs文件系统(含完整代码)

STM32F103VET6实战:SPI模式驱动SD卡与FatFs文件系统移植全解析

当我们需要在嵌入式系统中实现数据存储功能时,SD卡因其体积小、容量大、价格低廉等优势成为首选。本文将深入探讨如何在STM32F103VET6平台上通过SPI接口驱动SD卡,并完整移植FatFs文件系统。

1. 硬件准备与基础概念

在开始之前,我们需要明确几个关键点。STM32F103VET6是一款基于ARM Cortex-M3内核的微控制器,具有512KB Flash和64KB RAM,完全能够胜任文件系统操作的需求。SPI(Serial Peripheral Interface)是一种同步串行通信接口,相比SDIO模式,SPI实现更简单,对硬件要求更低。

所需硬件清单

  • STM32F103VET6开发板(带TFT屏幕和SD卡槽)
  • 8GB或以下容量的SD卡(建议使用Class4或Class6速度等级)
  • USB转TTL串口模块(用于调试输出)
  • 杜邦线若干

SPI引脚连接参考

SD卡引脚STM32 SPI1引脚功能说明
CSPA4片选信号
DIPA7数据输入
DOPA6数据输出
CLKPA5时钟信号

注意:不同开发板的SD卡槽可能使用不同的SPI接口,请根据实际硬件原理图确认连接方式。

2. FatFs文件系统架构解析

FatFs是一个专为小型嵌入式系统设计的通用FAT文件系统模块,具有以下特点:

  • 完全独立于平台,纯C语言实现
  • 代码占用空间小(根据配置不同约3-10KB)
  • 支持FAT12/FAT16/FAT32/exFAT
  • 支持多卷(物理驱动器和分区)
  • 支持多种编码(包括UTF-8)

FatFs的核心架构分为三个层次:

  1. 应用层:提供文件操作API如f_open、f_read等
  2. 中间层:处理FAT文件系统逻辑
  3. 设备层:与物理存储设备交互的接口

我们需要重点关注的是设备层的移植,主要实现diskio.c中的五个关键函数:

DSTATUS disk_status(BYTE pdrv); DSTATUS disk_initialize(BYTE pdrv); DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);

3. SPI模式下的SD卡驱动实现

SD卡在SPI模式下的通信遵循特定的协议和时序要求。以下是实现过程中的关键点:

3.1 SD卡初始化流程

  1. 硬件初始化
void SD_SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 使能SPI和GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置CS引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // SPI配置 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_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; 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); }
  1. SD卡初始化序列
  • 发送至少74个时钟周期(不选中卡)
  • 发送CMD0(GO_IDLE_STATE)使卡进入SPI模式
  • 发送CMD8(SEND_IF_COND)检查电压范围
  • 发送ACMD41(SD_SEND_OP_COND)初始化卡
  • 发送CMD58(READ_OCR)读取OCR寄存器
  • 发送CMD16(SET_BLOCKLEN)设置块长度(通常为512字节)

提示:SD卡在初始化阶段需要使用低速时钟(通常<400kHz),初始化完成后可切换到高速模式。

3.2 读写操作实现

单块读取函数示例

uint8_t SD_ReadSingleBlock(uint32_t sector, uint8_t* buffer) { uint8_t response; // 发送CMD17(READ_SINGLE_BLOCK) response = SD_SendCommand(CMD17, sector << 9); if(response != 0x00) { return response; } // 等待数据开始标记 while(SD_SPI_ReadByte() != 0xFE); // 读取512字节数据 for(uint16_t i = 0; i < 512; i++) { buffer[i] = SD_SPI_ReadByte(); } // 读取并丢弃2字节CRC SD_SPI_ReadByte(); SD_SPI_ReadByte(); return 0; }

多块写入函数示例

uint8_t SD_WriteMultipleBlocks(uint32_t sector, uint8_t* buffer, uint32_t count) { uint8_t response; // 发送CMD25(WRITE_MULTIPLE_BLOCK) response = SD_SendCommand(CMD25, sector << 9); if(response != 0x00) { return response; } for(uint32_t block = 0; block < count; block++) { // 发送数据开始标记 SD_SPI_WriteByte(0xFC); // 写入512字节数据 for(uint16_t i = 0; i < 512; i++) { SD_SPI_WriteByte(buffer[block * 512 + i]); } // 写入2字节伪CRC SD_SPI_WriteByte(0xFF); SD_SPI_WriteByte(0xFF); // 等待写入完成 while(SD_SPI_ReadByte() != 0xFF); } // 发送停止传输命令 SD_SPI_WriteByte(0xFD); return 0; }

4. FatFs移植关键步骤

4.1 diskio.c接口实现

disk_initialize函数

DSTATUS disk_initialize(BYTE pdrv) { if(pdrv != 0) return STA_NOINIT; static uint8_t initialized = 0; if(initialized) return 0; SD_Init(); initialized = 1; return 0; }

disk_read函数

DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count) { if(pdrv != 0) return RES_PARERR; for(UINT i = 0; i < count; i++) { if(SD_ReadSingleBlock(sector + i, buff + i * 512) != 0) { return RES_ERROR; } } return RES_OK; }

disk_ioctl函数

DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff) { if(pdrv != 0) return RES_PARERR; switch(cmd) { case CTRL_SYNC: return RES_OK; case GET_SECTOR_COUNT: *(DWORD*)buff = SD_GetSectorCount(); return RES_OK; case GET_SECTOR_SIZE: *(WORD*)buff = 512; return RES_OK; case GET_BLOCK_SIZE: *(DWORD*)buff = 1; return RES_OK; default: return RES_PARERR; } }

4.2 ffconf.h配置调整

以下是几个关键配置项:

#define FF_FS_READONLY 0 // 0:启用读写功能 #define FF_FS_MINIMIZE 0 // 0:启用所有功能 #define FF_USE_STRFUNC 1 // 1:启用字符串操作函数 #define FF_USE_MKFS 1 // 1:启用格式化功能 #define FF_USE_FASTSEEK 1 // 1:启用快速定位功能 #define FF_CODE_PAGE 936 // 936:简体中文 #define FF_USE_LFN 1 // 1:启用长文件名支持 #define FF_MAX_SS 512 // 最大扇区大小 #define FF_MIN_SS 512 // 最小扇区大小

5. 应用层开发与调试技巧

5.1 文件操作示例

基本文件操作流程

  1. 挂载文件系统
  2. 打开/创建文件
  3. 读写操作
  4. 关闭文件
  5. 卸载文件系统

示例代码

FATFS fs; FIL fil; UINT bw; // 挂载文件系统 f_mount(&fs, "0:", 1); // 创建并写入文件 f_open(&fil, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE); f_write(&fil, "Hello, STM32!", 13, &bw); f_close(&fil); // 读取文件内容 char buffer[20]; f_open(&fil, "0:/test.txt", FA_READ); f_read(&fil, buffer, sizeof(buffer), &bw); f_close(&fil); // 卸载文件系统 f_mount(NULL, "0:", 0);

5.2 常见问题排查

问题1:SD卡初始化失败

  • 检查硬件连接是否正确
  • 确保在初始化阶段使用低速时钟
  • 确认SD卡格式为FAT32(建议使用SD Formatter工具格式化)

问题2:文件系统挂载失败

  • 检查disk_initialize是否成功
  • 确认ffconf.h中的扇区大小设置与SD卡一致
  • 尝试使用f_mkfs格式化SD卡

问题3:写入速度慢

  • 初始化完成后提高SPI时钟频率
  • 使用多块写入代替单块写入
  • 减少文件系统操作(如f_sync)的频率

6. 性能优化与高级功能

6.1 提高读写速度

通过以下方法可以显著提高SD卡的读写性能:

  1. 提高SPI时钟频率:初始化完成后可提升至最高18MHz(SD卡规范限制)
  2. 使用DMA传输:减少CPU开销
  3. 批量读写操作:减少单次操作的开销
  4. 合理设置缓存:利用文件系统的缓存机制

DMA配置示例

void SD_SPI_DMA_Init(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel3); // SPI1_RX DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)0; // 运行时设置 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 512; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel3, &DMA_InitStructure); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); }

6.2 实现文件系统监控

通过定期检查文件系统状态,可以实现更健壮的存储系统:

FRESULT check_fs_health(void) { FATFS* fs; DWORD free_clust; // 获取空闲簇数量 if(f_getfree("0:", &free_clust, &fs) != FR_OK) { return FR_DISK_ERR; } // 检查文件系统一致性 if(fs->fs_type == 0) { return FR_NO_FILESYSTEM; } return FR_OK; }

在实际项目中,我发现SD卡在频繁断电情况下容易出现文件系统损坏。通过添加适当的异常处理机制和定期维护(如碎片整理),可以显著提高数据存储的可靠性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 21:18:50

GraalVM原生镜像编译:探索Java应用的新编译路径

GraalVM原生镜像编译&#xff1a;探索Java应用的新编译路径 在Java生态系统中&#xff0c;编译与部署一直是开发者关注的重点。传统的Java应用依赖于JVM&#xff08;Java虚拟机&#xff09;来运行&#xff0c;这虽然提供了跨平台的便利性&#xff0c;但也带来了启动延迟和较高的…

作者头像 李华
网站建设 2026/4/23 21:14:19

AI投毒情报预警 | Xinference国产推理框架遭受供应链窃密后门投毒

风险概述 北京时间4月22日16点&#xff0c;悬镜AI安全情报中心在Pypi官方仓库中监测到国产热门开源AI模型推理框架 Xinference 短时间内连续发布2.6.0、2.6.1及2.6.2三个版本更新&#xff0c;并且在这三个新版本框架源码中都检出混淆代码及高风险恶意行为。在混淆恶意代码中发现…

作者头像 李华
网站建设 2026/4/23 21:07:24

FreeRTOS任务通知的“隐藏玩法”:除了替代信号量,还能怎么玩?

FreeRTOS任务通知的进阶实战&#xff1a;解锁嵌入式开发的隐藏潜能 在资源受限的MCU开发中&#xff0c;每个字节和时钟周期都弥足珍贵。FreeRTOS的任务通知机制就像瑞士军刀中的隐藏工具——表面简单&#xff0c;实则蕴含惊人潜力。本文将带您超越官方文档&#xff0c;探索任务…

作者头像 李华