1. 程式人生 > >STM32例程之FATFS檔案系統(SPI方式)移植筆記(原始碼下載)

STM32例程之FATFS檔案系統(SPI方式)移植筆記(原始碼下載)

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. DSTATUS disk_initialize (BYTE);//SD卡的初始化
  2.         DSTATUS disk_status (BYTE);//獲取SD卡的狀態,這裡可以不用管
  3.         DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE);//從SD卡讀取資料
  4.         DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE);//將資料寫入SD卡,若該檔案系統為只讀檔案系統則不用實現該函式
  5.         DRESULT disk_ioctl (BYTE, BYTE, void*);//獲取SD卡檔案系統相關資訊
複製程式碼 圖1

圖1
    3、初步實現以上五個函式
        FATFS初始化函式:
  1. DSTATUS disk_initialize (
  2.                 BYTE drv                                /* Physical drive nmuber (0..) */
  3.         )
  4.         {
  5.                 switch (drv)
  6.                 {
  7.                         case 0 :
  8.                                 return RES_OK;
  9.                         case 1 :
  10.                                 return RES_OK;         
  11.                         case 2 :
  12.                                 return RES_OK;         
  13.                         case 3 :
  14.                                 return RES_OK;
  15.                         default:
  16.                                 return STA_NOINIT;
  17.                 }
  18.         }
複製程式碼 FATFS狀態獲取函式:
  1. DSTATUS disk_status (
  2.                 BYTE drv                /* Physical drive nmuber (0..) */
  3.         )
  4.         {
  5.                 switch (drv)
  6.                 {
  7.                         case 0 :
  8.                                 return RES_OK;
  9.                         case 1 :
  10.                                 return RES_OK;
  11.                         case 2 :
  12.                                 return RES_OK;
  13.                         default:
  14.                                 return STA_NOINIT;
  15.                 }
  16.         }
複製程式碼 FATFS底層讀資料函式:
  1. DRESULT disk_read (
  2.                 BYTE drv,                /* Physical drive nmuber (0..) */
  3.                 BYTE *buff,                /* Data buffer to store read data */
  4.                 DWORD sector,        /* Sector address (LBA) */
  5.                 BYTE count                /* Number of sectors to read (1..255) */
  6.         )
  7.         {
  8.                 if( !count )
  9.                 {   
  10.                         return RES_PARERR;  /* count不能等於0,否則返回引數錯誤 */
  11.                 }
  12.                 switch (drv)
  13.                 {
  14.                         case 0:
  15.                             if(count==1)            /* 1個sector的讀操作 */      
  16.                             {   
  17.                                         return RES_OK;   
  18.                             }                                                
  19.                             else                    /* 多個sector的讀操作 */     
  20.                             {  
  21.                                         return RES_OK;
  22.                             }                                                
  23.                         case 1:
  24.                             if(count==1)            /* 1個sector的讀操作 */      
  25.                             {   
  26.                                         return RES_OK;   
  27.                             }                                                
  28.                             else                    /* 多個sector的讀操作 */     
  29.                             {  
  30.                                         return RES_OK;
  31.                             }
  32.                         default:
  33.                                 return RES_ERROR;
  34.                 }
  35.         }
複製程式碼 FATFS底層寫資料函式:
  1. DRESULT disk_write (
  2.                 BYTE drv,                        /* Physical drive nmuber (0..) */
  3.                 const BYTE *buff,                /* Data to be written */
  4.                 DWORD sector,                /* Sector address (LBA) */
  5.                 BYTE count                        /* Number of sectors to write (1..255) */
  6.         )
  7.         {
  8.                 if( !count )
  9.                 {   
  10.                         return RES_PARERR;  /* count不能等於0,否則返回引數錯誤 */
  11.                 }
  12.                 switch (drv)
  13.                 {
  14.                         case 0:
  15.                             if(count==1)            /* 1個sector的寫操作 */      
  16.                             {   
  17.                                         return RES_OK;
  18.                             }                                                
  19.                             else                    /* 多個sector的寫操作 */   
  20.                             {  
  21.                                         return RES_OK;  
  22.                             }                                                
  23.                         case 1:
  24.                             if(count==1)            /* 1個sector的寫操作 */      
  25.                             {  
  26.                                         return RES_OK;
  27.                             }                                                
  28.                             else                    /* 多個sector的寫操作 */   
  29.                             {  
  30.                                         return RES_OK;
  31.                             }                                                
  32.                         default:return RES_ERROR;
  33.                 }
  34.         }
