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里面把這幾個宏定義寫成函數,這里貼一個出來:
1 WORD LD_WORD(void *pt) 2 { 3 BYTE *PT = (BYTE*)pt; //定義一個指針,將當前的指針指向的地址的值賦給PT 4 return (WORD)(PT[0]+PT[1]*256); //計算這個16位數,(低8位在前面,高8位在后面),並來個強制類型轉 5 //換,並返回 6 }
需要注意的是,LD_WORD返回的就必須是WORD。這樣做了,編譯器大部分的也可以編譯通過,但是ADS就是通不過,有3個地方,
1 finfo->fsize = LD_DWORD(&dir[DIR_FileSize]); /* Size */ 2 finfo->fdate = LD_WORD(&dir[DIR_WrtDate]); /* Date */ 3 finfo->ftime = LD_WORD(&dir[DIR_WrtTime]); /* Time */
其中,dir的是這樣定義的:const BYTE *dir,編譯器報錯是類型不匹配,因此,這里的幾個LD_WORD和LD_DWORD重寫,定義成一致的類型即可:
1 WORD LD_WORD_1(const BYTE *pt) 2 { 3 BYTE *PT = (BYTE*)pt; 4 return (WORD)(PT[0]+PT[1]*256); 5 } 6 7 DWORD LD_DWORD_1(const BYTE *pt) 8 { 9 BYTE *PT = (BYTE*)pt; 10 return ((DWORD)PT[0]+(DWORD)(PT[1]*256)+(DWORD)(PT[2]*65536)+(DWORD)(PT[3]*16777216)); 11 }
而后面改成:
1 finfo->fsize = LD_DWORD_1(&dir[DIR_FileSize]); /* Size */ 2 finfo->fdate = LD_WORD_1(&dir[DIR_WrtDate]); /* Date */ 3 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