1. 程式人生 > >轉一篇比較詳細介紹FatFs檔案系統移植的文章 FatFs檔案系統的移植

轉一篇比較詳細介紹FatFs檔案系統移植的文章 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是大端。這個相當重要,一會兒還要談到這裡。其他的,按照自己的需要來配置了,說明文件夠清楚了, 我就不多說啥了。

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

所有的函式都牽涉到了選擇第幾個磁碟的問題,如果僅僅用一個,可以不必理會這個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,編譯器的問題!!!!不過還好,找到問題了,就可以解決問題了,在 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

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