文章目录
前言
本文仅代表个人的记录和理解,不具有权威性,请谨慎参考。
STM32 MCU 读写SD/MicroSD卡最简单和快速的方式是使用SDIO口,但有些系列的STM32 MCU没有SDIO接口必须采用SPI操作SD/MicroSD卡。
一、FatFs是什么?

FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。FatFs 模块是按照 ANSI C (C89) 编写的,并且与磁盘 I/O 层完全分离。因此,它独立于平台。它可以集成到资源有限的小型微控制器中。如果我们需要MCU读写SD/MicroSD卡上的文件如bmp图片,就需要用到FatFs 文件系统。
1.应用界面层

FatFs 为应用程序提供了各种文件系统功能,如下所示:
-
文件访问
f_open - 打开/创建文件
f_close - 关闭打开的文件
f_read - 从文件中读取数据
f_write - 将数据写入文件
f_lseek - 移动读/写指针,扩展大小
f_truncate - 截断文件大小
f_sync - 刷新缓存的数据
f_forward - 将数据转发到流
f_expand - 为文件分配一个连续的块
f_gets - 读取字符串
f_putc - 写一个字符
f_puts - 写一个字符串
f_printf - 编写格式化字符串
f_tell - 获取当前读/写指针
f_eof - 测试文件结束
f_size - 获取尺寸
f_error - 测试错误 -
目录访问
f_opendir - 打开目录
f_closedir - 关闭打开的目录
f_readdir - 读取目录项
f_findfirst - 打开目录并读取匹配的第一个项目
f_findnext - 读取下一个匹配的项目 -
文件和目录管理
f_stat - 检查文件或子目录是否存在
f_unlink - 删除文件或子目录
f_rename - 重命名/移动文件或子目录
f_chmod - 更改文件或子目录的属性
f_utime - 更改文件或子目录的时间戳
f_mkdir - 创建子目录
f_chdir - 更改当前目录
f_chdrive - 更改电流驱动器
f_getcwd - 检索当前目录和驱动器 -
卷管理和系统配置
f_mount - 注册/注销卷的工作区
f_mkfs - 在逻辑驱动器上创建 FAT 卷
f_fdisk - 在物理驱动器上创建分区
f_getfree - 获取卷上的可用空间
f_getlabel - 获取卷标
f_setlabel - 设置卷标
f_setcp - 设置活动代码页
2.媒体访问接口层

由于 FatFs 模块是独立于平台和存储介质的文件系统层,因此它与物理设备(如存储卡、硬盘和任何类型的存储设备)完全分离。存储设备控制模块不是 FatFs 模块的任何部分,需要由实现者提供。FatFs 通过如下所示的简单媒体访问接口控制存储设备。此外,下载中还提供了某些平台的示例实现。此处提供了存储设备控制模块的功能检查器:
- 存储设备控制
disk_status - 获取设备状态
disk_initialize - 初始化设备
disk_read - 读取数据
disk_write - 写入数据
disk_ioctl - 控制设备相关功能 - 实时时钟
get_fattime - 获取当前时间
二、硬件
1.MicroSD卡转接板
为方便把MicroSD卡连接到STM32开发板,使用一个转接板进行转接。转接板接口如下:

2.STM32F103C8T6开发板
开发板上面有一颗STM32F103C8T6 MCU。

3.连接关系
MicroSD卡和STM32F103C8T6的连接关系如下表:
| 序号 | MicroSD卡 | MCU |
|---|---|---|
| 1 | CS | PA4 |
| 2 | SCK | PA5 |
| 3 | MOSI | PA7 |
| 4 | MISO | PA6 |
| 5 | VCC | 3.3V |
| 6 | GND | GND |
三、代码
使用CubeMx生成代码,步骤如下:(注意:这个工程只列出了关键步骤,为节省篇幅一些步骤省略了,如果对CubeMX和KEIIL不太熟悉,请谨慎参考)
1.SPI
使用SPI1控制MicroSD卡,采用软件片选信号。

2.GPIO
为SPI1接口增加片选信号SD_CS。

3.UART
使用USART1作为打印输出接口。

4.FatFs

5.生成代码
生成的MDK代码关于FatFs的文件如下:

为了使用FatFs文件系统通过SPI读写MicroSD卡我们需要完成的工作有两步:
- 编写SPI控制MicroSD卡的代码。
- 给user_diskio.c里的空函数加入实现方法。
7.SPI控制MicroSD卡代码
新建两个文件:fatfs_sd.h和fatfs_sd.c,并将其添加到工程中。代码如下:
fatfs_sd.h
#ifndef __FATFS_SD_H
#define __FATFS_SD_H
#include "stm32l5xx_hal.h"
#include "diskio.h"
/* Definitions for MMC/SDC command */
#define CMD0 (0x40+0) /* GO_IDLE_STATE */
#define CMD1 (0x40+1) /* SEND_OP_COND */
#define CMD8 (0x40+8) /* SEND_IF_COND */
#define CMD9 (0x40+9) /* SEND_CSD */
#define CMD10 (0x40+10) /* SEND_CID */
#define CMD12 (0x40+12) /* STOP_TRANSMISSION */
#define CMD16 (0x40+16) /* SET_BLOCKLEN */
#define CMD17 (0x40+17) /* READ_SINGLE_BLOCK */
#define CMD18 (0x40+18) /* READ_MULTIPLE_BLOCK */
#define CMD23 (0x40+23) /* SET_BLOCK_COUNT */
#define CMD24 (0x40+24) /* WRITE_BLOCK */
#define CMD25 (0x40+25) /* WRITE_MULTIPLE_BLOCK */
#define CMD41 (0x40+41) /* SEND_OP_COND (ACMD) */
#define CMD55 (0x40+55) /* APP_CMD */
#define CMD58 (0x40+58) /* READ_OCR */
/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC 0x01 /* MMC ver 3 */
#define CT_SD1 0x02 /* SD ver 1 */
#define CT_SD2 0x04 /* SD ver 2 */
#define CT_SDC 0x06 /* SD */
#define CT_BLOCK 0x08 /* Block addressing */
#define ACMD41_HCS 0x40000000
#define ACMD41_SDXC_POWER 0x10000000
#define ACMD41_S18R 0x04000000
#define ACMD41_VOLTAGE 0x00ff8000
#define ACMD41_ARG_HC (ACMD41_HCS|ACMD41_SDXC_POWER|ACMD41_VOLTAGE)
#define ACMD41_ARG_SC (ACMD41_VOLTAGE)
/* Functions */
DSTATUS SD_disk_initialize (BYTE pdrv);
DSTATUS SD_disk_status (BYTE pdrv);
DRESULT SD_disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
#define SPI_TIMEOUT 100
extern SPI_HandleTypeDef hspi2;
#define HSPI_SDCARD &hspi2
#define SD_CS_PORT GPIOB
#define SD_CS_PIN GPIO_PIN_11
#endif
fatfs_sd.c
#define TRUE 1
#define FALSE 0
#define bool BYTE
#include "fatfs_sd.h"
#include <stdio.h>
uint16_t Timer1, Timer2; /* 1ms Timer Counter */
static volatile DSTATUS Stat = STA_NOINIT; /* Disk Status */
static uint8_t CardType; /* Type 0:MMC, 1:SDC, 2:Block addressing */
static uint8_t PowerFlag = 0; /* Power flag */
/***************************************
* SPI functions
**************************************/
/* slave select */
static void SELECT(void)
{
HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
}
/* slave deselect */
static void DESELECT(void)
{
HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
HAL_Delay(1);
}
/* SPI transmit a byte */
static void SPI_TxByte(uint8_t data)
{
while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
HAL_SPI_Transmit(HSPI_SDCARD, &data, 1, SPI_TIMEOUT);
}
/* SPI transmit buffer */
static void SPI_TxBuffer(uint8_t *buffer, uint16_t len)
{
while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
HAL_SPI_Transmit(HSPI_SDCARD, buffer, len, SPI_TIMEOUT);
}
/* SPI receive a byte */
static uint8_t SPI_RxByte(void)
{
uint8_t dummy, data;
dummy = 0xFF;
while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
HAL_SPI_TransmitReceive(HSPI_SDCARD, &dummy, &data, 1, SPI_TIMEOUT);
return data;
}
/* SPI receive a byte via pointer */
static void SPI_RxBytePtr

5401

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



