目录

一、 SD卡文件系统概述

二、示例:阻塞式访问SD卡

1、示例功能与CubeMX项目设置

(1)USART6、CodeGenerator、SYS

(2)SDIO

(3)RCC

(4)GPIO

(5)RTC

(6)FATFS

(7)NVIC

2、项目文件组成和初始代码分析

(1)项目文件组成

(2)初始主程序

(3)SDIO接口和SD卡初始化

(4)FatFS的初始化

3、 SD卡的Disk IO函数实现

(1)文件sd_diskio.h/.c概览

(2)文件bsp_driver_sd.h.c概览

(3)函数SD_status()的实现

(4)函数SD_initialize()的实现

(5)函数SD_ioctl()的实现

(6)函数SD_read()的实现

(7)函数SD_write()的实现

4、 SD卡文件管理功能的实现

(1)主程序功能

(2)SD卡格式化

(3)获取FAT磁盘信息

(4)获取SD卡信息

(5)其它代码

三、运行与调试


        为了更好地阅读本文,推荐先阅读本文作者发布的相关文章,可参考的文章如下:

        参考文章1:细说STM32单片机使用轮询模式直接访问SD卡的方法及其应用-CSDN博客  细说STM32单片机使用轮询模式直接访问SD卡的方法及其应用-CSDN博客

        参考文章2:细说STM32单片机SPI-Flash芯片的FatFS移植及其应用实例详解_stm32 cube spi flash fats 创建-CSDN博客  细说STM32单片机SPI-Flash芯片的FatFS移植及其应用实例详解_stm32 cube spi flash fats 创建-CSDN博客

        参考文章1介绍了通过SD的HAL驱动程序直接读写SD卡数据块的方法,但是因为SD卡容量比较大,在实际使用中,一般是在SD卡上创建文件系统,将数据以文件形式进行管理。本文介绍如何用FatFS管理SD卡的文件系统。

一、 SD卡文件系统概述

        SD卡容量一般是16GB、32GB或更大,存储的数据格式可能也比较复杂,所以在实际使用中,一般是在SD卡上创建文件系统,用FatFS管理SD卡上的文件系统。

        参考文章2介绍了在User-defined存储介质上移植FatFS的原理,并以SPI-Flash存储芯片W25Q16为例,介绍了FatFS移植的具体实现过程。FatFS的移植主要就是针对存储介质实现几个Disk IO函数。如果将SD卡看作一个User-defined存储介质,可以用同样的方法进行FatFS的移植,只需使用SD的HAL驱动程序实现几个Disk IO函数即可。

        在使用CubeMX进行SD卡的FatFS文件系统管理开发时,无须自己实现FatFS底层Disk IO函数的移植,CubeMX生成的代码中,已经针对SDIO接口和SD卡做好了FatFS的移植,直接使用FatFS的应用层接口函数进行文件系统管理即可。

        使用FatFS管理SD卡的文件系统时,SD卡的数据读写可以采用轮询方式或DMA方式。本示例中,SDIO以轮询方式访问SD卡,介绍FatFS管理SD卡文件系统的原理,以及FatFS针对SD卡的Disk IO访问函数的实现原理。

二、示例:阻塞式访问SD卡

1示例功能与CubeMX项目设置

        设计一个示例,使用FatFS在SD卡上创建文件系统,并测试文件读写功能。本示例采用SD的HAL阻塞式数据传输函数访问SD卡。

        本示例继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。

(1)USART6、CodeGenerator、SYS

        与参考文件相同。

(2)SDIO

        SDIO的模式设置为SD 4 bits Wide bus,分频系数(SDIOCLK clock divide factor)决定了给SD卡的时钟信号SDIO_CK的频率,这里的系数设置为6,SDIO_CK的频率就是48MHz/8=6MHz。

 

(3)RCC

        本示例要使用RTC为FatFS提供时间戳,所以在RCC组件的模式设置中启用LSE,并在时钟树上将LSE设置为RTC的时钟源。启用SDIO后,在时钟树上会自动启用48MHz时钟信号。如果这个时钟信号不是48MHz,使用自动解决时钟信号。

(4)GPIO

        本示例要用到4个按键用于菜单切换。1个SD卡侦测线(可以不设置)。

 

(5)RTC

        在RTC的模式设置中,只需启用时钟源和日历。

(6)FATFS

        FatFS的模式设置选择SD Card。需要先启用SDIO接口后,才可以在此选择SD Card,否则该选项是不可选的。

        模式选择为SD Card后,FatFS的参数设置部分有4个页面,相对于使用User-defined模式时,多了Advanced Settings和Platform Settings页面。

        Set Defines页面的参数大部分保留其默认值,只需要将代码页(CODE_PAGE)设置为简体中文,而且本示例为测试使用长文件名,将USE_LFN参数设置为Enabled with dynamic working buffer on the HEAP,也就是由FatFS在其堆空间自动为LFN分配存储空间。MAX_SS(最大扇区大小)和MIN_SS(最小扇区大小)都被自动设置为512,因为SD卡的块大小固定为512字节,FatFS中的扇区就对应于SD卡的块。

        Advanced Settings页面几个参数的意义和设置选项如下。

  • SDIO instance,SDIO接口方式。这里只能设置为SDIO。STM32F4的SDIO接口只支持SDIO模式,不支持SPI模式。
  • Use dma template,是否使用DMA模板。如果要使用DMA方式进行SD卡数据读写,就需要设置为Enabled。本示例使用轮询方式进行SD卡数据读写,所以设置为Disabled。
  • BSP code for SD,SD卡的BSP(board support package)代码,这个参数固定为Generic,也就是只能使用CubeMX自动生成的SD卡BSP代码。

 

        FatFS的Platform Settings这个页面用于设置SD卡的卡检测信号引脚CD,本例使用的开发板上microSD卡槽有CD信号PF6,所以在第二个下拉列表框中,选择PF6即可。

        如果没有设置FatFS的Platform Settings页面的参数(有的开发板没有设计),在完成设置后,CubeMX会自动生成代码,这时会出现提示没有设置FatFS的Platform Settings页面的参数的警告对话框,单击Yes按钮,继续生成代码即可。

(7)NVIC

        把timebase时基的中断优先级修改为0。其它默认。

2项目文件组成和初始代码分析

