STM32系列文章( FatFs文件系统——使用SPI读写MicroSD卡)
介绍STM32通过SPI口读取SD卡上的FatFs文件系统
文章目录
前言
本文仅代表个人的记录和理解,不具有权威性,请谨慎参考。
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(uint8_t *buff)
{
*buff = SPI_RxByte();
}
/***************************************
* SD functions
**************************************/
/* wait SD ready */
static uint8_t SD_ReadyWait(void)
{
uint8_t res;
/* timeout 500ms */
Timer2 = 500;
/* if SD goes ready, receives 0xFF */
do {
res = SPI_RxByte();
} while ((res != 0xFF) && Timer2);
return res;
}
/* power on */
static void SD_PowerOn(void)
{
uint8_t args[6];
uint32_t cnt = 0x1FFF;
/* transmit bytes to wake up */
DESELECT();
for(int i = 0; i < 10; i++)
{
SPI_TxByte(0xFF);
}
/* slave select */
SELECT();
/* make idle state */
args[0] = CMD0; /* CMD0:GO_IDLE_STATE */
args[1] = 0;
args[2] = 0;
args[3] = 0;
args[4] = 0;
args[5] = 0x95; /* CRC */
SPI_TxBuffer(args, sizeof(args));
/* wait response */
while ((SPI_RxByte() != 0x01) && cnt)
{
cnt--;
}
DESELECT();
SPI_TxByte(0XFF);
PowerFlag = 1;
}
/* power off */
static void SD_PowerOff(void)
{
PowerFlag = 0;
}
/* check power flag */
static uint8_t SD_CheckPower(void)
{
return PowerFlag;
}
/* receive data block */
static bool SD_RxDataBlock(BYTE *buff, UINT len)
{
uint8_t token;
/* timeout 200ms */
Timer1 = 200;
/* loop until receive a response or timeout */
do {
token = SPI_RxByte();
} while((token == 0xFF) && Timer1);
/* invalid response */
if(token != 0xFE) return FALSE;
/* receive data */
do {
SPI_RxBytePtr(buff++);
} while(len--);
/* discard CRC */
SPI_RxByte();
SPI_RxByte();
return TRUE;
}
/* transmit data block */
#if _USE_WRITE == 1
static bool SD_TxDataBlock(const uint8_t *buff, BYTE token)
{
uint8_t resp;
uint8_t i = 0;
/* wait SD ready */
if (SD_ReadyWait() != 0xFF) return FALSE;
/* transmit token */
SPI_TxByte(token);
/* if it's not STOP token, transmit data */
if (token != 0xFD)
{
SPI_TxBuffer((uint8_t*)buff, 512);
/* discard CRC */
SPI_RxByte();
SPI_RxByte();
/* receive response */
while (i <= 64)
{
resp = SPI_RxByte();
/* transmit 0x05 accepted */
if ((resp & 0x1F) == 0x05) break;
i++;
}
/* recv buffer clear */
while (SPI_RxByte() == 0);
}
/* transmit 0x05 accepted */
if ((resp & 0x1F) == 0x05) return TRUE;
return FALSE;
}
#endif /* _USE_WRITE */
/* transmit command */
static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
{
uint8_t crc, res;
/* wait SD ready */
if (SD_ReadyWait() != 0xFF) return 0xFF;
/* transmit command */
SPI_TxByte(cmd); /* Command */
SPI_TxByte((uint8_t)(arg >> 24)); /* Argument[31..24] */
SPI_TxByte((uint8_t)(arg >> 16)); /* Argument[23..16] */
SPI_TxByte((uint8_t)(arg >> 8)); /* Argument[15..8] */
SPI_TxByte((uint8_t)arg); /* Argument[7..0] */
/* prepare CRC */
if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
else if(cmd == CMD8) crc = 0x87; /* CRC for CMD8(0x1AA) */
else crc = 1;
/* transmit CRC */
SPI_TxByte(crc);
/* Skip a stuff byte when STOP_TRANSMISSION */
if (cmd == CMD12) SPI_RxByte();
/* receive response */
uint8_t n = 10;
do {
res = SPI_RxByte();
} while ((res & 0x80) && --n);
return res;
}
/***************************************
* user_diskio.c functions
**************************************/
/* initialize SD */
DSTATUS SD_disk_initialize(BYTE drv)
{
uint8_t n, type, ocr[4], t1, t2, f1 = 0x00;
/* single drive, drv should be 0 */
if(drv) return STA_NOINIT;
/* no disk */
if(Stat & STA_NODISK) return Stat;
/* power on */
SD_PowerOn();
/* slave select */
SELECT();
/* check disk type */
type = 0;
/* send GO_IDLE_STATE command */
printf("\r\nCMD0\r\n");
if (SD_SendCmd(CMD0, 0) == 1)
{
/* SDC V2+ accept CMD8 command, http://elm-chan.org/docs/mmc/mmc_e.html */
printf("CMD8... ");
if (SD_SendCmd(CMD8, 0x1AA) == 1)
{
printf("succeeded, SDC V2+\r\n");
/* operation condition register */
for (n = 0; n < 4; n++)
{
ocr[n] = SPI_RxByte();
}
/* voltage range 2.7-3.6V */
if (ocr[2] == 0x01 && ocr[3] == 0xAA)
{
printf("ACMD41 ACMD41_HCS.. ");
/* timeout 1 sec */
Timer1 = 1000;
/* ACMD41 with HCS bit */
do {
t1 = SD_SendCmd(CMD55, 0);
t2 = SD_SendCmd(CMD41, ACMD41_HCS);
if (t1 <= 1 && t2 == 0)
{
f1 = 0x01;
break;
}
} while (Timer1);
if (f1 == 0x00)
{
printf("failed\r\ntry ACMD41_SDXC_POWER... ");
Timer1 = 1000;
do {
t1 = SD_SendCmd(CMD55, 0);
t2 = SD_SendCmd(CMD41, ACMD41_SDXC_POWER);
if (t1 <= 1 && t2 == 0)
{
f1 = 0x01;
break;
}
} while (Timer1);
}
if (f1 == 0x00)
{
printf("failed\r\ntry ACMD41_S18R... ");
Timer1 = 1000;
do {
t1 = SD_SendCmd(CMD55, 0);
t2 = SD_SendCmd(CMD41, ACMD41_S18R);
if (t1 <= 1 && t2 == 0)
{
f1 = 0x01;
break;
}
} while (Timer1);
}
if (f1 == 0x00)
{
printf("failed\r\ntry ACMD41_VOLTAGE... ");
Timer1 = 1000;
do {
t1 = SD_SendCmd(CMD55, 0);
t2 = SD_SendCmd(CMD41, ACMD41_VOLTAGE);
if (t1 <= 1 && t2 == 0)
{
f1 = 0x01;
break;
}
} while (Timer1);
}
if (f1 == 0x00)
{
printf("failed, stop trying\r\n");
}
else
{
printf("succeeded\r\nCMD58 ");
/* READ_OCR */
if (SD_SendCmd(CMD58, 0) == 0)
{
/* Check CCS bit */
for (n = 0; n < 4; n++)
{
ocr[n] = SPI_RxByte();
printf("%02X ", ocr[n]);
}
/* SDv2 (HC or SC) */
type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
printf("type:%02X\r\n", type);
}
}
}
}
else
{
printf("failed, SDC V1 or MMC\r\n");
/* timeout 1 sec */
Timer1 = 1000;
/* SDC V1 or MMC */
type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? CT_SD1 : CT_MMC;
do
{
if (type == CT_SD1)
{
if (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0) break; /* ACMD41 */
}
else
{
if (SD_SendCmd(CMD1, 0) == 0) break; /* CMD1 */
}
} while (Timer1);
/* SET_BLOCKLEN */
if (!Timer1 || SD_SendCmd(CMD16, 512) != 0) type = 0;
}
}
CardType = type;
/* Idle */
DESELECT();
SPI_RxByte();
/* Clear STA_NOINIT */
if (type)
{
Stat &= ~STA_NOINIT;
}
else
{
/* Initialization failed */
SD_PowerOff();
}
//printf("Stat:%02X\r\n", Stat);
return Stat;
}
/* return disk status */
DSTATUS SD_disk_status(BYTE drv)
{
if (drv) return STA_NOINIT;
return Stat;
}
/* read sector */
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
/* pdrv should be 0 */
if (pdrv || !count) return RES_PARERR;
/* no disk */
if (Stat & STA_NOINIT) return RES_NOTRDY;
/* if not block-addressing, convert to byte address */
if (!(CardType & CT_BLOCK)) sector *= 512;
SELECT();
if (count == 1)
{
/* READ_SINGLE_BLOCK */
if (SD_SendCmd(CMD17, sector) == 0)
{
if (SD_RxDataBlock(buff, 512))
{
count = 0;
}
}
}
else
{
/* READ_MULTIPLE_BLOCK */
if (SD_SendCmd(CMD18, sector) == 0)
{
do {
if (!SD_RxDataBlock(buff, 512)) break;
buff += 512;
} while (--count);
/* STOP_TRANSMISSION */
SD_SendCmd(CMD12, 0);
}
}
/* Idle */
DESELECT();
SPI_RxByte();
return count ? RES_ERROR : RES_OK;
}
/* write sector */
#if _USE_WRITE == 1
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
/* pdrv should be 0 */
if (pdrv || !count) return RES_PARERR;
/* no disk */
if (Stat & STA_NOINIT) return RES_NOTRDY;
/* write protection */
if (Stat & STA_PROTECT) return RES_WRPRT;
/* convert to byte address */
if (!(CardType & CT_BLOCK)) sector *= 512;
SELECT();
if (count == 1)
{
/* WRITE_BLOCK */
if ((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
count = 0;
}
else
{
/* WRITE_MULTIPLE_BLOCK */
if (CardType & CT_SD1)
{
SD_SendCmd(CMD55, 0);
SD_SendCmd(CMD23, count); /* ACMD23 */
}
if (SD_SendCmd(CMD25, sector) == 0)
{
do {
if(!SD_TxDataBlock(buff, 0xFC)) break;
buff += 512;
} while (--count);
/* STOP_TRAN token */
if(!SD_TxDataBlock(0, 0xFD))
{
count = 1;
}
}
}
/* Idle */
DESELECT();
SPI_RxByte();
return count ? RES_ERROR : RES_OK;
}
#endif /* _USE_WRITE */
/* ioctl */
DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void *buff)
{
DRESULT res;
uint8_t n, csd[16], *ptr = buff;
WORD csize;
/* pdrv should be 0 */
if (drv) return RES_PARERR;
res = RES_ERROR;
if (ctrl == CTRL_POWER)
{
switch (*ptr)
{
case 0:
SD_PowerOff(); /* Power Off */
res = RES_OK;
break;
case 1:
SD_PowerOn(); /* Power On */
res = RES_OK;
break;
case 2:
*(ptr + 1) = SD_CheckPower();
res = RES_OK; /* Power Check */
break;
default:
res = RES_PARERR;
}
}
else
{
/* no disk */
if (Stat & STA_NOINIT) return RES_NOTRDY;
SELECT();
switch (ctrl)
{
case GET_SECTOR_COUNT:
/* SEND_CSD */
if ((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16))
{
if ((csd[0] >> 6) == 1)
{
/* SDC V2 */
csize = csd[9] + ((WORD) csd[8] << 8) + 1;
*(DWORD*) buff = (DWORD) csize << 10;
}
else
{
/* MMC or SDC V1 */
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD) (csd[6] & 3) << 10) + 1;
*(DWORD*) buff = (DWORD) csize << (n - 9);
}
res = RES_OK;
}
break;
case GET_SECTOR_SIZE:
*(WORD*) buff = 512;
res = RES_OK;
break;
case CTRL_SYNC:
if (SD_ReadyWait() == 0xFF) res = RES_OK;
break;
case MMC_GET_CSD:
/* SEND_CSD */
if (SD_SendCmd(CMD9, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
break;
case MMC_GET_CID:
/* SEND_CID */
if (SD_SendCmd(CMD10, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
break;
case MMC_GET_OCR:
/* READ_OCR */
if (SD_SendCmd(CMD58, 0) == 0)
{
for (n = 0; n < 4; n++)
{
*ptr++ = SPI_RxByte();
}
res = RES_OK;
}
default:
res = RES_PARERR;
}
DESELECT();
SPI_RxByte();
}
return res;
}
8.给user_diskio.c里的函数加入实现方法
#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
* Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
* To be suppressed in the future.
* Kept to ensure backward compatibility with previous CubeMx versions when
* migrating projects.
* User code previously added there should be copied in the new user sections before
* the section contents can be deleted.
*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif
/* USER CODE BEGIN DECL */
/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#include "fatfs_sd.h"
/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;
/* USER CODE END DECL */
/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */
Diskio_drvTypeDef USER_Driver =
{
USER_initialize,
USER_status,
USER_read,
#if _USE_WRITE
USER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes a Drive
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS USER_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
/* USER CODE BEGIN INIT */
return SD_disk_initialize(pdrv);
/* USER CODE END INIT */
}
/**
* @brief Gets Disk Status
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS USER_status (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
/* USER CODE BEGIN STATUS */
return SD_disk_status(pdrv);
/* USER CODE END STATUS */
}
/**
* @brief Reads Sector(s)
* @param pdrv: Physical drive number (0..)
* @param *buff: Data buffer to store read data
* @param sector: Sector address (LBA)
* @param count: Number of sectors to read (1..128)
* @retval DRESULT: Operation result
*/
DRESULT USER_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to read */
)
{
/* USER CODE BEGIN READ */
return SD_disk_read(pdrv, buff, sector, count);
/* USER CODE END READ */
}
/**
* @brief Writes Sector(s)
* @param pdrv: Physical drive number (0..)
* @param *buff: Data to be written
* @param sector: Sector address (LBA)
* @param count: Number of sectors to write (1..128)
* @retval DRESULT: Operation result
*/
#if _USE_WRITE == 1
DRESULT USER_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to write */
)
{
/* USER CODE BEGIN WRITE */
/* USER CODE HERE */
return SD_disk_write(pdrv, buff, sector, count);
/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */
/**
* @brief I/O control operation
* @param pdrv: Physical drive number (0..)
* @param cmd: Control code
* @param *buff: Buffer to send/receive control data
* @retval DRESULT: Operation result
*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
/* USER CODE BEGIN IOCTL */
return SD_disk_ioctl(pdrv, cmd, buff);
/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
9.添加实验代码
在main.c里添加下列代码:
- 添加头文件
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
- printf重定向
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0xffff);
return ch;
}
/* USER CODE END 0 */
- 添加SPI读写MicroSD卡代码
/* USER CODE BEGIN PV */
FATFS fs;
FATFS *pfs;
FIL fil;
FRESULT fres;
DWORD fre_clust;
uint32_t totalSpace, freeSpace;
char buffer[100];
/* USER CODE END PV */
/* Mount SD Card */
FRESULT res2 = f_mount(&fs, "", 0x01);
printf("f_mount result: %02X\r\n", res2);
if(res2 != FR_OK)
{
printf("f_mount failed\r\n");
Error_Handler();
}
/* Check freeSpace space */
if(f_getfree("", &fre_clust, &pfs) != FR_OK)
{
printf("f_getfree failed\r\n");
Error_Handler();
}
totalSpace = (uint32_t)((pfs->n_fatent - 2) * pfs->csize * 0.5);
freeSpace = (uint32_t)(fre_clust * pfs->csize * 0.5);
printf("total:%dKB, free:%dKB\r\n", totalSpace, freeSpace);
/* free space is less than 1kb */
if(freeSpace < 1)
{
printf("freeSpace not enough\r\n");
Error_Handler();
}
/* Open file to write */
printf("f_open first.txt\r\n");
if(f_open(&fil, "first.txt", FA_OPEN_ALWAYS|FA_WRITE|FA_READ ) != FR_OK)
{
printf("f_open failed\r\n");
Error_Handler();
}
/* Writing text */
f_puts("STM32 SD Card I/O Example via SPI\n", &fil);
f_puts("Black Sheep Wall!!!", &fil);
/* Close file */
printf("f_close first.txt\r\n");
if(f_close(&fil) != FR_OK)
{
printf("f_close failed\r\n");
Error_Handler();
}
/* Open file to read */
printf("f_open first.txt\r\n");
if(f_open(&fil, "first.txt", FA_READ) != FR_OK)
{
printf("f_open failed\r\n");
Error_Handler();
}
printf("f_gets first.txt\r\n");
f_gets(buffer, sizeof(buffer), &fil);
/* SWV output */
printf("%s", buffer);
// fflush(stdout);
printf("\r\ndone\r\n");
/* Close file */
printf("f_close first.txt\r\n");
if(f_close(&fil) != FR_OK)
{
printf("f_close failed\r\n");
Error_Handler();
}
/* Unmount SDCARD */
printf("f_mount unmount\r\n");
if(f_mount(NULL, "", 1) != FR_OK)
{
printf("f_mount failed\r\n");
Error_Handler();
}
10.编译运行代码
编译代码后下载到开发板中,打开串口助手会打印出下列信息,表明MCU通过SPI在MicroSD卡上读写FatFs文件成功,
参考文献
FatFs官网
FATFS文件系统详解
Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
STM32_SPI_SDCARD
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)