STM32例程之FATFS檔案系統(SPI方式)移植筆記(原始碼下載)
阿新 • • 發佈:2019-02-07
STM32的FATFS檔案系統移植筆記
一、序言
經常在網上、群裡看到很多人問關於STM32的FATFS檔案系統移植的問題,剛好自己最近也在除錯這個程式,為了讓大家少走彎路,我把我的除錯過程和方法也貢獻給大家。
二、FATFS簡介
FatFs Module是一種完全免費開源的FAT檔案系統模組,專門為小型的嵌入式系統而設計。它完全用標準C語言編寫,所以具有良好的硬體平臺獨立性,可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列微控制器上而只需做簡單的修改。它支援FATl2、FATl6和FAT32,支援多個儲存媒介;有獨立的緩衝區,可以對多個檔案進行讀/寫,並特別對8位微控制器和16位微控制器做了優化。
三、移植準備
1、FATFS原始碼的獲取,可以到官網下載: http://elm-chan.org/fsw/ff/00index_e.html 最新版本是R0.09版本,我們就移植這個版本的。
2、解壓檔案會得到兩個資料夾,一個是doc資料夾,這裡是FATFS的一些使用文件和說明,以後在檔案程式設計的時候可以檢視該文件。另一個是src資料夾,裡面就是我們所要的原始檔。
3、建立一個STM32的工程,為方便除錯,我們應過載printf()底層函式實現串列埠列印輸出。可以參考已經建立好的printf()列印輸出工程:http://www.viewtool.com/bbs/foru ... d=77&extra=page%3D1
四、開始移植
1、在已經建立好的工程目錄User資料夾下新建兩個資料夾,FATFS_V0.09和SPI_SD_Card,FATFS_V0.09用於存放FATFS原始檔,SPI_SD_Card用於存放SPI的驅動檔案。
2、如圖1將ff.c新增到工程資料夾中,並新建diskio.c檔案,在diskio.c檔案中實現五個函式:
圖1
3、初步實現以上五個函式
FATFS初始化函式:
4、實現disk_initialize()函式
該函式在掛載檔案系統的時候會被呼叫,主要是實現讀寫SD卡前對SD卡進行初始化,根據SD卡的傳輸協議,我們按照如下步驟初始化SD卡:
a、判斷SD卡是否插入,可以通過檢查SD卡卡座的CD腳電平進行判斷,一般插入卡後該引腳會變成低電平。
b、稍微延時一段時間後傳送至少74個時鐘給SD卡。
c、傳送CMD0命令給SD卡,直到SD卡返回0x01為止,這裡可以迴圈多次傳送。
程式如下:
e、根據卡的型別對卡進行初始化。具體初始化方式可以參考附件程式。
注:在初始化SD卡之前應該初始化SPI介面和相關的管腳。
實現後的程式如下:
5、實現disk_read()函式
該函式是讀取SD卡扇區資料的函式,根據SD卡資料傳輸協議可知有讀取單扇區和讀取多扇區兩種操作模式,為提高讀檔案的速度應該實現讀取多扇區函式。
實現後的程式如下:
6、實現disk_write()函式
該函式主要實現對SD卡進行寫資料操作,和讀資料操作一樣也分單塊寫和多塊寫,建議實現多塊寫的方式,這樣可以提高寫資料速度。
實現後的程式如下:
7、實現disk_ioctl()函式
該函式在磁碟格式化、獲取檔案系統資訊等操作時會被呼叫。
實現後的程式如下:
8、到此diskio.c這個檔案中的所有函式就已經實現,下一步就是實現SPI_MSD0_Driver.c檔案中的相關函式,SPI_MSD0_Driver.c檔案可以在網上下載,參考的程式比較多,本工程使用的這個檔案也是在網上下載並進行一定的修改過的。本檔案中函式的實現方式可以參考原始碼。
五、檔案系統測試
1、測試寫檔案
測試程式碼如下:
2、測試讀檔案
測試結果如圖2所示。
圖2
六、中文長檔名支援
1、要支援長檔名需要在ffconf.h檔案中修改兩個巨集定義。如下為我們修改後的巨集定義。
#define _CODE_PAGE 936
#define _USE_LFN 1 /* 0 to 3 */
2、新增支援中文編碼的檔案
重新編譯會發現有如圖3的錯誤。原因是要支援中文檔名需要包含另外一個檔案cc936.c,該檔案在FATFS檔案系統原始碼的.\src\option目錄下,將它新增到工程檔案目錄FATFS中。如圖4是我們新增檔案後的工程檔案結構,再次編譯就通過了。如圖5所示。我們發現增加這個檔案後代碼量增加了很多,主要原因是這個檔案是我們支援中文所需要的中文編碼檔案。
圖3
圖4
圖5
3、再次下載到板子中執行,發現中文的長檔名顯示正常了。如圖6所示。
4、若不需要支援中文長檔名而只支援英文長檔名則可以將巨集定義做如下修改:
#define _CODE_PAGE 437
#define _USE_LFN 1 /* 0 to 3 */
同時將ccsbcs.c新增到工程目錄中,這樣就可以減小很多大程式碼量。將程式下載板子後再次執行結果如圖7所示,可以看到可以支援英文的長檔名。
七、原始檔下載
一、序言
經常在網上、群裡看到很多人問關於STM32的FATFS檔案系統移植的問題,剛好自己最近也在除錯這個程式,為了讓大家少走彎路,我把我的除錯過程和方法也貢獻給大家。
二、FATFS簡介
FatFs Module是一種完全免費開源的FAT檔案系統模組,專門為小型的嵌入式系統而設計。它完全用標準C語言編寫,所以具有良好的硬體平臺獨立性,可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列微控制器上而只需做簡單的修改。它支援FATl2、FATl6和FAT32,支援多個儲存媒介;有獨立的緩衝區,可以對多個檔案進行讀/寫,並特別對8位微控制器和16位微控制器做了優化。
三、移植準備
1、FATFS原始碼的獲取,可以到官網下載:
2、解壓檔案會得到兩個資料夾,一個是doc資料夾,這裡是FATFS的一些使用文件和說明,以後在檔案程式設計的時候可以檢視該文件。另一個是src資料夾,裡面就是我們所要的原始檔。
3、建立一個STM32的工程,為方便除錯,我們應過載printf()底層函式實現串列埠列印輸出。可以參考已經建立好的printf()列印輸出工程:http://www.viewtool.com/bbs/foru ... d=77&extra=page%3D1
四、開始移植
1、在已經建立好的工程目錄User資料夾下新建兩個資料夾,FATFS_V0.09和SPI_SD_Card,FATFS_V0.09用於存放FATFS原始檔,SPI_SD_Card用於存放SPI的驅動檔案。
2、如圖1將ff.c新增到工程資料夾中,並新建diskio.c檔案,在diskio.c檔案中實現五個函式:
- DSTATUS disk_initialize (BYTE);//SD卡的初始化
- DSTATUS disk_status (BYTE);//獲取SD卡的狀態,這裡可以不用管
- DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE);//從SD卡讀取資料
- DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE);//將資料寫入SD卡,若該檔案系統為只讀檔案系統則不用實現該函式
- DRESULT disk_ioctl (BYTE, BYTE, void*);//獲取SD卡檔案系統相關資訊
圖1
3、初步實現以上五個函式
FATFS初始化函式:
- DSTATUS disk_initialize (
- BYTE drv /* Physical drive nmuber (0..) */
- )
- {
- switch (drv)
- {
- case 0 :
- return RES_OK;
- case 1 :
- return RES_OK;
- case 2 :
- return RES_OK;
- case 3 :
- return RES_OK;
- default:
- return STA_NOINIT;
- }
- }
- DSTATUS disk_status (
- BYTE drv /* Physical drive nmuber (0..) */
- )
- {
- switch (drv)
- {
- case 0 :
- return RES_OK;
- case 1 :
- return RES_OK;
- case 2 :
- return RES_OK;
- default:
- return STA_NOINIT;
- }
- }
- DRESULT disk_read (
- BYTE drv, /* Physical drive nmuber (0..) */
- BYTE *buff, /* Data buffer to store read data */
- DWORD sector, /* Sector address (LBA) */
- BYTE count /* Number of sectors to read (1..255) */
- )
- {
- if( !count )
- {
- return RES_PARERR; /* count不能等於0,否則返回引數錯誤 */
- }
- switch (drv)
- {
- case 0:
- if(count==1) /* 1個sector的讀操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的讀操作 */
- {
- return RES_OK;
- }
- case 1:
- if(count==1) /* 1個sector的讀操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的讀操作 */
- {
- return RES_OK;
- }
- default:
- return RES_ERROR;
- }
- }
- DRESULT disk_write (
- BYTE drv, /* Physical drive nmuber (0..) */
- const BYTE *buff, /* Data to be written */
- DWORD sector, /* Sector address (LBA) */
- BYTE count /* Number of sectors to write (1..255) */
- )
- {
- if( !count )
- {
- return RES_PARERR; /* count不能等於0,否則返回引數錯誤 */
- }
- switch (drv)
- {
- case 0:
- if(count==1) /* 1個sector的寫操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的寫操作 */
- {
- return RES_OK;
- }
- case 1:
- if(count==1) /* 1個sector的寫操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的寫操作 */
- {
- return RES_OK;
- }
- default:return RES_ERROR;
- }
- }
- DRESULT disk_ioctl (
- BYTE drv, /* Physical drive nmuber (0..) */
- BYTE ctrl, /* Control code */
- void *buff /* Buffer to send/receive control data */
- )
- {
- if (drv==0)
- {
- switch (ctrl)
- {
- case CTRL_SYNC :
- return RES_OK;
- case GET_SECTOR_COUNT :
- return RES_OK;
- case GET_BLOCK_SIZE :
- return RES_OK;
- case CTRL_POWER :
- break;
- case CTRL_LOCK :
- break;
- case CTRL_EJECT :
- break;
- /* MMC/SDC command */
- case MMC_GET_TYPE :
- break;
- case MMC_GET_CSD :
- break;
- case MMC_GET_CID :
- break;
- case MMC_GET_OCR :
- break;
- case MMC_GET_SDSTAT :
- break;
- }
- }else if(drv==1){
- switch (ctrl)
- {
- case CTRL_SYNC :
- return RES_OK;
- case GET_SECTOR_COUNT :
- return RES_OK;
- case GET_SECTOR_SIZE :
- return RES_OK;
- case GET_BLOCK_SIZE :
- return RES_OK;
- case CTRL_POWER :
- break;
- case CTRL_LOCK :
- break;
- case CTRL_EJECT :
- break;
- /* MMC/SDC command */
- case MMC_GET_TYPE :
- break;
- case MMC_GET_CSD :
- break;
- case MMC_GET_CID :
- break;
- case MMC_GET_OCR :
- break;
- case MMC_GET_SDSTAT :
- break;
- }
- }
- else{
- return RES_PARERR;
- }
- return RES_PARERR;
- }
4、實現disk_initialize()函式
該函式在掛載檔案系統的時候會被呼叫,主要是實現讀寫SD卡前對SD卡進行初始化,根據SD卡的傳輸協議,我們按照如下步驟初始化SD卡:
a、判斷SD卡是否插入,可以通過檢查SD卡卡座的CD腳電平進行判斷,一般插入卡後該引腳會變成低電平。
b、稍微延時一段時間後傳送至少74個時鐘給SD卡。
c、傳送CMD0命令給SD卡,直到SD卡返回0x01為止,這裡可以迴圈多次傳送。
程式如下:
- /* Start send CMD0 till return 0x01 means in IDLE state */
- for(retry=0; retry<0xFFF; retry++)
- {
- r1 = MSD0_send_command(CMD0, 0, 0x95);
- if(r1 == 0x01)
- {
- retry = 0;
- break;
- }
- }
e、根據卡的型別對卡進行初始化。具體初始化方式可以參考附件程式。
注:在初始化SD卡之前應該初始化SPI介面和相關的管腳。
實現後的程式如下:
- DSTATUS disk_initialize (
- BYTE drv /* Physical drive nmuber (0..) */
- )
- {
- int Status;
- switch (drv)
- {
- case 0 :
- Status = MSD0_Init();
- if(Status==0){
- return RES_OK;
- }else{
- return STA_NOINIT;
- }
- case 1 :
- return RES_OK;
- case 2 :
- return RES_OK;
- case 3 :
- return RES_OK;
- default:
- return STA_NOINIT;
- }
- }
5、實現disk_read()函式
該函式是讀取SD卡扇區資料的函式,根據SD卡資料傳輸協議可知有讀取單扇區和讀取多扇區兩種操作模式,為提高讀檔案的速度應該實現讀取多扇區函式。
實現後的程式如下:
- DRESULT disk_read (
- BYTE drv, /* Physical drive nmuber (0..) */
- BYTE *buff, /* Data buffer to store read data */
- DWORD sector, /* Sector address (LBA) */
- BYTE count /* Number of sectors to read (1..255) */
- )
- {
- int Status;
- if( !count )
- {
- return RES_PARERR; /* count不能等於0,否則返回引數錯誤 */
- }
- switch (drv)
- {
- case 0:
- if(count==1) /* 1個sector的讀操作 */
- {
- Status = MSD0_ReadSingleBlock( sector ,buff );
- if(Status == 0){
- return RES_OK;
- }else{
- return RES_ERROR;
- }
- }
- else /* 多個sector的讀操作 */
- {
- Status = MSD0_ReadMultiBlock( sector , buff ,count);
- if(Status == 0){
- return RES_OK;
- }else{
- return RES_ERROR;
- }
- }
- case 1:
- if(count==1) /* 1個sector的讀操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的讀操作 */
- {
- return RES_OK;
- }
- default:
- return RES_ERROR;
- }
- }
6、實現disk_write()函式
該函式主要實現對SD卡進行寫資料操作,和讀資料操作一樣也分單塊寫和多塊寫,建議實現多塊寫的方式,這樣可以提高寫資料速度。
實現後的程式如下:
- DRESULT disk_write (
- BYTE drv, /* Physical drive nmuber (0..) */
- const BYTE *buff, /* Data to be written */
- DWORD sector, /* Sector address (LBA) */
- BYTE count /* Number of sectors to write (1..255) */
- )
- {
- int Status;
- if( !count )
- {
- return RES_PARERR; /* count不能等於0,否則返回引數錯誤 */
- }
- switch (drv)
- {
- case 0:
- if(count==1) /* 1個sector的寫操作 */
- {
- Status = MSD0_WriteSingleBlock( sector , (uint8_t *)(&buff[0]) );
- if(Status == 0){
- return RES_OK;
- }else{
- return RES_ERROR;
- }
- }
- else /* 多個sector的寫操作 */
- {
- Status = MSD0_WriteMultiBlock( sector , (uint8_t *)(&buff[0]) , count );
- if(Status == 0){
- return RES_OK;
- }else{
- return RES_ERROR;
- }
- }
- case 1:
- if(count==1) /* 1個sector的寫操作 */
- {
- return RES_OK;
- }
- else /* 多個sector的寫操作 */
- {
- return RES_OK;
- }
- default:return RES_ERROR;
- }
- }
7、實現disk_ioctl()函式
該函式在磁碟格式化、獲取檔案系統資訊等操作時會被呼叫。
實現後的程式如下:
- DRESULT disk_ioctl (
- BYTE drv, /* Physical drive nmuber (0..) */
- BYTE ctrl, /* Control code */
- void *buff /* Buffer to send/receive control data */
- )
- {
- if (drv==0)
- {
- MSD0_GetCardInfo(&SD0_CardInfo);
- switch (ctrl)
- {
- case CTRL_SYNC :
- return RES_OK;
- case GET_SECTOR_COUNT :
- *(DWORD*)buff = SD0_CardInfo.Capacity/SD0_CardInfo.BlockSize;
- return RES_OK;
- case GET_BLOCK_SIZE :
- *(WORD*)buff = SD0_CardInfo.BlockSize;
- return RES_OK;
- case CTRL_POWER :
- break;
- case CTRL_LOCK :
- break;
- case CTRL_EJECT :
- break;
- /* MMC/SDC command */
- case MMC_GET_TYPE :
- break;
- case MMC_GET_CSD :
- break;
- case MMC_GET_CID :
- break;
- case MMC_GET_OCR :
- break;
- case MMC_GET_SDSTAT :
- break;
- }
- }else if(drv==1){
- switch (ctrl)
- {
- case CTRL_SYNC :
- return RES_OK;
- case GET_SECTOR_COUNT :
- return RES_OK;
- case GET_SECTOR_SIZE :
- return RES_OK;
- case GET_BLOCK_SIZE :
- return RES_OK;
- case CTRL_POWER :
- break;
- case CTRL_LOCK :
- break;
- case CTRL_EJECT :
- break;
- /* MMC/SDC command */
- case MMC_GET_TYPE :
- break;
- case MMC_GET_CSD :
- break;
- case MMC_GET_CID :
- break;
- case MMC_GET_OCR :
- break;
- case MMC_GET_SDSTAT :
- break;
- }
- }
- else{
- return RES_PARERR;
- }
- return RES_PARERR;
- }
8、到此diskio.c這個檔案中的所有函式就已經實現,下一步就是實現SPI_MSD0_Driver.c檔案中的相關函式,SPI_MSD0_Driver.c檔案可以在網上下載,參考的程式比較多,本工程使用的這個檔案也是在網上下載並進行一定的修改過的。本檔案中函式的實現方式可以參考原始碼。
五、檔案系統測試
1、測試寫檔案
測試程式碼如下:
- //寫檔案測試
- printf("write file test......\n\r");
- res = f_open(&fdst, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
- if(res != FR_OK){
- printf("open file error : %d\n\r",res);
- }else{
- res = f_write(&fdst, textFileBuffer, sizeof(textFileBuffer), &bw); /* Write it to the dst file */
- if(res == FR_OK){
- printf("write data ok! %d\n\r",bw);
- }else{
- printf("write data error : %d\n\r",res);
- }
- /*close file */
- f_close(&fdst);
- }
2、測試讀檔案
- //讀檔案測試
- printf("read file test......\n\r");
- res = f_open(&fsrc, "0:/test.txt", FA_OPEN_EXISTING | FA_READ);
- if(res != FR_OK){
- printf("open file error : %d\n\r",res);
- }else{
- res = f_read(&fsrc, buffer, sizeof(textFileBuffer), &br); /* Read a chunk of src file */
- if(res==FR_OK){
- printf("read data num : %d\n\r",br);
- printf("%s\n\r",buffer);
- }else{
- printf("read file error : %d\n\r",res);
- }
- /*close file */
- f_close(&fsrc);
- }
測試結果如圖2所示。
圖2
六、中文長檔名支援
1、要支援長檔名需要在ffconf.h檔案中修改兩個巨集定義。如下為我們修改後的巨集定義。
#define _CODE_PAGE 936
#define _USE_LFN 1 /* 0 to 3 */
2、新增支援中文編碼的檔案
重新編譯會發現有如圖3的錯誤。原因是要支援中文檔名需要包含另外一個檔案cc936.c,該檔案在FATFS檔案系統原始碼的.\src\option目錄下,將它新增到工程檔案目錄FATFS中。如圖4是我們新增檔案後的工程檔案結構,再次編譯就通過了。如圖5所示。我們發現增加這個檔案後代碼量增加了很多,主要原因是這個檔案是我們支援中文所需要的中文編碼檔案。
圖3
圖4
圖5
3、再次下載到板子中執行,發現中文的長檔名顯示正常了。如圖6所示。
4、若不需要支援中文長檔名而只支援英文長檔名則可以將巨集定義做如下修改:
#define _CODE_PAGE 437
#define _USE_LFN 1 /* 0 to 3 */
同時將ccsbcs.c新增到工程目錄中,這樣就可以減小很多大程式碼量。將程式下載板子後再次執行結果如圖7所示,可以看到可以支援英文的長檔名。
七、原始檔下載