複製程式碼 FATFS磁碟控制函式:
  1. DRESULT disk_ioctl (
  2.                 BYTE drv,                /* Physical drive nmuber (0..) */
  3.                 BYTE ctrl,                /* Control code */
  4.                 void *buff                /* Buffer to send/receive control data */
  5.         )
  6.         {
  7.                 if (drv==0)
  8.                 {   
  9.                         switch (ctrl)
  10.                         {
  11.                                 case CTRL_SYNC :
  12.                                         return RES_OK;
  13.                                 case GET_SECTOR_COUNT :
  14.                                 return RES_OK;
  15.                                 case GET_BLOCK_SIZE :
  16.                                 return RES_OK;        
  17.                                 case CTRL_POWER :
  18.                                         break;
  19.                                 case CTRL_LOCK :
  20.                                         break;
  21.                                 case CTRL_EJECT :
  22.                                         break;
  23.                         /* MMC/SDC command */
  24.                                 case MMC_GET_TYPE :
  25.                                         break;
  26.                                 case MMC_GET_CSD :
  27.                                         break;
  28.                                 case MMC_GET_CID :
  29.                                         break;
  30.                                 case MMC_GET_OCR :
  31.                                         break;
  32.                                 case MMC_GET_SDSTAT :
  33.                                         break;        
  34.                         }
  35.             }else if(drv==1){
  36.                         switch (ctrl)
  37.                         {
  38.                                 case CTRL_SYNC :
  39.                                         return RES_OK;
  40.                                 case GET_SECTOR_COUNT :
  41.                                 return RES_OK;
  42.                                 case GET_SECTOR_SIZE :
  43.                                         return RES_OK;
  44.                                 case GET_BLOCK_SIZE :
  45.                                 return RES_OK;        
  46.                                 case CTRL_POWER :
  47.                                         break;
  48.                                 case CTRL_LOCK :
  49.                                         break;
  50.                                 case CTRL_EJECT :
  51.                                         break;
  52.                         /* MMC/SDC command */
  53.                                 case MMC_GET_TYPE :
  54.                                         break;
  55.                                 case MMC_GET_CSD :
  56.                                         break;
  57.                                 case MMC_GET_CID :
  58.                                         break;
  59.                                 case MMC_GET_OCR :
  60.                                         break;
  61.                                 case MMC_GET_SDSTAT :
  62.                                         break;        
  63.                         }         
  64.                 }
  65.                 else{                                 
  66.                         return RES_PARERR;  
  67.                 }
  68.                 return RES_PARERR;
  69.         }
複製程式碼 以上函式都只是實現一個框架,並沒有做實際的事情,下一步就需要把操作SD卡的程式填充在這個框架裡面。
    4、實現disk_initialize()函式
        該函式在掛載檔案系統的時候會被呼叫,主要是實現讀寫SD卡前對SD卡進行初始化,根據SD卡的傳輸協議,我們按照如下步驟初始化SD卡:
        a、判斷SD卡是否插入,可以通過檢查SD卡卡座的CD腳電平進行判斷,一般插入卡後該引腳會變成低電平。
        b、稍微延時一段時間後傳送至少74個時鐘給SD卡。
        c、傳送CMD0命令給SD卡,直到SD卡返回0x01為止,這裡可以迴圈多次傳送。
                程式如下:
  1.   /* Start send CMD0 till return 0x01 means in IDLE state */
  2.                 for(retry=0; retry<0xFFF; retry++)
  3.                 {
  4.                         r1 = MSD0_send_command(CMD0, 0, 0x95);
  5.                         if(r1 == 0x01)
  6.                         {
  7.                                 retry = 0;
  8.                                 break;
  9.                         }
  10.                 }
