【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

1 、FAT 文件系统概述

1.1 FAT 概述

文件分配表 (FAT)文件系统是由比尔盖茨与麦克唐纳所开发。它是一种格式,某种程度上 也算是软件,它用于在存储设备 (比如磁盘驱动或内存)上保存和组织文件。它用于方便文 件与目录的访问。

FAT 文件系统提供一种途径来记录文件被创建或更改时的时间标记,并且提供了识别文件大 小的方法。这套系统提供了保存文件其它属性的一套机制,比如文件是否只读,是否应在目 录显示中隐藏,或者是否应在下一次磁盘备份中归档。

FAT 文件系统特别适合消费电子产品中的移动闪存介质,比如数码相机、媒体播放器和闪存 盘等。

FAT 文件系统可以在以下场合中带来帮助:

• 由于 FAT 文件系统具备向后兼容性,用户可以利用记忆棒或软盘在消费电子设备和采用 过时操作系统的计算机之间传输文件。

• FAT 文件系统让用户能够快速删除电子设备上的文件,就像在专业广播媒介中那样。

• FAT16 或 FAT32 的文件系统版本均适用于硬盘卷。

另外,如果用户想要通过软盘访问硬盘卷上的数据 (往往指系统恢复工具)来引导计算机的 话,这些版本也很有用处。

1.1.1 主引导记录

主引导记录(MBR)位于设备物理起始位置上的一个或多个扇区。MBR 的引导区包含 DOS 引导加载程序代码,该代码会在设备格式化后被写入(否则不会被动态 C FAT 文件系统所使 用)。引导区之后是分区表。分区表中含有四个 16 字节的条目,允许设备划分多达四个分区。

分区表条目中含有一些关键信息:分区类型 (动态 C FAT 可识别 FAT12 和 FAT16 的分区类

型)以及分区的起始与结束扇区号。另外还有一个字段指明分区中的扇区总数。如果该数字 为零,则对应的分区是空的可用分区。

【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

某些设备经格式化后没写入 MBR,因此便也没有分区表。目前在动态 C FAT 文件系统中并不 支持这种配置

1.1.2 FAT 分区

有效 FAT 文件系统分区的第一个扇区中包含 BIOS 参数块 (BPB) ,之后是文件分配表 (FAT),再之后是根目录。下图显示了具备两个 FAT 分区的设备。

【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

BIOS 参数块

BPB 中的字段包含该分区的描述信息:

• 每个扇区的字节数;

• 每个簇的扇区数;

• 该分区的扇区总数;

• 根目录下的条目数。

FAT 分配表

文件分配表是一种结构,也是 FAT 文件系统的命名由来。 FAT 中保存了关于簇的分配信息。 一个簇既可以分配给一个文件,也可以使用,还可以标记为坏簇。 FAT 分配表的副本会紧跟 着第一个 FAT 分配表存放。

根目录

根目录具有预定义的位置及大小。根目录有 512 个条目,每个条目 32 字节。根目录中的条目 既可以是空条目,也可以包含文件或子目录名称(以 8.3 格式)、文件大小、上一次修改的日 期时间以及文件或子目录的起始簇号。

数据区

数据区占据了分区中的大部分空间。其中包含文件数据与子目录。请注意,按照惯例,分区 的数据区必须从第 2 簇开始。

欲了解更多信息,请参考 Microsoft® EFI FAT32 文件系统规范。

2 、FatFs 文件系统

2.1 FatFs 概述

FatFs 是适用于小型嵌入式系统的 FAT 文件系统模块。FatFs 是按照 ANSI C 的标准来指定, 且与磁盘 I/O 层完全分隔开。因此, FatFs 与硬件架构完全无关,具有以下特点:

• 兼容 Windows 的 FAT 文件系统。

• 极小的代码量和工作区

• 丰富的配置选项:

– 多卷 (物理驱动与分区)。

– 多个 ANSI/OEM 代码页,包括 DBCS。

– 以 ANSI/OEM 或 Unicode 支持长文件名。

– 支持 RTOS。

– 支持多种扇区大小。

– 只读、小化的 API、 I/O 缓冲等等 ......

– FAT子类型:FAT12、 FAT16 和 FAT32。

