即便讀者可能不瞭解文件系統,讀者也必定對「文件」這個概念十分熟悉。數據在PC上是以文件的形式儲存在磁盤中的,這些數據的形式通常爲ASCII碼或二進制形式。在上一章咱們已經寫好了QSPI Flash芯片的驅動函數,咱們能夠很是方便的在QSPI Flash芯片上讀寫數據。如須要記錄本書的書名「零死角玩轉STM32-F7系列」,能夠把這些文字轉化成ASCII碼,存儲在數組中,而後調用QSPI_FLASH_BufferWrite函數,把數組內容寫入到QSPI Flash芯片的指定地址上,在須要的時候從該地址把數據讀取出來,再對讀出來的數據以ASCII碼的格式進行解讀。html
可是,這樣直接存儲數據會帶來極大的不便,如難以記錄有效數據的位置,難以肯定存儲介質的剩餘空間,以及應以何種格式來解讀數據。就如同一個巨大的圖書館無人管理,雜亂無章地存放着各類書籍,難以查找所需的文檔。想象一下圖書館的採購人員購書後,把書籍往館內一扔,拍拍屁股走人,當有人來借閱某本書的時候,就不得不一本本地查找。這樣直接存儲數據的方式對於小容量的存儲介質如EEPROM還能夠接受,但對於QSPI Flash芯片或者SD卡之類的大容量設備,咱們須要一種高效的方式來管理它的存儲內容。編程
這些管理方式即爲文件系統,它是爲了存儲和管理數據,而在存儲介質創建的一種組織結構,這些結構包括操做系統引導區、目錄和文件。常見的windows下的文件系統格式包括FAT32、NTFS、exFAT。在使用文件系統前,要先對存儲介質進行格式化。格式化先擦除原來內容,在存儲介質上新建一個文件分配表和目錄。這樣,文件系統就能夠記錄數據存放的物理地址,剩餘空間。windows
使用文件系統時, 數據都以文件的形式存儲。寫入新文件時,先在目錄中建立一個文件索引,它指示了文件存放的物理地址,再把數據存儲到該地址中。當須要讀取數據時,能夠從目錄中找到該文件的索引,進而在相應的地址中讀取出數據。具體還涉及到邏輯地址、簇大小、不連續存儲等一系列輔助結構或處理過程。數組
文件系統的存在使咱們在存取數據時,再也不是簡單地向某物理地址直接讀寫,而是要遵循它的讀寫格式。如通過邏輯轉換,一個完整的文件可能被分開成多段存儲到不連續的物理地址,使用目錄或鏈表的方式來獲知下一段的位置。緩存
上一章的SPI Flash芯片驅動只完成了向物理地址寫入數據的工做,而根據文件系統格式的邏輯轉換部分則須要額外的代碼來完成。實質上,這個邏輯轉換部分能夠理解爲當咱們須要寫入一段數據時,由它來求解向什麼物理地址寫入數據、以什麼格式寫入及寫入一些原始數據之外的信息(如目錄)。這個邏輯轉換部分代碼咱們也習慣稱之爲文件系統。網絡
上面提到的邏輯轉換部分代碼(文件系統)即爲本章的要點,文件系統龐大而複雜,它須要根據應用的文件系統格式而編寫,並且通常與驅動層分離開來,很方便移植,因此工程應用中通常是移植現成的文件系統源碼。app
FatFs是面向小型嵌入式系統的一種通用的FAT文件系統。它徹底是由AISI C語言編寫而且徹底獨立於底層的I/O介質。所以它能夠很容易地不加修改地移植到其餘的處理器當中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32等格式,因此咱們利用前面寫好的QSPI Flash芯片驅動,把FatFs文件系統代碼移植到工程之中,就能夠利用文件系統的各類函數,對QSPI Flash芯片以「文件」格式進行讀寫操做了。框架
FatFs文件系統的源碼能夠從fatfs官網下載:函數
http://elm-chan.org/fsw/ff/00index_e.html測試
在移植FatFs文件系統到開發板以前,咱們先要到FatFs的官網獲取源碼,最新版本爲R0.11a,官網有對FatFs作詳細的介紹,有興趣能夠了解。解壓以後可看到裏面有 doc 和 src 這兩個文件夾,見圖 25-1。doc 文件夾裏面是一些使用幫助文檔; src 纔是FatFs文件系統的源碼。
圖 25-1 FatFs文件目錄
打開 doc 文件夾,可看到如圖 25-2的文件目錄:
圖 25-2 doc文件夾的文件目錄
其中 en 和 ja 這兩個文件夾裏面是編譯好的html文檔,講的是FATFS裏面各個函數的使用方法,這些函數都是封裝得很是好的函數,利用這些函數咱們就能夠操做SPI Flash芯片。有關具體的函數咱們在用到的時候再講解。這兩個文件夾的惟一區別就是 en 文件夾下的文檔是英文的,ja 文件夾下的是日文的。img文件夾包含en和ja文件夾下文件須要用到的圖片,還有四個名爲app.c文件,內容都是FatFs具體應用例程。00index_e.html和00index_j.html是一些關於FATFS的簡介,至於另外兩個文件能夠不看。
打開 src 文件夾,可看到如圖 25-3的文件目錄:
圖 25-3 src文件夾的文件目錄
option 文件夾下是一些可選的外部c文件,包含了多語言支持須要用到的文件和轉換函數。
diskio.c文件是FatFs移植最關鍵的文件,它爲文件系統提供了最底層的訪問QSPI Flash芯片的方法,FatFs有且僅有它須要用到與QSPI Flash芯片相關的函數。diskio.h定義了FatFs用到的宏,以及diskio.c文件內與底層硬件接口相關的函數聲明。
00history.txt介紹了FatFs的版本更新狀況。
00readme.txt說明了當前目錄下 diskio.c 、diskio.h、ff.c、ff.h、integer.h的功能。
src文件夾下的源碼文件功能簡介以下:
q integer.h:文件中包含了一些數值類型定義。
q diskio.c:包含底層存儲介質的操做函數,這些函數須要用戶本身實現,主要添加底層驅動函數。
q ff.c: FatFs核心文件,文件管理的實現方法。該文件獨立於底層介質操做文件的函數,利用這些函數實現文件的讀寫。
q cc936.c:本文件在option目錄下,是簡體中文支持所須要添加的文件,包含了簡體中文的GBK和Unicode相互轉換功能函數。
q ffconf.h:這個頭文件包含了對FatFs功能配置的宏定義,經過修改這些宏定義就能夠裁剪FatFs的功能。如須要支持簡體中文,須要把ffconf.h中的_CODE_PAGE 的宏改爲936並把上面的cc936.c文件加入到工程之中。
建議閱讀這些源碼的順序爲:integer.h --> diskio.c --> ff.c 。
閱讀文件系統源碼ff.c文件須要必定的功底,建議讀者先閱讀FAT32的文件格式,再去分析ff.c文件。若僅爲使用文件系統,則只須要理解integer.h及diskio.c文件並會調用ff.c文件中的函數就能夠了。本章主要講解如何把FATFS文件系統移植到開發板上,並編寫一個簡單讀寫操做範例。
移植FatFs以前咱們先經過FatFs的程序結構圖瞭解FatFs在程序中的關係網絡,見圖 25-4。
圖 25-4 FatFs程序結構圖
用戶應用程序須要由用戶編寫,想實現什麼功能就編寫什麼的程序,通常咱們只用到f_mount()、f_open()、f_write()、f_read()就能夠實現文件的讀寫操做。
FatFs組件是FatFs的主體,文件都在源碼src文件夾中,其中ff.c、ff.h、integer.h以及diskio.h四個文件咱們不須要改動,只須要修改ffconf.h和diskio.c兩個文件。
底層設備輸入輸出要求實現存儲設備的讀寫操做函數、存儲設備信息獲取函數等等。咱們使用QSPI Flash芯片做爲物理設備,在上一章節已經編寫好了QSPI Flash芯片的驅動程序,這裏咱們就直接使用。
FatFs屬於軟件組件,不須要附帶其餘硬件電路。咱們使用QSPI Flash芯片做爲物理存儲設備,其硬件電路在上一章已經作了分析,這裏就直接使用。
上一章咱們已經實現了QSPI Flash芯片驅動程序,並實現了讀寫測試,爲移植FatFs方便,咱們直接拷貝一份工程,咱們在工程基礎上添加FatFs組件,並修改main函數的用戶程序便可。
1) 先拷貝一份QSPI Flash芯片測試的工程文件(整個文件夾),並修改文件夾名爲「QSPI—FatFs文件系統」。將FatFs源碼中的src文件夾整個文件夾拷貝一份至「QSPI—FatFs文件系統\USER\」文件夾下並修更名爲「FATFS」,見圖 25-5。
圖 25-5 拷貝FatFs源碼到工程
2) 使用KEIL軟件打開工程文件(..\QSPI—FatFs文件系統\Project\RVMDK(uv5)\ BH-F767.uvprojx),並將FatFs組件文件添加到工程中,須要添加有ff.c、diskio.c和cc936.c三個文件,見圖 25-6。
圖 25-6 添加FatFS文件到工程
3) 添加FATFS文件夾到工程的include選項中。打開工程選項對話框,選擇「C/C++」選項下的「Include Paths」項目,在彈出路徑設置對話框中選擇添加「FATFS」文件夾,見圖 25-7。
圖 25-7 添加FATFS路徑到工程選項
4) 若是如今編譯工程,能夠發現有兩個錯誤,一個是來自diskio.c文件,提示有一些頭文件沒找,diskio.c文件內容是與底層設備輸入輸出接口函數文件,不一樣硬件設計驅動就不一樣,須要的文件也不一樣;另一個錯誤來自cc936.c文件,提示該文件不是工程所必需的,這是由於FatFs默認使用日語,咱們想要支持簡體中文須要修改FatFs的配置,即修改ffconf.h文件。至此,將FatFs添加到工程的框架已經操做完成,接下來要作的就是修改diskio.c文件和ffconf.h文件。
FatFs文件系統與底層介質的驅動分離開來,對底層介質的操做都要交給用戶去實現,它僅僅是提供了一個函數接口而已。表 25-1爲FatFs移植時用戶必須支持的函數。經過表 251咱們能夠清晰知道不少函數是在必定條件下才須要添加的,只有前三個函數是必須添加的。咱們徹底能夠根據實際需求選擇實現用到的函數。
前三個函數是實現讀文件最基本需求。接下來三個函數是實現建立文件、修改文件須要的。爲實現格式化功能,須要在disk_ioctl添加兩個獲取物理設備信息選項。咱們通常只有實現前面六個函數就能夠了,已經足夠知足大部分功能。
爲支持簡體中文長文件名稱須要添加ff_convert和ff_wtoupper函數,實際這兩個已經在cc936.c文件中實現了,咱們只要直接把cc936.c文件添加到工程中就能夠了。
後面六個函數通常都不用。如真有須要能夠參考syscall.c文件(src\option文件夾內)。
表 25-1 FatFs移植須要用戶支持函數
函數 |
條件(ffconf.h) |
備註 |
disk_status |
老是須要 |
底層設備驅動函數 |
disk_write |
_FS_READONLY == 0 |
|
disk_ioctl (GET_SECTOR_COUNT) |
_USE_MKFS == 1 |
|
disk_ioctl (GET_SECTOR_SIZE) |
_MAX_SS != _MIN_SS |
|
disk_ioctl (CTRL_TRIM) |
_USE_TRIM == 1 |
|
ff_convert |
_USE_LFN != 0 |
Unicode支持,爲支持簡體中文,添加cc936.c到工程便可 |
ff_cre_syncobj |
_FS_REENTRANT == 1 |
FatFs可重入配置,須要多任務系統支持(通常不須要) |
ff_mem_alloc |
_USE_LFN == 3 |
長文件名支持,緩衝區設置在堆空間(通常設置_USE_LFN = 2 ) |
底層設備驅動函數是存放在diskio.c文件,咱們的目的就是把diskio.c中的函數接口與QSPI Flash芯片驅動鏈接起來。總共有五個函數,分別爲設備狀態獲取(disk_status)、設備初始化(disk_initialize)、扇區讀取(disk_read)、扇區寫入(disk_write)、其餘控制(disk_ioctl)。
接下來,咱們對每一個函數結合QSPI Flash芯片驅動作詳細講解。
代碼清單 25-1設備狀態獲取
1 DSTATUS TM_FATFS_FLASH_SPI_disk_status(BYTE lun)
2 {
3 FLASH_DEBUG_FUNC();
4 if (sFLASH_ID == QSPI_FLASH_ReadID()) { /*檢測FLASH是否正常工做*/
5 return TM_FATFS_FLASH_SPI_Stat &= ~STA_NOINIT; /* Clear STA_NOINIT flag */
6 } else {
7 return TM_FATFS_FLASH_SPI_Stat |= STA_NOINIT;
8 }
9 }
TM_FATFS_FLASH_SPI_disk_status函數只有一個參數lun,沒有使用。對於QSPI Flash芯片,咱們直接調用在QSPI_FLASH_ReadID()獲取設備ID,而後判斷是否正確,若是正確,函數返回正常標準;若是錯誤,函數返回異常標誌。
代碼清單 25-2 設備初始化
1 DSTATUS TM_FATFS_FLASH_SPI_disk_initialize(BYTE lun)
2 {
3 GPIO_InitTypeDef GPIO_InitStruct;
4
5 /* 使能 QSPI 及 GPIO 時鐘 */
6 QSPI_FLASH_CLK_ENABLE();
7 QSPI_FLASH_CLK_GPIO_ENABLE();
8 QSPI_FLASH_BK1_IO0_CLK_ENABLE();
9 QSPI_FLASH_BK1_IO1_CLK_ENABLE();
10 QSPI_FLASH_BK1_IO2_CLK_ENABLE();
11 QSPI_FLASH_BK1_IO3_CLK_ENABLE();
12 QSPI_FLASH_CS_GPIO_CLK_ENABLE();
13
14 //設置引腳
15 /*!< 配置 QSPI_FLASH 引腳: CLK */
16 GPIO_InitStruct.Pin = QSPI_FLASH_CLK_PIN;
17 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
18 GPIO_InitStruct.Pull = GPIO_NOPULL;
19 GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
20 GPIO_InitStruct.Alternate = QSPI_FLASH_CLK_GPIO_AF;
21
22 HAL_GPIO_Init(QSPI_FLASH_CLK_GPIO_PORT, &GPIO_InitStruct);
23
24 /*!< 配置 QSPI_FLASH 引腳: IO0 */
25 GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO0_PIN;
26 GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO0_AF;
27 HAL_GPIO_Init(QSPI_FLASH_BK1_IO0_PORT, &GPIO_InitStruct);
28
29 /*!< 配置 QSPI_FLASH 引腳: IO1 */
30 GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO1_PIN;
31 GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO1_AF;
32 HAL_GPIO_Init(QSPI_FLASH_BK1_IO1_PORT, &GPIO_InitStruct);
33
34 /*!< 配置 QSPI_FLASH 引腳: IO2 */
35 GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO2_PIN;
36 GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO2_AF;
37 HAL_GPIO_Init(QSPI_FLASH_BK1_IO2_PORT, &GPIO_InitStruct);
38
39 /*!< 配置 QSPI_FLASH 引腳: IO3 */
40 GPIO_InitStruct.Pin = QSPI_FLASH_BK1_IO3_PIN;
41 GPIO_InitStruct.Alternate = QSPI_FLASH_BK1_IO3_AF;
42 HAL_GPIO_Init(QSPI_FLASH_BK1_IO3_PORT, &GPIO_InitStruct);
43
44 /*!< 配置 SPI_FLASH_SPI 引腳: NCS */
45 GPIO_InitStruct.Pin = QSPI_FLASH_CS_PIN;
46 GPIO_InitStruct.Alternate = QSPI_FLASH_CS_GPIO_AF;
47 HAL_GPIO_Init(QSPI_FLASH_CS_GPIO_PORT, &GPIO_InitStruct);
48
49 /* QSPI_FLASH 模式配置 */
50
51 hqspi.Instance = QUADSPI;
52 hqspi.Init.ClockPrescaler = 1;
53 hqspi.Init.FifoThreshold = 4;
54 hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
55 hqspi.Init.FlashSize = 23;
56 hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
57 hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
58 HAL_QSPI_Init(&hqspi);
59
60 BSP_QSPI_Init();
61
62 return TM_FATFS_FLASH_SPI_disk_status(NULL);
63
64 }
TM_FATFS_FLASH_SPI_disk_initialize函數也是有一個參數lun,沒有使用。對於QSPI Flash芯片咱們調用HAL_QSPI_Init 函數實現對SPI Flash芯片引腳GPIO初始化配置,調用BSP_QSPI_Init ()函數對通訊參數配置。
最後調用TM_FATFS_FLASH_SPI_disk_status函數獲取QSPI Flash芯片狀態,並返回狀態值。
代碼清單 253 扇區讀取
1 DRESULT TM_FATFS_FLASH_SPI_disk_read(
2 BYTE lun,//物理扇區,多個設備時用到(0...)
3 BYTE *buff,//數據緩存區
4 DWORD sector, //扇區首地址
5 UINT count)//扇區個數(1..128)
6 {
7 FLASH_DEBUG_FUNC();
8 if ((TM_FATFS_FLASH_SPI_Stat & STA_NOINIT)) {
9 return RES_NOTRDY;
10 }
11 sector+=1536;//扇區偏移,外部Flash文件系統空間放在外部Flash後面6M空間
12 BSP_QSPI_Read(buff, sector <<12, count<<12);
13 return RES_OK;
14 }
TM_FATFS_FLASH_SPI_disk_read函數有四個形參。lun爲設備物理編號。buff是一個BYTE類型指針變量,buff指向用來存放讀取到數據的存儲區首地址。sector是一個DWORD類型變量,指定要讀取數據的扇區首地址。count是一個UINT類型變量,指定扇區數量。
BYTE類型實際是unsigned char類型,DWORD類型實際是unsigned long類型,UINT類型實際是 unsigned int類型,類型定義在integer.h文件中。
開發板使用的QSPI Flash芯片型號爲W25Q128FV,每一個扇區大小爲4096個字節(4KB),總共有16M字節空間,爲兼容後面實驗程序,咱們只將後部分10MB空間分配給FatFs使用,前部分6MB空間用於其餘實驗須要,即FatFs是從6MB空間開始,爲實現這個效果須要將全部的讀寫地址都偏移1536個扇區空間。
對於QSPI Flash芯片,主要是使用QSPI_FLASH_Read()實如今指定地址讀取指定長度的數據,它接收三個參數,第一個參數爲指定數據存放地址指針。第二個參數爲指定數據讀取地址,這裏使用左移運算符,左移12位實際是乘以4096,這與每一個扇區大小是息息相關的。第三個參數爲讀取數據個數,也是須要使用左移運算符。
代碼清單 25-4 扇區輸入
1 DRESULT TM_FATFS_FLASH_SPI_disk_write(
2 BYTE lun,//物理扇區,多個設備時用到(0...)
3 const BYTE *buff,//數據緩存區
4 DWORD sector, //扇區首地址
5 UINT count)//扇區個數(1..128)
6 {
7 uint32_t write_addr;
8 FLASH_DEBUG_FUNC();
9 sector+=1536;//扇區偏移,外部Flash文件系統空間放在外部Flash後面4M空間
10 write_addr = sector<<12;
11 BSP_QSPI_Erase_Block(write_addr);
12 BSP_QSPI_Write((uint8_t*)buff,write_addr,4096);
13 return RES_OK;
14 }
TM_FATFS_FLASH_SPI_disk_write函數有四個形參,lun爲設備物理編號。buff指向待寫入扇區數據的首地址。sector,指定要讀取數據的扇區首地址。count指定扇區數量。對於QSPI Flash芯片,在寫入數據以前須要先擦除,因此用到扇區擦除函數(BSP_QSPI_Erase_Block)。而後就是在調用數據寫入函數(BSP_QSPI_Write)把數據寫入到指定位置內。
代碼清單 25-5 其餘控制
1 DRESULT TM_FATFS_FLASH_SPI_disk_ioctl(BYTE lun,BYTE cmd, void *buff)
2 {
3
4 FLASH_DEBUG_FUNC();
5 switch (cmd) {
6 case GET_SECTOR_COUNT:
7 *(DWORD * )buff = 2560; /* 扇區數量:2560*4096/1024/1024=10(MB) */
8 break;
9 case GET_SECTOR_SIZE : /*獲取扇區讀寫的大小(字)*/
10
11 *(WORD * )buff = 4096; /*flash最小寫單元爲頁,256字節,此處取2頁爲一個讀寫單位*/
12 break;
13 case GET_BLOCK_SIZE : /* 同時擦除扇區個數(雙字) */
14 *(DWORD * )buff = 1; /*flash以1個sector爲最小擦除單位*/
15 break;
16 case CTRL_TRIM:
17 break;
18 case CTRL_SYNC :
19 break;
20 }
21 return RES_OK;
22 }
TM_FATFS_FLASH_SPI_disk_ioctl函數有三個形參,lun爲設備物理編號,cmd爲控制指令,包括髮出同步信號、獲取扇區數目、獲取扇區大小、獲取擦除塊數量等等指令,buff爲指令對應的數據指針。
對於QSPI Flash芯片,爲支持FatFs格式化功能,須要用到獲取扇區數量(GET_SECTOR_COUNT)指令和獲取擦除塊數量(GET_BLOCK_SIZE)。另外,SD卡扇區大小爲512字節,SPI Flash芯片通常設置扇區大小爲4096字節,因此須要用到獲取扇區大小(GET_SECTOR_SIZE)指令。
代碼清單 25-6 時間戳獲取
1 __weak DWORD get_fattime(void)
2 {
3 /* 返回當前時間戳 */
4 return ((DWORD)(2015 - 1980) << 25) /* Year 2015 */
5 | ((DWORD)1 << 21) /* Month 1 */
6 | ((DWORD)1 << 16) /* Mday 1 */
7 | ((DWORD)0 << 11) /* Hour 0 */
8 | ((DWORD)0 << 5) /* Min 0 */
9 | ((DWORD)0 >> 1); /* Sec 0 */
10 }
get_fattime函數用於獲取當前時間戳,在ff.c文件中被調用。FatFs在文件建立、被修改時會記錄時間,這裏咱們直接使用賦值方法設定時間戳。爲更好的記錄時間,可使用控制器RTC功能,具體要求返回值格式爲:
q bit31:25 ——從1980至今是多少年,範圍是 (0..127) ;
q bit24:21 ——月份,範圍爲 (1..12) ;
q bit20:16 ——該月份中的第幾日,範圍爲(1..31) ;
q bit15:11——時,範圍爲 (0..23);
q bit10:5 ——分,範圍爲 (0..59);
q bit4:0 ——秒/ 2,範圍爲 (0..29) 。
ffconf.h文件是FatFs功能配置文件,咱們能夠對文件內容進行修改,使得FatFs更符合咱們的要求。ffconf.h對每一個配置選項都作了詳細的使用狀況說明。下面只列出修改的配置,其餘配置採用默認便可。
代碼清單 257 FatFs功能配置選項
1 #define _USE_MKFS 1
2 #define _CODE_PAGE 936
3 #define _USE_LFN 2
4 #define _VOLUMES 3
5 #define _MIN_SS 512
6 #define _MAX_SS 4096
1) _USE_MKFS:格式化功能選擇,爲使用FatFs格式化功能,須要把它設置爲1。
2) _CODE_PAGE:語言功能選擇,並要求把相關語言文件添加到工程宏。爲支持簡體中文文件名須要使用「936」,正如在圖 256的操做,咱們已經把cc936.c文件添加到工程中。
3) _USE_LFN:長文件名支持,默認不支持長文件名,這裏配置爲2,支持長文件名,並指定使用棧空間爲緩衝區。
4) _VOLUMES:指定物理設備數量,這裏設置爲3,包括預留SD卡和qSPI Flash芯片。
5) _MIN_SS 、_MAX_SS:指定扇區大小的最小值和最大值。SD卡扇區大小通常都爲512字節,SPI Flash芯片扇區大小通常設置爲4096字節,因此須要把_MAX_SS改成4096。
移植操做到此,就已經把FatFs所有添加到咱們的工程了,這時咱們編譯功能,順利編譯經過,沒有錯誤。接下來,咱們就可使用編寫圖 254中用戶應用程序了。
主要的測試包括格式化測試、文件寫入測試和文件讀取測試三個部分,主要程序都在main.c文件中實現。
代碼清單 25-8 變量定義
1 FATFS fs; /* FatFs文件系統對象 */
2 FIL fnew; /* 文件對象 */
3 FRESULT res_flash; /* 文件操做結果 */
4 UINT fnum; /* 文件成功讀寫數量 */
5 BYTE buffer[1024]= {0}; /* 讀緩衝區 */
6 BYTE textFileBuffer[] = /* 寫緩衝區*/
7 "歡迎使用野火STM32 F429開發板 今天是個好日子,新建文件系統測試文件\r\n";
FATFS是在ff.h文件定義的一個結構體類型,針對的對象是物理設備,包含了物理設備的物理編號、扇區大小等等信息,通常咱們都須要爲每一個物理設備定義一個FATFS變量。
FIL也是在ff.h文件定義的一個結構體類型,針對的對象是文件系統內具體的文件,包含了文件不少基本屬性,好比文件大小、路徑、當前讀寫地址等等。若是須要在同一時間打開多個文件進行讀寫,才須要定義多個FIL變量,否則通常定義一個FIL變量便可。
FRESULT是也在ff.h文件定義的一個枚舉類型,做爲FatFs函數的返回值類型,主要管理FatFs運行中出現的錯誤。總共有19種錯誤類型,包括物理設備讀寫錯誤、找不到文件、沒有掛載工做空間等等錯誤。這在實際編程中很是重要,當有錯誤出現是咱們要中止文件讀寫,經過返回值咱們能夠快速定位到錯誤發生的可能地點。若是運行沒有錯誤才返回FR_OK。
fnum是個32位無符號整形變量,用來記錄實際讀取或者寫入數據的數組。
buffer和textFileBuffer分別對應讀取和寫入數據緩存區,都是8位無符號整形數組。
代碼清單 259 主函數
1 int main(void)
2 {
3 SystemClock_Config();
4 /* Enable I-Cache */
5 SCB_EnableICache();
6 /* Enable D-Cache */
7 SCB_EnableDCache();
8 /* 初始化LED */
9 LED_GPIO_Config();
10 LED_BLUE;
11
12 /* 初始化調試串口,通常爲串口1 */
13 DEBUG_USART_Config();
14 printf("****** 這是一個SPI FLASH 文件系統實驗 ******\r\n");
15 //連接驅動器,建立盤符
16 FATFS_LinkDriver(&QSPI_Driver, QSPIPath);
17 //在外部SPI Flash掛載文件系統,文件系統掛載時會對SPI設備初始化
18 res_flash = f_mount(&fs,"0:",1);
19
20 /*----------------------- 格式化測試 ---------------------------*/
21 /* 若是沒有文件系統就格式化建立建立文件系統 */
22 if (res_flash == FR_NO_FILESYSTEM) {
23 printf("》FLASH尚未文件系統,即將進行格式化...\r\n");
24 /* 格式化 */
25 res_flash=f_mkfs("0:",0,0);
26
27 if (res_flash == FR_OK) {
28 printf("》FLASH已成功格式化文件系統。\r\n");
29 /* 格式化後,先取消掛載 */
30 res_flash = f_mount(NULL,"0:",1);
31 /* 從新掛載 */
32 res_flash = f_mount(&fs,"0:",1);
33 } else {
34 LED_RED;
35 printf("《《格式化失敗。》》\r\n");
36 while (1);
37 }
38 } else if (res_flash!=FR_OK) {
39 printf("!!外部Flash掛載文件系統失敗。(%d)\r\n",res_flash);
40 printf("!!可能緣由:SPI Flash初始化不成功。\r\n");
41 while (1);
42 } else {
43 printf("》文件系統掛載成功,能夠進行讀寫測試\r\n");
44 }
45
46 /*----------------------- 文件系統測試:寫測試 -----------------------------*/
47 /* 打開文件,若是文件不存在則建立它 */
48 printf("\r\n****** 即將進行文件寫入測試... ******\r\n");
49 res_flash = f_open(&fnew, "0:FatFs讀寫測試文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
50 if ( res_flash == FR_OK ) {
51 printf("》打開/建立FatFs讀寫測試文件.txt文件成功,向文件寫入數據。\r\n");
52 /* 將指定存儲區內容寫入到文件內 */
53 res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
54 if (res_flash==FR_OK) {
55 printf("》文件寫入成功,寫入字節數據:%d\n",fnum);
56 printf("》向文件寫入的數據爲:\r\n%s\r\n",WriteBuffer);
57 } else {
58 printf("!!文件寫入失敗:(%d)\n",res_flash);
59 }
60 /* 再也不讀寫,關閉文件 */
61 f_close(&fnew);
62 } else {
63 LED_RED;
64 printf("!!打開/建立文件失敗。\r\n");
65 }
66
67 /*------------------- 文件系統測試:讀測試 ------------------------------------*/
68 printf("****** 即將進行文件讀取測試... ******\r\n");
69 res_flash = f_open(&fnew, "0:FatFs讀寫測試文件.txt", FA_OPEN_EXISTING | FA_READ);
70 if (res_flash == FR_OK) {
71 LED_GREEN;
72 printf("》打開文件成功。\r\n");
73 res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
74 if (res_flash==FR_OK) {
75 printf("》文件讀取成功,讀到字節數據:%d\r\n",fnum);
76 printf("》讀取得的文件數據爲:\r\n%s \r\n", ReadBuffer);
77 } else {
78 printf("!!文件讀取失敗:(%d)\n",res_flash);
79 }
80 } else {
81 LED_RED;
82 printf("!!打開文件失敗。\r\n");
83 }
84 /* 再也不讀寫,關閉文件 */
85 f_close(&fnew);
86
87 /* 再也不使用文件系統,取消掛載文件系統 */
88 f_mount(NULL,"0:",1);
89
90 /* 操做完成,停機 */
91 while (1) {
92 }
93 }
首先,初始化系統時鐘,RGB彩燈和調試串口,用來指示程序進程。
FatFs的第一步工做就是連接驅動器,建立盤符而後使用f_mount函數掛載工做區。f_mount函數有三個形參,第一個參數是指向FATFS變量指針,若是賦值爲NULL能夠取消物理設備掛載。第二個參數爲邏輯設備編號,使用設備根路徑表示,與物理設備編號掛鉤,這裏使用「0:」。第三個參數可選0或1,1表示當即掛載,0表示不當即掛載,延遲掛載。 f_mount函數會返回一個FRESULT類型值,指示運行狀況。
若是f_mount函數返回值爲FR_NO_FILESYSTEM,說明沒有FAT文件系統,好比新出廠的SPI Flash芯片就沒有FAT文件系統。咱們就必須對物理設備進行格式化處理。使用f_mkfs函數能夠實現格式化操做。f_mkfs函數有三個形參,第一個參數爲邏輯設備編號;第二參數可選0或者1,0表示設備爲通常硬盤,1表示設備爲軟盤。第三個參數指定扇區大小,若是爲0,表示經過代碼清單 255中TM_FATFS_FLASH_SPI_disk_ioctl函數獲取。格式化成功後須要先取消掛載原來設備,再從新掛載設備。
在設備正常掛載後,就能夠進行文件讀寫操做了。使用文件以前,必須使用f_open函數打開文件,再也不使用文件必須使用f_close函數關閉文件,這個跟電腦端操做文件步驟相似。f_open函數有三個形參,第一個參數爲文件對象指針。第二參數爲目標文件,包含絕對路徑的文件名稱和後綴名。第三個參數爲訪問文件模式選擇,能夠是打開已經存在的文件模式、讀模式、寫模式、新建模式、老是新建模式等的或運行結果。好比對於寫測試,使用FA_CREATE_ALWAYS和FA_WRITE組合模式,就是老是新建文件並進行寫模式。
f_close函數用於再也不對文件進行讀寫操做關閉文件,f_close函數只要一個形參,爲文件對象指針。f_close函數運行能夠確保緩衝區徹底寫入到文件內。
成功打開文件以後就可使用f_write函數和f_read函數對文件進行寫操做和讀操做。這兩個函數用到的參數是一致的,只不過一個是數據寫入,一個是數據讀取。f_write函數第一個形參爲文件對象指針,使用與f_open函數一致便可。第二個參數爲待寫入數據的首地址,對於f_read函數就是用來存放讀出數據的首地址。第三個參數爲寫入數據的字節數,對於f_read函數就是欲讀取數據的字節數。第四個參數爲32位無符號整形指針,這裏使用fnum變量地址賦值給它,在運行讀寫操做函數後,fnum變量指示成功讀取或者寫入的字節個數。
最後,再也不使用文件系統時,使用f_mount函數取消掛載。
保證開發板相關硬件鏈接正確,用USB線鏈接開發板「USB TO UART」接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。程序開始運行後,RGB彩燈爲藍色,在串口調試助手可看到格式化測試、寫文件檢測和讀文件檢測三個過程;最後若是全部讀寫操做都正常,RGB彩燈會指示爲綠色,若是在運行中FatFs出現錯誤RGB彩燈指示爲紅色。
雖然咱們經過RGB彩燈指示和串口調試助手信息打印方法來講明FatFs移植成功,並順利經過測試,但心底老是很踏實,所謂眼見爲實,雖然咱們建立了「FatFs讀寫測試文件.txt」這個文件,卻徹底看不到實體。這個確實是個問題,由於咱們這裏使用SPI Flash芯片做爲物理設備,並不像SD卡那麼方便直接用讀卡器就能夠在電腦端打開驗證。另一個問題,就目前來講,在QSPI Flash芯片上掛載FatFs好像沒有實際意義,沒法發揮文件系統功能。
實際上,這裏歸根到底就是咱們目前沒辦法在電腦端查看QSPI Flash芯片內FatFs的內容,沒辦法很是方便拷貝、刪除文件。咱們固然不會作無用功,STM32控制器還有一個硬件資源能夠解決上面的問題,就是USB!咱們能夠經過編程把整個開發板變成一個U盤,而U盤存儲空間就是SPI Flash芯片的空間。這樣很是方便實現文件讀寫。至於USB內容將在USB相關章節講解。
上個實驗咱們實現了FatFs的格式化、讀文件和寫文件功能,這個已經知足不少部分的運用須要。有時,咱們須要更多的文件操做功能,FatFs仍是提供了很多的功能的,好比設備存儲空間信息獲取、讀寫文件指針定位、建立目錄、文件移動和重命名、文件或目錄信息獲取等等功能。咱們接下來這個實驗內容就是展現FatFs衆多功能,提供一個很好了範例,之後有用到相關內容,參考使用很是方便。
本實驗主要使用FatFs軟件功能,不須要其餘硬件模塊,使用與FatFs移植實驗相同硬件配置便可。
上個實驗咱們已經移植好了FatFs,這個例程主要是應用,因此簡單起見,直接拷貝上個實驗的工程文件,保持FatFs底層驅動程序,咱們只改main.c文件內容,實現應用程序。
代碼清單 2510 FatFs多項功能測試
1 static FRESULT miscellaneous(void)
2 {
3 DIR dir;
4 FATFS *pfs;
5 DWORD fre_clust, fre_sect, tot_sect;
6
7 printf("\n*************** 設備信息獲取 ***************\r\n");
8 /* 獲取設備信息和空簇大小 */
9 res_flash = f_getfree("0:", &fre_clust, &pfs);
10
11 /* 計算獲得總的扇區個數和空扇區個數 */
12 tot_sect = (pfs->n_fatent - 2) * pfs->csize;
13 fre_sect = fre_clust * pfs->csize;
14
15 /* 打印信息(4096 字節/扇區) */
16 printf("》設備總空間:%10lu KB。\n》可用空間: %10lu KB。\n",
17 tot_sect *4, fre_sect *4);
18
19 printf("\n******** 文件定位和格式化寫入功能測試 ********\r\n");
20 res_flash = f_open(&fnew, "1:FatFs讀寫測試文件.txt",
21 FA_OPEN_EXISTING|FA_WRITE|FA_READ );
22 if ( res_flash == FR_OK ) {
23 /* 文件定位 */
24 res_flash = f_lseek(&fnew,f_size(&fnew)-1);
25 if (res_flash == FR_OK) {
26 /* 格式化寫入,參數格式相似printf函數 */
27 f_printf(&fnew,"\n在原來文件新添加一行內容\n");
28 f_printf(&fnew,"》設備總空間:%10lu KB。\n》可用空間: %10lu KB。\n",
29 tot_sect *4, fre_sect *4);
30 /* 文件定位到文件起始位置 */
31 res_flash = f_lseek(&fnew,0);
32 /* 讀取文件全部內容到緩存區 */
33 res_flash = f_read(&fnew,readbuffer,f_size(&fnew),&fnum);
34 if (res_flash == FR_OK) {
35 printf("》文件內容:\n%s\n",readbuffer);
36 }
37 }
38 f_close(&fnew);
39
40 printf("\n********** 目錄建立和重命名功能測試 **********\r\n");
41 /* 嘗試打開目錄 */
42 res_flash=f_opendir(&dir,"0:TestDir");
43 if (res_flash!=FR_OK) {
44 /* 打開目錄失敗,就建立目錄 */
45 res_flash=f_mkdir("0:TestDir");
46 } else {
47 /* 若是目錄已經存在,關閉它 */
48 res_flash=f_closedir(&dir);
49 /* 刪除文件 */
50 f_unlink("0:TestDir/testdir.txt");
51 }
52 if (res_flash==FR_OK) {
53 /* 重命名並移動文件 */
54 res_flash=f_rename("0:FatFs讀寫測試文件.txt",
55 "0:TestDir/testdir.txt");
56 }
57 } else {
58 printf("!! 打開文件失敗:%d\n",res_flash);
59 printf("!! 或許須要再次運行「FatFs移植與讀寫測試」工程\n");
60 }
61 return res_flash;
62 }
首先是設備存儲信息獲取,目的是獲取設備總容量和剩餘可用空間。f_getfree函數是設備空閒簇信息獲取函數,有三個形參,第一個參數爲邏輯設備編號;第二個參數爲返回空閒簇數量;第三個參數爲返回指向文件系統對象的指針。經過計算可獲得設備總的扇區個數以及空閒扇區個數,對於QSPI Flash芯片咱們設置每一個扇區爲4096字節大小。這樣很容易就算出設備存儲信息。
接下來是文件讀寫指針定位和格式化輸入功能測試。文件定位在一些場合很是有用,好比咱們須要記錄多項數據,但每項數據長度不肯定,但有個最長長度,使用咱們就可使用文件定位lseek函數功能把數據存放在規定好的地址空間上。當咱們須要讀取文件內容時就使用文件定位函數定位到對應地址讀取。
使用文件讀寫操做以前都必須使用f_open函數打開文件,開始文件是讀寫指針是在文件起始位置的,立刻寫入數據的話會覆蓋原來文件內容的。這裏,咱們使用f_lseek函數定位到文件末尾位置,再寫入內容。f_lseek函數有兩個形參,第一個參數爲文件對象指針,第二個參數爲須要定位的字節數,這個字節數是相對文件起始位置的,好比設置爲0,則將文件讀寫指針定位到文件起始位置了。
f_printf函數是格式化寫入函數,須要把ffconf.h文件中的_USE_STRFUNC配置爲1才支持。f_printf函數用法相似C庫函數printf函數,只是它將數據直接寫入到文件中。
最後是目錄建立和文件移動和重命名功能。使用f_opendir函數能夠打開路徑(這裏不區分目錄和路徑概念,下同),若是路徑不存在則返回錯誤,使用f_closedir函數關閉已經打開的路徑。新版的FatFs支持相對路徑功能,使路徑操做更加靈活。f_opendir函數有兩個形參,第一個參數爲指向路徑對象的指針,第二個參數爲路徑。f_closedir函數只須要指向路徑對象的指針一個形參。
f_mkdir函數用於建立路徑,若是指定的路徑不存在就建立它,建立的路徑存在形式就是文件夾。f_mkdir函數只要一個形參,就是指定路徑。
f_rename函數是帶有移動功能的重命名函數,它有兩個形參,第一個參數爲源文件名稱,第二個參數爲目標名稱。目標名稱可附帶路徑,若是路徑與源文件路徑不一樣見移動文件到目標路徑下。
代碼清單 2511 文件信息獲取
1 static FRESULT file_check(void)
2 {
3 FILINFO fno;
4
5 /* 獲取文件信息 */
6 res_flash=f_stat("0:TestDir/testdir.txt",&fno);
7 if (res_flash==FR_OK) {
8 printf("「testdir.txt」文件信息:\n");
9 printf("》文件大小: %ld(字節)\n", fno.fsize);
10 printf("》時間戳: %u/%02u/%02u, %02u:%02u\n",
11 (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,
12 fno.ftime >> 11, fno.ftime >> 5 & 63);
13 printf("》屬性: %c%c%c%c%c\n\n",
14 (fno.fattrib & AM_DIR) ? 'D' : '-', // 是一個目錄
15 (fno.fattrib & AM_RDO) ? 'R' : '-', // 只讀文件
16 (fno.fattrib & AM_HID) ? 'H' : '-', // 隱藏文件
17 (fno.fattrib & AM_SYS) ? 'S' : '-', // 系統文件
18 (fno.fattrib & AM_ARC) ? 'A' : '-'); // 檔案文件
19 }
20 return res_flash;
21 }
f_stat函數用於獲取文件的屬性,有兩個形參,第一個參數爲文件路徑,第二個參數爲返回指向文件信息結構體變量的指針。文件信息結構體變量包含文件的大小、最後修改時間和日期、文件屬性、短文件名以及長文件名等信息。
代碼清單 25-12 路徑掃描
1 static FRESULT scan_files (char* path)
2 {
3 FRESULT res; //部分在遞歸過程被修改的變量,不用全局變量
4 FILINFO fno;
5 DIR dir;
6 int i;
7 char *fn; // 文件名
8
9 #if _USE_LFN
10 /* 長文件名支持 */
11 /* 簡體中文須要2個字節保存一個「字」*/
12 static char lfn[_MAX_LFN*2 + 1];
13 fno.lfname = lfn;
14 fno.lfsize = sizeof(lfn);
15 #endif
16 //打開目錄
17 res = f_opendir(&dir, path);
18 if (res == FR_OK) {
19 i = strlen(path);
20 for (;;) {
21 //讀取目錄下的內容,再讀會自動讀下一個文件
22 res = f_readdir(&dir, &fno);
23 //爲空時表示全部項目讀取完畢,跳出
24 if (res != FR_OK || fno.fname[0] == 0) break;
25 #if _USE_LFN
26 fn = *fno.lfname ? fno.lfname : fno.fname;
27 #else
28 fn = fno.fname;
29 #endif
30 //點表示當前目錄,跳過
31 if (*fn == '.') continue;
32 //目錄,遞歸讀取
33 if (fno.fattrib & AM_DIR) {
34 //合成完整目錄名
35 sprintf(&path[i], "/%s", fn);
36 //遞歸遍歷
37 res = scan_files(path);
38 path[i] = 0;
39 //打開失敗,跳出循環
40 if (res != FR_OK)
41 break;
42 } else {
43 printf("%s/%s\r\n", path, fn); //輸出文件名
44 /* 能夠在這裏提取特定格式的文件路徑 */
45 }//else
46 } //for
47 }
48 return res;
49 }
scan_files函數用來掃描指定路徑下的文件。好比咱們設計一個mp3播放器,咱們須要提取mp3格式文件,諸如*.txt、*.c文件咱們通通不可要的,這時咱們就必須掃描路徑下全部文件並把*.mp3或*.MP3格式文件提取出來。這裏咱們提取特定格式文件,而是把全部文件名稱都經過串口打印出來。
咱們在ffconf.h文件中定義了長文件名稱支持(_USE_LFN=2),通常有用到簡體中文文件名稱的都要長文件名支持。短文件名稱是8.3格式,即名稱是8個字節,後綴名是3個字節,對於使用英文名稱還能夠,使用中文名稱就很容易長度不夠了。使能了長文件名支持後,使用以前須要指定文件名的存儲區還有存儲區的大小。
接下來就是使用f_opendir函數打開指定的路徑。若是路徑存在就使用f_readdir函數讀取路徑下內容,f_readdir函數能夠讀取路徑下的文件或者文件夾,並保存信息到文件信息對象變量內。f_readdir函數有兩個形參,第一個參數爲指向路徑對象變量的指針,第二個參數爲指向文件信息對象的指針。f_readdir函數另一個特性是自動讀取下一個文件對象,即循序運行該函數能夠讀取該路徑下的全部文件。因此,在程序中,咱們使用for循環讓f_readdir函數讀取全部文件,並在讀取全部文件以後退出循環。
在f_readdir函數成功讀取到一個對象時,咱們還不清楚它是一個文件仍是一個文件夾,此時咱們就可使用文件信息對象變量的文件屬性來判斷了,若是判斷得出是個文件那咱們就直接經過串口打印出來就行了。若是是個文件夾,咱們就要進入該文件夾掃描,這時就從新調用掃描函數scan_files就能夠了,造成一個遞歸調用結構,只是咱們此次用的參數與最開始時候是不一樣的,如今是使用子文件夾名稱。
代碼清單 25-13 主函數
1 int main(void)
2 {
3 /* 初始化調試串口,通常爲串口1 */
4 Debug_USART_Config();
5 printf("******** 這是一個QSPI FLASH 文件系統實驗 *******\r\n");
6 FATFS_LinkDriver(&QSPI_Driver, QSPIPath);
7 //在外部SPI Flash掛載文件系統,文件系統掛載時會對SPI設備初始化
8 res_flash = f_mount(&fs,"0:",1);
9 if (res_flash!=FR_OK) {
10 printf("!!外部Flash掛載文件系統失敗。(%d)\r\n",res_flash);
11 printf("!!可能緣由:QSPI Flash初始化不成功。\r\n");
12 while (1);
13 } else {
14 printf("》文件系統掛載成功,能夠進行測試\r\n");
15 }
16
17 /* FatFs多項功能測試 */
18 res_flash = miscellaneous();
19
20
21 printf("\n*************** 文件信息獲取測試 **************\r\n");
22 res_flash = file_check();
23
24
25 printf("***************** 文件掃描測試 ****************\r\n");
26 strcpy(fpath,"0:");
27 scan_files(fpath);
28
29
30 /* 再也不使用文件系統,取消掛載文件系統 */
31 f_mount(NULL,"0:",1);
32
33 /* 操做完成,停機 */
34 while (1) {
35 }
36 }
串口在程序調試中常用,能夠把變量值直觀打印到串口調試助手,這個信息很是重要,一樣在使用以前須要調用Debug_USART_Config函數完成調試串口初始化。
使用FatFs進行文件操做以前都使用f_mount函數掛載物理設備,這裏咱們使用SPI Flash芯片上的FAT文件系統。
接下來咱們直接調用miscellaneous函數進行FatFs設備信息獲取、文件定位和格式化寫入功能以及目錄建立和重命名功能測試。調用file_check函數進行文件信息獲取測試。
scan_files函數用來掃描路徑下的全部文件,fpath是咱們定義的一個包含100個元素的字符型數組,並將其賦值爲SPI Flash芯片物理編號對於的根目錄。這樣容許scan_files函數見打印SPI Flash芯片內FatFs全部文件到串口調試助手。注意,這裏的定義fpaht數組是必不可少的,由於scan_files函數自己是個遞歸函數,要求實際參數有較大空間的緩存區。
保證開發板相關硬件鏈接正確,用USB線鏈接開發板「USB TO UART」接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。程序開始運行,在串口調試助手可看到每一個階段測試相關信息狀況。