複製程式碼 d、傳送CMD8獲取卡的型別,不同型別的卡其初始化方式有所不同。
        e、根據卡的型別對卡進行初始化。具體初始化方式可以參考附件程式。
        注:在初始化SD卡之前應該初始化SPI介面和相關的管腳。
        實現後的程式如下:
  1. DSTATUS disk_initialize (
  2.                 BYTE drv                                /* Physical drive nmuber (0..) */
  3.         )
  4.         {
  5.                 int Status;
  6.                 switch (drv)
  7.                 {
  8.                         case 0 :
  9.                                 Status = MSD0_Init();
  10.                                 if(Status==0){
  11.                                         return RES_OK;
  12.                                 }else{
  13.                                         return STA_NOINIT;
  14.                                 }
  15.                         case 1 :
  16.                                 return RES_OK;         
  17.                         case 2 :
  18.                                 return RES_OK;         
  19.                         case 3 :
  20.                                 return RES_OK;
  21.                         default:
  22.                                 return STA_NOINIT;
  23.                 }
  24.         }
複製程式碼 MSD0_Init()函式在SPI_MSD0_Driver.c檔案中實現。
    5、實現disk_read()函式
        該函式是讀取SD卡扇區資料的函式,根據SD卡資料傳輸協議可知有讀取單扇區和讀取多扇區兩種操作模式,為提高讀檔案的速度應該實現讀取多扇區函式。
        實現後的程式如下:
  1. DRESULT disk_read (
  2.                 BYTE drv,                /* Physical drive nmuber (0..) */
  3.                 BYTE *buff,                /* Data buffer to store read data */
  4.                 DWORD sector,        /* Sector address (LBA) */
  5.                 BYTE count                /* Number of sectors to read (1..255) */
  6.         )
  7.         {
  8.                 int Status;
  9.                 if( !count )
  10.                 {   
  11.                         return RES_PARERR;  /* count不能等於0,否則返回引數錯誤 */
  12.                 }
  13.                 switch (drv)
  14.                 {
  15.                         case 0:
  16.                             if(count==1)            /* 1個sector的讀操作 */      
  17.                             {   
  18.                                         Status =  MSD0_ReadSingleBlock( sector ,buff );
  19.                                         if(Status == 0){
  20.                                                 return RES_OK;
  21.                                         }else{
  22.                                                 return RES_ERROR;
  23.                                         }   
  24.                             }                                                
  25.                             else                    /* 多個sector的讀操作 */     
  26.                             {  
  27.                                         Status = MSD0_ReadMultiBlock( sector , buff ,count);
  28.                                         if(Status == 0){
  29.                                                 return RES_OK;
  30.                                         }else{
  31.                                                 return RES_ERROR;
  32.                                         }
  33.                             }                                                
  34.                         case 1:
  35.                             if(count==1)            /* 1個sector的讀操作 */      
  36.                             {   
  37.                                         return RES_OK;   
  38.                             }                                                
  39.                             else                    /* 多個sector的讀操作 */     
  40.                             {  
  41.                                         return RES_OK;
  42.                             }
  43.                         default:
  44.                                 return RES_ERROR;
  45.                 }
  46.         }