– 打开的文件数量:无限制,取决于可用的内存。

– 卷的数量:多达 10 个。

– 文件大小:取决于 FAT 规范。(多达 4G-1 字节)

– 卷的大小:取决于 FAT 规范。(512 字节 / 扇区情况下,支持多达 2T 字节)

– 簇的大小:取决于 FAT 规范。(512 字节 / 扇区情况下,支持多达 64K 字节)

– 扇区的大小:取决于 FAT 规范。(多达 4K 字节)

2.2 FatFs 架构

FatFs 模块是一个中间件,提供许多用于访问 FAT 卷的函数,比如 f_open()、 f_close()、 f_read()、 f_write() 等等 (参考 ff.c)。

该模块没有平台依赖性,只要编译器符合 ANSI C 即可。

使用底层磁盘 I/O 模块来读取 / 写入物理驱动。

采用 RTC 模块来获取当前时间。

底层磁盘 I/O 和 RTC 模块均与 FatFs 模块完全分离。它们必须由用户提供,这是将 FatFs 模 块与其它平台相连的主要工作。


【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

2.3 FatFs 特性

2.3.1 复制文件访问

FatFs 模块默认并不支持对复制文件的共享控制。仅当文件的打开方式为只读模式时,才允许 共享控制。禁止以写入模式对文件进行复制打开,而且打开的文件不允许被重命名和删除,否 则会破坏卷的 FAT 结构。

当将 _FS_LOCK 设为 1 或更大的数值时,也可使用文件共享控制。 该数值设定了同时管理

的文件数目。在这种情况下,如果尝试 “ 打开 ”、“ 重命名 ” 或 “ 删除 ” 等违反上述文件共享规 则的操作,则文件功能失败,提示 FR_LOCKED。如果打开的文件数量大于 _FS_LOCK,那 么 f_open() 函数也会执行失败,提示 FR_TOO_MANY_OPEN_FILES。

2.3.2 可重入性

对不同卷的文件操作是可重入的,并且可以同时工作。对同一个卷的文件操作是不可重入的, 但可利用 _FS_REENTRANT option 选项配置为线程安全。在这种情况下,必须将依赖于操 作系统的同步对象控制函数ff_cre_syncobj()、ff_del_syncobj()、ff_req_grant()和ff_rel_grant() 添加到项目中。

当卷被其它任务使用时,如果文件函数被调用,则这个文件函数会保持挂起,直至该任务结 束。如果等待时间超过 _TIMEOUT 所设定的时间,那么文件函数会以 FR_TIMEOUT 退出。 某些 RTOS 并不支持这种超时功能。

f_mount() 和 f_mkfs() 函数是例外。这些函数对于同一个卷并不具备可重入性。当使用这些函 数时,所有其它任务必须关闭该卷上的对应文件,以避免对卷的访问。

请注意,这一节描述的是 FatFs 模块本身的可重入性,但底层磁盘 I/O 层也必须是可重入的。

2.3.3 长文件名

FatFs 模块从版本 0.07 开始便支持长文件名 (LFN)。除了 f_readdir() 函数以外,一个文件 的 SFN 和 LFN 这两种不同的文件名对于文件函数来说是透明的。如需使能 LFN 功能,请将 _USE_LFN 设为 1、 2 或 3,然后在项目中添加 Unicode 代码转换函数 ff_convert() 和 ff_wtoupper()。LFN 功能需要附加一个特定的工作缓冲。缓冲的大小可根据可用内存大小,利 用 _MAX_LFN 来进行配置。长文件名可达 255 个字符,所以 _MAX_LFN 应设为 255,以实 现完全的 LFN 功能。如果工作缓冲的大小不足以存放给定的文件名,则文件函数执行失败并 提示 FR_INVALID_NAME。当以可重入功能使能 LFN 特性时,_USE_LFN 必须设为 2 或 3。 这种情况下,文件函数会在栈或堆里分配工作缓冲。工作缓冲会占据 (_MAX_LFN + 1) * 2 个 字节。

当使能 LFN 功能时,模块大小会根据所选择的代码页而有所增大。右表显示了当以某些代码 页使能 LFN 功能时,所增加的字节数。