(1)项目文件组成

        完成设置后,CubeMX生成CubelIDE项目代码。项目的Middlewares目录下存有FatFS的源代码,其文件组成如图所示。FatFs\src\option目录下多了一个文件cc936.c,这是与简体中文Unicode码相关的一个文件,因为本示例设置了使用LFN和UTF-8编码。Middlewares\lFatFs\目录下其他文件的作用可以参考本文作者发布的其他相关文章。

 

         \Src和\Inc和\FATFS目录下的文件,包含针对SD卡生成的FatFS相关文件,或可修改的FatFS用户程序文件。其中,与FatFS的SD卡移植程序相关的几个文件描述如下。

 

  • sdio.h和sdio.c是SDIO接口的外设初始化程序文件,包含函数MX_SDIO_SD_Init(),但是这个函数与参考文章中的初始化函数MX_SDIO_SD_Init()有些差异。
  • ffconf.h是FatFS的配置文件,包含很多的宏定义,与CubeMX里的FatFS设置对应。
  • fatfs.h和fatfs.c是用户的FatFS初始化程序文件,包括FatFS初始化函数MX_FATFS_Init(),几个全局变量的定义,以及需要重新实现的获取RTC时间作为文件系统时间戳的函数get_fattime()。
  • sd_diskio.h和sd_diskio.c是实现了SD卡的Disk IO函数的程序文件,例如,读取SD卡数据的函数SD_read(),写数据的函数SD_write()。这些函数的代码已经针对SD卡自动移植好了,无须用户再编写任何代码。
  • bsp_driver_sd.h和bsp_driver_sd.c是SD卡各种具体操作的BSP函数的程序文件,如读取SD卡数据块的函数BSP_SD_ReadBlocks(),而这个函数则是调用HAL驱动函数HAL_SD_ReadBlocks()实现的。文件sd_diskio.c中的一些Disk IO函数的实现就是调用相应的BSP函数,例如,Disk IO函数SD_read()就是调用BSP_SD_ReadBlocks()。

        本示例中,SD卡的FatFS相关文件的层次和关系如下图所示。与使用SPI-Flash芯片的FatFS文件的层次和关系相比(参考文章2),两个示例的文件差别就在具体硬件访问层部分。在本示例中,SD卡的Disk IO函数在文件sd_diskio.c中实现,这些Disk IO函数的实现依赖于BSP驱动程序文件bsp_driver_sd.h/.c,而BSP驱动程序则依赖SD的HAL驱动程序实现SD卡的数据块的读写。

(2)初始主程序

        CubeMX生成的初始主程序代码如下,主要就是各个外设的初始化,以及FatFS的初始化:

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "rtc.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include "file_opera.h"
#include <stdio.h>
/* USER CODE END Includes */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
__IO uint8_t status = SD_PRESENT;
/* USER CODE END PM */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART6_UART_Init();
  MX_RTC_Init();
  MX_FATFS_Init();
  MX_SDIO_SD_Init();

//以下的代码接着的下文待续

(3)SDIO接口和SD卡初始化

        主程序中,函数MX_SDIO_SD_Init()对SDIO接口和SD卡进行初始化,这个函数在文件sdio.h和sdio.c中定义和实现,代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    sdio.c
  * @brief   This file provides code for the configuration
  *          of the SDIO instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "sdio.h"

SD_HandleTypeDef hsd;

/* SDIO init function */

void MX_SDIO_SD_Init(void)
{
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 6;
}

void HAL_SD_MspInit(SD_HandleTypeDef* sdHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(sdHandle->Instance==SDIO)
  {
    /* SDIO clock enable */
    __HAL_RCC_SDIO_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /**SDIO GPIO Configuration
    PC8     ------> SDIO_D0
    PC9     ------> SDIO_D1
    PC10     ------> SDIO_D2
    PC11     ------> SDIO_D3
    PC12     ------> SDIO_CK
    PD2     ------> SDIO_CMD
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
                          |GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
  }
}

        参考文章1中,通过SD的HAL驱动程序直接读写SD卡数据时,展示过SDIO接口和SD卡的初始化函数MX_SDIO_SD_Init()的代码。本示例的函数MX_SDIO_SD_Init()代码只是设置了SD对象变量hsd的属性,并没有调用函数HAL_SD_Init()进行初始化,也没有调用函数HAL_SD_ConfigWideBusOperation()设置SD总线为4位宽度。

        本示例中,SDIO接口和SD卡的初始化过程是在文件sd_diskio.c中定义的Disk IO函数SD_initialize()中完成的

(4)FatFS的初始化

        函数MX_FATFS_Init()是在文件fasfs.h和fastfs.c中定义和实现的,是FatFS的初始化函数。文件fatfs.h的完整代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file   fatfs.h
  * @brief  Header for fatfs applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __fatfs_H
#define __fatfs_H
#ifdef __cplusplus
 extern "C" {
#endif

#include "ff.h"
#include "ff_gen_drv.h"
#include "sd_diskio.h" /* defines SD_Driver as external */

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern uint8_t retSD; /* Return value for SD */
extern char SDPath[4]; /* SD logical drive path */
extern FATFS SDFatFS; /* File system object for SD logical drive */
extern FIL SDFile; /* File object for SD */

void MX_FATFS_Init(void);

/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__fatfs_H */

        创建了文件系统的SD卡称为SD逻辑驱动器,假设在SD卡上只有一个分区。用extern声明的4个变量是在文件fatfs.c中定义的。SDPath用于表示SD逻辑驱动器的路径,赋值后是“0:/”。SDFatFS是一个FATFS结构体变量,用于表示SD逻辑驱动器上的文件系统。SDFile是一个FIL类型结构体变量,表示文件对象,在操作文件时,可以使用这个文件对象。源程序文件fatfs.c的完整代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file   fatfs.c
  * @brief  Code for fatfs applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
#include "fatfs.h"

uint8_t retSD;    /* Return value for SD */
char SDPath[4];   /* SD logical drive path */
FATFS SDFatFS;    /* File system object for SD logical drive */
FIL SDFile;       /* File object for SD */

/* USER CODE BEGIN Variables */
#include "file_opera.h"
/* USER CODE END Variables */

void MX_FATFS_Init(void)
{
  /*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SDPath);

  /* USER CODE BEGIN Init */
  /* additional user code for init */
  /* USER CODE END Init */
}

/**
  * @brief  Gets Time from RTC
  * @param  None
  * @retval Time in DWORD
  */
DWORD get_fattime(void)
{
  /* USER CODE BEGIN get_fattime */
	return fat_GetFatTimeFromRTC();
  /* USER CODE END get_fattime */
}

        函数MX_FATFS_Init()用于FatFS的初始化,只有一行代码,即

retSD =FATFS_LinkDriver(&SD_Driver,SDPath);

        SD_Driver是在文件sd_diskio.c中定义的,是一个Diskio_drvTypeDef结构体类型的变量,它将Disk IO访问的函数指针指向文件sd_diskio.c中实现的SD卡访问的Disk IO函数。执行这行代码的作用就是将SD_Driver链接到系统的驱动器列表,以及为SDPath赋值。执行此行代码后,SDPath被赋值为“0:/”。

        函数get_fattime()用于获取RTC时间,作为创建文件或修改文件时的时间戳数据。这里显示了函数get_fattime()实现后的代码,这也是FatFS针对SD卡移植时唯一需要用户实现的一个Disk IO函数。函数get_fattime()里就是调用了文件file_opera.h中定义的一个函数fat_GetFatTimeFromRTC()。

3、 SD卡的Disk IO函数实现

        在FatFS相关文件中,与SD卡的Disk IO函数实现相关的就是具体硬件访问层的几个文件,即文件sd_diskio.h/.c和文件bsp_driver_sd.h/.c。

        文件sd_diskio.h/.c定义和实现了SD卡的Disk IO函数。文件bsp_driver_sd.b/.c定义和实现了SD卡常用操作的函数。而底层硬件接口驱动则是SD的HAL驱动程序。

        实现SD卡Disk IO访问函数的代码都是CubeMX自动生成的。

(1)文件sd_diskio.h/.c概览

        文件sd_diskio.h中只有一行有效语句,就是对外声明了变量SD_Driver,表示SD驱动器:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    sd_diskio.h
  * @brief   Header for sd_diskio.c module
  ******************************************************************************
  */
/* USER CODE END Header */

/* Note: code generation based on sd_diskio_template.h */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SD_DISKIO_H
#define __SD_DISKIO_H

/* USER CODE BEGIN firstSection */
/* can be used to modify / undefine following code or add new definitions */
/* USER CODE END firstSection */

/* Includes ------------------------------------------------------------------*/
#include "bsp_driver_sd.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
extern const Diskio_drvTypeDef  SD_Driver;

/* USER CODE BEGIN lastSection */
/* can be used to modify / undefine previous code or add new definitions */
/* USER CODE END lastSection */

#endif /* __SD_DISKIO_H */

        变量SD_Driver是在文件sd_diskio.c中定义的。文件sd_diskio.c还实现了FatFS的几个DiskIO函数,只是这些函数都是文件sd_diskio.c的私有函数,在定义变量SD_Driver时,将Disk IO操作函数指针指向这些函数即可。

        文件sd_diskio.c开头的声明部分的代码如下。为了使程序结构更清晰,这里省略了一些不成立的条件编译代码,删除了对编译条件_USE_WRITE和_USE_IOCTL的判断,这两个参数默认都是1。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    sd_diskio.c
  * @brief   SD Disk I/O driver
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"
#include "sd_diskio.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* use the default SD timout as defined in the platform BSP driver*/
#if defined(SDMMC_DATATIMEOUT)
#define SD_TIMEOUT SDMMC_DATATIMEOUT
#elif defined(SD_DATATIMEOUT)
#define SD_TIMEOUT SD_DATATIMEOUT
#else
#define SD_TIMEOUT 30 * 1000
#endif

#define SD_DEFAULT_BLOCK_SIZE 512

/*
 * Depending on the use case, the SD card initialization could be done at the
 * application level: if it is the case define the flag below to disable
 * the BSP_SD_Init() call in the SD_Initialize() and add a call to
 * BSP_SD_Init() elsewhere in the application.
 */
/* USER CODE BEGIN disableSDInit */
/* #define DISABLE_SD_INIT */
/* USER CODE END disableSDInit */

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* Private function prototypes -----------------------------------------------*/
static DSTATUS SD_CheckStatus(BYTE lun);         //检查SD卡状态
//以下是DISKIO访问关联函数
DSTATUS SD_initialize (BYTE);                    //SD卡初始化,关联函数disk_initialize()
DSTATUS SD_status (BYTE);                        //SD卡状态,关联函数disk_status()
DRESULT SD_read (BYTE, BYTE*, DWORD, UINT);      //读取SD卡,关联函数disk_read()
#if _USE_WRITE == 1
DRESULT SD_write (BYTE, const BYTE*, DWORD, UINT);//写入SD卡,关联函数disk_write()
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT SD_ioctl (BYTE, BYTE, void*);            //SD卡IO控制,关联函数disk_ioct1()
#endif  /* _USE_IOCTL == 1 */

/*下面的代码是对结构体变量SD_Driver的各成员变量赋值,结构体类型Diskio_drvTypeDef在文件*ff_gen_drv.h中定义,其成员变量是函数指针,赋值使其指向本文件中定义的SD函数
*/
const Diskio_drvTypeDef  SD_Driver =
{
  SD_initialize,            //函数指针disk_initialize指向函数SD_initialize()
  SD_status,                //函数指针disk_status指向函数SD_status()
  SD_read,                  //函数指针disk_write指向函数SD_write()
#if  _USE_WRITE == 1
  SD_write,                 //函数指针disk_write指向函数SD_write()
#endif /* _USE_WRITE == 1 */

#if  _USE_IOCTL == 1
  SD_ioctl,                 //函数指针disk_ioct1指向函数SD_ioct1()
#endif /* _USE_IOCTL == 1 */
};

/* USER CODE BEGIN beforeFunctionSection */
/* can be used to modify / undefine following code or add new code */
/* USER CODE END beforeFunctionSection */

/* Private functions ---------------------------------------------------------*/

//以下代码省略,需要的去IDE

        此处,声明了5个Disk IO函数,即SD_initialize()、SD_status()、SD_read()、SD_write()、SD_ioctl()。由于这几个函数是文件内的私有函数,因此在文件sd_diskio.c的开头部分定义函数原型。

        定义的变量SD_Driver是结构体类型Diskio_drvTypeDef,定义的时候就给SD_Driver的各成员变量赋值了,也就是指向这5个Disk IO函数。Diskio_drvTypeDef是在文件ff_gen_drv.h中定义的,其成员变量就是5个函数指针。ff_gen_drv.h和ff_gen_drv.c是FatFS的不可修改的源程序文件。

(2)文件bsp_driver_sd.h.c概览

        bsp_driver_sd.h/.c是SD卡的BSP驱动程序,也就是针对开发板上SD卡具体硬件电路实现的SD卡常用操作函数的程序文件。BSP驱动程序是比HAL驱动程序高一级的驱动程序。文件bsp_driver_sd.h的代码如下:

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    bsp_driver_sd.h for F4 (based on stm324x9i_eval_sd.h)
  * @brief   This file contains the common defines and functions prototypes for
  *          the bsp_driver_sd.c driver.
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F4_SD_H
#define __STM32F4_SD_H

#ifdef __cplusplus
 extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
#include "fatfs_platform.h"

/* Exported types --------------------------------------------------------*/
/**
  * @brief SD Card information structure
  */
#define BSP_SD_CardInfo HAL_SD_CardInfoTypeDef

/* Exported constants --------------------------------------------------------*/
/**
  * @brief  SD status structure definition
  */
#define   MSD_OK                        ((uint8_t)0x00)
#define   MSD_ERROR                     ((uint8_t)0x01)

/**
  * @brief  SD transfer state definition
  */
#define   SD_TRANSFER_OK                ((uint8_t)0x00)
#define   SD_TRANSFER_BUSY              ((uint8_t)0x01)

#define SD_PRESENT               ((uint8_t)0x01)
#define SD_NOT_PRESENT           ((uint8_t)0x00)
#define SD_DATATIMEOUT           ((uint32_t)100000000)

#ifdef OLD_API
/* kept to avoid issue when migrating old projects. */
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */
#else
/* USER CODE BEGIN BSP_H_CODE */
/* Exported functions --------------------------------------------------------*/
uint8_t BSP_SD_Init(void);
uint8_t BSP_SD_ITConfig(void);
void    BSP_SD_DetectIT(void);
void    BSP_SD_DetectCallback(void);
uint8_t BSP_SD_ReadBlocks(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_ReadBlocks_DMA(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_WriteBlocks_DMA(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr);
void BSP_SD_IRQHandler(void);
void BSP_SD_DMA_Tx_IRQHandler(void);
void BSP_SD_DMA_Rx_IRQHandler(void);
uint8_t BSP_SD_GetCardState(void);
void    BSP_SD_GetCardInfo(HAL_SD_CardInfoTypeDef *CardInfo);
uint8_t BSP_SD_IsDetected(void);

/* These functions can be modified in case the current settings (e.g. DMA stream)
   need to be changed for specific application needs */
void    BSP_SD_AbortCallback(void);
void    BSP_SD_WriteCpltCallback(void);
void    BSP_SD_ReadCpltCallback(void);
/* USER CODE END BSP_H_CODE */
#endif

#ifdef __cplusplus
}
#endif

#endif /* __STM32F4_SD_H */

        文件bsp_driver_sd.h定义了一些宏和函数。从这些函数的名字上可大致知道这些函数的功能,例如,BSP_SD_Init()用于SD卡初始化,BSP_SD_ReadBlocks()用于读取SD卡数据块。

        文件bsp_driver_sd.c开头部分的代码如下:

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    bsp_driver_sd.c for F4 (based on stm324x9i_eval_sd.c)
 */
/* USER CODE END Header */

#ifdef OLD_API
/* kept to avoid issue when migrating old projects. */

#else
/* USER CODE BEGIN FirstSection */
/* can be used to modify / undefine following code or add new definitions */
/* USER CODE END FirstSection */
/* Includes ------------------------------------------------------------------*/
#include "bsp_driver_sd.h"

/* Extern variables ---------------------------------------------------------*/

extern SD_HandleTypeDef hsd;

//此处省略以下的代码,需要看就去IDE

        这里只声明了一个外部变量hsd,也就是文件sdio.c中定义的SD对象变量。因为文件bsp_driver_sd.c要使用SD的HAL驱动函数,需要用到SD对象变量。

(3)函数SD_status()的实现

        文件sd_diskio.c中的函数SD_status()关联通用Disk IO函数disk_status(),用于检测SD卡的状态。文件sd_diskio.c中函数SD_status()和相关函数的代码如下。函数中的参数lun表示逻辑驱动器编号,只有一个驱动器时lun就是0。

/**
  * @brief  Gets Disk Status
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_status(BYTE lun)    //检查SD卡状态
{
  return SD_CheckStatus(lun);
}
/* USER CODE BEGIN beforeFunctionSection */
/* can be used to modify / undefine following code or add new code */
/* USER CODE END beforeFunctionSection */

/* Private functions ---------------------------------------------------------*/

static DSTATUS SD_CheckStatus(BYTE lun)    //检查SD卡状态

{
  Stat = STA_NOINIT;

  if(BSP_SD_GetCardState() == MSD_OK)      //调用相应的BSP函数
  {
    Stat &= ~STA_NOINIT;
  }

  return Stat;
}

        函数SD_CheckStatus()调用了BSP函数BSP_SD_GetCardState(),文件bsp_driver_sd.c中,这个函数的代码如下:

/**
  * @brief  Gets the current SD card data status.
  * @param  None
  * @retval Data transfer state.
  *          This value can be one of the following values:
  *            @arg  SD_TRANSFER_OK: No data transfer is acting
  *            @arg  SD_TRANSFER_BUSY: Data transfer is acting
  */
__weak uint8_t BSP_SD_GetCardState(void)    //检查SD卡状态
{
  return ((HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER ) ? SD_TRANSFER_OK : SD_TRANSFER_BUSY);
}

        函数BSP_SD_GetCardState()调用SD驱动函数HAL_SD_GetCardState()检测SD卡状态。如果状态为HAL_SD_CARD_TRANSFER,函数就返回SD_TRANSFER_OK,表示SD卡处于空闲状态;否则,函数返回SD_TRANSFER_BUSY,表示SD卡有未完成的操作。

        检测SD卡状态在SD卡的应用程序里很重要,为了提高程序的鲁棒性IDE自动生成的程序代码里多次出现检查SD卡的状态,因此,如果使用者忽略了对SD卡状态的检查(检查是否插入、检查是否装载),程序的容错能力就会很弱,就会发生意想不到的失误,比如,不能装载、不能写入、不能读出,偏偏有的时候还能装载或写入或读出。

(4)函数SD_initialize()的实现

        文件sd_diskio.c中的函数SD_initialize()关联通用Disk IO函数disk_initialize(),用于SD卡的初始化。文件sd_diskio.c中,函数SD_initialize()的代码如下:

/**
  * @brief  Initializes a Drive
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_initialize(BYTE lun)    //SD卡初始化
{
Stat = STA_NOINIT;

#if !defined(DISABLE_SD_INIT)

  if(BSP_SD_Init() == MSD_OK)      //调用相应BSP函数
  {
    Stat = SD_CheckStatus(lun);    //检查SD卡状态
  }

#else
  Stat = SD_CheckStatus(lun);
#endif

  return Stat;
}

        函数SD_initialize()调用BSP函数BSP_SD_Init()进行SD卡初始化。文件bsp_driver_sd.c中,函数BSP_SD_Init()以及相关函数的代码如下:

/* USER CODE BEGIN BeforeInitSection */
/* can be used to modify / undefine following code or add code */
/* USER CODE END BeforeInitSection */
/**
  * @brief  Initializes the SD card device.
  * @retval SD status
  */
__weak uint8_t BSP_SD_Init(void)            //完成SDIO接口和SD卡初始化过程
{
  uint8_t sd_state = MSD_OK;
  /* Check if the SD card is plugged in the slot */
  if (BSP_SD_IsDetected() != SD_PRESENT)    //检测SD卡是否插入了卡槽
  {
    return MSD_ERROR;
  }
  /* HAL SD initialization */
  sd_state = HAL_SD_Init(&hsd);             //SDIO接口和SD卡初始化  
  /* Configure SD Bus width (4 bits mode selected) */
  if (sd_state == MSD_OK)
  {
    /* Enable wide operation配置SD总线宽度为4位*/
    if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
    {
      sd_state = MSD_ERROR;
    }
  }

  return sd_state;
}
/**
 * @brief  Detects if SD card is correctly plugged in the memory slot or not.
 * @param  None
 * @retval Returns if SD is detected or not
 */
/*检测SD卡是否插入了卡槽,如果没有CD信号引脚,所以总是返回SD_PRESENT 
* 如果配置了CD信号引脚,就要检测引脚,此时需要重写BSP_SD_IsDetected(void)*/
__weak uint8_t BSP_SD_IsDetected(void)
{
  __IO uint8_t status = SD_PRESENT;

  if (BSP_PlatformIsDetected() == 0x0)
  {
    status = SD_NOT_PRESENT;
  }

  return status;
}

        函数BSP_SD_Init()首先调用了函数BSP_SD_IsDetected(),检测SD卡是否已插入卡槽。函数BSP_SD_Init()还调用了HAL_SD_Init()和HAL_SD_ConfigWideBusOperation(),也就是完成了函数MX_SDIO_SD_Init()里没有完成的SDIO接口和SD卡初始化。

(5)函数SD_ioctl()的实现

        文件sd_diskio.c中的函数SD_ioctl()关联通用Disk IO函数disk_ioctl(),用于响应SD卡的一些IO控制指令。函数SD_ioctl()的代码如下:

/* USER CODE BEGIN beforeIoctlSection */
/* can be used to modify previous code / undefine following code / add new code */
/* USER CODE END beforeIoctlSection */
/**
  * @brief  I/O control operation
  * @param  lun : not used
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff)
{
  DRESULT res = RES_ERROR;
  BSP_SD_CardInfo CardInfo;                    //SD卡信息结构体变量

  if (Stat & STA_NOINIT) return RES_NOTRDY;

  switch (cmd)
  {
  /* Make sure that no pending write process */
  case CTRL_SYNC :
    res = RES_OK;
    break;

  /* Get number of sectors on the disk (DWORD) */
  case GET_SECTOR_COUNT :
    BSP_SD_GetCardInfo(&CardInfo);           //获取SD卡信息
    *(DWORD*)buff = CardInfo.LogBlockNbr;    //扇区个数就是SD卡的逻辑块个数
    res = RES_OK;
    break;

  /* Get R/W sector size (WORD) */
  case GET_SECTOR_SIZE :
    BSP_SD_GetCardInfo(&CardInfo);            //获取SD卡信息
    *(WORD*)buff = CardInfo.LogBlockSize;     //扇区大小就是逻辑块的大小,默认为512字节
    res = RES_OK;
    break;

  /* Get erase block size in unit of sector (DWORD) */
  case GET_BLOCK_SIZE :
    BSP_SD_GetCardInfo(&CardInfo);            //获取SD卡信息

    //FAT块大小=SD卡逻辑块大小/SD卡数据块默认大小
    *(DWORD*)buff = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
    res = RES_OK;
    break;

  default:
    res = RES_PARERR;
  }

  return res;
}
#endif /* _USE_IOCTL == 1 */

        在上述程序中,多处使用了函数BSP_SD_GetCardInfo()获取SD卡信息。文件bsp_driver_sd.c中实现的这个函数的代码如下,其功能就是调用函数HAL_SD_GetCardInfo()获取SD卡信息。

/**
  * @brief  Get SD information about specific SD card.
  * @param  CardInfo: Pointer to HAL_SD_CardInfoTypedef structure
  * @retval None
  */
__weak void BSP_SD_GetCardInfo(HAL_SD_CardInfoTypeDef *CardInfo)
{
  /* Get SD card Information */
  HAL_SD_GetCardInfo(&hsd, CardInfo);
}

        从函数SD_ioctl()的代码可以看出FAT的参数与SD卡的参数之间的关系。

  • GET_SECTOR_COUNT指令的响应代码返回FAT的扇区(Sector)个数。从代码可以看出:FAT的扇区个数就是SD卡的逻辑块个数,而SD卡的逻辑块个数一般就等于实际的数据块个数。
  • GET_SECTOR_SIZE指令的响应代码返回FAT扇区大小,单位是字节。从代码可以看出:FAT扇区大小就等于SD卡逻辑块的大小,也就是512字节。
  • GET_BLOCK_SIZE指令的响应代码返回FAT块的大小,单位是扇区个数。FAT擦除存储介质的最小单位是块,不要与SD卡的数据块混淆。从代码可以看出:FAT块的大小等于SD卡逻辑块大小除以SD卡默认块大小,而CardInfo.LogBlockSize和SD_DEFAULT_BLOCK_SIZE的值都是512,所以计算出来的FAT块大小就是1。

        所以,FAT的一个扇区就等于SD卡的一个数据块,扇区大小就是512字节,FAT擦除数据的最小单位就是一个SD卡数据块。

(6)函数SD_read()的实现

        文件sd_diskio.c中的函数SD_read()关联通用Disk IO函数disk_read(),用于从SD卡读取数据。读取数据的最小单位是扇区,也就是SD卡的数据块,可以一次读取一个或多个扇区的数据。函数SD_read()的代码如下:

/* USER CODE BEGIN beforeReadSection */
/* can be used to modify previous code / undefine following code / add new code */
/* USER CODE END beforeReadSection */
/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @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 SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_ERROR;

  if(BSP_SD_ReadBlocks((uint32_t*)buff,
                       (uint32_t) (sector),
                       count, SD_TIMEOUT) == MSD_OK)
  {
    /* wait until the read operation is finished */
    while(BSP_SD_GetCardState()!= MSD_OK)
    {
    }
    res = RES_OK;
  }

  return res;
}

        上述程序调用了BSP函数BSP_SD_ReadBlocks()。文件bsp_driver_sd.c中实现的这个函数热代码如下,其原理就是调用SD的HAL驱动函数HAL_SD_ReadBlocks()读取SD卡的数据块。

/* USER CODE BEGIN BeforeReadBlocksSection */
/* can be used to modify previous code / undefine following code / add code */
/* USER CODE END BeforeReadBlocksSection */
/**
  * @brief  Reads block(s) from a specified address in an SD card, in polling mode.
  * @param  pData: Pointer to the buffer that will contain the data to transmit
  * @param  ReadAddr: Address from where data is to be read
  * @param  NumOfBlocks: Number of SD blocks to read
  * @param  Timeout: Timeout for read operation
  * @retval SD status
  */
__weak uint8_t BSP_SD_ReadBlocks(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout)
{
  uint8_t sd_state = MSD_OK;

  if (HAL_SD_ReadBlocks(&hsd, (uint8_t *)pData, ReadAddr, NumOfBlocks, Timeout) != HAL_OK)
  {
    sd_state = MSD_ERROR;
  }

  return sd_state;
}

(7)函数SD_write()的实现

        文件sd_diskio.c中的函数SD_write()关联通用Disk IO函数disk_write(),用于向SD卡写入数据。写入数据的最小单位是扇区,也就是SD卡的数据块,可以一次写入一个或多个扇区的数据。函数SD_write()的代码如下:

/* USER CODE BEGIN beforeWriteSection */
/* can be used to modify previous code / undefine following code / add new code */
/* USER CODE END beforeWriteSection */
/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @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 SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
  DRESULT res = RES_ERROR;

  if(BSP_SD_WriteBlocks((uint32_t*)buff,
                        (uint32_t)(sector),
                        count, SD_TIMEOUT) == MSD_OK)
  {
	/* wait until the Write operation is finished */
    while(BSP_SD_GetCardState() != MSD_OK)
    {
    }
    res = RES_OK;
  }

  return res;
}
#endif /* _USE_WRITE == 1 */

        上述程序调用了BSP函数BSP_SD_WriteBlocks()。文件bsp_driver_sd.c中实现的这个函数的代码如下,其原理就是调用SD的HAL驱动函数HAL_SD_WriteBlocks()向SD卡写入数据。

