轉一篇比較詳細介紹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個扇區(當然也可以讀一個扇區,讀或者寫多 少個扇區在扇區
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_read - Read sector(s)
- disk_write - Write sector(s)
- disk_ioctl - Control device dependent features
所有的函式都牽涉到了選擇第幾個磁碟的問題,如果僅僅用一個,可以不必理會這個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
總結這次移植,差點失敗就在於編譯器的指標的轉換問題,寫出來,希望兄弟姐妹們在移植的時候不會遇到這種問題。