2.4、FatFs API

FatFs 的 API 层用于执行文件系统 API。它采用磁盘 I/O 接口与适当的物理驱动通信。这些 API 可划分为四组:

• 操作逻辑卷或分区的 API 分组。

• 操作目录的 API 分组。

• 操作文件的 API 分组。

• 操作文件和目录的 API 分组。

以下列出了 FatFs 访问 FAT 卷时所能执行的操作:

• f_mount():挂载 / 卸载逻辑磁盘

• f_open():打开 / 创建文件

• f_close():关闭文件

• f_read():读取文件

• f_write():写入文件

• f_lseek():移动读 / 写指针,扩大文件的大小

• f_truncate():截取文件到当前已读 / 已写指针位置

• f_sync():同步内存中的数据到磁盘

• f_opendir():打开一个目录

• f_readdir():读取目录条目

• f_getfree():获取空闲的簇的数量

• f_stat():检查对象是否存在,并获取其状态

• f_mkdir():创建目录

• f_unlink():删除文件或目录

• f_chmod():更改属性

• f_utime():更改时间戳

• f_rename():重命名 / 移动文件或目录

• f_chdir():更改当前目录

• f_chdrive():更改当前驱动

• f_getcwd():检索当前目录

• f_getlabel():获取卷标签

• f_setlabel():设置卷标签

• f_forward():将文件数据直接转发至流

• f_mkfs():在驱动上创建文件系统

• f_fdisk():划分物理驱动

• f_gets():读取字符串

• f_putc():写入字符

• f_puts():写入字符串

• f_printf():写入格式化的字符串

• f_tell():获取当前的读 / 写指针

• f_eof():检验是否达到文件末尾

• f_size():获取文件大小

• f_error():检验文件是否出错

2.5 FatFs 底层 API

由于 FatFs 模块与磁盘 I/O 和 RTC 模块完全分离,所以需要一些底层功能来操作物理驱动: 读 / 写和获取当前时间。因为底层磁盘 I/O 功能和 RTC 模块并非 FatFs 模块的组成部分,所 以必须由用户提供。

FatFs 中间件解决方案为某些支持的磁盘驱动(RAMDisk、uSD、USBDisk)提供底层磁盘 I/O 驱动。

已添加额外接口层 diskio.c,用于为 FatFs 模块动态添加 / 删除(链接)物理介质,提供如下 所述的底层磁盘 I/O 函数:

• disk_initialize():初始化物理磁盘驱动

• disk_status():返回所选物理驱动的状态

• disk_read():读取磁盘中的扇区

• disk_write():将扇区写入磁盘

• disk_ioctl():控制设备的专用功能

• get_fattime():返回当前时间 应用程序禁止调用这些函数,这些函数仅可由 FatFs 文件系统函数调用,比如 f_mount()、 f_read()、 f_write()...

2.6 将 FatFs 整合至 STM32CubeF4

在 STM32CubeF4 解决方案中,已添加额外的接口层,用于动态地添加 / 删除 FatFs 模块的 物理介质。如需以底层磁盘 I/O 驱动来连接 FatFs 模块,用户可以使用 FATFS_LinkDriver() 和 FATFS_UnLinkDriver() 动态地添加或者删除磁盘 I/O 驱动;应用程序可能需要知道当前连 接的磁盘 I/O 驱动数量,这一点可通过 FATFS_GetAttachedDriversNbr() API 来实现。


【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

通用的底层驱动 ff_gen_drv.c/h 位于 FatFs 模块的根目录下。采用两个磁盘 I/O 驱动类型定义 结构,协助动态管理 ff_gen_drv.h 文件下所连接的磁盘驱动,如下所述:


【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

如需将 FatFs 模块与底层磁盘 I/O 驱动相连接,用户可以使用以下 API:

• FATFS_LinkDriver():用于动态添加磁盘 I/O 驱动。

• FATFS_UnLinkDriver():用于动态删除磁盘 I/O 驱动。

• FATFS_GetAttachedDriversNbr():了解当前所连接的磁盘 I/O 驱动数量

2.6.1 FATFS_LinkDriver()

该函数用于连接兼容的磁盘 I/O 驱动,并增加已激活连接的驱动的数量。如果成功则返回 0, 失败则返回 1。

