# 文件系统

写在前面: 这里目前只实现 SFUD+ff14b 基于标准库,板载 W25Q64FV 8M 的 FLASH.

# 维基百科介绍

计算机的文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据不必关心数据实际保存在硬盘(或者光盘)的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中.

其实这么长一段话的核心就在最前那里,文件系统其实就是一个抽象的数据类型。位于物理存储介质之上.
与一般的 ADT 相比文件系统仅仅是更加庞大了,无非就是 一堆数据类型加上增删查改.
话是这么说,我是写不出来这么庞大的 ADT, 狗头.

# 个人理解

言归正传,当我们使用文件系统时,数据都以文件的形式存储.
如果想要留下一块空间的话那么建议手动限制一下 flash 大小或者做一下首地址偏移.

写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中
需要读取数据时,可以从该目录中找到该文件的索引,进而在相应的地址中读取数据
文件系统的存在使我们在存储数据时,不再是简单的向某个物理地址直接读写,而是要遵循它的读写格式
一个完整的文件经过 逻辑转换 可能被分成 多段 并存储到不连续的物理地址,以及使用目录或链表的方式来获知下一段的位置

简单来说,这个转换部分可以理解为,当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入,以及写入一些原始数据以外的信息 (如目录)
类似于我们去寄快递,逻辑层就是快递站。你把东西给快递之后,你和收件人不需要知道快递是怎么到的,只需要知道目的地址就好.
这个 "快递站" 的代码实现,我们习惯称之为文件系统

# 搭载文件系统的动机

为了让单片机能够更好的和服务器进行交互,我打算移植一个文件系统到 STM32F4 上.
板子上自带了一块 8M 的 FLASH, 我打算让文件系统彻底接管这块 FLASH, 如果以后有可能的话让 文件系统接管片上 FLASH.

# 为什么是 Fatfs

嘿嘿,我只知道 FatFs, 所以我选了它,但是后面我找了一下还有不少的针对于 FLASH 的文件系统.
例如,esayflash, jffs2, yaffs, Cramfs, SPIFFS... 但是这些都是针对于 FLASH 的文件系统,而 Fatfs 则是可以兼容各种设备的较为完善的文件系统,便于以后扩展.
(害,说那么多,其实主要还是只知道 Fatfs)

# FatFS

FATFS 是面向小型嵌入式系统的一种通用的 FAT 文件系统,它由 ANSI C 语言编写且完全独立于底层的 I/O 介质,因此它可以很容易的不加修改的移植到其他处理器当中,就可以利用文件系统的各种函数,对已格式化的 SD 卡的文件进行读写

# 源码获取

可以去 Fatfs 官网自行下载传送门
也可以在这里走阿里云盘下载传送门
个人建议是去官网下载,我这里只有 ff14b 版本的 fatfs, 以后可能会有新的,我不一定会同步到阿里云盘

# 文件分布

document 文件
document
source 文件夹文件分布
FatFs_source

# 接口函数

FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);							/* 打开或创建文件 */
FRESULT f_close (FIL* fp);														/* 关闭文件 */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);						/* 读取文件 */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);				/* 写入文件 */
FRESULT f_opendir (DIR* dp, const TCHAR* path);									/* 打开文件夹 */
FRESULT f_closedir (DIR* dp);													/* 关闭文件夹 */
FRESULT f_readdir (DIR* dp, FILINFO* fno);										/* 读取文件夹 */
FRESULT f_mkdir (const TCHAR* path);											/* 创建文件夹 */
FRESULT f_unlink (const TCHAR* path);											/* 删除存在的文件或文件夹 */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);				/* 获取当前卷剩余空间 */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new);				/* 重命名文件或文件夹 */
FRESULT f_chdir (const TCHAR* path);											/* 更改当前路径 */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);						/* 挂载或卸载逻辑卷 */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len);	/* 格式化 (创建) FAT 卷 */
#define f_size(fp) ((fp)->obj.objsize)											/* 获取当前文件大小 */

# 底层驱动

一般来说,我们移植的时候只需要实现一下 iodisk 中的这些个函数即可,如果需要加时间戳,自行实现,这里我不用 rtc 所以不实现时间戳
iodisk.h

