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.pswp.cn/news/894381.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894381.shtml
英文地址,請注明出處:http://en.pswp.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;點…

【數據分析】案例03:當當網近30日熱銷圖書的數據采集與可視化分析(scrapy+openpyxl+matplotlib)

當當網近30日熱銷圖書的數據采集與可視化分析(scrapy+openpyxl+matplotlib) 當當網近30日熱銷書籍官網寫在前面 實驗目的:實現當當網近30日熱銷圖書的數據采集與可視化分析。 電腦系統:Windows 使用軟件:Visual Studio Code Python版本:python 3.12.4 技術需求:scrapy、…

數據庫對象

數據庫對象 數據庫對象是構成數據庫結構的基本單位&#xff0c;它們定義了數據庫存儲的數據類型、數據的組織方式以及數據之間的關系。在數據庫中&#xff0c;對象可以包括表&#xff0c;視圖&#xff0c;索引&#xff0c;觸發器&#xff0c;存儲過程&#xff0c;函數等多種類…

Super AGI 2025 ,人形機器人,芯片半導體,價值+量化投資最佳實踐

Super AGI 2025&#xff1a;人形機器人、芯片半導體與價值量化投資最佳實踐 關鍵詞&#xff1a;Super AGI、人形機器人、芯片半導體、價值投資、量化投資、技術趨勢、投資策略 摘要&#xff1a;本文探討了Super AGI、人形機器人和芯片半導體領域的發展前景&#xff0c;并結合價…

AI學習指南Ollama篇-使用Ollama構建自己的私有化知識庫

一、引言 (一)背景介紹 隨著企業對數據隱私和效率的重視,私有化知識庫的需求日益增長。私有化知識庫不僅可以保護企業數據的安全性,還能提供高效的知識管理和問答系統,提升企業內部的工作效率和創新能力。 (二)Ollama和AnythingLLM的結合 Ollama和AnythingLLM的結合…

省級-新質生產力數據(2010-2022年)-社科數據

省級-新質生產力數據&#xff08;2010-2022年&#xff09;-社科數據https://download.csdn.net/download/paofuluolijiang/90028612 https://download.csdn.net/download/paofuluolijiang/90028612 新質生產力是指在現代科技和經濟社會發展的推動下&#xff0c;由新的生產要素…

用一個例子詳細說明python單例模式

單例模式是一種設計模式&#xff0c;它確保一個類只有一個實例&#xff0c;并提供一個全局訪問點來訪問該實例。這在需要控制資源&#xff08;如數據庫連接、文件系統等&#xff09;的訪問時非常有用。 下面是一個使用Python實現單例模式的例子&#xff1a; class Singleton:…

【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;現場錄制…