STM32系列文章( FatFs文件系统——使用SPI读写MicroSD卡)


前言

本文仅代表个人的记录和理解,不具有权威性,请谨慎参考。

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
1CSPA4
2SCKPA5
3MOSIPA7
4MISOPA6
5VCC3.3V
6GNDGND

三、代码

使用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

  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单片机STM32F4可以通过SPI接口与SD进行通信,并使用FATFS库来实现对SD读写操作。下面是一个简单示例程序。 首先,需要在STM32F4上配置SPI接口。在STM32CubeMX中配置SPI相关引脚,并设置SPI的时钟频率、数据位长度等参数。 然后,在代码中包含相关的库文件和头文件。 ```c #include "stm32f4xx_hal.h" #include "fatfs.h" #include "ff.h" ``` 使用以下代码初始化FATFS库: ```c FATFS fs; FIL file; FRESULT res; res = f_mount(&fs, "0:", 1); if(res != FR_OK) { // 初始化错误处理 } ``` 接下来,可以通过以下代码打开SD上的文件并进行读写操作: ```c res = f_open(&file, "test.txt", FA_READ | FA_WRITE | FA_CREATE_ALWAYS); if(res != FR_OK) { // 打开文件错误处理 } // 写入数据到文件 const char* data = "Hello, SD card!"; UINT bytes_written; res = f_write(&file, data, strlen(data), &bytes_written); if(res != FR_OK) { // 写入数据错误处理 } // 读取文件数据 char read_buffer[100]; UINT bytes_read; res = f_read(&file, read_buffer, sizeof(read_buffer), &bytes_read); if(res != FR_OK) { // 读取数据错误处理 } // 关闭文件 res = f_close(&file); if(res != FR_OK) { // 关闭文件错误处理 } ``` 最后,使用以下代码卸载SD和释放FATFS资源: ```c res = f_mount(NULL, "0:", 1); if(res != FR_OK) { // 卸载SD错误处理 } ``` 这是一个简单的单片机STM32F4通过SPISD进行读写操作的示例程序。具体的代码细节和错误处理可以根据实际需求进行修改和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值