Cubemx文件系统挂载多设备

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_SSWORD	ssize;			/* 扇区大小(以字节为单位,值为512、1024、2048、4096) */
#endif
#if _USE_LFN != 0WCHAR*	lfnbuf;			/* 长文件名(LFN)工作缓冲区,在ffconf.h中决定在堆还是栈中分配 */
#endif
#if _FS_EXFATBYTE*	dirbuf;			/* 目录条目块缓冲区(仅适用于exFAT),大小为几百个字节,按ffconf.h的定义为608字节,分配方式和lfnbuf一致 */
#endif
#if _FS_REENTRANT_SYNC_t	sobj;			/* 同步对象标识符(在多线程模式下启用) */
#endif
#if !_FS_READONLYDWORD	last_clst;		/* 最后分配的簇号。FAT 文件系统分配文件时用于快速查找下一个空簇 */DWORD	free_clst;		/* 空闲簇的数量。用于加速 f_getfree 函数计算可用空间 */
#endif
#if _FS_RPATH != 0DWORD	cdir;			/* 当前目录的起始簇号(根目录为 0)。用于跟踪当前工作目录。 */
#if _FS_EXFATDWORD	cdc_scl;		/* 包含目录的起始簇号(仅适用于 exFAT)。当cdir为0(在根目录)时无效。 */DWORD	cdc_size;		/* 包含目录的大小和链状态。bit31-bit8: 目录大小(以字节为单位)。
bit7-bit0: 链状态。 */DWORD	cdc_ofs;		/* 包含目录的偏移量。当cdir为0(在根目录)时无效。*/
#endif
#endifDWORD	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_READONLYDWORD	dir_sect;		/* 记录文件的目录项所在扇区位置,用于更新文件的目录项(如文件长度、时间戳等)在写入文件时,便于快速找到文件的目录信息。 */BYTE*	dir_ptr;		/* 指向文件目录项在文件系统窗口win[]中的位置,用于直接修改文件的目录项数据,用于文件写入或文件关闭时的目录项更新 */
#endif
#if _USE_FASTSEEKDWORD*	cltbl;			/* 指向簇链映射表的指针,用于支持快速定位文件的特性(快速查找)*/
#endif
#if !_FS_TINYBYTE	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 == 1DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT (*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 managementelse 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 ifconfigCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function iscalled if a stack overflow is detected. */
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11033.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电子电气架构 --- 汽车电子拓扑架构的演进过程

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

2025 年,链上固定收益领域迈向新时代

“基于期限的债券市场崛起与Secured Finance的坚定承诺” 2025年&#xff0c;传统资产——尤其是股票和债券——大规模涌入区块链的浪潮将创造历史。BlackRock 首席执行官 Larry Fink 近期在彭博直播中表示&#xff0c;代币化股票和债券将逐步融入链上生态&#xff0c;将进一步…

数据密码解锁之DeepSeek 和其他 AI 大模型对比的神秘面纱

本篇将揭露DeepSeek 和其他 AI 大模型差异所在。 目录 ​编辑 一本篇背景&#xff1a; 二性能对比&#xff1a; 2.1训练效率&#xff1a; 2.2推理速度&#xff1a; 三语言理解与生成能力对比&#xff1a; 3.1语言理解&#xff1a; 3.2语言生成&#xff1a; 四本篇小结…

Ollama部署指南

什么是Ollama&#xff1f; Ollama是一个专为在本地机器上便捷部署和运行大型语言模型&#xff08;LLM&#xff09;而设计的开源工具。 如何部署Ollama&#xff1f; 我是使用的云平台&#xff0c;大家也可以根据自己的云平台的特点进行适当的调整。 使用系统&#xff1a;ubun…

群晖Alist套件无法挂载到群晖webdav,报错【连接被服务器拒绝】

声明&#xff1a;我不是用docker安装的 在套件中心安装矿神的Alist套件后&#xff0c;想把夸克挂载到群晖上&#xff0c;方便复制文件的&#xff0c;哪知道一直报错&#xff0c;最后发现问题出在两个地方&#xff1a; 1&#xff09;挂载的路径中&#xff0c;直接填 dav &…

Kubernetes组成及常用命令

Pods(k8s最小操作单元)ReplicaSet & Label(k8s副本集和标签)Deployments(声明式配置)Services(服务)k8s常用命令Kubernetes(简称K8s)是一个开源的容器编排系统,用于自动化应用程序的部署、扩展和管理。自2014年发布以来,K8s迅速成为容器编排领域的行业标准,被…

hexo部署到github page时,hexo d后page里面绑定的个人域名消失的问题

Hexo 部署博客到 GitHub page 后&#xff0c;可以在 setting 中的 page 中绑定自己的域名&#xff0c;但是我发现更新博客后绑定的域名消失&#xff0c;恢复原始的 githubio 的域名。 后面搜索发现需要在 repo 里面添加 CNAME 文件&#xff0c;内容为 page 里面绑定的域名&…

vim的特殊模式-可视化模式

可视化模式&#xff1a;按 v进入可视化模式 选中 y复制 d剪切/删除 可视化块模式: ctrlv 选中 y复制 d剪切/删除 示例&#xff1a; &#xff08;vim可视化模式的进阶使用&#xff1a;vim可视化模式的进阶操作-CSDN博客&#xff09;

【教程】在CMT上注册账号并声明Conflicts

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 注册账号 声明冲突 账号验证 每位作者都要注册并声明冲突&#xff0c;不然会直接拒稿&#xff01; 注册账号 https://cmt3.research.microsoft…

拉格朗日定理

根号n为枚举的条件 d从c开始循环&#xff08;防止重复计算平方和&#xff09; #include<bits/stdc.h> using namespace std; using lllong long; const int N5e69;int n; int C[N],D[N];int main() {cin>>n;memset(C,-1,sizeof C);for(int c0;c*c<n;c)for(int d…

什么是线性化PDF?

线性化PDF是一种特殊的PDF文件组织方式。 总体而言&#xff0c;PDF是一种极为优雅且设计精良的格式。PDF由大量PDF对象构成&#xff0c;这些对象用于创建页面。相关信息存储在一棵二叉树中&#xff0c;该二叉树同时记录文件中每个对象的位置。因此&#xff0c;打开文件时只需加…

省级-新质生产力数据(2010-2022年)-社科数据

省级-新质生产力数据&#xff08;2010-2022年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90028612 https://download.csdn.net/download/paofuluolijiang/90028612 新质生产力是指在现代科技和经济社会发展的推动下&#xff0c;由新的生产要素…

17.2 图形绘制6

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.2.7 Screen类 Screen类从字面上看就知道是与屏幕显示相关的&#xff0c;表示单个系统上的一个或多个显示设备。 Screen常用属性…

第一个Python程序

目录 1.命令行模式 2.Python交互模式 3.命令行模式和Python交互模式 4.SyntaxError 5.小结 2.使用文本编辑器 1.Visual Studio Code! 2.直接运行py文件 3.输入和输出 1.输出 2.输入 3.小结 在正式编写第一个Python程序前&#xff0c;我们先复习一下什么是命令行模式…

14-9-1C++STL的set容器

&#xff08;一&#xff09;set容器的基本概念 1. set是一个集合容器&#xff0c;其中所包含的元素是唯一的&#xff0c;集合中的元素按一定的顺序排列&#xff0c;元素插入过程是按排序规则插入&#xff0c;所以不能指定插入位置 2. set深用红黑树变体的数据结构实现&#xff…

数据分析系列--②RapidMiner导入数据和存储过程

一、下载数据 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从本地选择.csv或.xlsx 三、界面说明 四、存储过程 1.保存 Congratulations, you are done. 一、下载数据 点击下载AssociationAnalysisData.xlsx数据集 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从…

被裁与人生的意义--春节随想

还有两个月就要被迫离开工作了十多年的公司了&#xff0c;不过有幸安安稳稳的过了一个春节&#xff0c;很知足! 我是最后一批要离开的&#xff0c;一百多号同事都没“活到”蛇年。看着一批批仁人志士被“秋后斩首”&#xff0c;马上轮到我们十来个&#xff0c;个中滋味很难言清…

Docker自定义镜像

Dockerfile自定义镜像 一&#xff1a;镜像结构 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。 我们以MySQL为例&#xff0c;来看看镜像的组成结构&#xff1a; 简单来说&#xff0c;镜像就是在系统函数库、运行环境基础上&#xff0c;添加应用程序文件、…

论文阅读(十六):利用线性链条件随机场模型检测阵列比较基因组杂交数据的拷贝数变异

1.论文链接&#xff1a;Detection of Copy Number Variations from Array Comparative Genomic Hybridization Data Using Linear-chain Conditional Random Field Models 摘要&#xff1a; 拷贝数变异&#xff08;CNV&#xff09;约占人类基因组的12%。除了CNVs在癌症发展中的…

ASP.NET Core 中间件

目录 一、常见的内置中间件 二、自定义中间件 三、中间件的执行顺序 四、其他自动逸中间件案例 1. 身份验证中间件 2、跨域中间件&#xff08;CORS&#xff09; ASP.NET Core 中&#xff0c;中间件&#xff08;Middleware&#xff09;是处理 HTTP 请求和响应的组件链。你…