FatFs文件系統的移植

因爲需要,又不想自己寫,所以就移植了一個文件系統。


說下我的硬件和開發工具:接成
TRUE IDE 模式下的CF卡(也就是相當於一塊硬盤了),三星S3C2440的ARM9,開發工具是很老很老的D版的ADS1.2。


我在網上看到的嵌入式系統上面常用的文件系統有UCOSII公司的
UC/FS,支持CF卡,硬盤,SD/MMC卡,還有NAND FLASH等等,比較多,不過是商用的,需要銀子的,有周立功的用於教學用(爲什麼說是用於教學用的,呵呵,等下就說)的ZLG/FS,還找到了開源、免費的兩個,其中一個叫做 efsl ,另一個叫做 FatFs

    現在先不考慮版權的問題,選擇一個比較合適的文件系統。第一個UC/FS文件系統沒得什麼說的,UCOSII那個公司開發的,穩定性,兼容性應該都不會差。第二個是ZLG/FS。周立功的很多的開發板上面都送了這個文件系統的源代碼的,在網上找到一個現成的讀寫硬盤的,只是是基於LPC2200系列的處理器的。第三個是efsl,是一個開源的項目,免費,只需要提供讀扇區和寫扇區 2個函數。第四個是FatFs,跟efsl一樣,也是一個開源的項目,移植的時候比efsl多幾個簡單的函數。


    這裏補充一下CF卡和硬盤的簡單的資料,CF卡有三種模式,其中有一個叫TRUE IDE,接成這個模式以後,就跟他的模式名字一樣,他就是一個硬盤,對他進行讀寫,也就相當於對一個硬盤進行讀寫。當引腳OE(好像是叫OE,具體參考 CF卡文檔)在上電的時候檢測到拉低,那麼CF卡就進入TRUE IDE模式。讀寫硬盤的時候,在只寫一次LBA,只發送一個命令(讀或者寫)的情況下,最多可以讀或者寫256個扇區(當然也可以讀一個扇區,讀或者寫多少個扇區在扇區計數器count裏面),其中,發一個讀或者寫命令,讀或者寫256個扇區所需要的時間,比分256次去讀寫這些扇區所需要的時間要短得多,效率要高得多,我現在需要的是一個讀寫的速度比較快,效率比較高的文件系統,因此,底層的讀寫扇區必須要每寫一個命令就可以讀寫多個扇區,讀寫扇區的函數必須要有扇區計數器(前面的count)這個參數,纔可能滿足要求


    UC/FS也是在網上搜了個代碼,看了下,很標準的幾個層,什麼硬件層,文件系統層,API層,等等(具體參見UC/FS的文檔),跟UCOSII一個公司的,穩定性應該不錯,需要提供的函數也是讀扇區,寫扇區等等幾個。但是底層的讀寫扇區的函數不需要提供扇區計數器count這個參數,也就是說,這個文件系統不能在只寫一個讀或者寫命令的情況下,讀或者寫多個扇區,本來效感覺不錯的一個文件系統,效率就大大的降低了。


    然後看了下efls這個文件系統,開源的項目,免費的項目,好東西,移植也很簡單,同樣移植的時候也是提供讀寫扇區等幾個函數,但是面臨的跟UC/FS同樣的問題,每次讀寫的時候也只能讀寫一個扇區。

 

    絕望之餘看到了周立功的文件系統,大概看了下(沒有仔細閱讀源代碼),硬件驅動上面能夠在發一次讀命令的情況下,讀寫多個扇區,而且感覺上比較簡單,同樣,層次也很清楚,移植需要做的事情也是修改後面的讀寫扇區等等幾個函數。於是就開幹了。功夫不負苦心人,過了幾天,CF卡能夠讀寫了,拿到電腦上面看寫的數據,沒問題。從CF卡里面讀文件出來,打印到超級終端,也沒有問題,以爲就萬事OK了,想了下,我們需要的,最關心的,第一是速度,然後就開始測試速度,不測不知道,一測嚇一跳!太“快”了,TMD,才5,6個K Bytes 每秒!!!!!(我的驅動已經測試了,上M字節每秒的)   於是跟蹤到寫裏面去,發現一個很,十分,非常嚴重的問題:ZLG/FS提供了讀一個字節的函數,忘了叫做啥,這裏暫時叫 ReadOneByte(***),然後讀多個字節,或者說讀大塊字節的函數用的是啥,呵呵,

for(i=0;i < N ; i++) ReadOneByte(***),這種機制,不慢纔怪事!!!於是傷心的拋棄了ZLG/FS,這東東,學習還是可以的,商用的話,差太遠了!!!


    我那點東西,文件系統可以不上,但是必須有個文件存儲協議,或者說叫做自己的文件系統,自己寫個簡單的存儲協議,試過,很麻煩。但是如果上文件系統,自己寫的話,寫要累死人的,寫出來的不一定效率就高,速度就快,所以,還是在網上漫無目的的找,覺得應該有效率很高的文件系統的。


    還是那句話,功夫不負苦心人,終於讓我找到了,也就是現在所用的,FatFs,開源,免費,高效!(說一下這裏幾個文件系統都有的一個缺點,由於微軟的 FAT版權的問題,FatFs,ZLG/FS,efsl都只支持 DOS 8.3 文件名,即8個字節的文件名,一個”.“,然後3個字節的擴展名,我找到的那個UC/FS也不支持,不知道在更新的版本里面支持不,看哪天有空了,把那個 FatFs改下,讓他支持,呵呵)。FatFs 的底層可以寫一次命令,讀寫多個扇區。FatFs 的設計的讀寫的思想就很好,小塊的數據,我就經過Buffer來存儲,大塊的數據,我就直接進行存取,那樣速度,效率高了很多,看圖:

FatFs文件系統的結構也很清晰,也是看圖:

 

補充一點,FatFs的作者寫了兩個,一個是正宗的 FatFs,比較適合大的RAM的設備,另一個是FatFs/Tiny,比較適合小RAM的系統,比如單片機,FatFs/Tiny佔用較小的RAM,代價是更慢的讀寫速度和更少的API函數。不過兩個都支持FAT12,FAT16,FAT32文件系統。

下載下來的FatFs的FatFs有兩個文件夾,一個是 doc ,FatFs的說明,包括特性,系統函數,以及可能的一些問題,另一個就是源代碼文件夾src了,總共8個文件,diskio.c和diskio.h是硬件層,ff.c和 ff.h是FatFs的文件系統層和文件系統的API層,integer.h是文件系統所用到的數據類型的定義,tff.c和tff.h是Tiny的文件系統層和文件系統的API層,還有一個00readme.txt簡要的介紹了FatFSHE FatFs/Tiny,包括他們所支持的API,怎麼配置等等。

移植的問題,第一個是數據類型,在integer.h裏面去定義好數據的類型。第二個,就是配置,打開ff.h(我用的FatFs,不是 Tiny),_MCU_ENDIAN,選擇你的CPU是大端存儲(big endding)還是小端存儲(little endding),一般的都用的小端存儲,1是小端,2是大端。這個相當重要,一會兒還要談到這裏。

其他的,按照自己的需要來配置了,說明文檔夠清楚了,我就不多說啥了。

第三件事情,就是寫底層的驅動函數,包括:

* disk_initialize - Initialize disk drive
* disk_status - Get disk status
* disk_read - Read sector(s)
* disk_write - Write sector(s)
* disk_ioctl - Control device dependent features
* get_fattime - Get current time

所有的函數都牽涉到了選擇第幾個磁盤的問題,如果僅僅用一個,可以不必理會這個drv 參數。

disk_initialize ,如果不需要的話,直接返回0就行

disk_status ,這個嘛,先不管了,直接返回0就OK

disk_read - Read sector(s)
disk_write - Write sector(s)
讀寫扇區,注意參數哦!

disk_ioctl 需要回應CTRL_SYNC,GET_SECTOR_COUNT,GET_BLOCK_SIZE 三個命令,正確返回0即
RES_OK,不正確返回RES_ERROR。
所有的命令都從 ctrl 裏面去讀,返回值僅僅返回這次操作是否有效,而需要傳遞回去的數據在buff
裏面,以下是我的:
CTRL_SYNC命令,直接返回0;
GET_SECTOR_COUNT,得到所有可用的扇區數目(邏輯尋址即LBA尋址方式)
GET_BLOCK_SIZE,得到每個扇區有多少個字節,比如 *((DWORD*)buff) = 512;
其他的命令,返回RES_PARERR

disk_ioctl 這個函數僅僅在格式化的時候被使用,在調試讀寫的時候,這個函數直接讓他返回0就OK 了。

get_fattime - 得到系統的時間,格式請見文檔。不用的話,返回0就行。

這樣移植了,也基本上就成功了,但是在我的板子上面死活不行,每次一執行到幾個宏定義比如
LD_WORD(ptr)        (WORD)(*(WORD*)(BYTE*)(ptr)) 就產生數據終止異常( DATA ABORT exception),但是網上的一個兄弟的(ouravr上的一個兄弟,用的SD卡,IAR編譯器,平臺是STM32,已經成功了,還公佈了源碼的,這裏沒有問題啊),沒問題。分析下這個幾個宏的意思:

LD_WORD(ptr)        (WORD)(*(WORD*)(BYTE*)(ptr)) 是在little endding裏面定義的