/* USER CODE BEGIN BeforeWriteDMABlocksSection */
/* can be used to modify previous code / undefine following code / add code */
/* USER CODE END BeforeWriteDMABlocksSection */
/**
  * @brief  Writes block(s) to a specified address in an SD card, in DMA mode.
  * @param  pData: Pointer to the buffer that will contain the data to transmit
  * @param  WriteAddr: Address from where data is to be written
  * @param  NumOfBlocks: Number of SD blocks to write
  * @retval SD status
  */
__weak uint8_t BSP_SD_WriteBlocks_DMA(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks)
{
  uint8_t sd_state = MSD_OK;

  /* Write block(s) in DMA transfer mode */
  if (HAL_SD_WriteBlocks_DMA(&hsd, (uint8_t *)pData, WriteAddr, NumOfBlocks) != HAL_OK)
  {
    sd_state = MSD_ERROR;
  }

  return sd_state;
}

4 SD卡文件管理功能的实现

(1)主程序功能

        有了CubeMX生成的代码,就可以直接使用FatFS的API函数管理SD卡上的文件系统了。本示例在主程序中创建两级菜单,在SD卡上测试使用文件管理功能。

        将KEY_LED和FILE_TEST添加到项目的搜索路径。

        其中,FILE_TEST目录下包含文件file_opera.h和file_opera.c,使用其中的函数,或添加新的测试函数。详见参考文章2。

        添加用户功能代码后的主程序代码如下:

//继续前文main.c程序代码

  /* USER CODE BEGIN 2 */
  // Start Menu
  printf("Demo14_1: FatFS on SD card.\r\n\r\n");

  //SD detect
  BSP_SD_IsDetected();
  if(status)
	  printf("SD has been present.\r\n");
  else
	  printf("SD has been absent.\r\n");

  //Mount the file system
  FRESULT res = FR_OK;
  res=f_mount(NULL,"0:",1);		// Uninstall the drive first
  //HAL_Delay(10);

  res = f_mount(&SDFatFS, "0:", 1);	// Mount the SD card file system
  // If the mount is not successful, uninstall it first and then mount it.
  if(res != FR_OK)
  {
	  BYTE workBuffer[4*512];
	  res=f_mkfs("0:",FM_FAT32,0,workBuffer,4*512);
  }
  else
  {
	  res=f_mount(NULL,"0:",1);				//uninstall it first
	  HAL_Delay(10);
	  res=f_mount(&SDFatFS,"0:",1);			//and then mount it
  }


  if (res == FR_OK)							// Mounted successfully
	  printf("FatFS is mounted, OK.\r\n\r\n");
  else
	  printf("No file system, to format.\r\n\r\n");

  //Menu Item 1
  printf("[S2]KeyUp   =Format SD card. \r\n");
  printf("[S1]KeyRight=SD card info. \r\n");
  printf("[S4]KeyLeft =FAT disk info. \r\n");
  printf("[S3]KeyDown =Next menu page. \r\n\r\n");

  KEYS waitKey;
  while(1)
  {
	  waitKey=ScanPressedKey(KEY_WAIT_ALWAYS); 	// Waiting for the button pressed

	  if(waitKey == KEY_UP) 					// KeyUp=Format SD card
	  {
		  BYTE workBuffer[4*BLOCKSIZE];   		// Working cache area
		  DWORD clusterSize=0;	 				// The cluster size must be greater than or equal to 1 sector, 0 is automatically set.
		  printf("\r\nFormating (10secs)...\r\n");
		  FRESULT res=f_mkfs("0:", FM_FAT32, clusterSize, workBuffer, 4*BLOCKSIZE); //FM_FAT32
		  if (res == FR_OK)
			  printf("Format OK, to reset.\r\n");
		  else
			  printf("Format fail, to reset.\r\n");
	  }
	  else if(waitKey == KEY_LEFT)  			//KeyLeft =FAT disk info
		  fatTest_GetDiskInfo();
	  else if (waitKey == KEY_RIGHT)  			//KeyRight=SD card info"
		  SDCard_ShowInfo();
	  else
		  break;								//turn into the next menu

	  printf("Reselect menu item or reset.\r\n\r\n");
	  HAL_Delay(500);							//Delay, eliminate the impact of key jitter
  }


  //Menu Item 2
  printf("\r\n");
  printf("[S2]KeyUp   =Write files.\r\n");
  printf("[S3]KeyDown =Get a file info.\r\n");
  printf("[S4]KeyLeft =Read a TXT file.\r\n");
  printf("[S1]KeyRight=Read a BIN file.\r\n\r\n");

  HAL_Delay(500);

  while(2)
  {
	  waitKey=ScanPressedKey(KEY_WAIT_ALWAYS);

	  //Test using long file names
	  if (waitKey==KEY_UP )
	  {
		  fatTest_WriteTXTFile("SD_readme.txt",2025,07,11);
		  fatTest_WriteTXTFile("SD_help.txt",  2025,07,11);
		  fatTest_WriteBinFile("SD_ADC2000.dat",30, 2000);
		  fatTest_WriteBinFile("SD_ADC1000.dat",100,1000);
		  f_mkdir("0:/SD_SubDirectory");
		  f_mkdir("0:/SD_Documents");
	  }
	  else if (waitKey==KEY_LEFT )
		  fatTest_ReadTXTFile("SD_readme.txt");
	  else if (waitKey==KEY_RIGHT)
		  fatTest_ReadBinFile("SD_ADC2000.dat");
	  else if (waitKey==KEY_DOWN)
		  fatTest_GetFileInfo("SD_ADC1000.dat");

	  printf("Reselect menu item or reset.\r\n\r\n");

	  HAL_Delay(500);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

//以下的main.c代码未完,下文待续

        完成外设初始化后,关键的操作就是先检查SD卡是否插入,然后先卸载驱动器再挂在驱动器,多数情况下遇到的SD卡不能加载、不能初始化、不能读写,都是违反了这样的原则。只要改过来,一切都顺利了。

        f_mount(&SDFatFS,"0:",1)函数中,SDFatFS是在文件fatfs.c中定义的FATFS类型变量,表示SD卡上的文件系统。系统中只有一个驱动器,驱动器号是“0:”。这样挂载后,SDFatFS就表示逻辑驱动器0上的文件系统。

        如果函数f_mount()的返回值为FR_OK,就表示驱动器挂载成功,SD卡已经被格式化过,可以进行文件系统的操作了;否则,就可能是没有文件系统,需要先执行函数f_mkfs()进行SD卡格式化操作。

        不管函数f_mount()的返回结果是什么,程序都会在串口助手上显示一组菜单,内容如下:

[S2]KeyUp   =Format SD card. 
[S1]KeyRight=SD card info. 
[S4]KeyLeft =FAT disk info. 
[S3]KeyDown =Next menu page. 

        使用开发板上的4个按键进行选择操作,函数ScanPressedKey()是文件keyled.h中定义的轮询方式检测按键的函数。按下KeyDown键后会显示第二组菜单,内容如下:

[S2]KeyUp   =Write files.
[S3]KeyDown =Get a file info.
[S4]KeyLeft =Read a TXT file.
[S1]KeyRight=Read a BIN file.

        按下某个按键就会执行相应的操作,在某一级菜单中可以重新按键选择操作。

(2)SD卡格式化

        要在SD卡上使用FAT文件系统,必须先使用函数f_mkfs()在SD卡上创建文件系统,也就是进行SD卡格式化操作。在主程序中,响应菜单项[S2]KeyUp=Format SD card,对SD卡进行格式化操作的代码如下:

	  if(waitKey == KEY_UP) 					// KeyUp=Format SD card
	  {
		  BYTE workBuffer[4*BLOCKSIZE];   		// Working cache area
		  DWORD clusterSize=0;	 				// The cluster size must be greater than or equal to 1 sector, 0 is automatically set.
		  printf("\r\nFormating (10secs)...\r\n");
		  FRESULT res=f_mkfs("0:", FM_FAT32, clusterSize, workBuffer, 4*BLOCKSIZE); //FM_FAT32
		  if (res == FR_OK)
			  printf("Format OK, to reset.\r\n");
		  else
			  printf("Format fail, to reset.\r\n");
	  }

        在FAT文件系统中,簇(Cluster)的大小必须是扇区的整数倍,在调用函数f_mkfs()进行SD卡格式化时,如果设置簇大小为0,就由FatFS自动确定簇的大小。由于测试中使用的SD卡容量是8GB,不能再使用FAT12或FAT16文件系统,而只能使用FAT32文件系统,因此在函数f_mkfs()中指定文件系统类型为FM_FAT32。

(3)获取FAT磁盘信息

        将SD卡格式化后,我们就可以通过FatFS的API函数f_getfree()获取FAT磁盘信息。菜单项[S4]KeyLeft=FAT disk info的响应代码就是调用了测试函数fatTest_GetDiskInfo()。对于SD卡,参数_MAX_SS和_MIN_SS都等于512。

        在已经执行菜单项[S2]KeyUp=Write files创建了4个文件和2个目录后,一个8GB的SD卡的磁盘信息在串口助手上显示的内容如下:

FAT type=  3
[1=FAT12,2=FAT16,3=FAT32,4=exFAT] 
Sector size(bytes)=  512
Cluster size(sectors)=  64
Total cluster count=  236321
Total sector count=  15124544
Total space(MB)=  7385
Free cluster count=  236320
Free sector count=  15124480
Free space(MB)=  7385
Get FAT disk info OK.
Reselect menu item or reset.

(4)获取SD卡信息

        菜单项[S1]KeyRight=SD card info用于获取SD卡的原始参数信息,包括SD卡类型、数据块个数、容量等信息。这个菜单项的响应代码调用了文件mian.h/.c中定义和实现的函数SDCard_ShowInfo()。

        函数SDCard_ShowInfo()显示的是SD卡的原始信息,即使SD卡没有被格式化,也可以返回这些信息

Card Type= 1 
Card Version= 1 
Card Class= 1461 
Relative Card Address= 4660 
Block Count= 15126528 
Block Size(Bytes)= 512 
LogiBlockCount= 15126528 
LogiBlockSize(Bytes)= 512 
SD Card Capacity(MB)= 7386 

(5)其它代码

         main.h私有函数声明:

/* USER CODE BEGIN Private defines */
void SDCard_ShowInfo();	//SDCard Show Info
/* USER CODE END Private defines */

        main.c私有函数定义:

/* USER CODE BEGIN 4 */
/* Show SD Information */
void SDCard_ShowInfo()
{
	HAL_SD_CardInfoTypeDef cardInfo;
	HAL_StatusTypeDef res=HAL_SD_GetCardInfo(&hsd,&cardInfo);

	if (res!=HAL_OK)
	{
		printf("HAL_SD_GetCardInfo() error. \r\n");
		return;
	}

	printf("\r\n*** SD card info *** \r\n\r\n");

	printf("Card Type= %ld \r\n", cardInfo.CardType);
	printf("Card Version= %ld \r\n", cardInfo.CardVersion);
	printf("Card Class= %ld \r\n", cardInfo.Class);
	printf("Relative Card Address= %ld \r\n", cardInfo.RelCardAdd);
	printf("Block Count= %ld \r\n", cardInfo.BlockNbr);
	printf("Block Size(Bytes)= %ld \r\n", cardInfo.BlockSize);
	printf("LogiBlockCount= %ld \r\n", cardInfo.LogBlockNbr);
	printf("LogiBlockSize(Bytes)= %ld \r\n", cardInfo.LogBlockSize);

	uint32_t cap = cardInfo.BlockNbr/1024;	//KB
	cap	= cap*cardInfo.BlockSize;			//KB
	cap = cap/1024;							//MB

	printf("SD Card Capacity(MB)= %ld \r\n\r\n", cap);
	//return HAL_OK;
}

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}

//SD card detect
//Low level indicates that a card is inserted
uint8_t BSP_SD_IsDetected(void)
{
   if(HAL_GPIO_ReadPin(SD_DETECT_GPIO_PORT, SD_DETECT_PIN) == GPIO_PIN_RESET)
  	 status = SD_PRESENT;
   else
  	 {
	   status = SD_NOT_PRESENT;
  	 }
   return status;
}
/* USER CODE END 4 */

         fatfs.c私有文件包含、函数重写:

/* USER CODE BEGIN Variables */
#include "file_opera.h"
/* USER CODE END Variables */

/**
  * @brief  Gets Time from RTC
  * @param  None
  * @retval Time in DWORD
  */
DWORD get_fattime(void)
{
  /* USER CODE BEGIN get_fattime */
	return fat_GetFatTimeFromRTC();
  /* USER CODE END get_fattime */
}

三、运行与调试

        下载后,可以运行、调试。只要注意到程序的鲁棒性,不要忽略了SD卡状态检测这一细节。一般情况下都可以完美实现SD的FATFS文件操作。

        本文相关代码运行、调试后,各个操作结果通过串口助手存储到一个TXT文件,读者可以自己去印证:

Demo14_1: FatFS on SD card.

SD has been present.
FatFS is mounted, OK.

[S2]KeyUp   =Format SD card. 
[S1]KeyRight=SD card info. 
[S4]KeyLeft =FAT disk info. 
[S3]KeyDown =Next menu page. 


Formating (10secs)...
Format OK, to reset.
Reselect menu item or reset.


*** SD card info *** 

Card Type= 1 
Card Version= 1 
Card Class= 1461 
Relative Card Address= 4660 
Block Count= 15126528 
Block Size(Bytes)= 512 
LogiBlockCount= 15126528 
LogiBlockSize(Bytes)= 512 
SD Card Capacity(MB)= 7386 

Reselect menu item or reset.


*** FAT disk info ***.

FAT type=  3
[1=FAT12,2=FAT16,3=FAT32,4=exFAT] 
Sector size(bytes)=  512
Cluster size(sectors)=  64
Total cluster count=  236321
Total sector count=  15124544
Total space(MB)=  7385
Free cluster count=  236320
Free sector count=  15124480
Free space(MB)=  7385
Get FAT disk info OK.
Reselect menu item or reset.


[S2]KeyUp   =Write files.
[S3]KeyDown =Get a file info.
[S4]KeyLeft =Read a TXT file.
[S1]KeyRight=Read a BIN file.

Write file OK: SD_readme.txt
Write file OK: SD_help.txt
Write file OK: SD_ADC2000.dat
Write file OK: SD_ADC1000.dat
Reselect menu item or reset.

File info of:  SD_ADC1000.dat
File size(bytes)= 418
File attribute=  32
File Name=  SD_ADC1000.dat
File Date= 2025- 7-11
File Time= 11:27: 2
Reselect menu item or reset.

Reading TXT file: Line1: Hello, FatFS
Reading TXT file: Line2: UPC, AnHui Hefei
Reading TXT file: Line3: Date: 2025-7-11
Reselect menu item or reset.

Reading BIN file: ADC1-IN5
Sampling freq: 2000
Point count: 30
Reselect menu item or reset.

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