細說STM32單片機SPI-Flash芯片的FatFS移植

目錄

一、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為168MHzPCLK1的頻率為42MHzSPI2是掛在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編碼頁,編碼頁如果選擇不正常,可能
導致打開文件失敗。如果要支持中文,應該選擇Simplified Chinese
(DBCS),對應的_CODE_PAGE參數值是936

USE_LFN

Disabled

是否使用長文件名(LFN)

MAX_LFN

255

設定值范圍為12至255,是LFN的最大長度

LFN_UNICODE

ANSI/OEM

是否將FatFS API中的字符編碼切換為Unicode。當USE_LFN設置為
Enabled時, LFN_UNICODE才可以設置為1(Unicode)。當USE_LFN
設置為Disabled時, LFN_UNICODE只能設置為ANSI/OEM

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字節,所
以設置為4096。當MAX_SS大于512時,在disk_ioctl()函數中需要
實現GET_SECTOR_SIZE指令

MIN_SS

512

最小扇區大小(字節數),只能設置為512、1024、2048或4096

MULTI_PARTITION

Disabled

設置為Disabled時,每個卷與相同編號的物理驅動器綁定,只會掛
載第一個分區。設置為Enabled時,每個卷與分區表VolToPart[]關聯

USE_TRIM

Disabled

是否使用ATA_TRIM特性。要想使用Trim特性,需要在disk_ioctl()
函數中實現CTRL_TRIM指令

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)可減少
內存占用512字節

FS_EXFAT

Disabled

是否支持exFAT文件系統,當_USE_LFN設置為0時,這個參數只
能設置為Disabled

FS_NORTC

Dynamic
timestamp

如果系統有RTC提供實時的時間,就設置為Dynamic timestamp;如
果系統沒有RTC提供實時的時間,就設置為Fixed timestamp

FS_REENTRANT

Disabled

設置FatFS的可重入性,在CubeMX里如果沒有啟用FreeRTOS,這
個參數只能設置為Disabled,如果啟用了FreeRTOS這個參數只能
設置為Enabled

FS_TIMEOUT

1000

超時設置,單位是節拍數。FS_REENTRANT設置為Disabled時,這
個參數無效

FS_LOCK

2

如果要啟用文件鎖定功能,設定FS_LOCK的值大于或等于1表示
可同時打開的文件個數。設定值范圍為0~255

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/87624.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/87624.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/87624.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ubuntu 22.04 安裝部署logstash 7.10.0詳細教程

安裝部署logstash 7.10.0詳細教程 一、下載并安裝二、新建配置文件三、賦權文件權限四、檢測文件grok語法是否異常五、啟動服務六、安裝啟動常見問題 【背景】 整個elk安裝是基于ubuntu 22.04和jdk 11環境。logstash采用 *.deb方式安裝&#xff0c;需要服務器能聯網。ubuntu 22…

JVM對象創建與內存分配機制深度剖析

對象創建的主要流程 類加載檢查 在創建對象之前&#xff0c;JVM 首先會檢查該類是否已經加載、解析并初始化&#xff1a; 如果沒有&#xff0c;則會通過類加載機制加載類元信息&#xff08;Class Metadata&#xff09;到方法區。 這個過程包括&#xff1a;加載&#xff08;load…

Navicat 技術指引 | TiDB 的 AI 查詢交互功能

目前&#xff0c;Navicat 兩款工具支持對 TiDB 數據庫的管理開發功能&#xff1a;一款是旗艦款 Navicat Premium&#xff0c;另一款是其輕量化功能的 Navicat Premium Lite&#xff08;官方輕量級免費版&#xff09;。Navicat 自版本 17.1 開始支持 TiDB 7。它支持的系統有 Win…

以list為輸入條件,查詢數據庫表,java中的mapper層和mybatis層應該怎么寫?

根據一個 List 中的兩個字段 rangeCode 和 unitcd&#xff0c;查詢數據庫表 model_engineering_spatial_unit。這個需求在 Java MyBatis 項目中非常常見&#xff0c;下面我將為你詳細寫出 Mapper 接口&#xff08;Java&#xff09; 和 MyBatis XML 映射文件 的寫法。 ? 前提…

pyspark 創建DataFrame