LD_WORD(ptr) ,LD就是load,WORD在integer.h裏面定義的是16位的無符號數,那這個需要完成的就是載入一個16位的數,或者說是2個字節,後面的 ptr是參數。(WORD)(*(WORD*)(BYTE*)(ptr)) ,先將這個ptr轉換成一個指向BYTE類型數據的指針(BYTE *),在將這個指針轉換成一個指向 16位無符號數的指針(WORD *),然後用一個 ” * “將這個數據取出來,轉換成一個無符號的16位數據,這個僅僅從C語言的角度來看,實際上呢,這個完成的就是從ptr指針指向的位置,取出2個字節,作爲一個16位的無符號數取出,而這2個字節是little endding,即小端模式,低字節是低8位,高字節是高8位。


既然是這樣的,測試了下,定義了一個BYTE buf[512],定義一個WORD類型 zz,用一個指針pt,讓pt指向buf[0],調用LD_WORD(ptr),zz=LD_WORD(pt);沒問題,將pt指向 buf[1],呵呵,問題馬上出來了,數據終止異常,然後測試了指針指向 buf[3],buf[5]等等奇數個,都是這樣的問題,我就鬱悶了啊,

TMD,編譯器的問題!!!!

下面我加上的(282280072)

{

 

#define _MCU_ENDIAN   1 //下面分析的 只須把1——>2即可

1: Word access. Do not choose this unless following condition is met.

2: Byte-by-byte access. Always compatible with all platforms.

 

MCU_ENDIAN   這個名字很有歧義 表面是處理器的大端小段,其實不是 在FATFS的最新版本 007e裏面已經改名字了 改爲_WORD_ACCESS

 

#define _WORD_ACCESS 0 /* 0 or 1 */
/* The _WORD_ACCESS option defines which access method is used to the word
/ data on the FAT volume.
/
/   0: Byte-by-byte access. Always compatible with all platforms.
/   1: Word access. Do not choose this unless following condition is met.
/
/ When the byte order on the memory is big-endian or address miss-aligned
/ word access results incorrect behavior, the _WORD_ACCESS must be set to 0.
/ If it is not the case, the value can also be set to 1 to improve the
/ performance and code size. */


}

不過還好,找到問題了,就可以解決問題了,在 ff.h裏面的宏定義裏面把這即個東東給註釋掉,然後在ff.c裏面把這幾個宏定義寫成函數,這裏貼一個出來:
WORD    LD_WORD(void *pt)
{
BYTE *PT = (BYTE*)pt;     //定義一個指針,將當前的指針指向的地址的值賦給PT
return (WORD)(PT[0]+PT[1]*256); //計算這個16位數,(低8位在前面,高8位在後面),並來個強制類

型轉
//換,並返回
}
需要注意的是,LD_WORD返回的就必須是WORD。這樣做了,編譯器大部分的也可以編譯通過,但是

ADS就是通不過,有3個地方,
finfo->fsize = LD_DWORD(&dir[DIR_FileSize]);    /* Size */
finfo->fdate = LD_WORD(&dir[DIR_WrtDate]);        /* Date */
finfo->ftime = LD_WORD(&dir[DIR_WrtTime]);        /* Time */
其中,dir的是這樣定義的:const BYTE *dir,編譯器報錯是類型不匹配,因此,這裏的幾個LD_WORD和

LD_DWORD重寫,定義成一致的類型即可:
WORD    LD_WORD_1(const BYTE *pt)
{
BYTE *PT = (BYTE*)pt;
return (WORD)(PT[0]+PT[1]*256);
}

DWORD    LD_DWORD_1(const BYTE *pt)
{
BYTE *PT = (BYTE*)pt;
return ((DWORD)PT[0]+(DWORD)(PT[1]*256)+(DWORD)(PT[2]*65536)+(DWORD)(PT[3]*16777216));


}

而後面改成:
finfo->fsize = LD_DWORD_1(&dir[DIR_FileSize]);    /* Size */
finfo->fdate = LD_WORD_1(&dir[DIR_WrtDate]);        /* Date */
finfo->ftime = LD_WORD_1(&dir[DIR_WrtTime]);        /* Time */

編譯,一路OK,然後寫一個文件,哇,哈哈哈哈!!!!終於出來了!!!!寫文件沒問題,讀也沒問

題!@~~~~~測試了常用的函數,都沒有問題,包括格式化(f_mkfs,前提是你的disk_ioctl 沒問題),

測試
了下速度,讀12.5M的MP3,大約3秒,寫這個12.5M的MP3大約6.5秒,勉強達到要求,再優化下驅動那邊

就可以更快了!~~~~~~~

發個FatFs的官方網址 http://elm-chan.org/fsw/ff/00index_e.html

總結這次移植,差點失敗就在於編譯器的指針的轉換問題,寫出來,希望兄弟姐妹們在移植的時候不會

遇到這種問題。



發佈了27 篇原創文章 · 獲贊 72 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章