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. */
}