from pyspark.sql import SparkSession from pyspark.sql import StructType, StructField, IntegerType,StringType spark SparkSession.builder.appName(test).getOrCreate() 1、 從列表中創建DataFrame data [(1,"alice"),(2,Blob),(3,Charlie)] columns [&qu…

Vim:從入門到進階的高效文本編輯器之旅

目錄 一、Vim簡介 二、Vim的基礎操作 2.1 進入和退出Vim 2.2 Vim的三種模式 2.3 基礎移動 三、Vim的高效編輯技巧 3.1 文本編輯 3.2 文本刪除與修改 3.3 復制與粘貼 四、Vim的進階使用 4.1 搜索與替換 4.2 寄存器與宏 4.3 插件與配置 五、結語 在編程界&#xff0…

Docker基礎理論與阿里云Linux服務器安裝指南

文章目錄 一、Docker核心概念二、阿里云環境準備三、Docker安裝與配置四、核心容器部署示例五、開發環境容器化六、運維管理技巧七、安全加固措施 一、Docker核心概念 容器化本質&#xff1a; 輕量級虛擬化技術&#xff0c;共享主機內核進程級隔離&#xff08;cgroups/namespac…

c#使用筆記之try catch和throw

一、try catch 一種報錯的捕捉機制&#xff0c;try塊里運行的代碼出現錯誤的時候就會去執行catch塊所以一般catch塊里都是把錯誤打印出來或者保存到log日志里&#xff1b; 1.1、具體使用 catch可以用&#xff08;&#xff09;來選擇捕捉什么類型的錯誤&#xff0c;一般用Exc…

(新手友好)MySQL學習筆記(9):索引(常見索引類型,查找結構的發展(二分查找法,二叉搜索樹,平衡二叉樹,B樹,B+樹))

目錄 索引 常見索引類型 B樹 二分查找法 二叉搜索樹和平衡二叉樹 B樹和B樹 索引 index&#xff0c;是存儲引擎用于快速找到數據的一種數據結構。 MySQL默認使用InnoDB存儲引擎&#xff0c;該存儲引擎是最重要&#xff0c;使用最廣泛的&#xff0c;除非有非常特別的原因需要使用…

進程間通信1(匿名管道)Linux

1 進程間通信的必要性 首先要明確進程間是相互獨立的&#xff08;獨享一份虛擬地址空間&#xff0c;頁表&#xff0c;資源&#xff09;&#xff0c;那怎么樣才能使得兩個進程間實現資源的發送&#xff1f;所以&#xff0c;兩個進程一定需要看到同一份資源&#xff0c;并且?個…

CAN2.0、DoIP、CAN-FD汽車協議詳解與應用

一、CAN2.0 協議詳解與應用示例 1. 技術原理與特性 協議架構&#xff1a;基于 ISO 11898 標準&#xff0c;采用載波監聽多路訪問 / 沖突檢測&#xff08;CSMA/CD&#xff09;機制&#xff0c;支持 11 位&#xff08;CAN2.0A&#xff09;或 29 位&#xff08;CAN2.0B&#xff…

使用nvm管理npm和pnpm

1.使用nvm管理npm // 查看nvm版本 nvm -v // 查看可安裝的 node 版本 nvm ls-remote // 安裝指定 node 版本 nvm install 24.0.0 // 查看當前已安裝的 node 版本及當前使用的版本 nvm list // 使用某個版本 node nvm use 24.0.0 // 卸載指定 node 版本 nvm uninstall 16.20.1…

YOLO11+QT6+Opencv+C++訓練加載模型全過程講解

實現效果&#xff1a; Yolov11環境搭建&#xff08;搭建好的可以直接跳過&#xff09; 最好使用Anconda進行包管理&#xff0c;安裝可參考【文章】。下面簡單過一下如何快速部署環境。如果搭建過或可以參考其他文章可以跳過Yolo11環境搭建這一章節。總體來說Yolov11環境搭建越…

Python 腳本,用于將 PDF 文件高質量地轉換為 PNG 圖像

import os import fitz # PyMuPDF from PIL import Image import argparse import logging from tqdm import tqdm# 配置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(PDF2PNG)def convert_pdf_…

【CUDA GPU 支持安裝全攻略】PyTorch 深度學習開發者指南

PyTorch 的 CUDA GPU 支持 安裝五條鐵律&#xff08;最新版 2025 修訂&#xff09;&#xff08;適用于所有用戶&#xff09;-CSDN博客 是否需要預先安裝 CUDA Toolkit&#xff1f;——按使用場景分級推薦及進階說明-CSDN博客 “100% 成功的 PyTorch CUDA GPU 支持” 安裝攻略…

Cyberith 運動模擬器Virtualizer2:提升虛擬現實沉浸體驗

奧地利Cyberith公司是一家專注于虛擬現實&#xff08;VR&#xff09;互動解決方案的創新型科技企業&#xff0c;以其研發的Virtualizer虛擬現實步態模擬設備而聞名。該公司的核心技術體現在其設計和制造的全方位跑步機式VR交互平臺上&#xff0c;使得用戶能夠在虛擬環境中實現自…

常見的數據處理方法有哪些?ETL中的數據處理怎么完成

在數字化轉型縱深推進的背景下&#xff0c;數據作為新型生產要素已成為驅動企業戰略決策、科研創新及智能化運營的核心戰略資產。數據治理價值鏈中的處理環節作為關鍵價值節點&#xff0c;其本質是通過系統化處理流程將原始觀測數據轉化為結構化知識產物&#xff0c;以支撐預測…

WHAT - 為甲方做一個官網(二)- 快速版

文章目錄 一、明確需求優先級&#xff08;快速決策&#xff09;二、推薦零代碼/低代碼工具&#xff08;附對比&#xff09;方案1&#xff1a;低代碼建站平臺&#xff08;適合無技術用戶&#xff0c;拖拽式操作&#xff09;方案2&#xff1a;CMS系統&#xff08;適合內容更新頻繁…

音視頻之H.264視頻編碼傳輸及其在移動通信中的應用

系列文章&#xff1a; 1、音視頻之視頻壓縮技術及數字視頻綜述 2、音視頻之視頻壓縮編碼的基本原理 3、音視頻之H.264/AVC編碼器原理 4、音視頻之H.264的句法和語義 5、音視頻之H.264/AVC解碼器的原理和實現 6、音視頻之H.264視頻編碼傳輸及其在移動通信中的應用 7、音視…

C#語言入門-task2 :C# 語言的基本語法結構

下面從四個方面對C#的基本語法進行簡單介紹&#xff1a; 1. 數據類型 C#的類型可分為值類型和引用類型。值類型變量直接存儲數據&#xff0c;引用類型變量則存儲對象的引用。 值類型&#xff1a;涵蓋整數類型&#xff08;像int、long&#xff09;、浮點類型&#xff08;例如…