複製程式碼 MSD0_ReadSingleBlock()和MSD0_ReadMultiBlock()函式都是SD卡操作的底層函式,我們在SPI_MSD0_Driver.c檔案中實現。
    6、實現disk_write()函式
        該函式主要實現對SD卡進行寫資料操作,和讀資料操作一樣也分單塊寫和多塊寫,建議實現多塊寫的方式,這樣可以提高寫資料速度。
        實現後的程式如下:
  1. DRESULT disk_write (
  2.                 BYTE drv,                        /* Physical drive nmuber (0..) */
  3.                 const BYTE *buff,                /* Data to be written */
  4.                 DWORD sector,                /* Sector address (LBA) */
  5.                 BYTE count                        /* Number of sectors to write (1..255) */
  6.         )
  7.         {
  8.                 int Status;
  9.                 if( !count )
  10.                 {   
  11.                         return RES_PARERR;  /* count不能等於0,否則返回引數錯誤 */
  12.                 }
  13.                 switch (drv)
  14.                 {
  15.                         case 0:
  16.                             if(count==1)            /* 1個sector的寫操作 */      
  17.                             {   
  18.                                         Status = MSD0_WriteSingleBlock( sector , (uint8_t *)(&buff[0]) );
  19.                                         if(Status == 0){
  20.                                                 return RES_OK;
  21.                                         }else{
  22.                                                 return RES_ERROR;
  23.                                         }
  24.                             }                                                
  25.                             else                    /* 多個sector的寫操作 */   
  26.                             {  
  27.                                         Status = MSD0_WriteMultiBlock( sector , (uint8_t *)(&buff[0]) , count );
  28.                                         if(Status == 0){
  29.                                                 return RES_OK;
  30.                                         }else{
  31.                                                 return RES_ERROR;
  32.                                         }   
  33.                             }                                                
  34.                         case 1:
  35.                             if(count==1)            /* 1個sector的寫操作 */      
  36.                             {  
  37.                                         return RES_OK;
  38.                             }                                                
  39.                             else                    /* 多個sector的寫操作 */   
  40.                             {  
  41.                                         return RES_OK;
  42.                             }                                                
  43.                         default:return RES_ERROR;
  44.                 }
  45.         }
複製程式碼 MSD0_WriteSingleBlock()和MSD0_WriteMultiBlock()函式都是SD卡操作的底層函式,我們在SPI_MSD0_Driver.c檔案中實現。
    7、實現disk_ioctl()函式
        該函式在磁碟格式化、獲取檔案系統資訊等操作時會被呼叫。
        實現後的程式如下:
  1. DRESULT disk_ioctl (
  2.                 BYTE drv,                /* Physical drive nmuber (0..) */
  3.                 BYTE ctrl,                /* Control code */
  4.                 void *buff                /* Buffer to send/receive control data */
  5.         )
  6.         {
  7.                 if (drv==0)
  8.                 {   
  9.                         MSD0_GetCardInfo(&SD0_CardInfo);
  10.                         switch (ctrl)
  11.                         {
  12.                                 case CTRL_SYNC :
  13.                                         return RES_OK;
  14.                                 case GET_SECTOR_COUNT :
  15.                                         *(DWORD*)buff = SD0_CardInfo.Capacity/SD0_CardInfo.BlockSize;
  16.                                 return RES_OK;
  17.                                 case GET_BLOCK_SIZE :
  18.                                         *(WORD*)buff = SD0_CardInfo.BlockSize;
  19.                                 return RES_OK;        
  20.                                 case CTRL_POWER :
  21.                                         break;
  22.                                 case CTRL_LOCK :
  23.                                         break;
  24.                                 case CTRL_EJECT :
  25.                                         break;
  26.                         /* MMC/SDC command */
  27.                                 case MMC_GET_TYPE :
  28.                                         break;
  29.                                 case MMC_GET_CSD :
  30.                                         break;
  31.                                 case MMC_GET_CID :
  32.                                         break;
  33.                                 case MMC_GET_OCR :
  34.                                         break;
  35.                                 case MMC_GET_SDSTAT :
  36.                                         break;        
  37.                         }
  38.             }else if(drv==1){
  39.                         switch (ctrl)
  40.                         {
  41.                                 case CTRL_SYNC :
  42.                                         return RES_OK;
  43.                                 case GET_SECTOR_COUNT :
  44.                                 return RES_OK;
  45.                                 case GET_SECTOR_SIZE :
  46.                                         return RES_OK;
  47.                                 case GET_BLOCK_SIZE :
  48.                                 return RES_OK;        
  49.                                 case CTRL_POWER :
  50.                                         break;
  51.                                 case CTRL_LOCK :
  52.                                         break;
  53.                                 case CTRL_EJECT :
  54.                                         break;
  55.                         /* MMC/SDC command */
  56.                                 case MMC_GET_TYPE :
  57.                                         break;
  58.                                 case MMC_GET_CSD :
  59.                                         break;
  60.                                 case MMC_GET_CID :
  61.                                         break;
  62.                                 case MMC_GET_OCR :
  63.                                         break;
  64.                                 case MMC_GET_SDSTAT :
  65.                                         break;        
  66.                         }         
  67.                 }
  68.                 else{                                 
  69.                         return RES_PARERR;  
  70.                 }
  71.                 return RES_PARERR;
  72.         }
