因為需要,又不想自己寫,所以就移植了一個檔案系統。
說下我的硬體和開發工具:接成 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,編譯器的問題。。。。不過還好,找到問題了,就可以解決問題了,在 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
總結這次移植,差點失敗就在於編譯器的指標的轉換問題,寫出來,希望兄弟姐妹們在移植的時候不會遇到這種問題。