# SFUD

这里使用了 一个万能的 FLASH 驱动组件 SFUD. 避免以后修改 FLASH 芯片的时候,还得到处去改也写入的相关方法.
sfud
这里不详细说明 SFUD 的移植,只列出一下用到的函数
关于 sfud 的移植可以参考我另一篇博客传送门 (其实直接去 github 上看人家的 readme 就可以了)

// 用于 disk_init
sfud_err sfud_init(void); 
// 用于获取 要写入的 flash 对象
sfud_flash *sfud_get_device(size_t index);  
// 用于 disk_read 函数
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data);
// 用于 disk_write 函数
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);

# disk_initialize

对接了 SUFD 这一步还是很好实现的。直接 sfud_init 后判断 是否成功即可
这边为了兼容性和便欲拓展以及减少代码量,额外加了一层 本地函数函数,下面的所有接口实现基本都是这个逻辑
代码实现如下

static FRESULT flash_disk_initialize(void);
DSTATUS disk_initialize(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		sfud_norflash0 = (sfud_flash *)(sfud_get_device_table() + 0);
		res = flash_disk_initialize();
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_F(res, "!!!pdrv is not exit.\r\n");
		break;
	}
	return ((!res) ? RES_OK : RES_PARERR);
}
/**
 * @brief 			FLASH 初始化函数
 *
 * @return FRESULT 	初始化结果
 */
static FRESULT flash_disk_initialize(void)
{
	if (SFUD_SUCCESS == sfud_init())
	{
		return FR_OK;
	}
	else
	{
		DEBUG_LOG_F(FR_INT_ERR, "!!!disk_initialize ERR\r\n");
		return FR_INT_ERR;
	}
}

# disk_write

这里需要注意几点

  1. 在写 flash 之前需要先擦除,所以我们不使用 sfud_write 而是使用 sfud_erase_write , 这样子就不需要自行去实现先擦后写的逻辑了
  2. 对于 文件系统 来说,操作以扇区为单位,而我们的 flash 的读写操作一般以 Byte 为单位,所以需要自行乘上扇区大小

代码实现如下:

static FRESULT flash_write(const BYTE *buff, UINT count, LBA_t sector);
DRESULT disk_write(
	BYTE pdrv,		  /* Physical drive nmuber to identify the drive */
	const BYTE *buff, /* Data to be written */
	LBA_t sector,	  /* Start sector in LBA */
	UINT count		  /* Number of sectors to write */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_write(buff, count, sector);
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_D("!!!pdrv is not exit.\r\n");
		break;
	}
	return ((!res) ? RES_OK : RES_ERROR);
}
/**
 * @brief 			FLASH 写函数
 *
 * @param buff 		要写入的数组
 * @param count 	要写入的扇区数量
 * @param sector 	要写入的扇区的起始地址
 * @return FRESULT 	写操作结果
 */
static FRESULT flash_write(const BYTE *buff, UINT count, LBA_t sector)
{
	sfud_err res;
    // 这里需要对扇区数量转为字节数
	res = sfud_erase_write(sfud_norflash0, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE, buff);
	if (SFUD_SUCCESS == res)
	{
		return FR_OK;
	}
	DEBUG_LOG_F(FR_DISK_ERR, "!!!disk_write ERR\r\n");
	return FR_DISK_ERR;
}

# disk_read

写函数需要注意的地方和读函数一致,别最后读写了半天都只在读写一个字节

代码实现如下:

static FRESULT flash_read(BYTE *buff, UINT count, LBA_t sector);
DRESULT disk_read(
	BYTE pdrv,	  /* Physical drive nmuber to identify the drive */
	BYTE *buff,	  /* Data buffer to store read data */
	LBA_t sector, /* Start sector in LBA */
	UINT count	  /* Number of sectors to read */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_read(buff, count, sector);
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_F(res, "!!!pdrv is not exit.\r\n")
		break;
	}
	return ((!res) ? RES_OK : RES_ERROR);
}
/**
 * @brief 			FLASH 读函数
 *
 * @param buff 		读出的数据
 * @param count		要读出的扇区数量
 * @param sector	数据所在地址
 * @return FRESULT	读取结果
 */
static FRESULT flash_read(BYTE *buff, UINT count, LBA_t sector)
{
	sfud_err res;
	res = sfud_read(sfud_norflash0, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE, buff);
	if (SFUD_SUCCESS == res)
	{
		return FR_OK;
	}
	DEBUG_LOG_F(FR_DISK_ERR, "!!!disk_read ERR\r\n");
	return FR_DISK_ERR;
}

# disk_ioctl

这里是 FatFs 获取 设备扇区大小,扇区数量和 块数量的的地方。对于 FLASH 的扇区其实手册里都有。不过其实我们可以自行划分
例如我的代码,8M 的 FLASH 分为 8 个块,一个块 2048 个扇区,一个扇区 512 字节,整个 FLASH 分为 2048*8 个扇区 一共 8M,
也可以按 FLASH 手册上写的 一个 FLASH 1 个块,2048 个扇区,一个扇区 4096 字节.
这里如果使用 超过 512 大小的扇区 自行去 ffconf.h 中修改 FF_SS_MAX 的宏
代码实现如下:

#define FLASH_SECTOR_SIZE (512)
#define FLASH_SECTOR_COUNT (2048*8) /* 2048*4096/1024/1024=8(MB) */
#define FLASH_BLOCK_SIZE (8)
static DRESULT flash_disk_ioctl(BYTE cmd, void *buff);
DRESULT disk_ioctl(
	BYTE pdrv, /* Physical drive nmuber (0..) */
	BYTE cmd,  /* Control code */
	void *buff /* Buffer to send/receive control data */
)
{
	DRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_disk_ioctl(cmd, buff);
		break;
	default:
		res = RES_PARERR;
		DEBUG_LOG_D("!!!disk_ioctl ERR\r\n");
		break;
	}
	return res;
}
/**
 * @brief 			获取 FLASH 扇区 块 数据
 *
 * @param cmd 		获取指令
 * @param buff 		对应数组
 * @return DRESULT 	指令执行结果
 */
static DRESULT flash_disk_ioctl(BYTE cmd, void *buff)
{
	switch (cmd)
	{
	case CTRL_SYNC:
		return RES_OK;
	case GET_SECTOR_COUNT:
		/* 扇区数量 */
		*(DWORD *)buff = FLASH_SECTOR_COUNT; // W25Q64 有 2048 个大小为 4K 的扇区
		return RES_OK;
	case GET_SECTOR_SIZE:
		/* 扇区大小 */
		*(WORD *)buff = FLASH_SECTOR_SIZE; //spi flash 的扇区大小是 4096 Bytes
		return RES_OK;
	case GET_BLOCK_SIZE:
		/* 块大小 */
		*(DWORD *)buff = FLASH_BLOCK_SIZE;
		return RES_OK;
	default:
		return RES_PARERR;
	}
}

# disk_status

这个就很简单了,想实现就实现,不想实现就直接 return FR_OK; 就好了
如果想实现 可以使用 sfud_read 读取一下 flash id, 判断一下作为返回值即可
代码实现如下:

DSTATUS disk_status(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	if (DEV_FLASH == pdrv)
	{
		return RES_OK; // 直接返回 OK 即可
	}
	else
	{
		DEBUG_LOG_D("!!!pdrv is not exit.\r\n");
		return RES_PARERR;
	}
}

# iodisk.c 文件之外的东西

fatfs 还有一个 ffconf.h 文件,这里会涉及到 扇区大小,读写功能,以及一些函数的禁用和启用

这里只列出常用的,贴 code 太占空间

#define FF_FS_READONLY	0       // 只读标识符,如果你的文件系统只需要实现读功能就 改为 1, disk write 等函数就不会被编译
#define FF_USE_MKFS		1       // 使用格式化函数,我们 FLASH 就必须使用这个函数,因为我们只能通过 FatFs 来格式化 FLASH
#define FF_USE_LABEL	1       // 是否使用卷标签,这个看个人习惯,FLASH 就一块,也无所谓也不用标签
#define FF_USE_STRFUNC	1       // 使用 f_putc, f_getc 等函数,更详细的 子级宏定义,自行查看文件
#define FF_CODE_PAGE	932     // 文件名编码格式,默认万国码
#define FF_USE_LFN		1       // 是否启用长文件名
#define FF_MAX_LFN		255     // 文件名最大长度
#define FF_VOLUMES		1       // 文件系统 物理卷 的数据量,现在只有一个 FLASH 所以是 1
#define FF_MIN_SS		512     // 扇区最小大小,这个一般不变动
#define FF_MAX_SS		4096    // 扇区最大大小,更具情况自行调整

# 完整代码

整个 iodisk.c 文件如下

/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/
#include "ff.h"		/* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "sfud.h"
/* Definitions of physical drive number for each drive */
// #define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
#define DEV_FLASH 0
// #define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
// #define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
// FLASH 划分扇区和块
// FLASH 大小 = FLASH_SECTOR_SIZE * FLASH_SECTOR_COUNT
#define FLASH_SECTOR_SIZE (512)
#define FLASH_SECTOR_COUNT (2048*8)
#define FLASH_BLOCK_SIZE (8)
// DBEUG 信息
#define DEBUG_DSTRING_FORMAT "[FATFS]: %s. \r\n"
#define DEBUG_FSTRING_FORMAT "[FATFS]: %s. (Error Code: %d(%s))\r\n"
#define DEBUG_LOG_F(res, string) printf(DEBUG_FSTRING_FORMAT, string, res, FR_Table[res]);
#define DEBUG_LOG_D(string) printf(DEBUG_DSTRING_FORMAT, string);
/*-----------------------------------------------------------------------*/
/* static gloal variable */
/*-----------------------------------------------------------------------*/
// 错误状态码
const char *FR_Table[] =
	{
		"FR_OK: Succeeded",																 /* (0) Succeeded */
		"FR_DISK_ERR: A hard error occurred in the low level disk I/O layer ",			 /* (1) A hard error occurred in the low level disk I/O layer */
		"FR_INT_ERR: Assertion failed ",												 /* (2) Assertion failed */
		"FR_NOT_READY: The physical drive cannot work ",								 /* (3) The physical drive cannot work */
		"FR_NO_FILE: Could not find the file ",											 /* (4) Could not find the file */
		"FR_NO_PATH: Could not find the path ",											 /* (5) Could not find the path */
		"FR_INVALID_NAME: The path name format is invalid ",							 /* (6) The path name format is invalid */
		"FR_DENIED: Access denied due to prohibited access or directory full ",			 /* (7) Access denied due to prohibited access or directory full */
		"FR_EXIST: Access denied due to prohibited access ",							 /* (8) Access denied due to prohibited access */
		"FR_INVALID_OBJECT: The file/directory object is invalid ",						 /* (9) The file/directory object is invalid */
		"FR_WRITE_PROTECTED: The physical drive is write protected ",					 /* (10) The physical drive is write protected */
		"FR_INVALID_DRIVE: The logical drive number is invalid ",						 /* (11) The logical drive number is invalid */
		"FR_NOT_ENABLED: The volume has no work area ",									 /* (12) The volume has no work area */
		"FR_NO_FILESYSTEM: There is no valid FAT volume ",								 /* (13) There is no valid FAT volume */
		"FR_MKFS_ABORTED: The f_mkfs() aborted due to any problem ",					 /* (14) The f_mkfs() aborted due to any parameter error */
		"FR_TIMEOUT: Could not get a grant to access the volume within defined period ", /* (15) Could not get a grant to access the volume within defined period */
		"FR_LOCKED: The operation is rejected according to the file sharing policy ",	 /* (16) The operation is rejected according to the file sharing policy */
		"FR_NOT_ENOUGH_CORE: LFN working buffer could not be allocated ",				 /* (17) LFN working buffer could not be allocated */
		"FR_TOO_MANY_OPEN_FILES: Number of open files > FF_FS_LOCK ",					 /* (18) Number of open files > _FS_SHARE */
		"FR_INVALID_PARAMETER: Given parameter is invalid "								 /* (19) Given parameter is invalid */
};
//flash 设备实例
static sfud_flash *sfud_norflash0;
/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	if (DEV_FLASH == pdrv)
	{
		return RES_OK; // 直接返回 OK 即可
	}
	else
	{
		DEBUG_LOG_D("!!!pdrv is not exit.\r\n");
		return RES_PARERR;
	}
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/
static FRESULT flash_disk_initialize(void);
DSTATUS disk_initialize(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		sfud_norflash0 = (sfud_flash *)(sfud_get_device_table() + 0);
		res = flash_disk_initialize();
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_F(res, "!!!pdrv is not exit.\r\n");
		break;
	}
	return ((!res) ? RES_OK : RES_PARERR);
}
/**
 * @brief 			FLASH 初始化函数
 *
 * @return FRESULT 	初始化结果
 */
static FRESULT flash_disk_initialize(void)
{
	if (SFUD_SUCCESS == sfud_init())
	{
		return FR_OK;
	}
	else
	{
		DEBUG_LOG_F(FR_INT_ERR, "!!!disk_initialize ERR\r\n");
		return FR_INT_ERR;
	}
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/
static FRESULT flash_read(BYTE *buff, UINT count, LBA_t sector);
DRESULT disk_read(
	BYTE pdrv,	  /* Physical drive nmuber to identify the drive */
	BYTE *buff,	  /* Data buffer to store read data */
	LBA_t sector, /* Start sector in LBA */
	UINT count	  /* Number of sectors to read */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_read(buff, count, sector);
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_F(res, "!!!pdrv is not exit.\r\n")
		break;
	}
	return ((!res) ? RES_OK : RES_ERROR);
}
/**
 * @brief 			FLASH 读函数
 *
 * @param buff 		读出的数据
 * @param count		要读出的扇区数量
 * @param sector	数据所在地址
 * @return FRESULT	读取结果
 */
static FRESULT flash_read(BYTE *buff, UINT count, LBA_t sector)
{
	sfud_err res;
	res = sfud_read(sfud_norflash0, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE, buff);
	if (SFUD_SUCCESS == res)
	{
		return FR_OK;
	}
	DEBUG_LOG_F(FR_DISK_ERR, "!!!disk_read ERR\r\n");
	return FR_DISK_ERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
static FRESULT flash_write(const BYTE *buff, UINT count, LBA_t sector);
DRESULT disk_write(
	BYTE pdrv,		  /* Physical drive nmuber to identify the drive */
	const BYTE *buff, /* Data to be written */
	LBA_t sector,	  /* Start sector in LBA */
	UINT count		  /* Number of sectors to write */
)
{
	FRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_write(buff, count, sector);
		break;
	default:
		res = FR_DISK_ERR;
		DEBUG_LOG_D("!!!pdrv is not exit.\r\n");
		break;
	}
	return ((!res) ? RES_OK : RES_ERROR);
}
/**
 * @brief 			FLASH 写函数
 *
 * @param buff 		要写入的数组
 * @param count 	要写入的扇区数量
 * @param sector 	要写入的扇区的起始地址
 * @return FRESULT 	写操作结果
 */