複製程式碼 MSD0_GetCardInfo()函式也在SPI_MSD0_Driver.c檔案中實現,其中SD0_CardInfo為PMSD_CARDINFO型別的全域性變數,它在SPI_MSD0_Driver.h檔案中被定義。
    8、到此diskio.c這個檔案中的所有函式就已經實現,下一步就是實現SPI_MSD0_Driver.c檔案中的相關函式,SPI_MSD0_Driver.c檔案可以在網上下載,參考的程式比較多,本工程使用的這個檔案也是在網上下載並進行一定的修改過的。本檔案中函式的實現方式可以參考原始碼。
五、檔案系統測試
    1、測試寫檔案
    測試程式碼如下:
  1. //寫檔案測試
  2.         printf("write file test......\n\r");
  3.         res = f_open(&fdst, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
  4.         if(res != FR_OK){
  5.                 printf("open file error : %d\n\r",res);
  6.         }else{
  7.                 res = f_write(&fdst, textFileBuffer, sizeof(textFileBuffer), &bw);               /* Write it to the dst file */
  8.                 if(res == FR_OK){
  9.                         printf("write data ok! %d\n\r",bw);
  10.                 }else{
  11.                         printf("write data error : %d\n\r",res);
  12.                 }
  13.                 /*close file */
  14.                 f_close(&fdst);
  15.         }
複製程式碼 注意:成功開啟檔案後一定要呼叫f_close()函式,否則資料無法寫入SD卡中。
    2、測試讀檔案
  1. //讀檔案測試
  2.         printf("read file test......\n\r");
  3.         res = f_open(&fsrc, "0:/test.txt", FA_OPEN_EXISTING | FA_READ);
  4.         if(res != FR_OK){
  5.                 printf("open file error : %d\n\r",res);
  6.         }else{
  7.                 res = f_read(&fsrc, buffer, sizeof(textFileBuffer), &br);     /* Read a chunk of src file */
  8.                 if(res==FR_OK){
  9.                         printf("read data num : %d\n\r",br);
  10.                         printf("%s\n\r",buffer);
  11.                 }else{
  12.                         printf("read file error : %d\n\r",res);
  13.                 }
  14.                 /*close file */
  15.                 f_close(&fsrc);
  16.         }
複製程式碼 3、測試結果
    測試結果如圖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所示。我們發現增加這個檔案後代碼量增加了很多,主要原因是這個檔案是我們支援中文所需要的中文編碼檔案。
圖4
圖3
圖5
圖4
圖3
圖5
    3、再次下載到板子中執行,發現中文的長檔名顯示正常了。如圖6所示。
圖6.jpg
    4、若不需要支援中文長檔名而只支援英文長檔名則可以將巨集定義做如下修改:
    #define        _CODE_PAGE        437
    #define        _USE_LFN        1                /* 0 to 3 */
    同時將ccsbcs.c新增到工程目錄中,這樣就可以減小很多大程式碼量。將程式下載板子後再次執行結果如圖7所示,可以看到可以支援英文的長檔名。
圖7.jpg
七、原始檔下載