STM32FATFS檔案系統移植
1。 FATFS簡介
FATFS檔案系統是一個用於在微控制器上執行的開原始檔系統,支援FAT/FATFS、NTFS、exFAT等主流檔案系統,且一直保持更新。在此以FatFs官網最新版本v0.15進行移植。
2. 移植具體操作
2.1 下載FatFs原始碼
FATFS原始碼在其官網就有下載連結,下載後解壓即可,官網頁面如圖1所示:
將其翻至最下面,就可以找到下載連結,如圖2所示:
2.2 FATFS程式碼結構
FATFS原始碼解壓後,其一級目錄結構如圖3所示:
其source資料夾下各檔案作用如下所示:
source
├── 00history.txt //歷史版本資訊
├── 00readme.txt //FATFS簡介
├── diskio.c //磁碟IO適配檔案
├── diskio.h //磁碟IO適配標頭檔案
├── ff.c //FATFS主要實現檔案
├── ff.h //FATFS主要實現標頭檔案
├── ffconf.h //FATFS配置標頭檔案
├── ffsystem.c //系統呼叫適配檔案
└── ffunicode.c //Unicode適配檔案
在移植過程中,主要對diskio.c、ffconf.h進行修改。
2.3 修改前後檔案對比
後文所有程式碼比對均預設左側為原始碼,右側為修改後的程式碼。
2.3.1 diskio.c檔案修改比對
diskio.c檔案主要實現對儲存介質的硬體適配,需要將FLASH讀寫、初始化等程式碼移植至此檔案內的固定介面。
2.3.1.1標頭檔案新增及宏定義變數修改對比
如圖4所示:
#include "Spi.h" //引用SPI初始化
#include "W25q64.h" //引用FLASH檔案操作函式,檔案內程式碼均在上篇文章中。
#define SPI_FLASH 3 //定義驅動卷名
FATFS中檔案目錄格式為為驅動卷名+“:”+檔名,假如說SPI_FLASH檔案系統一級目錄內有“aaa.txt”這個檔案,那麼其目錄格式為"3:aaa.txt"。3就是宏定義的驅動卷名,檔案系統掛載什麼的如果要掛載SPIFLASH就是掛載"3:"目錄,其餘驅動卷或裝置同理。
在C語言中上述的寫法更簡單,C語言支援宏定義的字串拼接,具體示範如下列程式碼所示:
#define SPI_FLASH_DIR "3:"
res_flash = f_mount(&fs,SPI_FLASH_DIR,1);
res_flash = f_open(&fnew,SPI_FLASH_DIR"ABC.txt",FA_CREATE_ALWAYS | FA_WRITE);
#define SPI_FLASH_DIR 3
res_flash = f_mount(&fs,"3:",1);
res_flash = f_open(&fnew,"3:ABC.txt",FA_CREATE_ALWAYS | FA_WRITE);
這兩段程式碼在實現效果上並無差別。
2.3.1.2 磁碟儲存介質狀態查詢介面修改對比
如圖5所示:
需要將對磁碟儲存介質的硬體狀態查詢移植至此檔案內的固定介面。具體操作為在switch (pdrv) 內新增一條分支,返回值分為STATUS_NOINIT、STATUS_NODISK、STATUS_PROTECT與RES_OK四種情況,分別代表磁碟儲存介質未初始化、沒有對應驅動卷名、磁碟防寫與磁碟正常。
2.3.1.3 磁碟儲存介質初始化介面修改對比
如圖6所示:
需要對磁碟儲存介質的硬體初始化移植至此檔案內的固定介面。具體操作為在switch (pdrv) 內新增一條分支,呼叫SPI_FLASH_Init()函式進行初始化。可用返回值與狀態查詢的返回值一致,在這裡我圖省事,直接呼叫了狀態查詢函式。
2.3.1.4 磁碟儲存介質資料讀取介面修改對比
如圖7所示:
需要對磁碟儲存介質的硬體資料讀取移植至此檔案內的固定介面。具體操作為在switch (pdrv) 內新增一條分支,呼叫SPI_FLASH_Read()函式進行讀取。其中讀取資料的儲存地址為*buff,讀取資料的扇區邏輯位號為sector,讀取資料的扇區數量為count。
在圖7中可以看到,扇區邏輯區塊地址(LBA)與扇區數量均左移12位,即乘4096。然而這兩個資料乘4096的原因不一樣。扇區邏輯區塊地址乘4096是為了將扇區邏輯區塊地址轉化位扇區實體地址,而扇區數量乘4096是為了將扇區數量轉化為扇區資料讀取數量。
PS:LBA是非常單純的一種定址模式﹔從0開始編號來定位區塊,第一區塊LBA=0,第二區塊LBA=1,依此類推。這種定址模式取代了原先作業系統必須面對儲存裝置硬體構造的方式。
2.3.1.5 磁碟儲存介質資料寫入介面修改對比
如圖8所示:
需要對磁碟儲存介質的硬體資料寫入移植至此檔案內的固定介面。具體操作為在switch (pdrv) 內新增一條分支,呼叫SPI_FLASH_Write()函式進行寫入。其中寫入資料的儲存地址為*buff,寫入資料的扇區邏輯位號為sector,寫入資料的扇區數量為count。至於為什麼向左移12位,與讀取資料相同,都是將扇區邏輯區塊地址轉化為扇區實體地址,將扇區數量轉化為扇區資料寫入數量。
2.3.1.6 磁碟儲存介質資訊介面修改對比
如圖9所示:
需要對磁碟儲存介質的硬體資訊查詢移植至此檔案內的固定介面。具體操作為在switch (pdrv) 內新增一條分支,在其中新增一個switch,檢索傳入的cmd,需要對cmd建立3條分支,分別為GET_SECTOR_COUNT、GET_SECTOR_SIZE與GET_BLOCK_SIZE,分別將物理扇區總數量、物理扇區大小與擦除塊數量返回給*buff並返回RES_OK即可。
2.3.1.7 磁碟儲存介質寫入時間函式
在FATFS檔案系統中並不附帶獲取時間的函式介面,但是建立檔案、修改檔案等操作都需要獲取當前時間,因此需要新增一個獲取當前時間的函式介面。具體操作為使用弱定義,定義FATFS檔案系統中的獲取時間函式內容
__weak DWORD get_fattime(void) // 獲取時間
{
return ((DWORD)(2024-1980)<<25) // 設定年份為2024
| ((DWORD)1<<21) // 設定月份為1
| ((DWORD)1<<16) // 設定日期為1
| ((DWORD)1<<11) // 設定小時為1
| ((DWORD)1<<5) // 設定分鐘為1
| ((DWORD)1<<1); // 設定秒數為1
}
FATFS採用時間戳的方式來記錄時間,具體格式如圖10所示:
2.3.2 ffconf.h檔案修改對比
ffconf.h檔案為FATFS檔案系統配置檔案,其中定義了FATFS檔案系統的配置引數。
2.3.2.1 檔案系統格式化宏定義修改對比
如圖11所示:
這個選項會開啟f_mkfs()函式,允許對檔案系統格式化,或在沒有檔案系統的情況下建立檔案系統
2.3.2.2 檔案系統命名格式與名稱空間宏定義修改對比
如圖12所示:
由於FATFS檔案系統預設命名為日文,需要將FF_CODE_PAGE的值修改,以支援中文命名。FF_USE_LEN決定了實現長檔案支援所使用的記憶體方式,0為不使用LFN,1為使用LFN,但沒有執行緒安全,2為使用LFN並使用堆記憶體,3為使用LFN並使用棧記憶體。
2.3.2.3 檔案系統驅動卷數量與最大扇區記憶體修改對比
如圖13所示:
修改FF_VOLUMES的值,以便支援多個卷。修改FF_MAX_SS的值,以便支援更大的扇區。
2.3.2.5 檔案系統時間戳宏定義修改對比
如圖14所示:
FF_FS_NORTC=1表示使用時間函式,FF_FS_NORTC=-1表示不使用時間函式。
3. 移植後main檔案使用演示
首先需要引用“ff.h”標頭檔案,然後定義如下全域性變數:
FATFS fs;
FIL fnew;
FRESULT res_flash;
UINT fnum;
由於FATFS檔案系統的結構體都比較大,在main函式中定義會導致堆疊溢位。
然後定義一些臨時變數,用於儲存檔名和檔案內容以及FATFS檔案系統執行狀態:
BYTE buffer[4096] = {0};
BYTE textFileBuffer[] = "ABCDEFG";
uint8_t c[256] = {0};
我所使用的開發板為野火STM32F103指南者開發板,驅動卷命名為"3",具體執行程式碼如下所示
int main()
{
HSE_SetSysClock(RCC_PLLMul_9); // 設定系統時鐘為9倍,72MHz
Usart_init(); // 初始化串列埠
USART_SendString(USART1,"Systeam is OK.");
res_flash = f_mount(&fs,"3:",1); // 掛載檔案系統
USART_SendByte(USART1,res_flash);
if(res_flash == FR_NO_FILESYSTEM) // 檢測是否存在檔案系統
{
res_flash = f_mkfs("3:",NULL,buffer,4096); // 建立檔案系統
if(res_flash == FR_OK) // 判斷是否建立成功
{
USART_SendString(USART1,"FATFS has been mkf.");
res_flash = f_mount(NULL,"3:",0); // 解除安裝檔案系統
res_flash = f_mount(&fs,"3:",1); // 重新掛載檔案系統
}
else // 建立失敗
{
USART_SendString(USART1,"FATFS mkf filed.");
USART_SendByte(USART1,res_flash);
while(1) // 死迴圈
{
}
}
}
else if(res_flash !=FR_OK) // 掛載失敗
{
USART_SendString(USART1,"mount ERROR.");
while(1) // 死迴圈
{
}
}
else // 掛載成功
{
USART_SendString(USART1,"mount OK.");
}
res_flash = f_open(&fnew,"3:ABC.txt",FA_CREATE_ALWAYS | FA_WRITE); // 建立檔案
USART_SendByte(USART1,res_flash);
if(res_flash == FR_OK) // 判斷是否建立成功
{
USART_SendString(USART1,"File open is OK.");
}
res_flash = f_write(&fnew,"ABCDEFG",7,&fnum); // 寫入資料
if(res_flash == FR_OK) // 判斷是否寫入成功
{
USART_SendString(USART1,"File write is OK.");
}
else // 寫入失敗
{
USART_SendByte(USART1,res_flash);
}
f_close(&fnew); // 關閉檔案
if(res_flash == FR_OK) // 判斷是否關閉成功
{
USART_SendString(USART1,"File close is OK.");
}
else // 關閉失敗
{
USART_SendByte(USART1,res_flash);
}
res_flash = f_unmount("3:"); // 解除安裝檔案系統
USART_SendByte(USART1,res_flash);
res_flash = f_mount(&fs,"3:",1); // 重新掛載檔案系統
USART_SendByte(USART1,res_flash); // 判斷是否重新掛載成功
res_flash = f_open(&fnew,"3:ABC.txt",FA_OPEN_EXISTING | FA_READ); // 開啟檔案
if(res_flash == FR_OK) // 判斷是否開啟成功
{
USART_SendString(USART1,"File open is OK.");
USART_SendString(USART1,c);
}
else // 開啟失敗
{
USART_SendByte(USART1,res_flash);
}
res_flash = f_read(&fnew,c,7,&fnum); // 讀取檔案內容
if(res_flash == FR_OK) // 判斷是否讀取成功
{
USART_SendString(USART1,"File read is OK.");
USART_SendString(USART1,c);
}
else // 讀取失敗
{
USART_SendByte(USART1,res_flash);
}
f_close(&fnew); // 關閉檔案
res_flash = f_unmount("3:"); // 解除安裝檔案系統
USART_SendByte(USART1,res_flash);
if(res_flash == FR_OK) // 判斷是否解除安裝成功
{
USART_SendString(USART1,"unmount OK.");
}
while(1){
}
}
燒錄結果
如圖15所示: