目錄
一、SPI-Flash芯片硬件電路?
二、CubeMX項目基礎設置
1、RCC、SYS、Code Generator、USART6、NVIC
2、RTC
3、SPI2
4、GPIO
5、FatFS模式?
6、FatFS參數設置概述
(1)Version組
(2)Function Parameters組
1)參數FS_READONLY
2)參數S_MINIMIZE
3)參數USE_STRFUNC
(3)Locale and Namespace Parameters組
1)參數USE_LFN
2)參數STRF_ENCODE
3)參數FS_RPATH
(4)Physical Driver Parameters組
(5)System Parameters組
1)參數FS_NORTC
2)參數FS_REENTRANT
三、項目FatFS的文件組成及詳細分析
1、main()的初始化代碼
2、文件fatfs.h和fatfs.c
3、文件user_diskio.h和user_diskio.c
4、文件ff_gen_drv.h和ff_gen_drv.c
5、文件diskio.h和diskio.c
????????本文以開發板上的SPI-Flash芯片W25Q16為媒介詳細介紹CubeMX中FatFS各個參數的意義和設置,分析生成的代碼中FatFS各個文件的作用和關聯,完成針對W25Q16的硬件訪問層的移植。
? ? ? ? 繼續使用旺寶紅龍開發板STM32F407ZGT6 KITV1.0。相關原理圖如下:
一、SPI-Flash芯片硬件電路?
????????開發板上有一個SPI-Flash存儲芯片W25Q16,它與STM32F407的SPI2接口連接,W25Q16總容量是2M字節,存儲空間參數如下。
- 總共32個塊(block),每個塊64KB。
- 每個塊又分為16個扇區,共512個扇區,每個扇區4KB。
- 每個扇區又分為16個頁(page),共8192個頁,每個頁256B。
二、CubeMX項目基礎設置
????????本文創建一個示例針對SPI-Flash存儲芯片W25Q16進行FatFS移植,使用FatFS進行文件讀寫等操作。此外,使用RTC為FatFS提供時間戳數據。
1、RCC、SYS、Code Generator、USART6、NVIC
? ? ? ? 參數設置可以本文作者發布的其他文章文章。
2、RTC
- 啟用RTC的時鐘源和日歷,設置初始日期和時間,其他功能無須開啟。在示例中讀取RTC的當前日期和時間。
- 在RCC組件中啟用LSE,在時鐘樹上,使用LSE作為RTC的時鐘源。
3、SPI2
- 設置HCLK為168MHz,PCLK1的頻率為42MHz,SPI2是掛在APB1總線上的,這樣是為方便計算SPI2的波特率。
- SPI2的模式設置為Full-Duplex Master,不使用硬件NSS信號,分頻系數(Prescaler)設置為8,波特率為5.25Mbit/s。數據傳輸是MSB先行,CPOL和CPHA的組合是SPI時序模式3。
4、GPIO
?
5、FatFS模式?
????????模式設置就是選擇FatFS應用的存儲介質,有以下4個選項。
?
- External SRAM,外部SRAM存儲器。例如,開發板上有一個外部SRAM芯片IS62WV51216,可以將此芯片的一部分或全部存儲區域用作文件系統,使用FatFS管理SRAM上的文件,實現內存上的高速文件讀寫。
- SD Card,SD卡。此項需要啟用SDIO接口后才可以選擇。
- USB Disk,U盤。需要將USB-OTG-FS或USB-OTG-HS組件的模式設置為Host-Only(僅作為主機),并且將Middleware分組中的USB_HOST組件的IP類型設置為MassStorage Host Class(大容量存儲主機類)之后,此項才可以選擇。
- User-defined,用戶定義器件。除以上3項之外的其他存儲介質,例如,開發板上連接在SPI2接口上的Flash存儲芯片W25Q16。
????????使用前3種存儲介質時,CubeMX生成的代碼里有FatFS完整的移植程序,因此用戶無須再編程實現硬件層的Disk IO函數。使用用戶定義器件時,CubeMX生成的代碼里有FatFS移植代碼框架,用戶需要自己編程實現器件的Disk IO函數。本文的示例使用的存儲介質是連接在SPI2接口上的Flash存儲芯片W25Q16屬于用戶定義器件。通過這個示例,詳細地了解FatFS程序移植的原理。
6、FatFS參數設置概述
????????設置FatFS參數。這些參數分為多個組,大多數與FatFS配置文件ffconf.h中的宏定義對應,用于設置FatFS的一些參數,以及進行功能裁剪。
(1)Version組
????????只有一個參數,顯示了FatFS的版本。圖中顯示的版本為R0.12c。
(2)Function Parameters組
????????用于配置是否包含某些函數,就是FatFS的功能裁剪。
?
?????????Function Parameters組參數,第一列是參數名稱,對應于源文件中的宏,宏的名稱就是參數名稱前加“_”,例如,參數FS_READONLY對應的宏是_FS_READONLY,參數USE_FIND對應的宏是_USE_FIND。第二列是參數值,這些參數一般是邏輯值,Disabled表示設置為0,Enabled表示設置為1。
????????這些參數有的用于定義系統的特性,如FS_MINIMIZE定義系統最小化級別,會影響多個函數;有的用于裁剪某個功能,可同時影響多個函數,如USE_FIND影響函數f_findfirst()和f_findnext();有的參數只影響一個函數,如USE_CHMOD只控制是否使用函數f_chmod()。
參數 | 默認值 | 可設置內容和影響的函數 |
FS_READONLY | Disabled | 只能設置為Disabled,表示不使用只讀功能 |
FS_MINIMIZE | Disabled | 最小化級別,可設置為0、1、2、3等4種級別 |
USE_STRFUNC | 2 | 是否使用字符串函數,可設置為0、1或2 |
USE_FIND | Disabled | 是否使用查找函數f_findfirst()和f_findnext() |
USE_MKFS | Enabled | 是否使用函數f_mkfs() |
USE_FASTSEEK | Enabled | 是否使用快速尋找功能,無具體影響函數 |
USE_EXPAND | Disabled | 是否使用函數f_expand() |
USE_CHMOD | Disabled | 是否使用函數f_chmod() |
USE_LABEL | Disabled | 是否使用獲取和設置卷標簽的函數f_getlable()和f_setlabel() |
USE_FORWARD | Disabled | 是否使用函數f_forward() |
1)參數FS_READONLY
????????在CubeMX中,參數FS_READONLY只能設置為Disabled,表示不使用只讀功能。若在代碼中設置_FS_READONLY為1,表示系統為只讀系統,將移除用于寫操作的函數,包括f_write()、f_sync()、f_unlink()、f_mkdir()、f_chmod()、f_rename()和f_truncate(),并且函數f_getfree()將變得無用。
2)參數S_MINIMIZE
????????參數S_MINIMIZE設置系統的最小化級別,有如下4種選項。
- Disabled:對應級別0,啟用所有基本函數。
- Enabled with 6 functions removed:對應級別1,移除函數f_stat()、f_getfree()、f_unlink()、f_mkdir()、f_truncate()和f_rename()。
- Enabled with 9 functions removed:對應級別2,在級別1的基礎上,再移除函數f_opendir()、f_readdir()和f_closedir()。
- Enabled with 10 functions removed:對應級別3,在級別2的基礎上,再移除函數f_lseek()。
3)參數USE_STRFUNC
????????參數USE_STRFUNC設置是否使用字符串相關函數,以及如何使用,有如下3種選項。
- Disabled:對應值0,不使用字符串相關的函數,如f_gets()、f_putc()、f_puts()等。
- Enabled without LF->CRLF conversion:對應值1,使用字符串相關函數,但不使用LF->CRLF轉換。
- Enabled with LF->CRLF conversion:對應值2,使用字符串相關函數,并且使用LF->CRLF轉換,也就是字符串中的'\n'會被轉換為\r1+'n'。
(3)Locale and Namespace Parameters組
????????本地化和名稱空間參數,例如,設置代碼頁、是否使用長文件名等。
參數 | 默認值 | 可設置內容和功能描述 |
CODE_PAGE | Latin 1 | 設置目標系統上使用的OEM編碼頁,編碼頁如果選擇不正常,可能 |
USE_LFN | Disabled | 是否使用長文件名(LFN) |
MAX_LFN | 255 | 設定值范圍為12至255,是LFN的最大長度 |
LFN_UNICODE | ANSI/OEM | 是否將FatFS API中的字符編碼切換為Unicode。當USE_LFN設置為 |
STRF_ENCODE | UTF-8 | 啟用Unicode后,FatFSAPI中的字符編碼都需要轉換為Unicode。這 |
FS_RPATH | Disabled | 是否使用相對路徑,以及使用相對路徑時的特性 |
1)參數USE_LFN
????????參數USE_LFN控制是否使用LFN(Long File Name,長文件名),有如下4種選項。
- 0=Disabled:不使用LFN,參數MAX_LFN無影響。
- 1=Enable LFN with static working buffer on the BSS:使用LFN,且使用BSS段的靜態工作緩沖區。這種情況下,LFN工作緩沖區是BSS段上的靜態變量,總是不可重入的,即不是線程安全的。
- 2=Enable LFN with dynamic working buffer on the STACK:使用LFN,且在棧空間為LFN分配動態工作緩沖區。
- 3=Enable LFN with dynamic working buffer on the HEAP:使用LFN,且在堆空間為LFN分配動態工作緩沖區。
????????要使用LFN功能,請務必將處理Unicode的函數ff_convert()和f_wtoupper()添加到項目中。LFN工作緩沖區占用(MAX_LFN+1)×2字節。當使用棧空間作為LFN工作緩沖區時,要注意棧溢出問題。使用堆空間作為LFN工作緩沖區時,需要將內存管理函數ff_memalloc()和ff_memfree()添加到項目中。
????????如果不使用LFN,即參數USE_LFN設置為Disabled時,不含后綴的文件名的長度不能超過8個ASCII字符,后綴為3個ASCII字符。如果在嵌入式設備上使用LFN,最好也不要在文件名中使用漢字,即LFN_UNICODE不要設置為Unicode,而是設置為ANSI/OEM。本示例暫時不使用LFN,所以將USE_LFN設置為Disabled。但是FatFS需要能支持中文,所以CODE_PAGE設置為Simplified Chinese(DBCS)。
2)參數STRF_ENCODE
????????參數STRF_ENCODE用于設置字符串的編碼。當LFN_UNICODE設置為0(ANSI/OEM)時,這個參數無效。當LFN_UNICODE設置為1(Unicode)時,FatFS API中的字符編碼都需要轉換為Unicode。這個參數用于選擇字符串操作相關函數,如f_gets()、f_putc()、f_puts()、f_printf()等,在讀寫文件時使用的編碼,有如下幾種選項。
- 0=ANSI/OEM。
- 1=UTF-16LE。
- 2=UTF-16BE。
- 3=UTF-8。
????????其中,UTF-8是最常用的漢字編碼,需要使用漢字時,就將此參數設置為UTF-8。
3)參數FS_RPATH
????????參數FS_RPATH用于設置是否使用相對路徑,以及使用相對路徑時的特性,有如下3種選項。
- 0=Disabled,不使用相對路徑,移除相關函數。
- 1=Enabled without f_getcwd,使用相對路徑,可使用函數f_chdrive()和f_chdir()。
- 2=Enabled with f_getcwd,在選項1的基礎上,增加可使用函數f_getcwd()。
????????讀取目錄的函數f_readdir()的返回結果與此選項有關。
(4)Physical Driver Parameters組
????????物理驅動器參數,包括卷的個數、扇區大小等。
參數 | 默認值 | 可設置內容和功能描述 |
VOLUMES | 1 | 使用的邏輯驅動器的個數,設置范圍為1~9 |
MAX_SS | 512 | 最大扇區大小(字節數),只能設置為512、1024、2048或4096,比如使用的Flash存儲芯片W25Q128的扇區大小為4096字節,所 |
MIN_SS | 512 | 最小扇區大小(字節數),只能設置為512、1024、2048或4096 |
MULTI_PARTITION | Disabled | 設置為Disabled時,每個卷與相同編號的物理驅動器綁定,只會掛 |
USE_TRIM | Disabled | 是否使用ATA_TRIM特性。要想使用Trim特性,需要在disk_ioctl() |
FS_NOFSINFO | 0 | 參數取值為0、1、2或3,它設置了函數f_getfree()的運行特性 |
????????FatFS可以支持多個卷,這多個卷可以是多個不同的存儲介質,例如,一個嵌入式設備上同時有Flash存儲芯片和SD卡。存儲介質的扇區大小是固定的,但不同存儲介質的扇區大小不一樣,例如,SD卡的扇區大小是512字節,而Flash存儲芯片W25Q16的扇區大小是4096字節。
????????參數FS_NOFSINFO是兩位二進制的數[bit1,bit0],組成的參數值是0、1、2或3,用于設置函數f_getfree()的運行特性。函數f_getfree()用于獲取一個卷的剩余簇個數。如果bit0=0,在卷掛載后,首次執行函數f_getfree()時,會強制進行完整的FAT掃描,得到的剩余簇個數會記錄到返回的FATFS對象的free_clst變量中,bit1根據bit0的設置控制最后分配的簇編號,也就是結構體FATFS中的成員變量last_clst。結構體FATFS中的成員變量free_clst和last_clst稱為FSINFO,也就是剩余空間信息(free space information)。
- bit0=0:使用FSINFO中的剩余簇個數,即成員變量free_clst。
- bit0=1:不要相信FSINFO中的剩余簇個數,因為不會進行完全的FAT掃描。
- bit1=0:使用FSINFO中的最后分配簇編號,即成員變量last_clst。
- bit1=1:不要相信FSINFO中的最后分配簇編號。
????????參數FS_NOFSINFO影響f_getfree()的運行效果。在后面的示例中,我們會講到如何用函數f_getfree()獲取存儲介質空間信息。
(5)System Parameters組
????????系統參數,一些系統級的參數定義,如是否支持exFAT文件系統。在設置參數時,用戶可以單擊參數設置界面右上方的顯示描述信息的小按鈕,這樣就可以讓每個參數的描述信息顯示出來,例如參數設置的數值范圍等。
????????System Parameters組參數用于設置FatFS的一些系統級別信息。
參數 | 默認值 | 可設置內容和功能描述 |
FS_TINY | Disabled | 微小緩沖區模式,如果設置為Enabled,每個文件對象(FIL)可減少 |
FS_EXFAT | Disabled | 是否支持exFAT文件系統,當_USE_LFN設置為0時,這個參數只 |
FS_NORTC | Dynamic | 如果系統有RTC提供實時的時間,就設置為Dynamic timestamp;如 |
FS_REENTRANT | Disabled | 設置FatFS的可重入性,在CubeMX里如果沒有啟用FreeRTOS,這 |
FS_TIMEOUT | 1000 | 超時設置,單位是節拍數。FS_REENTRANT設置為Disabled時,這 |
FS_LOCK | 2 | 如果要啟用文件鎖定功能,設定FS_LOCK的值大于或等于1,表示 |
1)參數FS_NORTC
????????參數FS_NORTC用于設置是否有RTC為FatFS提供時間戳。如果系統有RTC提供實時的時間,就設置為Dynamic timestamp,在移植時,實現硬件層訪問函數get_fattime(),讀取RTC的當前時間作為文件的時間戳。如果系統沒有RTC提供實時的時間,就設置為Fixedtimestamp,會出現NORTC_YEAR(年)、NORTC_MON(月)和NORTC_MDAY(日)這3個參數,用于設置一個固定的時間戳數據。
2)參數FS_REENTRANT
????????參數FS_REENTRANT用于設置FatFS的可重入性。可重入性表示是否線程安全,在使用RTOS時,才有可重入性問題。所以,在CubeMX里如果沒有啟用FreeRTOS,這個參數只能設置為Disabled;如果啟用了FreeRTOS,這個參數只能設置為Enabled。
????????如果FS_REENTRANT設置為Enabled,會出現參數SYNC_t和USE_MUTEX。其中,SYNC_t是用于同步的對象類型。當USE_MUTEX設置為Disabled時,SYNC_t固定為oSSemaphoreld_t,即使用信號量;當USE_MUTEX設置為Enabled時,SYNC_t固定為osMutexId_t,即使用互斥量。
????????如果設置為可重入的,還需要在FatFS中移植函數ff_req_grant()、ff_rel_grant()、ff_del_syncobj()和ff_cre_syncobj()。
三、項目FatFS的文件組成及詳細分析
????????完成設置后,CubeMX會自動生成代碼。在CubeIDE中打開項目,將KEY_LED驅動程序目錄添加到項目搜索路徑。在本示例中,用到W25Q16芯片,其驅動程序FLASH目錄也添加到項目搜索路徑。
????????項目中FatFS相關的文件是自動加入的。使用CubeMX生成的FatFS的文件組成(對比:全手工移植的FatFS文件組成,兩者區別很大)。Middlewares目錄下加入的FatFS的源代碼文件,這些是不允許用戶修改的FatFS源程序文件,各個文件的作用描述如下。
- 文件integer.h:包含FatFS中用到的各種基礎數據類型的定義。
- 文件ff.h和ff.c:這是FatFS應用程序接口API函數所在的文件,是與具體硬件無關的軟件模塊。
- 文件diskio.h和diskio.c:這是存儲介質Disk IO訪問通用接口函數所在的文件。在CubeMX生成的項目代碼中,與具體存儲介質相關的Disk IO函數在另外的文件里實現,例如,本示例使用的存儲介質是User-defined,會自動生成用戶程序文件user_diskio.h和user_diskio.c。文件diskio.c中的函數只是調用文件user_diskio.c中定義的具體Disk IO函數。
- 文件ff_gen_drv.h和ff_gen_drv.c:實現驅動器列表管理功能的文件,這些功能包括鏈接一個驅動器,或解除一個驅動器的鏈接。FatFS初始化時,就調用其中的函數FATFS_LinkDriver()鏈接驅動器。
- option子目錄下的文件syscall.c:包含在使用RTOS系統時需要實現的一些函數的示例代碼,如果使用FreeRTOS,需要重新實現這些函數。
????????與具體存儲介質相關,可以由用戶修改的FatFS相關文件在目錄\FATFS下。這些文件的功能描述如下。
- 文件fatfs.h和fatfs.c:是用戶的FatFS初始化程序文件。其中有FatFS初始化函數MX_FATFS_Init()、幾個全局變量的定義,以及需要重新實現的獲取RTC時間作為文件系統時間戳的函數get_fattime()。
- 文件ffconf.h,FatFS的配置文件,包含很多的宏定義,與CubeMX里的FatFS設置對應。
- 文件user_diskio.h和user_diskio.c,是user-defined存儲介質的Disk IO函數的程序文件,自動生成了各個函數的框架,只需針對SPI-Flash芯片編寫具體的函數代碼即可。
????????FatFS相關文件的層次和關系如下圖所示:
- 文件fatfs.h中包含CubeMX生成的FatFS初始化函數MX_FATFS_Init(),在主程序中進行外設初始化時,會調用這個函數。
- 函數MX_FATFS_Init()會調用文件ff_gen_drv.h中的函數FATFS_LinkDriver(),將文件user_diskio.h中定義的驅動器對象USER_Driver鏈接到FatFS管理的驅動器列表里,相當于完成了驅動器的注冊。
- 文件user_diskio.c中實現了針對W25Q16芯片的Disk IO訪問函數。文件user_diskio.h中定義了驅動器對象USER_Driver,并且使用函數指針將diskio.h中的Disk IO通用函數指向文件user_diskio.c中實現的針對W25Q16芯片的Diok IO函數。
- 在文件user_diskio.c中實現W25Q16芯片的Disk IO訪問函數時,需要用到W25Q16芯片的驅動程序文件w25flash.b/.c,而這個驅動程序使用SPI接口的HAL驅動程序實現對W25Q16芯片的訪問。
1、main()的初始化代碼
????????在CubeMX生成的CubeIDE項目代碼中,main()函數的初始代碼如下:
/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_FATFS_Init();MX_RTC_Init();MX_SPI2_Init();MX_USART6_UART_Init();//以下代碼先省略
}
????????在外設初始化部分,函數MX_FATFS_Init()是FatFS的初始化函數,在文件fatfs.h中定義。?
2、文件fatfs.h和fatfs.c
????????文件fatfs.h定義了FatFS初始化函數MX_FATFS_Init(),還定義了幾個變量。由于在CubeMX里FatFS的模式被設置為User-defined,因此稱這個存儲介質為USER邏輯驅動器。文件fatfs.h的完整代碼如下:
/* USER CODE BEGIN Header */
/********************************************************************************* @file fatfs.h* @brief Header for fatfs applications******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __fatfs_H
#define __fatfs_H
#ifdef __cplusplusextern "C" {
#endif#include "ff.h"
#include "ff_gen_drv.h"
#include "user_diskio.h" /* defines USER_Driver as external *//* USER CODE BEGIN Includes *//* USER CODE END Includes */extern uint8_t retUSER; /* Return value for USER */
extern char USERPath[4]; /* USER logical drive path */
extern FATFS USERFatFS; /* File system object for USER logical drive */
extern FIL USERFile; /* File object for USER */void MX_FATFS_Init(void);/* USER CODE BEGIN Prototypes *//* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__fatfs_H */
????????其中,用extern聲明的4個變量是在文件fatfs.c中定義的。USERPath用于表示邏輯驅動器的路徑,如“0:/”;USERFatFS是一個FATFS結構體變量,用于表示USER邏輯驅動器上的文件系統;USERFile是一個FIL結構體類型變量,表示文件對象,在文件操作時可以使用這個文件對象。
/* USER CODE BEGIN Header */
/********************************************************************************* @file fatfs.c* @brief Code for fatfs applications******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
#include "fatfs.h"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 */
#include "file_opera.h"
/* USER CODE END Variables */void MX_FATFS_Init(void)
{/*## FatFS: Link the USER driver ###########################*/retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);/* USER CODE BEGIN Init *//* additional user code for init *//* USER CODE END Init */
}/*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return fat_GetFatTimeFromRTC();/* USER CODE END get_fattime */
}/* USER CODE BEGIN Application *//* USER CODE END Application */
????????函數MX_FATFS_Init()用于FatFS的初始化,只有一行代碼,即
retUSER=FATFS_LinkDriver(&USER_Driver,USERPath);
????????函數FATFS_LinkDriver()是在文件ff_gen_drv.h中定義的,USER_Driver是在文件user_diskio.c中定義的一個Diskio_drvTypeDef結構體類型的變量。執行這行代碼的作用是將USER_Driver鏈接到FatFS管理的驅動器列表,將USERPath賦值為“0:”。
3、文件user_diskio.h和user_diskio.c
????????文件user_diskio.h中只有一行有效語句,就是聲明了變量USER_Driver:
extern Diskio_drvTypeDef USER_Driver;
????????變量USER_Driver是在文件user_diskio.c中定義的,這個文件包含SPI-Flash芯片的Disk IO函數框架——用戶需要自己編寫代碼實現這些函數。文件user_diskio.c的完整代碼如下。為使程序結構更清晰,這里刪除了對預編譯條件_USE_WRITE和_USE_IOCTL的判斷(默認情況下,這兩個參數都是1),刪除了代碼沙箱段的定義,刪除了函數參數的注釋說明。這些代碼都是CubeMX生成的初始代碼。
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2017 */
/* */
/* Portions COPYRIGHT 2017 STMicroelectronics */
/* Portions Copyright (C) 2017, ChaN, all right reserved */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various existing */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff_gen_drv.h"#if defined ( __GNUC__ )
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#endif/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern Disk_drvTypeDef disk;/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @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;
}/*** @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 disk_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 */
)
{DRESULT res;res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);return res;
}/*** @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 disk_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 */
)
{DRESULT res;res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);return res;
}
#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 disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res;res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);return res;
}
#endif /* _USE_IOCTL == 1 *//*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
__weak DWORD get_fattime (void)
{return 0;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED#ifdef __cplusplus
extern "C" {
#endif#define _USE_WRITE 1 /* 1: Enable disk_write function */
#define _USE_IOCTL 1 /* 1: Enable disk_ioctl function */#include "integer.h"/* Status of Disk Functions */
typedef BYTE DSTATUS;/* Results of Disk Functions */
typedef enum {RES_OK = 0, /* 0: Successful */RES_ERROR, /* 1: R/W Error */RES_WRPRT, /* 2: Write Protected */RES_NOTRDY, /* 3: Not Ready */RES_PARERR /* 4: Invalid Parameter */
} DRESULT;/*---------------------------------------*/
/* Prototypes for disk control functions */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);
DWORD get_fattime (void);/* Disk Status Bits (DSTATUS) */#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected *//* Command code for disk_ioctrl fucntion *//* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) *//* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media *//* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status *//* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */#ifdef __cplusplus
}
#endif#endif
????????這個文件定義了幾個以“USER_”為前綴的函數,用于實現具體的Disk IO訪問功能。這些函數在文件diskio.h中定義了,用作FatFS的通用Disk IO函數。
????????文件user_diskio.c定義了一個結構體Diskio_drvTypeDef類型的變量USER_Driver,用于表示USER驅動器訪問接口。結構體Diskio_drvTypeDef在文件ff_gen_drv.h中定義,其成員變量就是5個函數指針,在定義USER_Driver時就為其成員變量賦值了,也就是指向本文件內定義的USER驅動器的Disk IO函數。
????????針對SPI-Flash芯片的FatFS移植,主要就是在文件user_diskio.c中完善這幾個以“USER_”為前綴的函數,實現存儲介質初始化、讀取介質狀態信息、以扇區為基本單位寫入數據或讀取數據。
4、文件ff_gen_drv.h和ff_gen_drv.c
????????文件ff_gen_drv.h定義了單個驅動器的硬件訪問接口結構體類型Diskio_drvTypeDef,還定義了驅動器組的硬件訪問結構體類型Disk_drvTypeDef。這個文件還定義了函數FATFS_LinkDriver(),就是MX_FATFS_Init()內調用的函數,用于將文件user_diskio.h中定義的驅動器對象USER_Driver鏈接到FatFS管理的驅動器列表里。文件ff_gen_drv.h的完整代碼如下:
/********************************************************************************* @file ff_gen_drv.h* @author MCD Application Team* @brief Header for ff_gen_drv.c module.****************************************************************************** @attention** Copyright (c) 2017 STMicroelectronics. All rights reserved.** This software component is licensed by ST under BSD 3-Clause license,* the "License"; You may not use this file except in compliance with the* License. You may obtain a copy of the License at:* opensource.org/licenses/BSD-3-Clause*******************************************************************************
**//* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __FF_GEN_DRV_H
#define __FF_GEN_DRV_H#ifdef __cplusplusextern "C" {
#endif/* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff.h"
#include "stdint.h"/* Exported types ------------------------------------------------------------*//*** @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;/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path);
uint8_t FATFS_UnLinkDriver(char *path);
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, BYTE lun);
uint8_t FATFS_UnLinkDriverEx(char *path, BYTE lun);
uint8_t FATFS_GetAttachedDriversNbr(void);#ifdef __cplusplus
}
#endif#endif /* __FF_GEN_DRV_H *//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
????????結構體Diskio_drvTypeDef是單個驅動器的Disk IO接口定義,其成員就是5個函數指針,用于指向具體存儲介質的Disk IO函數。文件user_diskio.c定義的Diskio_drvTypeDef類型變量USER_Driver就是User-defined介質的Disk IO接口定義,在定義USER_Driver時就為其各個函數指針賦值了,指向user_diskio.c中定義的各個“USER_”函數。
????????文件ff_gen_drv.h還定義了一個結構體類型Disk_drvTypeDef,這是全局的驅動器的硬件接口定義,成員變量nbr表示物理驅動器個數,其他成員變量都是數組,數組長度是全局參數_VOLUMES,也就是卷的個數。其中Diskio_drvTypeDef*drv[_VOLUMES]是各個驅動器的硬件訪問接口指針數組。所以,在FatFS中可以管理多個存儲介質,例如,同時使用SPI-Flash芯片和SD卡,它們的硬件訪問層接口函數可以分別管理。
????????文件ff_gen_drv.h定義了幾個函數,FATFS_LinkDriver()和FATFS_LinkDriverEx()用于連接驅動器,FATFS_GetAttachedDriversNbr()用于返回FatFS當前連接的驅動器的個數,其他兩個函數用于解除驅動器的連接。文件ff_gen_drv.c的代碼如下:
/********************************************************************************* @file ff_gen_drv.c* @author MCD Application Team* @brief FatFs generic low level driver.****************************************************************************** @attention** Copyright (c) 2017 STMicroelectronics. All rights reserved.** This software component is licensed by ST under BSD 3-Clause license,* the "License"; You may not use this file except in compliance with the* License. You may obtain a copy of the License at:* opensource.org/licenses/BSD-3-Clause*******************************************************************************
**/
/* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
Disk_drvTypeDef disk = {{0},{0},{0},0};/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @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);
}/*** @brief Unlinks a diskio driver and decrements the number of active linked* drivers.* @param path: pointer to the logical drive path* @param lun : not used* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_UnLinkDriverEx(char *path, uint8_t lun)
{uint8_t DiskNum = 0;uint8_t ret = 1;if(disk.nbr >= 1){DiskNum = path[0] - '0';if(disk.drv[DiskNum] != 0){disk.drv[DiskNum] = 0;disk.lun[DiskNum] = 0;disk.nbr--;ret = 0;}}return ret;
}/*** @brief Unlinks a diskio driver and decrements the number of active linked* drivers.* @param path: pointer to the logical drive path* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_UnLinkDriver(char *path)
{return FATFS_UnLinkDriverEx(path, 0);
}/*** @brief Gets number of linked drivers to the FatFs module.* @param None* @retval Number of attached drivers.*/
uint8_t FATFS_GetAttachedDriversNbr(void)
{return disk.nbr;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*返回FatFS模塊連接的驅動器的個數*/
uint8_t FATFS_GetAttachedDriversNbr(void)
{return disk.nbr; //FatFS模塊連接的驅動器的個數
}
????????文件ff_gen_drv.c定義了一個Disk_drvTypeDef類型的全局變量disk,用于管理FatFS連接的所有驅動器的Disk IO驅動。在定義時就進行了初始化賦值,各數組成員變量只有一個元素,因為本示例中參數_VOLUMES為1。
Disk_drvTypeDef disk =({0),{0},{0},0}; //全局變量,FatFS連接的驅動器
????????在main()函數中調用的FatFS初始化函數MX_FATFS_Init()的代碼如下:
void MX_FATFS_Init(void)
{/*##FatFs:連接USER驅動器###########################*/retUSER = FATFS_LinkDriver(&USER_Driver,USERPath);
}
????????其功能就是調用函數FATFS_LinkDriver(),將文件user_diskio.c中定義的USER驅動器的Disk IO驅動接口USER_Driver連接到FatFS。通過觀察FATFS_LinkDriver()和FATFS_LinkDriverEx()的代碼,讀者就會發現,執行MX_FATFS_Init()后,USER驅動器的驅動器路徑USERPath被賦值為“0:/”,USER_Driver被添加到了全局的驅動器管理變量disk中,特別是設置了Disk IO驅動器,即
disk.drv[disk.nbr]=drv;//驅動器的Disk IO驅動結構體
????????所以,disk.drv[disk.nbr]就是一個Diskio_drvTypeDef類型的驅動器Disk IO訪問結構體指針,文件diskio.c中的各個Disk IO訪問通用函數中會用到它。
5、文件diskio.h和diskio.c
????????文件diskio.h是Disk IO訪問的通用定義文件,其中定義了基本的結構體、宏定義和DiskIO訪問通用函數。在CubeMX生成的代碼中,diskio.h和diskio.c是作為Disk IO訪問的通用文件,具體器件的Disk IO訪問函數的實現放在另外的文件里,例如,本示例User-defined介質的Disk IO訪問函數是在文件user_diskio.c中實現的。
????????文件diskio.h的完整代碼如下:
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED#ifdef __cplusplus
extern "C" {
#endif#define _USE_WRITE 1 /* 1: Enable disk_write function */
#define _USE_IOCTL 1 /* 1: Enable disk_ioctl function */#include "integer.h"/* Status of Disk Functions */
typedef BYTE DSTATUS;/* Results of Disk Functions */
typedef enum {RES_OK = 0, /* 0: Successful */RES_ERROR, /* 1: R/W Error */RES_WRPRT, /* 2: Write Protected */RES_NOTRDY, /* 3: Not Ready */RES_PARERR /* 4: Invalid Parameter */
} DRESULT;/*---------------------------------------*/
/* Prototypes for disk control functions */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);
DWORD get_fattime (void);/* Disk Status Bits (DSTATUS) */#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected *//* Command code for disk_ioctrl fucntion *//* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) *//* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media *//* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status *//* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */#ifdef __cplusplus
}
#endif#endif
????????上述代碼定義了Disk IO訪問的6個基本函數,其中還有一些宏定義,主要是函數disk_ioctrl()里需要用到的一些指令碼的定義。
????????對應的源程序文件diskio.c的完整代碼如下:
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2017 */
/* */
/* Portions COPYRIGHT 2017 STMicroelectronics */
/* Portions Copyright (C) 2017, ChaN, all right reserved */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various existing */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff_gen_drv.h"#if defined ( __GNUC__ )
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#endif/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern Disk_drvTypeDef disk;/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @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;
}/*** @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 disk_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 */
)
{DRESULT res;res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);return res;
}/*** @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 disk_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 */
)
{DRESULT res;res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);return res;
}
#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 disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res;res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);return res;
}
#endif /* _USE_IOCTL == 1 *//*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
__weak DWORD get_fattime (void)
{return 0;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
????????從文件diskio.c的各函數代碼可以看到,幾個Disk IO通用函數的代碼實質上就是執行了變量disk中物理驅動器的Disk IO函數。對于本示例來說,只有一個驅動器,disk.drv[pdrv]就是USER_Driver,也就是User-defined驅動器,它的Disk IO函數就是文件user_diskio.c中前綴為“USER_”的幾個函數。
????????在文件diskio.c中,函數get_fattime()使用了編譯修飾符_weak,是一個弱函數。這個函數在任何文件里都可以重新實現,其框架在文件fatfs.c中重新定義。函數get_fatime()用于從RTC獲取日期時間作為文件系統的時間戳數據。
????????所以,要針對SPI-Flash芯片W25Q16進行移植,只需實現文件user_diskio.c中前綴為“USER_”的幾個Disk IO訪問函數,以及文件fatfs.c中的函數get_fattime(),其他的工作都由CubeMX自動生成的代碼完成了。
四、Disk IO函數的實現
????????要實現SPI-Flash芯片W25Q16的FatFS移植,只需實現文件user_diskio.c中前綴為“USER_”的幾個Disk IO函數,以及文件fatfs.c中的函數get_fattime()。
1、獲取驅動器狀態的函數USER_status()
????????文件diskio.h中有如下3個驅動器狀態位宏定義:
#define STA_NOINIT0x01 //驅動器未初始化
#define STA_NODISK0x02 //驅動器中無存儲介質
#define STA_PROTECT0x04 //寫保護
????????函數USER_status()用于返回驅動器的狀態,如果存在以上的狀態,就將相應的狀態位置1,否則,返回0x00即可。對于開發板上的W25Q16芯片來說,沒有寫保護問題,只存在是否已初始化的問題。所以,完成后的USER_status()函數代碼以及user_diskio.c文件頭的一些定義如下:
/* USER CODE BEGIN Header */
/********************************************************************************* @file user_diskio.c* @brief This file includes a diskio driver skeleton to be completed by the user.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************//* USER CODE END Header */#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/** Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)* To be suppressed in the future.* Kept to ensure backward compatibility with previous CubeMx versions when* migrating projects.* User code previously added there should be copied in the new user sections before* the section contents can be deleted.*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif/* USER CODE BEGIN DECL *//* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*//* Private variables ---------------------------------------------------------*/
#include "w25flash.h"
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;/* USER CODE END DECL *//* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */Diskio_drvTypeDef USER_Driver =
{USER_initialize,USER_status,USER_read,
#if _USE_WRITEUSER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};/* Private functions ---------------------------------------------------------*//*** @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 */Stat =USER_status(pdrv); /* Get the drive status */return Stat;/* 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 */Stat = STA_NOINIT; // Drive not initialized, Stat=0x01if (0 != Flash_ReadID()) // Read the ID of the Flash chip. As long as it is not 0, it means that En25Q16 has been initialized.Stat &= ~STA_NOINIT; // Stat=0x00return Stat;/* 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 */uint32_t globalAddr= sector<<12; //The sector number is shifted left by 12 bits to get the absolute starting addressuint16_t byteCount = count<<12; //The number of bytes, shifting left by 12 bits is multiplied by 4096, and each sector is 4096 bytes.Flash_ReadBytes(globalAddr, buff, byteCount);//Read datareturn RES_OK;/* 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 globalAddr = sector<<12; // Absolute addressuint16_t byteCount = count<<12; // Number of bytesFlash_WriteSector(globalAddr, buff, byteCount);return RES_OK;/* 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 res = RES_OK;switch(cmd){case CTRL_SYNC: /* Complete the pending write operation process when _FS_READONLY == 0 */break;case GET_SECTOR_COUNT: /* Obtain storage media capacity when _USE_MKFS == 1 */*(DWORD *)buff=FLASH_SECTOR_COUNT; /* Total number of sectors is 4096 */break;case GET_SECTOR_SIZE: /* Get sector size when _MAX_SS != _MIN_SS */*(DWORD *)buff=FLASH_SECTOR_SIZE; /* each sector is 4096 bytes */break;case GET_BLOCK_SIZE: /* When _USE_MKFS == 1, get the size of the erase block */*(DWORD *)buff=16; /* W25Q16錕斤拷32 Block錕斤拷16*32=512 sector */break;default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
????????上述代碼包含了頭文件w25flash.h,這是Flash存儲芯片W25Q16的驅動程序文件,在實現這些Disk IO函數時,要用到W25Q16的驅動函數。Stat是文件user_diskio.c中定義的一個表示驅動器狀態的私有變量,在多個函數中都會用到。
????????函數USER_status()的輸入參數pdrv是驅動器編號,如果系統中有多個驅動器,就需要通過參數pdrv區分不同的驅動器。本例只有一個驅動器,pdrv為0,也就無須區分驅動器。
????????使用W25Q16驅動程序文件w25flash.h中定義的函數Flash_ReadID()讀取芯片ID,只要讀取的ID不為0,就說明連接芯片W25Q16的SPI2接口已經初始化。
????????函數USER_status()的返回值類型為DRESULT,這是文件diskio.h中定義的枚舉類型,詳見前面文件diskio.h的完整代碼。返回值為0x00(即枚舉值RES_OK),表示驅動器狀態正常;否則,返回STA_NOINIT,表示驅動器未初始化。
?2、驅動器初始化函數USER_initialize()?
????????函數USER_initialize()用于驅動器硬件接口的初始化,對于W25Q16來說,就是與其連接的SPI2接口的初始化。在手工移植FatFS的代碼時,一般要在函數disk_initialize()里進行存儲介質的硬件接口初始化,但是在本示例中,SPI2接口的初始化是由CubeMX自動生成的函數MX_SPI2_Init()完成的,在執行MX_FATFS_Init()之前就已經執行了MX_SPI2_Init()。所以,這里無須再對SPI2接口進行初始化。
????????完成后USER_initialize()函數的代碼如下,這里調用函數USER_status()獲取驅動器狀態,而函數USER_status()的返回值總是0x00,所以表示初始化成功。
DSTATUS USER_initialize(BYTE pdrv)
{/*USER CODE BEGIN INIT*/Stat =USER_status(pdrv); //獲取驅動器狀態return Stat;/*USER CODE END INIT*/
}
?3、驅動器IO控制函數USER_ioctl()?
????????函數USER_ioctl()用于執行Disk IO訪問時的一些操作,如獲取總的扇區個數、獲取扇區大小等。只有當宏定義_USE_IOCTL等于1時才有這個函數,這個宏在文件diskio.h中定義,默認值為1。完成后函數USER_ioctl()的代碼如下:
/*** @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 res = RES_OK;switch(cmd){case CTRL_SYNC: /* Complete the pending write operation process when _FS_READONLY == 0 */break;case GET_SECTOR_COUNT: /* Obtain storage media capacity when _USE_MKFS == 1 */*(DWORD *)buff=FLASH_SECTOR_COUNT; /* Total number of sectors is 4096 */break;case GET_SECTOR_SIZE: /* Get sector size when _MAX_SS != _MIN_SS */*(DWORD *)buff=FLASH_SECTOR_SIZE; /* each sector is 4096 bytes */break;case GET_BLOCK_SIZE: /* When _USE_MKFS == 1, get the size of the erase block */*(DWORD *)buff=16; /* W25Q16錕斤拷32 Block錕斤拷16*32=512 sector */break;default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
????????其中,參數cmd是操作指令,這些指令是一些宏定義常數,在文件diskio.h中定義;buff是用于接收或發送數據的緩沖區指針。函數USER_ioctl()實現了如下4個通用指令的處理。
- 指令CTRL_SYNC用于完成掛起的寫操作過程,只要_FS_READONLY==0就需要響應這個指令。這是指存儲介質寫入數據時是否有緩存操作:如果有緩存,就需要將緩存數據寫入介質,如果寫操作都是直接寫入存儲介質的,直接返回RES_OK即可。
- 指令GET_SECTOR_COUNT用于獲取存儲介質的扇區個數,如果_USE_MKFS==1,則需要響應此指令。在使用函數f_mkfs()和f_fdisk()時,這個指令決定卷的大小是需要用到的。芯片W25Q16共有512個扇區。
- 指令GET_SECTOR_SIZE用于獲取扇區大小,如果_MAX_SS不等于_MIN_SS,則要用到這個指令。芯片W25Q16的扇區大小是4096字節(4KB)。
- 指令GET_BLOCK_SIZE用于獲取擦除塊的大小(以扇區為單位),必須是1和32768之間的2的冪次數。如果返回值是1,則表示擦除塊的大小是未知的,或沒有Flash存儲介質。這個指令只有函數f_mkfs()使用,在_USE_MKFS ==1時需要響應此指令。W25Q16的一個塊有16個扇區,所以這里設置為16。
4、讀取扇區數據的函數USER_read()
????????函數USER_read()用于從W25Q16芯片讀取一個或多個扇區的數據,完成后的代碼如下:
/*** @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 */uint32_t globalAddr= sector<<12; //The sector number is shifted left by 12 bits to get the absolute starting addressuint16_t byteCount = count<<12; //The number of bytes, shifting left by 12 bits is multiplied by 4096, and each sector is 4096 bytes.Flash_ReadBytes(globalAddr, buff, byteCount);//Read datareturn RES_OK;/* USER CODE END READ */
}
????????其中,參數buff是用來存儲讀出數據的緩沖區,sector是讀取數據的起始扇區編號,count是要讀出數據的扇區個數。
????????上述程序使用W25Q16的驅動函數Flash_ReadBytes()讀出數據,這個函數需要數據絕對起始地址作為輸入參數。對于W25Q16來說,將扇區編號sector左移12位得到的就是這個扇區的絕對起始地址。每個扇區是4096字節,將扇區個數count左移12位就等于乘以4096,也就是總的字節數。
????????函數Flash_ReadBytes()是文件w25flash.c中的W25Q16驅動程序函數,其代碼如下。
//從任何地址開始讀取指定長度的數據
//globalAddr:開始讀取的地址(24bit), pBuffer:數據存儲區指針,byteCount:要讀取的字節數
void Flash_ReadBytes(uint32_t globalAddr,uint8_t* pBuffer,uint16_t byteCount)
{ uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr,&byte2,&byte3,&byte4);//24位地址分解為3個字節__Select_Flash(); //CS=0SPI_TransmitOneByte(0x03); //Command=0x03, read dataSPI_TransmitOneByte(byte2); //發送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);SPI_ReceiveBytes(pBuffer,byteCount);//接收byteCount個字節數據__Deselect_Flash(); //CS=1
}
?5、將數據寫入扇區的函數USER_write()?
????????函數USER_write()用于將一個緩沖區內的數據寫入Flash芯片W25Q16,完成后的代碼如下:
/*** @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 globalAddr = sector<<12; // Absolute addressuint16_t byteCount = count<<12; // Number of bytesFlash_WriteSector(globalAddr, buff, byteCount);return RES_OK;/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */
????????其中,buff是待寫入Flash芯片的數據緩沖區的指針,sector是起始扇區編號,count是需要寫入的扇區個數。
????????上述程序使用W25Q16的驅動函數Flash_WriteSector()寫入數據,同樣,先通過扇區號得到絕對地址,通過扇區個數得到字節個數。函數Flash_WriteSector()的代碼如下:
// 從某個Sector的起始位置開始寫數據,數據可能跨越多個Page,甚至跨越Sector,不必提前擦除
// globalAddr是寫入初始地址,全局地址,是扇區的起始地址,
// pBuffer是要寫入數據緩沖區指針
// byteCount是需要寫入的數據字節數,byteCount不能超過64K,也就是一個Block(16個扇區)的大小,但是可以超過一個Sector(4K字節)
// 如果數據超過一個Page,自動分成多個Page,調用EN25Q_WriteInPage分別寫入
void Flash_WriteSector(uint32_t globalAddr,const uint8_t* pBuffer,uint16_t byteCount)
{
//需要先擦除扇區,可能是重復寫文件uint8_t secCount = (byteCount/FLASH_SECTOR_SIZE);//數據覆蓋的扇區個數if ((byteCount%FLASH_SECTOR_SIZE) > 0)secCount++;uint32_t startAddr = globalAddr;for (uint8_t k=0;k<secCount;k++){Flash_EraseSector(startAddr); //擦除扇區startAddr += FLASH_SECTOR_SIZE; //移到下一個扇區}//分成Page寫入數據,寫入數據的最小單位是Pageuint16_t leftBytes = byteCount%FLASH_PAGE_SIZE; //非整數個Page剩余的字節數,即最后一個Page寫入的數據uint16_t pgCount = byteCount/FLASH_PAGE_SIZE; //前面整數個Pageuint8_t* buff = (uint8_t*)pBuffer;for(uint16_t i=0;i<pgCount;i++) //寫入前面pgCount個Page的數據,{Flash_WriteInPage(globalAddr,buff,FLASH_PAGE_SIZE);//寫一整個Page的數據globalAddr += FLASH_PAGE_SIZE; //地址移動一個Pagebuff += FLASH_PAGE_SIZE; //數據指針移動一個Page大小}if (leftBytes > 0)Flash_WriteInPage(globalAddr,buff,leftBytes); //最后一個Page,不是一整個Page的數據
}
????????W25Q16擦除操作的最小單位是扇區,寫入數據操作的基本單位是頁。在寫入數據之前,程序需要調用Flash_EraseSector()擦除要用到的扇區,因為可能是已有文件的重復寫入。然后,調用函數Flash_WriteInPage()將數據分解為多個頁寫入Flash芯片。
?6、獲取RTC時間的函數get_fattime()?
????????函數get_fattime()用于獲取RTC時間,作為創建文件或修改文件的時間戳數據。文件diskio.c中的函數get_fattime()是用編譯修飾符_weak定義的弱函數,在文件fatfs.c中重新實現這個函數。完成后的函數代碼如下:
/*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return fat_GetFatTimeFromRTC();/* USER CODE END get_fattime */
}
? ? ? ? 其中at_GetFatTimeFromRTC(),在用戶程序file_opera.c中定義:
//Get time from RTC as the file system time stamp data
DWORD fat_GetFatTimeFromRTC()
{RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);WORD date=(2000+sDate.Year-1980)<<9;date = date |(sDate.Month<<5) |sDate.Date;WORD time=sTime.Hours<<11;time = time | (sTime.Minutes<<5) | (sTime.Seconds>1);DWORD dt=(date<<16) | time;return dt;}elsereturn 0;
}
????????函數get_fattime()需要返回一個DWORD類型的數,這個數的高16位是日期,低16位是時間。其中秒的數據是實際秒時間的一半。
????????在保存文件時,FatFS會自動調用函數get_fattime()獲取RTC時間,然后作為文件的時間戳信息寫入FAT里。在使用函數f_stat()獲取文件信息時,返回的是一個FILINFO結構體變量,其成員變量fdate和ftime就是文件的修改時間。
????????針對SPI-Flash芯片和RTC完成以上的6個函數后,就完成了FatFS針對硬件層的移植,后面就可以使用FatFS的應用層API函數在SPI-Flash芯片上創建FAT文件系統,管理文件了。
五、在SPI-Flash芯片上使用文件系統
1、主程序功能
????????完成硬件層移植后,我們就可以使用FatFS的API函數在W25Q16芯片上創建FAT文件系統,進行文件和目錄的管理,以及文件讀寫操作。
????????在首次使用一個存儲介質時,我們需要先執行函數f_mkfs()將存儲介質格式化,也就是創建FAT或exFAT文件系統。在格式化之后,存儲介質就成了一個驅動器。在嵌入式系統中,一般不會在一個存儲介質上進行分區,所以一個物理驅動器上只有一個卷,也就是一個邏輯驅動器。
????????要使用一個驅動器,需要先使用函數f_mount()將其掛載到文件系統對象,然后才可以進行文件管理和文件讀寫操作。如果需要在程序運行期間彈出一個驅動器,還可以使用函數f_mount()卸載它,只需將文件系統指針參數設置為NULL即可。
????????本示例演示FAT文件系統的一些基本操作,包括磁盤格式化、創建文件、讀取文件、獲取磁盤信息、獲取文件信息等。主程序代碼如下:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "rtc.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ff.h"
#include "keyled.h"
#include "w25flash.h"
#include "file_opera.h"
#include <stdio.h>
/* USER CODE END Includes *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_FATFS_Init();MX_RTC_Init();MX_SPI2_Init();MX_USART6_UART_Init();/* USER CODE BEGIN 2 */// Start Menuuint8_t startstr[] = "Demo12_1: FatFS on SPI-Flash chip.\r\n";HAL_UART_Transmit(&huart6,startstr,sizeof(startstr),0xFFFF);FRESULT res=f_mount(&USERFatFS, "0:", 1); //Mount the driveif (res==FR_OK) //Mounted successfullyprintf("FatFS is mounted, OK.\r\n");elseprintf("No file system.\r\n");//The menu item start line is used to clear the area when drawing the second group of menus.//Group 1 Menuprintf("[1][S2]KeyUp = Format chip.\r\n");printf("[2][S4]KeyLeft = FAT disk info.\r\n");printf("[3][S5]KeyRight = List all entries.\r\n");printf("[4][S3]KeyDown = Next menu page.\r\n");KEYS waitKey; //Key inputwhile(1){waitKey=ScanPressedKey(KEY_WAIT_ALWAYS); //Waiting for the button//Format Flash chips and create file systemif (waitKey == KEY_UP){BYTE workBuffer[FLASH_SECTOR_SIZE]; //FLASH_SECTOR_SIZE=4096DWORD clusterSize=2*FLASH_SECTOR_SIZE;//The cluster must be greater than or equal to 1 sectorprintf("Formatting the chip...\r\n");FRESULT res=f_mkfs("0:", FM_FAT, clusterSize, workBuffer, FLASH_SECTOR_SIZE);//Create a file system. The cluster size must be greater than or equal to 1 sector.//The workBuffer size should be an integer multiple of the sector size.if (res ==FR_OK)printf("Format OK.\r\n");elseprintf("Format fail.\r\n");}else if(waitKey == KEY_LEFT)fatTest_GetDiskInfo(); //Get and display disk informationelse if (waitKey == KEY_RIGHT)fatTest_ScanDir("0:/"); //Scan the files and directories in the root directoryelse if (waitKey == KEY_DOWN)break; //Scan the files and directories in the root directoryprintf("Reselect menu item or reset.\r\n");HAL_Delay(500); //Delay 500 to eliminate the impact of key jitter}//Group 2 Menuprintf("[5][S2]KeyUp = Write files.\r\n");printf("[6][S4]KeyLeft = Read a TXT file.\r\n");printf("[7][S5]KeyRight = Read a BIN file.\r\n");printf("[8][S3]KeyDown = Get a file info.\r\n");HAL_Delay(500); //Delay 500 to eliminate the impact of key jitterwhile(2){//Waiting for the buttonwaitKey=ScanPressedKey(KEY_WAIT_ALWAYS);if (waitKey==KEY_UP ) //Write a file test{fatTest_WriteTXTFile("readme.txt",2019,3,5);fatTest_WriteTXTFile("help.txt",2016,11,15);fatTest_WriteBinFile("ADC500.dat",20,500);fatTest_WriteBinFile("ADC1000.dat",50,1000);f_mkdir("0:/SubDir1"); //Create a directoryf_mkdir("0:/MyDocs"); //Create a directory}else if (waitKey==KEY_LEFT )fatTest_ReadTXTFile("readme.txt"); //Test reading text fileselse if (waitKey==KEY_RIGHT)fatTest_ReadBinFile("ADC500.dat"); //Test reading binary fileselse if (waitKey==KEY_DOWN)fatTest_GetFileInfo("ADC1000.dat"); //Test to obtain file informationprintf("Reselect menu item or reset.\r\n");HAL_Delay(500); //Delay, eliminate the impact of key jitter}/* USER CODE END 2 */// 省略/* USER CODE BEGIN 4 */
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END 4 */// 省略以下代碼
????????其中,include部分包含了一個文件file_opera.h,是本示例創建的用于測試文件操作的程序文件。因為這些文件操作測試函數與硬件無關,所以也可以在后面用于SD卡、U盤的文件操作測試。
????????在外設初始化部分,函數MX_SPI2_Init()對與W25Q16連接的SPI2接口進行初始化,函數MX_FATFS_Init()用于FatFS的初始化。硬件初始化完成后,執行函數f_mount()立即掛載文件系統,即
FRESULT res=f_mount(&USERFatFS,"0:",1); //掛載文件系統
????????其中,USERFatFS是在文件fatfs.c中定義的FATFS類型變量,表示文件系統。系統中只有一個驅動器,驅動器號是“0:”。這樣掛載后,USERFatFS就表示邏輯驅動器0上的文件系統。
????????如果函數f_mount()的返回值為FR_OK,就表示驅動器掛載成功,可以進行文件系統的操作了;否則,就是沒有文件系統,需要先執行函數f_mkfs()進行格式化操作。
????????不管函數f_mount()的返回值是什么,程序會在串口助手上顯示一組菜單,內容如下:
[1][S2]KeyUp =Format chip
[2][S4]KeyLeft =FAT disk info
[3][S5]KeyRight=List all entries
[4][S3]KeyDown =Next menu page
????????使用開發板上的4個按鍵進行選擇操作,函數ScanPressedKey()是文件keyled.h中定義的輪詢方式檢測按鍵的函數。按下KeyDown后會顯示第2組菜單,內容如下:
[5][S2]KeyUp=Write files
[6][S4]KeyLeft =Read a TXT file
[7][S5]KeyRight=Read a BIN file
[8][S3]KeyDown =Get a file info