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.mzph.cn/news/894381.shtml

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

相关文章

java练习(2)

回文数&#xff08;题目来自力扣&#xff09; 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数 是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整…

使用 Tauri 2 + Next.js 开发跨平台桌面应用实践:Singbox GUI 实践

Singbox GUI 实践 最近用 Tauri Next.js 做了个项目 - Singbox GUI&#xff0c;是个给 sing-box 用的图形界面工具。支持 Windows、Linux 和 macOS。作为第一次接触这两个框架的新手&#xff0c;感觉收获还蛮多的&#xff0c;今天来分享下开发过程中的一些经验~ 为啥要做这个…

ComfyUI安装调用DeepSeek——DeepSeek多模态之图形模型安装问题解决(ComfyUI-Janus-Pro)

ComfyUI 的 Janus-Pro 节点&#xff0c;一个统一的多模态理解和生成框架。 试用&#xff1a; https://huggingface.co/spaces/deepseek-ai/Janus-1.3B https://huggingface.co/spaces/deepseek-ai/Janus-Pro-7B https://huggingface.co/spaces/deepseek-ai/JanusFlow-1.3B 安装…

索引的底层数据结构、B+树的结构、为什么InnoDB使用B+树而不是B树呢

索引的底层数据结构 MySQL中常用的是Hash索引和B树索引 Hash索引&#xff1a;基于哈希表实现的&#xff0c;查找速度非常快&#xff0c;但是由于哈希表的特性&#xff0c;不支持范围查找和排序&#xff0c;在MySQL中支持的哈希索引是自适应的&#xff0c;不能手动创建 B树的…

RK3568中使用QT opencv(显示基础图像)

文章目录 一、查看对应的开发环境是否有opencv的库二、QT使用opencv一、查看对应的开发环境是否有opencv的库 在开发板中的/usr/lib目录下查看是否有opencv的库: 这里使用的是正点原子的ubuntu虚拟机,在他的虚拟机里面已经安装好了opencv的库。 二、QT使用opencv 在QT pr…

29.Word:公司本财年的年度报告【13】

目录 NO1.2.3.4 NO5.6.7​ NO8.9.10​ NO1.2.3.4 另存为F12&#xff1a;考生文件夹&#xff1a;Word.docx选中绿色标记的标题文本→样式对话框→单击右键→点击样式对话框→单击右键→修改→所有脚本→颜色/字体/名称→边框&#xff1a;0.5磅、黑色、单线条&#xff1a;点…

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

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

【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙

目录 1. 梯度基本计算 2. 控制梯度计算 3. 梯度计算注意 4. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活…

【数据分析】案例04:豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask)

豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask) 豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:实现豆瓣电影Top250详情的数据分析与Web网页可视化。电脑系统:Windows使用软件:PyCharm、NavicatPython版本:Python 3.…

Ubuntu20.04 深度学习环境配置(持续完善)

文章目录 常用的一些命令安装 Anaconda创建conda虚拟环境查看虚拟环境大小 安装显卡驱动安装CUDA安装cuDNN官方仓库安装 cuDNN安装 cuDNN 库验证 cuDNN 安装确认 CUDA 和 cuDNN 是否匹配&#xff1a; TensorRT下载 TensorRT安装 TensorRT 本地仓库配置 GPG 签名密钥安装 Tensor…

元宇宙与Facebook:社交互动的未来方向

随着技术的飞速发展&#xff0c;元宇宙逐渐成为全球科技领域关注的焦点。作为一种集沉浸式体验、虚拟空间和数字社交互动为一体的新型平台&#xff0c;元宇宙正在重新定义人类的社交方式。而在这一变革中&#xff0c;Facebook&#xff08;现改名为Meta&#xff09;作为全球领先…

【赵渝强老师】K8s中Pod探针的ExecAction

在K8s集群中&#xff0c;当Pod处于运行状态时&#xff0c;kubelet通过使用探针&#xff08;Probe&#xff09;对容器的健康状态执行检查和诊断。K8s支持三种不同类型的探针&#xff0c;分别是&#xff1a;livenessProbe&#xff08;存活探针&#xff09;、readinessProbe&#…

python 语音识别

目录 一、语音识别 二、代码实践 2.1 使用vosk三方库 2.2 使用SpeechRecognition 2.3 使用Whisper 一、语音识别 今天识别了别人做的这个app,觉得虽然是个日记app 但是用来学英语也挺好的,能进行语音识别,然后矫正语法,自己说的时候 ,实在不知道怎么说可以先乱说,然…

Node.js——body-parser、防盗链、路由模块化、express-generator应用生成器

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算 一、 插入音频及音频设置 1.插入音频&#xff1a;点击菜单栏插入-音频-选择PC上的音频&#xff08;已存在的音频&#xff09;或者录制音频&#xff08;现场录制…

3D图形学与可视化大屏:什么是材质属性,有什么作用?

一、颜色属性 漫反射颜色 漫反射颜色决定了物体表面对入射光进行漫反射后的颜色。当光线照射到物体表面时&#xff0c;一部分光被均匀地向各个方向散射&#xff0c;形成漫反射。漫反射颜色的选择会直接影响物体在光照下的外观。例如&#xff0c;一个红色的漫反射颜色会使物体在…

Jenkins未在第一次登录后设置用户名,第二次登录不进去怎么办?

Jenkins在第一次进行登录的时候&#xff0c;只需要输入Jenkins\secrets\initialAdminPassword中的密码&#xff0c;登录成功后&#xff0c;本次我们没有修改密码&#xff0c;就会导致后面第二次登录&#xff0c;Jenkins需要进行用户名和密码的验证&#xff0c;但是我们根本就没…

Qt常用控件 输入类控件

文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1&#xff0c;录入用户信息1.4 例子2&#xff0c;正则验证手机号1.5 例子3&#xff0c;验证输入的密码1.6 例子4&#xff0c;显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1&#xff0c;获取输入框的内容2.4 例…

有没有个性化的UML图例

绿萝小绿萝 (53****338) 2012-05-10 11:55:45 各位大虾&#xff0c;有没有个性化的UML图例 绿萝小绿萝 (53****338) 2012-05-10 11:56:03 例如部署图或时序图的图例 潘加宇 (35***47) 2012-05-10 12:24:31 "个性化"指的是&#xff1f; 你的意思使用你自己的图标&…

Go学习:字符、字符串需注意的点

Go语言与C/C语言编程有很多相似之处&#xff0c;但是Go语言中在声明一个字符时&#xff0c;数据类型与其他语言声明一个字符数据时有一点不同之处。通常&#xff0c;字符的数据类型为 char&#xff0c;例如 &#xff1a;声明一个字符 (字符名称为 ch) 的语句格式为 char ch&am…