注 :因为 FatFs方 面 的 限 制 , 所 连 接 的 磁 盘 大 数 目 ( _VOLUMES )为 10个。

FATFS_LinkDriver 的实现:

<code>uint8_t FATFS_LinkDriver(Diskio_drvTypeDef *drv, char *path) 
{
uint8_t ret = 1; uint8_t DiskNum = 0;
if(disk.nbr <= _VOLUMES) {
disk.drv[disk.nbr] = drv;
DiskNum = disk.nbr++;
path[0] = DiskNum + '0';
path[1] = ':';
path[2] = '/';
path[3] = 0;
ret = 0;
}
return ret;
}/<code>

2.6.2 FATFS_UnlinkDriver()

该函数用于解除与磁盘 I/O 驱动的连接,并减少已激活连接的驱动数量。如果成功则返回 0, 失败则返回 1。

FATFS_UnLinkDriver 的实现:

<code>uint8_t FATFS_UnLinkDriver(char *path)  

{
uint8_t DiskNum = 0;
uint8_t ret = 1;
if(disk.nbr >= 1)
{
DiskNum = path[0] - '0';
if(DiskNum <= disk.nbr)0
{
disk.drv[disk.nbr--] = 0;
ret = 0;
}
}
return ret;
}/<code>

2.6.3 FATFS_GetAttachedDriverNbr()

该函数返回已连接至 FatFs 模块的驱动数量。

FATFS_GetAttachedDriversNbr 的实现:

<code>uint8_t FATFS_GetAttachedDriversNbr(void)
{ return disk.nbr; }
/<code>

2.7 将自己的磁盘连接至 FatFs

如果工作存储控制模块可用,应通过粘合函数连接至 FatFs,而不是进行修改。用户可以开发 适当的磁盘 I/O 底层驱动来连接任意的新磁盘(mynewdisk_diskio.c/.h),并将这些驱动文件 保存在:\\Middlewares\\Third_Party\\FatFs\\src\\drivers。

值得注意的是,所提供的 FatFs 磁盘 I/O 底层驱动依赖于板级 BSP 驱动。如需移除这种 BSP 依赖性,用户可以将 “BSP_...” 的 API 调用替换为自己的代码,以确保实现正确的功能性。

如需从头开始开发磁盘 I/O 底层驱动,用户可以从下述的粘合函数骨架开始,用已定义的 API 将现有的存储控制模块添加到 FatFs。

适用于 FatFs 的底层磁盘 I/O 模块骨架:

<code>/*-------------------------------------------------------------------------*/ 
/* mynewdisk_diskio.c:适用于 FAT 的底层磁盘 I/O 模块框架 */
/*-------------------------------------------------------------------------*/
/* 包含的头文件 -------------------------------------------------------------*/
#include <string.h> #include "ff_gen_drv.h"
/* 私有的宏定义 ---------------------------------------------------------*/
#define BLOCK_SIZE 512 /* Block Size in Bytes */
/* 私有变量 --------------------------------------- */
static volatile DSTATUS Stat = STA_NOINIT; /* 磁盘状态 */
/* 私有函数原型 --------------------------------------------*/
DSTATUS mynewdisk_initialize (void);
DSTATUS mynewdisk_status (void);
DRESULT mynewdisk_read (BYTE*, DWORD, BYTE);
#if _USE_WRITE == 1
DRESULT mynewdisk_write (const BYTE*, DWORD, BYTE);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT mynewdisk_ioctl (BYTE, void*);
#endif /* _USE_IOCTL == 1 */
Diskio_drvTypeDef mynewdisk_Driver =
{
mynewdisk_initialize,
mynewdisk_status,
mynewdisk_read,
#if _USE_WRITE == 1
mynewdisk_write,
#endif /* _USE_WRITE == 1 */
/*------------------------ 初始化驱动 ---------------------------*/
DSTATUS mynewdisk_initialize (void)
{
Stat = STA_NOINIT;
// 在此写入自己的代码,对驱动进行初始化

Stat &= ~STA_NOINIT;
return Stat;
}
/*------------------------- 获取磁盘状态 -----------------------------*/
DSTATUS mynewdisk_status (void)
{
Stat = STA_NOINIT;
// 在此写入自己的代码
return Stat;
}
/*-------------------------- 读取扇区 -----------------------------*/
DRESULT mynewdisk_read (BYTE *buff, /* 保存读取数据的数据缓冲 */
DWORD sector, /* 扇区地址 (LBA) */
BYTE count) /* 读取的扇区数 (1..128) */
{
DRESULT res = RES_ERROR;
// 在此写入自己的代码,读取驱动中的扇区
return res;
}
/*--------------------------- 写入扇区 ---------------------------*/
#if _USE_WRITE == 1
DRESULT mynewdisk_write (const BYTE *buff, /* 需写入的数据缓存指针 */
DWORD sector, /* 扇区地址 (LBA) */
BYTE count) /* 需写入的扇区数 (1..128) */
{
DRESULT res = RES_ERROR;
// 在此写入自己的代码,向驱动写入扇区
return res;
}
#endif /* _USE_WRITE == 1 */
/*------------------------ 其它函数 ----------------------*/
#if _USE_IOCTL == 1
DRESULT mynewdisk_ioctl (BYTE cmd, /* 控制代码 */
void *buff) /* 用于发送 / 接收控制数据的代码 */
{
DRESULT res = RES_ERROR;
// 在此写入自己的代码,控制驱动专用功能 //

CTRL_SYNC、 GET_SECTOR_SIZE、 GET_SECTOR_COUNT、 GET_BLOCK_SIZE //// 如控制同步,获取扇区大小,获取扇区数量,获取块大小等
return res; } #endif /* _USE_IOCTL == 1 *//<string.h>/<code>

底层磁盘 I/O 模块的头文件:

<code>/*-------------------------------------------------------------------------*/
/* mynewdisk_diskio.h:底层磁盘 I/O 模块的头文件 */
/*-------------------------------------------------------------------------*/
/* 避免递归包含的定义 ----------------------------------*/
#ifndef __MYNEWDISK_DISKIO_H #define __MYNEWDISK_DISKIO_H

extern Diskio_drvTypeDef mynewdisk_Driver;

#endif /* __MYNEWDISK_DISKIO_H *//<code>

3、FatFs 应用程序

STM32CubeF4 解决方案中提供了许多基于 FatFs 中间件的应用程序。下表显示了如 何在不同例子中使用 FatFs 中间件组件,这些例子按照复杂度分类,并取决于所使用 的物理驱动接口 (uSD、 RAMDisk、 USBDisk):


【STM32】SD卡读写(七)-在 STM32Cube 上开发 FatFs 相关应用

STM32CubeF4 解决方案所提供的上述 FatFs 应用程序是一套有两种模式的固件:

• 独立模式

• RTOS 模式,使用 FreeRTOS 中间件组件 值得注意的是,当使用或者开发基于意法半导体磁盘 I/O 底层驱动的 FatFs 应用程序时,用户 必须保证合适的堆栈值。

因此,当使用基于 USB 主机大容量存储类的 U 盘时,由于对齐的原因,栈的值必须根据大 扇区大小值 _MAX_SS 来增加。

当在 RTOS 模式中开发任何 FatFs 应用程序时,也必须使用基于 CMSIS-OS 包覆层通用 API 的 FreeRTOS 中间件组件对堆数值进行调整。

3.1 HAL 驱动配置

STM32CubeF4解决方案中所提供的FatFs应用程序是一套用于与各种物理磁盘驱动(uSD、 RAM 盘、 USB 盘)相连接的固件。用户需要某些运行 FatFs 应用程序所必需的 HAL 驱动。 相应的 HAL 驱动可通过 HAL 配置文件 stm32f4xx_hal_conf.h 来使能,解除 HAL 驱动中所使 用的适当模块的注释即可。

HAL 配置文件中,各个支持的磁盘驱动之间的主要差异在于与所使用的磁盘驱动相对应的正 确 HAL 驱动的定义。根据各种驱动,以下宏定义必须可用:

• FatFs_uSD:

– #define HAL_SD_MODULE_ENABLED

• FatFs_RAMDisk:

– #define HAL_SDRAM_MODULE_ENABLED 或

– #define HAL_SRAM_MODULE_ENABLED

• FatFs_USBDisk:

– #define HAL_HCD_MODULE_ENABLED

3.2 FatFs 文件系统配置

FatFs 模块中包含各种配置选项。在这一层级,我们提供信息帮助用户根据所连接的物理磁盘 驱动选择正确的选件,满足用户需求,达到高性能。

3.2.1 可重入性

可重入性是独立和 RTOS 模式配置之间的主要差异,这一点可以在 FatFs 配置文件 ffconf.h 中进行设置。 • 独立模式中禁用可重入性: – #define _FS_REENTRANT 0 • RTOS 模式中使能可重入性: – #define _FS_REENTRANT 1

一旦使能后,用户必须提供依赖于 OS 的同步对象 (#define _SYNC_t osSemaphoreId)

RTOS 模式应用程序的项目需要包含 syscall.c 文件,以提供 OS 依赖函数,可在以下路径找 到:\\Middlewares\\Third_Party\\FatFs\\src\\option

3.2.2 长文件名

FatFs 模块支持长文件名 (LFN)以及 8.3 格式文件名 (SFN)。

请注意,FAT 文件系统上的 LFN 功能是微软公司的专利。虽然在 FAT32 上不是这样,但大多 数 FAT32 驱动包含 LFN 功能。 FatFs 可以通过配置选项切换 LFN 功能。当在商业产品上使 能 LFN 功能时,需要根据终目标获取微软的许可。当使能 LFN 功能时,可以使用 LFN,这 项功能可以在 FatFs 配置文件 ffconf.h 中设置:FatFs 配置文件 ffconf.h 中 (_USE_LFN > 0)

• 禁用 LFN 功能:

– #define _USE_LFN 0

• 使能 LFN 功能,其中 3 ≥ _USE_LFN > 0:

一旦在 ffconf.h 配置文件中使能之后,应用程序项目需要包含 syscall.c/unicode.c 文件,以提 供内存管理功能,该文件可在以下路径中找到:\\Middlewares\\Third_Party\\FatFs\\src\\option

用户在独立模式应用或 RTOS 模式应用中均可使能 LFN 功能。

3.3 FatFs 示例应用程序

如果用户已经连接了自己的磁盘,开发了适当的磁盘 I/O 底层驱动 (mynewdisk_diskio.c/.h), 请参考 第 2.8 章节 : 将自己的磁盘连接至 FatFs,可按照以下方式将其驱动连接至 FatFs 模块 以及使用逻辑磁盘:

<code>/*-------------------------------------------------------------------------*/ 
/* main.c:主程序 */
/*-------------------------------------------------------------------------*/
/* 包括 ---------------------------------------------------------------*/
#include "main.h"

/* 私有变量 --------------------------------------- */
FATFS mynewdiskFatFs; /* 用户逻辑驱动的文件系统对象 */
FIL MyFile; /* 文件对象 */
char mynewdiskPath[4]; /* 用户逻辑驱动路径 */
int main(void)
{
uint32_t wbytes; /* 写入文件的字节计数 */
uint8_t wtext[] = "text to write logical disk"; /* 写入文件的缓冲 */
if(FATFS_LinkDriver(&mynewdisk_Driver, mynewdiskPath) == 0)
{
if(f_mount(&mynewdiskFatFs, (TCHAR const*)mynewdiskPath, 0) == FR_OK)
{
if(f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK)
{
if(f_write(&MyFile, wtext, sizeof(wtext), (void *)&wbytes) == FR_OK);
{
f_close(&MyFile);
}
}
}
}
FATFS_UnLinkDriver(mynewdiskPath);
}/<code>

用户必须包含通用驱动头文件 ff_gen_drv.h 以及磁盘 I/O 模块头文件 mynewdisk_diskio.h

<code>/*-------------------------------------------------------------------------*/ 
/* main.h:main.c 模块的头文件 */
/*-------------------------------------------------------------------------*/

/* 包括 ---------------------------------------------------------------*/
#include "ff_gen_drv.h"
#include "mynewdisk_diskio.h"/<code>

详细应用可参考UM1721手册


分享到:


相關文章: