cubumx版本:6.13.0
芯片:STM32F407VET6
在上一篇文章中介绍了Cubemx的FATFS和SD卡的配置,由于SD卡使用的是SDIO通讯,因此具体驱动不需要自己实现,Cubemx中就可以直接配置然后生成SDIO的驱动,并将SD卡驱动和FATFS绑定。这里我们再实现一个外部FLASH来作为FATFS的另一个存储设备,FLASH的型号为W25Q64,Cubemx的FATFS需要在原来的SD卡配置的基础上增加一个User-defined的配置,用于说明还会在FATFS上挂载一个用户自定义驱动的存储设备。同时挂载多个设备时,FATFS的MIN_SS和MAX_SS范围需要涵盖所有设备的扇区大小,Cubemx的FATFS设置如下:
FATFS参数配置说明
ffconf.h:
/*-----------------------------------------------------------------------------/
/ Function Configurations FATFS功能裁剪配置
/-----------------------------------------------------------------------------*/
#define _FS_READONLY 0 /* 0:Read/Write or 1:Read only */
//定义:设置文件系统是否只读。
//0:读写模式(可读写文件)。
//1:只读模式(只能读取文件)。
//影响:只读模式会禁用写入相关函数,例如 f_write、f_sync、f_unlink 等,减小代码体积。
#define _FS_MINIMIZE 0 /* 0 to 3 */
//定义:设置精简级别,移除部分 API 函数。
//0:启用所有基本函数。
//1:移除 f_stat、f_getfree、f_unlink、f_mkdir、f_truncate 和 f_rename。
//2:在级别 1 的基础上,移除 f_opendir、f_readdir 和 f_closedir。
//3:在级别 2 的基础上,移除 f_lseek。
#define _USE_STRFUNC 2 /* 0:Disable or 1-2:Enable */
//定义:控制字符串相关函数(如 f_gets、f_putc、f_puts 和 f_printf)的启用。
//0:禁用字符串函数。
//1:启用字符串函数,无换行符转换。
//2:启用字符串函数,并在 LF 与 CRLF 之间转换。
#define _USE_FIND 0
//定义:控制目录过滤读取功能(f_findfirst 和 f_findnext)。
//0:禁用。
//1:启用。
//2:启用,同时匹配备用文件名。
#define _USE_MKFS 1
//定义:控制 f_mkfs(格式化磁盘)函数的启用。
//0:禁用。
//1:启用。
#define _USE_FASTSEEK 1
//定义:启用快速文件定位功能。
//0:禁用。
//1:启用(需要文件系统的 SEEK 表支持)。
#define _USE_EXPAND 0
//定义:启用 f_expand(扩展文件大小)功能。
//0:禁用。
//1:启用。
#define _USE_CHMOD 0
//定义:启用属性修改函数(f_chmod 和 f_utime)。
//0:禁用。
//1:启用(需要 _FS_READONLY = 0)。
#define _USE_LABEL 0
//定义:控制卷标操作函数(f_getlabel 和 f_setlabel)。
//0:禁用。
//1:启用。
#define _USE_FORWARD 0
//定义:启用 f_forward 函数,用于流式数据转发(例如,直接发送到 UART)。
//0:禁用。
//1:启用。
/*-----------------------------------------------------------------------------/
/ Locale and Namespace Configurations 读写文件操作的格式设置
/-----------------------------------------------------------------------------*/
#define _CODE_PAGE 850
//定义:设置目标系统使用的 OEM 代码页,用于字符编码。
//常见值:437(美国),850(Latin 1),936(简体中文),932(日文)。
//如果设置不正确,可能导致文件打开失败。
#define _USE_LFN 2 /* 0 to 3 */
#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) */
//定义:_USE_LFN:启用长文件名(LFN);_MAX_LFN:设置最长支持的文件名长度(12~255),建议为255。
//启用LFN时必须添加Unicode处理功能(在option/unicode.c中实现),同时LFN工作缓冲区会占用 //(_MAX_LFN + 1)*2字节,如果文件系统类型为exFAT还会额外占用608字节。当_MAX_LFN设置为255时可支 //持完整的长文件名操作。
//_USE_LFN:
//0:禁用。
//1:启用(使用静态缓冲区,不支持线程安全)。
//2:启用(使用堆栈动态缓冲区)。
//3:启用(使用堆动态缓冲区)。
#define _LFN_UNICODE 0 /* 0:ANSI/OEM or 1:Unicode */
//定义:切换 API 的字符编码方式。
//0: 使用 ANSI/OEM 编码(通常是非 Unicode 的本地编码,如 ASCII)。
//1: 使用 UTF-16(Unicode 编码),支持更多语言字符。
#define _STRF_ENCODE 3
//定义:设置字符串 I/O 函数(如 f_gets、f_puts)在文件中读写的字符编码。
//0:ANSI/OEM。
//1:UTF-16LE。
//2:UTF-16BE。
//3:UTF-8。
#define _FS_RPATH 0 /* 0 to 2 */
//定义:支持相对路径。
//0:禁用相对路径。
//1:启用相对路径(支持 f_chdir 和 f_chdrive)。
//2:在级别 1 的基础上启用 f_getcwd。
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/----------------------------------------------------------------------------*/
#define _VOLUMES 3
//定义:设置支持的逻辑驱动器数(卷)。
/* USER CODE BEGIN Volumes */
#define _STR_VOLUME_ID 0 /* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */
//定义:切换卷标(Volume ID)的格式。如果设置为1启用字符串模式,还需定义_VOLUME_STRS
//0: 卷标只使用数字(如 0:, 1: 表示逻辑驱动器)。
//1: 卷标可以使用字符串。
#define _VOLUME_STRS "RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
//定义:每个逻辑驱动器单独的字符串ID。例如:"SD1" 表示第一个 SD 卡设备,"USB1" 表示第一个 USB 存储设 //备。注意:字符串卷标的字符限制为A-Z和0-9,数量应等于_VOLUMES 的值。
/* USER CODE END Volumes */
#define _MULTI_PARTITION 0 /* 0:Single partition, 1:Multiple partition */
//定义:是否支持切换一个物理设备上的多个分区。
//0: 不支持多分区。每个逻辑驱动器号固定绑定到一个物理驱动器号。仅能挂载物理驱动器上存在的第一个 //FAT 分区。例如一个SD卡中有C盘和D盘两个分区,挂载时只会挂载SD卡第一个分区盘。
//1: 支持多分区。可以通过 VolToPart[]数组配置逻辑驱动器与物理驱动器、分区的绑定关系。还可以使用 //f_fdisk()函数操作分区表。适用场景:在需要管理多个分区(例如 SD 卡或硬盘有多个分区)的场景下启 //用。
#define _MIN_SS 512 /* 512, 1024, 2048 or 4096 */
#define _MAX_SS 4096 /* 512, 1024, 2048 or 4096 */
//定义:设置存储设备扇区大小范围。
//值:SD卡通常为512,外部FLASH可能会更大,所用存储设备扇区大小应该在_MIN_SS和_MAX_SS之间,如果
//_MAX_SS大于_MIN_SS,则FATFS会调用disk_ioctl()函数中的GET_SECTOR_SIZE命令去获取设备扇区大小
#define _USE_TRIM 0
//定义:是否支持ATA-TRIM指令。启用该功能需要在disk_ioctl()函数中实现CTRL_TRIM命令,用于触发 //TRIM操作。适用场景:使用SSD或其他支持TRIM的存储设备时,启用此功能可以提高性能和延长设备寿命。
//0: 禁用TRIM功能。
//1: 启用TRIM功能。
#define _FS_NOFSINFO 0 /* 0,1,2 or 3 */
//定义:控制对FAT32的FSINFO区的使用。FSINFO是FAT32文件系统中的一个特定结构,用于记录剩余空闲簇
//数和最后分配的簇号。如果文件系统可能损坏或设备需要准确计算空闲空间,设置bit0=1。如果启用了动态 //内存管理或对性能要求较高,可以信任FSINFO(bit0=0 和 bit1=0)
//不同的位设置方式如下:
//bit 0: 是否信任空闲簇计数(free cluster count)。
//0: 使用 FSINFO 中的空闲簇计数(默认)。
//1: 不信任 FSINFO,首次调用 f_getfree() 时执行全盘 FAT 表扫描以计算空闲簇。
//bit 1: 是否信任最后分配簇号(last allocated cluster number)。
//0: 使用 FSINFO 中的最后分配簇号(默认)。
//1: 不信任 FSINFO 中的最后分配簇号。
/*---------------------------------------------------------------------------/
/ System Configurations
/----------------------------------------------------------------------------*/
#define _FS_TINY 0 /* 0:Normal or 1:Tiny */
//定义: 决定是否使用“Tiny”模式的缓冲区配置。
//0 (Normal):使用标准模式。每个打开的文件对象 (FIL) 包含一个私有的扇区缓冲区。这种方式占用更多 //内存,但文件访问效率较高,特别是对多个文件进行并发操作时。
//1 (Tiny):启用Tiny配置。在Tiny模式下,每个文件对象(FIL)中会移除私有的扇区缓冲区。所有文件共享 //一个公共的扇区缓冲区,这个缓冲区存储在文件系统对象 (FATFS) 中。好处是减少内存消耗,适合低内存环 //境,但多个文件并发操作时性能可能会下降。
#define _FS_EXFAT 1
//定义:支持 exFAT 文件系统(需启用 LFN)。
//0:不支持
//1:支持
#define _FS_NORTC 0
#define _NORTC_MON 6
#define _NORTC_MDAY 4
#define _NORTC_YEAR 2015
//定义:禁用RTC时间戳功能,禁用后文件修改时间将会被设置为_NORTC_YEAR、_NORTC_MON、_NORTC_MDAY
//但是对只读文件不起作用
//0:禁用
//1:不禁用
#define _FS_LOCK 2 /* 0:Disable or >=1:Enable */
//定义:文件锁控制,避免并发操作问题。当_FS_READONLY为1时,本设置必须为0
//0:禁用。
//>0:启用,并设置最大同时打开的文件数量。
#define _FS_REENTRANT 1 /* 0:Disable or 1:Enable */
//定义:决定是否启用文件系统的可重入性(线程安全)。启用可重入性后涉及文件/目录访问的函数调用需要通 //过同步对象(如信号量、互斥锁)控制访问。因此需要用户提供以下函数的实现:ff_req_grant():请求同 //步对象;ff_rel_grant():释放同步对象;ff_cre_syncobj():创建同步对象;ff_del_syncobj():删除 //同步对象;
//0:禁用可重入性。不提供线程安全机制。如果多个线程同时访问同一个文件系统卷(比如读写同一个文 //件),可能会导致数据损坏。
//1:启用可重入性。增加线程同步机制,确保多个线程访问同一文件系统卷时不发生冲突。
#define _USE_MUTEX 0 /* 0:Disable or 1:Enable */
//定义: 决定是否使用互斥锁作为文件系统可重入性的同步机制。
//0:禁用互斥锁。需要实现其他同步机制(如信号量)。
//1:启用互斥锁。使用互斥锁作为线程同步的主要工具。
#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */
//定义: 定义同步操作的超时时间。取决于系统的时间单位(通常是“时钟节拍”,一般为1ms)。如果线程在等 //待同步对象时超过了指定的时间,就会返回超时错误。
#define _SYNC_t osSemaphoreId_t
//定义: 定义同步对象的类型。该类型取决于具体操作系统的同步机制,比如信号量或互斥锁。在 FreeRTOS //中,可以定义为 SemaphoreHandle_t。在 CMSIS RTOS中,可以定义为 osSemaphoreId_t。需要在ff.h的//范围内包含操作系统的头文件,以确保同步对象类型定义有效。
/* define the ff_malloc ff_free macros as FreeRTOS pvPortMalloc and vPortFree macros */
#if !defined(ff_malloc) && !defined(ff_free)
#define ff_malloc pvPortMalloc
#define ff_free vPortFree
//设置FATFS的动态分配内存和动态释放内存函数,这里直接使用FreeRTOS的相关函数,也说明了Cubemx配置
//FATFS时必须也要配置FreeRTOS
#endif
FATFS会为每一个存储设备对象分配一个单独的win缓冲区,大小一般为_MAX_SS个字节。在不开启_FS_TINY的情况下,存储设备对象每打开一个FIL文件,还会为该文件分配一个私有的扇区缓冲区。在Tiny模式下,存储设备对象打开的所有文件共享该对象的win缓冲区;
需要注意的是这里的文件缓冲区和f_mkfs ( const TCHAR* path, BYTE opt,DWORD au, void* work, UINT len )格式化缓冲区不是一个概念,格式化时使用的缓冲区是专门用于格式化操作的,与 FATFS 文件系统的内部缓冲区无直接关系。主要用于存储临时数据(例如扇区数据)在格式化文件系统过程中使用,格式化之后就不需要了。如果内存需求较大或设备内存有限,可以使用malloc动态分配格式化缓冲区内存,格式化结束后释放。缓冲区大小必须要为大于等于MAX_SS;
FATFS中,比较重要的两个数据类型是FATFS存储设备对象和FIL文件对象,如下:
ff.h:
/* File system object structure (FATFS) */
typedef struct {
BYTE fs_type; /* 文件系统类型标志,0表示未挂载,其他值表示FAT12、FAT16、FAT32 或 exFAT等 */
BYTE drv; /* 物理驱动器号(逻辑盘号) */
BYTE n_fats; /* FAT表的数量(1或2)大多数情况为 2,表示有主FAT和备份FAT*/
BYTE wflag; /* win[]缓冲区状态标志,标志位bit0=1表示缓冲区已修改,需要同步写回到存储设备*/
BYTE fsi_flag; /* FSINFO节点状态标志(仅适用于FAT32)bit7=1: 禁用FSINFO节点。
bit0=1: FSINFO节点已被修改,需要写回*/
WORD id; /* 文件系统挂载 ID,用于标识挂载的卷。每次挂载或重新格式化时都会更新此ID,用于防止错误访问已卸载的卷 */
WORD n_rootdir; /* 根目录条目数(仅适用于FAT12/16,每个条目为32字节,默认大小为 512条目)在FAT32中根目录大小是动态分配的,此参数为0。
*/
WORD csize; /* 每个簇包含的扇区数(簇大小) */
#if _MAX_SS != _MIN_SS
WORD ssize; /* 扇区大小(以字节为单位,值为512、1024、2048、4096) */
#endif
#if _USE_LFN != 0
WCHAR* lfnbuf; /* 长文件名(LFN)工作缓冲区,在ffconf.h中决定在堆还是栈中分配 */
#endif
#if _FS_EXFAT
BYTE* dirbuf; /* 目录条目块缓冲区(仅适用于exFAT),大小为几百个字节,按ffconf.h的定义为608字节,分配方式和lfnbuf一致 */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; /* 同步对象标识符(在多线程模式下启用) */
#endif
#if !_FS_READONLY
DWORD last_clst; /* 最后分配的簇号。FAT 文件系统分配文件时用于快速查找下一个空簇 */
DWORD free_clst; /* 空闲簇的数量。用于加速 f_getfree 函数计算可用空间 */
#endif
#if _FS_RPATH != 0
DWORD cdir; /* 当前目录的起始簇号(根目录为 0)。用于跟踪当前工作目录。 */
#if _FS_EXFAT
DWORD cdc_scl; /* 包含目录的起始簇号(仅适用于 exFAT)。当cdir为0(在根目录)时无效。 */
DWORD cdc_size; /* 包含目录的大小和链状态。bit31-bit8: 目录大小(以字节为单位)。
bit7-bit0: 链状态。 */
DWORD cdc_ofs; /* 包含目录的偏移量。当cdir为0(在根目录)时无效。*/
#endif
#endif
DWORD n_fatent; /* FAT 表的总条目数(簇总数 + 2)用于计算卷的实际容量。 */
DWORD fsize; /* FAT 表的大小(以扇区为单位)*/
DWORD volbase; /* 卷的起始扇区号。用于支持多分区模式 */
DWORD fatbase; /* FAT 表的起始扇区号 */
DWORD dirbase; /* 根目录的起始扇区号(在FAT32中为起始簇号) */
DWORD database; /* 数据区的起始扇区号。文件和目录的实际数据存储区 */
DWORD winsect; /* 记录当前存储在文件系统对象的win[]缓冲区中的逻辑扇区号*/
BYTE win[_MAX_SS]; /* 磁盘访问缓冲区(用于 FAT、目录和文件数据)。win[]是一个扇区缓存,用于暂时存储存储设备中的某个扇区数据。每次需要读取或写入文件系统元数据(如目录表、FAT 表等)时,系统会先将目标扇区加载到 win[];当需要修改文件系统元数据时,修改首先发生在 win[]缓冲区中,而不是直接写回存储设备。修改完成后,需要调用sync操作(如f_sync())将 win[] 中的数据写回存储设备的对应扇区。_FS_TINY 配置为1时不创建文件的私有缓冲区,win[]缓冲区用于所有文件传输操作 */
} FATFS;
/* File object structure (FIL) */
typedef struct {
_FDID obj; /* 对象标识符,用于管理和验证文件对象。) */
BYTE flag; /* 用于表示文件的状态,如打开模式、访问权限等。 */
BYTE err; /* 当操作出错时,通过此标志提供具体错误信息 */
FSIZE_t fptr; /* 指示当前文件读/写操作的位置(以字节为单位,从存储设备的第0字节开始计算) */
DWORD clust; /* 当前读/写位置的簇号,当fptr为0时,clust无效 */
DWORD sect; /* 记录文件私有缓冲区buf[]中当前数据所对应的逻辑扇区号,0表示无效,用于加速数据访问,避免频繁的磁盘读写 */
#if !_FS_READONLY
DWORD dir_sect; /* 记录文件的目录项所在扇区位置,用于更新文件的目录项(如文件长度、时间戳等)在写入文件时,便于快速找到文件的目录信息。 */
BYTE* dir_ptr; /* 指向文件目录项在文件系统窗口win[]中的位置,用于直接修改文件的目录项数据,用于文件写入或文件关闭时的目录项更新 */
#endif
#if _USE_FASTSEEK
DWORD* cltbl; /* 指向簇链映射表的指针,用于支持快速定位文件的特性(快速查找)*/
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS]; /* 文件的私有数据读/写缓冲区,功能类似与FATFS数据类型中的win[],使用私有缓冲区可以加快文件读写速度 */
#endif
} FIL;
W25Q64驱动如下:
HAL库W25Qxx系列芯片驱动-CSDN博客
在实现完驱动之后,我们需要手动将驱动和FATFS绑定在一起,绑定步骤如下:
STM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)_stm32 fatfs-CSDN博客
CubeMX配置STM32实现FatFS文件系统(五)_stm32cubemx文件系统-CSDN博客
关于FATFS设备驱动绑定的具体实现如下:
注意这里我把SD卡和SPI_FLASH的驱动都写到了user_diskio.c中,但其实SD卡的驱动本质上还是调用的Cubemx生成的sd_diskio.c中的函数,因此我们挂载设备时使用SD_Driver还是USER_Driver都可以。
user_diskio.c:
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define SD_CARD 0 // 外部SD卡 这里定义的值和lun参数要对应上
#define SPI_FLASH 1 // 外部SPI Flash
#define SPI_FLASH_OFFSET 0 // 外部SPI Flash偏移量,偏移后的空间给FATFS,偏移前的空间用于存储类似固件等非FATFS控制下的信息
/* Private variables ---------------------------------------------------------*/
/**
* @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 */
uint16_t i;
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SD_CARD: /* SD卡 */
{
status = SD_Driver.disk_initialize(pdrv);
break;
}
case SPI_FLASH: /* SPI Flash */
{
/* 初始化SPI Flash */
// 检查初始化函数返回值
if(BSP_W25Qx_Init() != W25Qx_OK) {
status = STA_NOINIT;
break;
}
/* 获取SPI Flash芯片状态 */
status = USER_status(SPI_FLASH);
break;
}
default:
status = STA_NOINIT;
}
return status;
/* 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 */
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SD_CARD: /* SD卡 */
{
status = SD_Driver.disk_status(pdrv);
break;
}
case SPI_FLASH:
{
uint8_t ID[4]; //设备ID缓存数组
BSP_W25Qx_Read_ID(ID);
/* SPI Flash状态检测:读取SPI Flash 设备ID */
if((ID[0] == W25Q64_FLASH_ID >> 8) && (ID[1] == (W25Q64_FLASH_ID&0xFF)))
{
/* 设备ID读取结果正确 */
status &= ~STA_NOINIT;
}
else
{
/* 设备ID读取结果错误 */
status = STA_NOINIT;;
}
break;
}
default:
status = STA_NOINIT;
}
return status;
/* 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 */
DRESULT status = RES_PARERR;
switch (pdrv) {
case SD_CARD: /* SD卡 */
{
status = SD_Driver.disk_read(pdrv,buff,sector,count);
break;
}
case SPI_FLASH:
{
/* 扇区偏移SPI_FLASH_OFFSET,外部Flash文件系统放在SPI Flash后面的空间,
前面的SPI_FLASH_OFFSET空间可以用于存储固件等非文件系统控制下的信息 */
sector += SPI_FLASH_OFFSET;
if (BSP_W25Qx_Read(buff, sector <<12, count<<12) != W25Qx_OK){
status = RES_ERROR;
}
else {
status = RES_OK;
}
break;
}
default:
status = RES_PARERR;
}
return status;
/* 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 */
uint32_t write_addr;
DRESULT status = RES_PARERR;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv) {
case SD_CARD: /* SD卡 */
{
status = SD_Driver.disk_write(pdrv,buff,sector,count);
break;
}
case SPI_FLASH:
{
/* 扇区偏移SPI_FLASH_OFFSET,外部Flash文件系统放在SPI Flash后面的空间,
前面的空间可以用于存储固件等非文件系统控制下的信息 */
sector += SPI_FLASH_OFFSET;
write_addr = sector << 12; // 假设扇区大小4096字节
for (UINT i = 0; i < count; i++) { //默认文件缓冲区为1个扇区大小时这个循环没用,因为一次只会写入一个扇区
uint32_t current_addr = write_addr + (i << 12);
// 擦除当前扇区对应的块
if (BSP_W25Qx_Erase_Block(current_addr) != W25Qx_OK) {
return RES_ERROR;
}
}
// 写入所有扇区
if (BSP_W25Qx_Write((uint8_t *)buff, write_addr, count << 12) != W25Qx_OK) {
return RES_ERROR;
}
status = RES_OK;
break;
}
default:
status = RES_PARERR;
}
return status;
/* 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 */
DRESULT status = RES_PARERR;
switch (pdrv) {
case SD_CARD: /* SD卡 */
{
status = SD_Driver.disk_ioctl(pdrv,cmd,buff);
break;
}
case SPI_FLASH:
{
switch (cmd) {
/* 扇区数量: */
case GET_SECTOR_COUNT:
*(DWORD * )buff = W25Q64FV_SUBSECTOR_NUM - SPI_FLASH_OFFSET;
break;
/* 扇区大小 */
case GET_SECTOR_SIZE :
*(WORD * )buff = 4096;
break;
/* 同时擦除扇区个数 */
case GET_BLOCK_SIZE :
*(DWORD *)buff = 1;
break;
}
status = RES_OK;
break;
}
default:
status = RES_PARERR;
}
return status;
/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
如果设置了RTC,还可以将RTC的获取时间函数和FATFS的相关函数绑定
fatfs.c:
/**
* @brief Gets Time from RTC
* @param None
* @retval Time in DWORD
*/
DWORD get_fattime(void)
{
/* USER CODE BEGIN get_fattime */
return 0;
/* USER CODE END get_fattime */
}
注意进行驱动绑定后,如果你的user_diskio.c中实现了不止一种设备的驱动或者设备的逻辑驱动器路径不为0:/,在注册设备时需要将Disk_drvTypeDef数据类型变量disk的lun参数进行修改,原因是绑定设备时我们会通过disk这个全局变量将FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)函数将我们FATFS的驱动层user_diskio.c或者sd_diskio.c这类.c文件中的底层设备驱动函数和FATFS的中间层diskio.c文件中的中间设备驱动函数进行绑定,而在使用f_open()、f_close()这些FATFS操作函数时我们都是先调用diskio.c中的下述几个函数:
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
然后通过上述几个中间设备驱动函数调用全局变量disk从而间接调用我们自己实现的底层设备驱动,我们以disk_status和disk_initialize这两个中间设备驱动函数为例,我们可以看出来其调用的是disk全局变量中关于设备驱动函数数组drv[]中的驱动,向底层驱动函数中传入的参数就是lun[]数组中的值,根据ff.c中关于f_open()、f_close()这些FATFS操作函数的实现我们可以知道,当设备的逻辑驱动路径为0-9时,中间设备驱动函数传入的参数BYTE pdrv就是对路径0:/-9:/解析后的数字0-9,而FATFS设备驱动绑定时就是一个设备对应全局变量disk中一个drv[]数组和一个lun[]数组中的元素值,由于每次调用FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)设备驱动绑定函数时都默认lun参数为0,因此每一个设备对应disklun[]数组元素的值都为0,这样向底层驱动函数中传入的参数就是0。
diskio.c:
/**
* @brief Gets Disk Status
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
DSTATUS stat;
stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);
return stat;
}
/**
* @brief Initializes a Drive
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat = RES_OK;
if(disk.is_initialized[pdrv] == 0)
{
stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
if(stat == RES_OK)
{
disk.is_initialized[pdrv] = 1;
}
}
return stat;
}
ff_gen_drv.h:
/**
* @brief Disk IO Driver structure definition
*/
typedef struct
{
DSTATUS (*disk_initialize) (BYTE); /*!< Initialize Disk Drive */
DSTATUS (*disk_status) (BYTE); /*!< Get Disk Status */
DRESULT (*disk_read) (BYTE, BYTE*, DWORD, UINT); /*!< Read Sector(s) */
#if _USE_WRITE == 1
DRESULT (*disk_write) (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0 */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT (*disk_ioctl) (BYTE, BYTE, void*); /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */
}Diskio_drvTypeDef;
/**
* @brief Global Disk IO Drivers structure definition
*/
typedef struct
{
uint8_t is_initialized[_VOLUMES];
const Diskio_drvTypeDef *drv[_VOLUMES];
uint8_t lun[_VOLUMES];
volatile uint8_t nbr;
}Disk_drvTypeDef;
ff_gen_drv.c:
Disk_drvTypeDef disk = {
{0},{0},{0},0};
/**
* @brief Links a compatible diskio driver/lun id and increments the number of active
* linked drivers.
* @note The number of linked drivers (volumes) is up to 10 due to FatFs limits.
* @param drv: pointer to the disk IO Driver structure
* @param path: pointer to the logical drive path
* @param lun : only used for USB Key Disk to add multi-lun management
else the parameter must be equal to 0
* @retval Returns 0 in case of success, otherwise 1.
*/
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)
{
uint8_t ret = 1;
uint8_t DiskNum = 0;
if(disk.nbr < _VOLUMES)
{
disk.is_initialized[disk.nbr] = 0;
disk.drv[disk.nbr] = drv;
disk.lun[disk.nbr] = lun;
DiskNum = disk.nbr++;
path[0] = DiskNum + '0';
path[1] = ':';
path[2] = '/';
path[3] = 0;
ret = 0;
}
return ret;
}
/**
* @brief Links a compatible diskio driver and increments the number of active
* linked drivers.
* @note The number of linked drivers (volumes) is up to 10 due to FatFs limits
* @param drv: pointer to the disk IO Driver structure
* @param path: pointer to the logical drive path
* @retval Returns 0 in case of success, otherwise 1.
*/
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)
{
return FATFS_LinkDriverEx(drv, path, 0);
}
这样就有一个问题,由于我们自己编写底层驱动函数时如果涉及到多个不同存储设备的使用,那么我们往往会在user_diskio.c中通过传入的参数pdrv结合switch判断来切换不同的存储设备底层驱动,这里传入的参数pdrv为该设备对应的lun参数,但我们看到FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)设备驱动绑定函数时都默认lun参数为0,因此当用switch对存储设备底层驱动进行切换时,如果case的值不为0就不会执行其中的代码。因此我们对存储设备进行FATFS初始化时建议不使用FATFS_LinkDriver函数,而是使用uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)函数,这里我们可以自定义存储设备的lun参数值,和user_diskio.c中的switch-case判断值对应即可。例如user_diskio.c中SD卡的case判断为0执行,SPI_FLASH卡中的case判断为1执行,那对这两个设备进行初始化时就和USER_DRIVER绑定,然后lun参数分别设置为0和1,如下:
/* 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 */
uint8_t retUSER; /* Return value for USER */
char USERPath[4]; /* USER logical drive path */
FATFS USERFatFS; /* File system object for USER logical drive */
FIL USERFile; /* File object for USER */
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
void MX_FATFS_Init(void)
{
/*## FatFS: Link the SD driver ###########################*/
//retSD = FATFS_LinkDriver(&SD_Driver, SDPath);
/*## FatFS: Link the USER driver ###########################*/
//retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);
/* USER CODE BEGIN Init */
/*## FatFS: Link the USER_2 driver ###########################*/
retSD = FATFS_LinkDriverEx(&USER_Driver, SDPath,0);
retUSER = FATFS_LinkDriverEx(&USER_Driver, USERPath,1);
/* additional user code for init */
/* USER CODE END Init */
}
配置并绑定完SD卡和SPI_FLASH存储设备的底层驱动之后,我们发现外部FLASH的USERFatFS结构体中关于卷起始扇区volbase的值为0x3F = 63,第0个扇区为MBR(含分区表和引导代码),第1到第62扇区通常为保留未用区域,可能包含自定义数据或二级引导程序。FAT文件系统的引导扇区(BPB)从第63扇区处开始,至于为什么这样设置,可以说是一种文件系统的标准。
测试文件系统使用SD卡和SPI_FLASH多设备程序和对应结果如下:
这里注意保证分配给使用FATFS任务的堆栈足够大,否则容易产生堆栈溢出进入hardfault,我这里给FATFS_Task任务4K字节堆大小还是会溢出,最后分配了10K字节正常运行,建议实际跑操作系统任务时用StackOverflowHookHook钩子函数调试:
void FATFS_Task(void *argument)
{
Mount_FatFs(&SDFatFS, SDPath, 1 ,FM_EXFAT,NULL,FATFS_Init_workBuffer_SIZE); //挂载时会自动调用相关存储设备初始化函数
FatFs_GetDiskInfo(SDPath);
/*----------------------- 文件系统测试:写测试 -----------------------------*/
FatFs_WriteTXTFile(SDPath,"test.txt",SDFile,2025,1,14);
/*------------------- 文件系统测试:读测试 ------------------------------------*/
FatFs_ReadTXTFile(SDPath,"test.txt",SDFile);
/*------------------- 文件系统测试:删除测试 ------------------------------------*/
FatFs_DeleteFile(SDPath,"test.txt",SDFile);
// /*****外部FLASH文件系统测试*****/
Mount_FatFs(&USERFatFS, USERPath, 1 ,FM_ANY ,NULL,
FATFS_Init_workBuffer_SIZE); //挂载时会自动调用相关存储设备初始化函数
FatFs_GetDiskInfo(USERPath);
/*----------------------- 文件系统测试:写测试 -----------------------------*/
FatFs_WriteTXTFile(USERPath,"test3.txt",USERFile,2025,1,14);
/*------------------- 文件系统测试:读测试 ------------------------------------*/
FatFs_ReadTXTFile(USERPath,"test3.txt",USERFile);
// /*------------------- 文件系统测试:删除测试 ------------------------------------*/
FatFs_DeleteFile(USERPath,"test3.txt",USERFile);
while (1)
{
// 队列为空时,任务可以进入挂起或等待
osDelay(5);
}
}
void LED_Task(void *argument)
{
uint32_t task_cnt=0;
printf("***gpioProcess_MainTask is running!!");
/* Infinite loop */
for(;;)
{
if (task_cnt%5==0)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
}
task_cnt++;
osDelay(30);
}
}
freertos.c:
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
printf("Stack overflow in task: %s\n", pcTaskName);
/* Run time stack overflow checking is performed if
configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is
called if a stack overflow is detected. */
}