static FRESULT flash_write(const BYTE *buff, UINT count, LBA_t sector)
{
	sfud_err res;
	res = sfud_erase_write(sfud_norflash0, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE, buff);
	if (SFUD_SUCCESS == res)
	{
		return FR_OK;
	}
	DEBUG_LOG_F(FR_DISK_ERR, "!!!disk_write ERR\r\n");
	return FR_DISK_ERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/
DWORD get_fattime(void)
{
	// 暂不添加时间获取,需要的话就把 RTC 数据传入这里 */
	// DWORD time; /* 返回当前时间戳 */
	return 0;
}
static DRESULT flash_disk_ioctl(BYTE cmd, void *buff);
DRESULT disk_ioctl(
	BYTE pdrv, /* Physical drive nmuber (0..) */
	BYTE cmd,  /* Control code */
	void *buff /* Buffer to send/receive control data */
)
{
	DRESULT res;
	switch (pdrv)
	{
	case DEV_FLASH:
		res = flash_disk_ioctl(cmd, buff);
		break;
	default:
		res = RES_PARERR;
		DEBUG_LOG_D("!!!disk_ioctl ERR\r\n");
		break;
	}
	return res;
}
/**
 * @brief 			获取 FLASH 扇区 块 数据
 *
 * @param cmd 		获取指令
 * @param buff 		对应数组
 * @return DRESULT 	指令执行结果
 */
static DRESULT flash_disk_ioctl(BYTE cmd, void *buff)
{
	switch (cmd)
	{
	case CTRL_SYNC:
		return RES_OK;
	case GET_SECTOR_COUNT:
		/* 扇区数量 */
		*(DWORD *)buff = FLASH_SECTOR_COUNT; // W25Q64 有 2048 个大小为 4K 的扇区
		return RES_OK;
	case GET_SECTOR_SIZE:
		/* 扇区大小 */
		*(WORD *)buff = FLASH_SECTOR_SIZE; //spi flash 的扇区大小是 4096 Bytes
		return RES_OK;
	case GET_BLOCK_SIZE:
		/* 块大小 */
		*(DWORD *)buff = FLASH_BLOCK_SIZE;
		return RES_OK;
	default:
		return RES_PARERR;
	}
}

# 测试函数

写的比较随意,因为一些特殊原因没有做判断处理,直接流水,自行修改

// 全局变量
FATFS fs; /* Filesystem object */
FATFS *fsptr = &fs;
FIL fil;                    /* File object */
FRESULT res;                /* API result code */
UINT bw, br;                /* Bytes written */
BYTE work[FF_MAX_SS] = {0}; /* Work area (larger is better for processing time) */
BYTE mm[] = "This is file system test file from the STM32F407VGT6.\r\n";
void flash_data_display(uint8_t *buff, uint32_t size, uint32_t flash_add_start)
{
  printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
  for (uint32_t i = 0; i < size; i++)
  {
    if (i % 16 == 0)
    {
      printf("[%08X] ", flash_add_start + i);
    }
    printf("%02X ", buff[i]);
    if (((i + 1) % 16 == 0) || i == size - 1)
    {
      printf("\r\n");
    }
  }
  printf("\r\n");
}
static void file_system_test(void)
{
  char fileName[] = "0:6";
  res = f_mkfs("0:", 0, work, sizeof(work));    // 格式化
  FATFS_LOG(res);
  
  res = f_mount(&fs, "0:", 1);                  // 挂载
  FATFS_LOG(res);
  res = f_getfree("0:", &br, &fsptr);           // 获取空余空间
  FATFS_LOG(res);
  printf("The free space: %dKB.\r\n", br);
  // 打开文件准备写入,没有文件就创建,有文件就覆盖
  res = f_open(&fil, fileName, FA_CREATE_ALWAYS | FA_WRITE);
  //FATFS_LOG(res);
  //res = f_unlink(fileName);
  // 写入
  res = f_write(&fil, mm, strlen(mm), &bw);
  printf("Wrote %d bytes to %s file.\r\n", bw, fileName);
  FATFS_LOG(res);
 
  // 写入完毕,关闭文件
  f_close(&fil);
  FATFS_LOG(res);
  // 打开文件准备读取
  res = f_open(&fil, fileName, FA_READ);
  FATFS_LOG(res);
  // 读出
  res = f_read(&fil, readBuf, strlen(mm), &br);
  printf("%d bytes were read from %s.\r\n", br, fileName);
  printf("%s\r\n", readBuf);
  FATFS_LOG(res);
  // 关闭文件
  f_close(&fil);
  FATFS_LOG(res);
}

# END

等有空我去琢磨琢磨 FatFs 的源码以及各个 API 的使用方法
至于标准库直接对接 FatFs, 我觉得还是没有必要了,HAL 都不必实现,直接套 SFUD 即可
找个时间我把 标准库 和 HAL 库 一致 SFUD 的教程写了


大道五十,天衍四十九,人遁其一!