STM32 FatFS 移植經驗分享


STM32中 FatFS移植

http://www.amobbs.com/forum.php?mod=viewthread&tid=5464257&highlight=STM32%2BFatFS%2B%E7%A7%BB%E6%A4%8D%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB
前言與廢話
        做項目時網找資料,不會的東西上網查閱一下多半可以解決,一些尚未解決的問題也會有所啟發。最近由於項目的需要,仔細閱讀了SD卡相關內容,順藤摸瓜學習FatFS。網上關於SD卡和FatFS的內容非常的多,重復的部分我就不介紹了,我把移植和使用部分的經驗和大家分享一下。
剛開始的時候,我找來一些現成的代碼研究一下,不用說看的是一頭霧水。看FatFS示例代碼,也不知如何移植。最后還是下定決心,慢慢的閱讀FatFS的相關文檔和范例代碼,對於移植部分一點一點的研究,相信一定會有所收獲。
一、硬件准備
        開始移植之前,你必須要有一塊SD卡。從形狀上來說,有普通的SD卡,有很小的microSD卡,microSD卡就是手機中長見的TF卡。購買microSD卡的時候,往往會附帶一個SD卡套,那么小個頭的microSD卡就變成了普通的SD卡,接口都是一樣的。
        但是還是您注意了,建議大家購買2G以下的SD卡(如果可以的話,買個128M的SD卡就可以達到實驗的效果,價格也非常便宜)。剛開始移植的時候,我使用了4G的SD卡,但是發現程序無法完成SD卡的初始化。查閱網上相關的資料,發現SD卡技術已2G作為分界線,大於或者等於4G的卡屬於高速SD卡,和小於或者等於4G的SD卡略有區別。
二、軟件准備
        在進行移植之前,先編寫一些最簡單的STM32程序。在調試之前,我都會完成USART的初始化和發送函數,通過串口把STM32的運行狀態打印出來,這樣配合Jlink硬件調試,可以很快的找到錯誤。由於SD卡可以使用SPI進行讀寫操作,所以還需要完成SPI的初始化工作。
        先來說一下USART的操作,我個人比較喜歡使用系統的printf函數,所以還需要引入stdio頭文件。在IAR中必須設定option的某個選項。如下圖所示。

<IGNORE_JS_OP> Option.JPG


        除了完成USART的初始化工作以外,還需要重寫fputc函數,具體的代碼如下。

  1. int fputc(int ch, FILE * f)
  2. {
  3.   USART_SendData(USART1, (uint8_t)ch);
  4.   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );
  5.   return ch;
  6. }
復制代碼

然后說一下SPI的初始化工作。閱讀網上的代碼,發現STM32 V2的庫函數和V3函數中,關於SPI端口初始化的部分還是有些出入的。
        V2庫中,把SCK,MOSI,MISO全部設置為復用輸出。而V3庫中,SCK,MOSI設置為復用輸出,而MISO設置為浮動輸入。在SD的SPI接口中,SCK,MOSI和MOSI,甚至包括CS都使用了上拉電阻。
您需要注意一下幾點
1.         沒有上拉電阻時 MISO應該如何設置
由於我的開發板中沒有使用上拉電阻,若設定MISO為浮動輸入的話,或許會有某些問題,由於SD卡的輸出端口驅動能力很弱,很有可能就接收不到返回數據,事實也正是如此。所以MISO最后被我甚至成了上拉輸入模式,具體的代碼如下。(所以還是要相信過來人的電路圖,老實的加一個上拉電阻。)
2.        SPI的模式應該如何選擇
           SPI的速度不能太快,在初始化時時鍾設為400k以下為宜。
3.        SPI的速度應該如何選擇
           SD卡使用SPI的模式0和模式3,這兩個模式是等價的。

  1. void SPI1_Config(void)
  2. {
  3.   //使能APB2上相關時鍾
  4.   //使能SPI時鍾,使能GPIOA時鍾
  5.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 |\
  6.                          RCC_APB2Periph_GPIOA ,ENABLE );
  7.   //定義一個GPIO結構體
  8.   GPIO_InitTypeDef  GPIO_InitStructure;
  9.   //SPI SCK MOSI
  10.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5  |  GPIO_Pin_7;
  11.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  12.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//復用推挽輸出
  13.   GPIO_Init(GPIOA, &GPIO_InitStructure);
  14.   //SPI MISO
  15.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  16.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  17.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉輸入
  18.   GPIO_Init(GPIOA, &GPIO_InitStructure);
  19.   
  20.   //自定義SPI結構體
  21.   SPI_InitTypeDef SPI_InitStructure;
  22.   //雙線雙向全雙工
  23.   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  24. //主機模式
  25.   SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  26.   //8位幀結構
  27.   SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  28.   //時鍾空閑時為低
  29.   SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;        
  30.   //第一個上升沿捕獲數據。模式,0
  31.   SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;      
  32.   //MSS 端口軟件控制,實際沒有使用
  33.   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;         
  34.   //SPI時鍾72Mhz / 256 = 281.25K  < 400K
  35.   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
  36.   //數據傳輸高位在前
  37.   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  38.   SPI_InitStructure.SPI_CRCPolynomial = 7;//
  39.   //初始化SPI1
  40.   SPI_Init(SPI1, &SPI_InitStructure);
  41.   //使能SPI1
  42.   SPI_Cmd(SPI1, ENABLE);
  43. }
復制代碼

除了初始化操作以外,還需要一個SPI發送函數和一個SPI接收函數。由於SPI是同步通信方式,所以SPI接收函數,實際上只需要發送0xFF就可以,具體的代碼如下。

  1. uint8_t SPI1_SendByte(uint8_t byte)
  2. {
  3.   //等待發送緩沖寄存器為空
  4.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
  5.   //發送數據
  6.   SPI_I2S_SendData(SPI1, byte);               
  7.   //等待接收緩沖寄存器為非空
  8.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
  9.   //返回從SPI通信中接收到的數據
  10.   return SPI_I2S_ReceiveData(SPI1);
  11. }
  12. uint8_t SPI1_ReceiveByte()
  13. {
  14.   //等待發送緩沖寄存器為空
  15.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
  16.   //發送數據,通過發送xff,獲得返回數據
  17.   SPI_I2S_SendData(SPI1, 0xff);               
  18.   //等待接收緩沖寄存器為非空
  19.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
  20.   //返回從SPI通信中接收到的數據
  21.   return SPI_I2S_ReceiveData(SPI1);
  22. }
復制代碼

三、移植前的“心靈”准備
        “移植”實際上就是研究他人的代碼,你必須敏銳的看清代碼的核心內容,了解你必須要做什么,哪一些可以以后再實現。在移植的初步階段,我建議您使用最簡單的方法完成某些內容,而不是去重視代碼效率。例如,在移植過程中需要使用到延時函數,可以使用軟件延時,可以配合Systick延時,甚至可以使用uCOS的延時函數。但是我建議您在面對選擇的時候選擇最簡單的函數——軟件延時,雖然它不准確效率也不高,但是您可以把更多的精力投入到其他重要的內容中去,你會覺得移植是那么簡單,而延時函數的效率提高是錦上添花的事情。
        例如您在移植之前會查看FatFS中關於STM32的移植范例。在該范例中,有關於SD卡插入,SD卡上電控制,SD卡寫保護檢測的函數。除了這些函數之外,代碼中通過宏定義的方法,可以選擇使用DMA來傳送SPI數據,初始化SD卡時使用低速SPI,讀寫塊的時候使用高速SPI,雖然這些改動讓您覺得代碼強大而高效,但是對您的移植一定用處都沒有。您需要從最簡單的generic開始,如果從這個文件開始,您會覺得移植是那么的簡單,僅需要十幾分鍾。我相信您看完文章就會了,其實非常的簡單。

四、移植開始——從generic開始
        您所需要操作的只是mmcbb文件,里面主要包括SD卡的初始化、讀塊和寫塊函數。其實修改僅需要三步。
        第一步,修改宏定義,添加合適的頭文件,添加延時函數
        第二步,修改多字節發送函數
        第三步,修改多字節接收函數
        下面我通過原代碼和移植代碼的比較,來說明這個移植問題。
4.1  修改頭文件和宏定義
原代碼如下

  1. /* Include device specific declareation file here */
  2. #include <device.h>
  3. /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */
  4. #define        INIT_PORT()        { init_port(); }        
  5. /* Delay n microseconds */
  6. #define DLY_US(n)        { dly_us(n); }               
  7. #define CS_H()                bset(P0)           /* Set MMC CS "high" */
  8. #define CS_L()                 bclr(P0)            /* Set MMC CS "low" */
  9. #define CK_H()                bset(P1)           /* Set MMC SCLK "high" */
  10. #define  CK_L()                bclr(P1)            /* Set MMC SCLK "low" */
  11. #define DI_H()                 bset(P2)           /* Set MMC DI "high" */
  12. #define DI_L()                 bclr(P2)            /* Set MMC DI "low" */
  13. #define DO                     btest(P3)          /* Get MMC DO value (high:true, low:false) */
  14. /* Socket: Card is inserted (yes:true, no:false, default:true) */
  15. #define        INS                        (1)                        
  16. /* Socket: Card is write protected (yes:true, no:false, default:false) */.
  17. #define        WP                        (0)                        
復制代碼

==========修改后的代碼如下==========

  1. /* Include device specific declareation file here */
  2. #include "stm32f10x.h"
  3. #include "spi1.h"
  4. #include <stdio.h>
  5. /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */
  6. #define        INIT_PORT()        { init_port(); }        
  7. /* Set MMC CS "high" */
  8. #define        CS_H()                  GPIO_SetBits(GPIOE,GPIO_Pin_7)
  9. /* Set MMC CS "low" */
  10. #define CS_L()                  GPIO_ResetBits(GPIOE,GPIO_Pin_7)
  11. /* Delay n microseconds */
  12. #define DLY_US(n)        { dly_us(n); }               
  13. /* Socket: Card is inserted (yes:true, no:false, default:true) */
  14. #define        INS                          (1)               
  15. /* Socket: Card is write protected (yes:true, no:false, default:false) */
  16. #define        WP                          (0)        
復制代碼

使用STM32時需要包含STM3210x頭文件;spi1.h包括了spi相關操作函數。修改了CS操作的宏定義。
        除了一個宏定義外,還需要些一個延時函數和一個初始化函數。延時函數使用軟件延時,很不精確,但是可以說明問題。初始化函數,只是配置CS端口,而SPI初始化工作在調用fatfs API函數時已完成初始化。(若是SPI初始化也完成了CS的操作,init_port()可以省略)

  1. //初始化端口
  2. void init_port()
  3. {
  4.   //初始化時鍾GPIOE
  5.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE ,ENABLE );
  6.   //配置GPIOE.7
  7.   //定義一個GPIO結構體
  8.   GPIO_InitTypeDef  GPIO_InitStructure;
  9.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  10.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  11.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  12.   GPIO_Init(GPIOE, &GPIO_InitStructure);
  13. }
  14. //軟件演示函數
  15. void dly_us(uint16_t n)
  16. {
  17.   for( ; n > 0 ; n--)
  18.     for(uint8_t i = 100 ; i > 0 ; i--);
  19. }
復制代碼

4.2 多字節發送函數
原代碼和修改后的代碼如下。

  1. static
  2. void xmit_mmc (
  3.         const BYTE* buff,        /* Data to be sent */
  4.         UINT bc                                /* Number of bytes to send */
  5. )
  6. {
  7.         BYTE d;
  8.         do {
  9.                 d = *buff++;        /* Get a byte to be sent */
  10.                 if (d & 0x80) DI_H(); else DI_L();        /* bit7 */
  11.                 CK_H(); CK_L();
  12.                 if (d & 0x40) DI_H(); else DI_L();        /* bit6 */
  13.                 CK_H(); CK_L();
  14.                 if (d & 0x20) DI_H(); else DI_L();        /* bit5 */
  15.                 CK_H(); CK_L();
  16.                 if (d & 0x10) DI_H(); else DI_L();        /* bit4 */
  17.                 CK_H(); CK_L();
  18.                 if (d & 0x08) DI_H(); else DI_L();        /* bit3 */
  19.                 CK_H(); CK_L();
  20.                 if (d & 0x04) DI_H(); else DI_L();        /* bit2 */
  21.                 CK_H(); CK_L();
  22.                 if (d & 0x02) DI_H(); else DI_L();        /* bit1 */
  23.                 CK_H(); CK_L();
  24.                 if (d & 0x01) DI_H(); else DI_L();        /* bit0 */
  25.                 CK_H(); CK_L();
  26.         } while (--bc);
  27. }
復制代碼

==========修改后的代碼===========

  1. static void xmit_mmc (const BYTE* buff,        UINT bc)
  2. {
  3.         BYTE d;
  4.         do {
  5.     /* Get a byte to be sent */
  6.         d = *buff++;        
  7.     //通過SPI發送
  8.     SPI1_SendByte(d);
  9.         } while (--bc);
  10. }
復制代碼

4.3 多字節接收函數
原代碼和修改后的代碼如下。

  1. static
  2. void rcvr_mmc (
  3.         BYTE *buff,        /* Pointer to read buffer */
  4.         UINT bc                /* Number of bytes to receive */
  5. )
  6. {
  7.         BYTE r;
  8.         DI_H();         /* Send 0xFF */
  9.         do {
  10.                 r = 0;   if (DO) r++;        /* bit7 */
  11.                 CK_H(); CK_L();
  12.                 r <<= 1; if (DO) r++;        /* bit6 */
  13.                 CK_H(); CK_L();
  14.                 r <<= 1; if (DO) r++;        /* bit5 */
  15.                 CK_H(); CK_L();
  16.                 r <<= 1; if (DO) r++;        /* bit4 */
  17.                 CK_H(); CK_L();
  18.                 r <<= 1; if (DO) r++;        /* bit3 */
  19.                 CK_H(); CK_L();
  20.                 r <<= 1; if (DO) r++;        /* bit2 */
  21.                 CK_H(); CK_L();
  22.                 r <<= 1; if (DO) r++;        /* bit1 */
  23.                 CK_H(); CK_L();
  24.                 r <<= 1; if (DO) r++;        /* bit0 */
  25.                 CK_H(); CK_L();
  26.                 *buff++ = r;                        /* Store a received byte */
  27.         } while (--bc);
  28. }
復制代碼

===========修改后的函數===========

  1. static void rcvr_mmc ( BYTE *buff,        UINT bc        )
  2. {
  3.         BYTE r;
  4.         do {
  5.     //重新賦值
  6.                 r = 0;   
  7.     //通過SPI獲得數據
  8.     r = SPI1_ReceiveByte();
  9.     /* Store a received byte */
  10.                 *buff++ = r;               
  11.         } while (--bc);
  12. }
復制代碼

在這里多說一句,源代碼中
DI_H();         /* Send 0xFF */
        作者的本意應該是把IO設為輸入狀態,51系列單片機就是這么操作的,但是寫代碼注釋寫成了發送0xFF,其實並不需要發送0xFF。
到這里就完成了fatfs的STM32移植工作,雖然只有簡單的三步,但是卻花了我整整三天的時間。我想您看了這樣的描述,不知道能否在10分鍾之內完成修改。

五 FatFS初步使用
        接下來就是使用FatFS了,看了這個函數我找回了當初初學C語言的感覺,打開一個文件,然后讀一些數據,然后創建另一個文件,在文件中寫一些數據,最后關閉文件。

  1. int main(void)
  2. {
  3.   //初始化Systick
  4.   RCC_Config();
  5.   //初始化串口
  6.   USART1_Config();
  7.   //初始化SPI1
  8.   SPI1_Config();
  9.   printf("start to read file\n");
  10.   /* Register volume work area (never fails) */
  11.   f_mount(0, &fatfs);               
  12.   printf("\nOpen a test file (test.txt).\n");
  13. rc = f_open(&fil, "test.txt", FA_READ);
  14. if (rc) die(rc);
  15.   
  16.         printf("\nType the file content.\n");
  17.         for (;;) {
  18.                 rc = f_read(&fil, buff, sizeof(buff), &br);        /* Read a chunk of file */
  19.                 if (rc || !br) break;                                    /* Error or end of file */
  20.                 for (i = 0; i < br; i++)                                /* Type the data */
  21.                         putchar(buff[i]);
  22.         }
  23.         if (rc) die(rc);
  24.         printf("\nClose the file.\n");
  25.         rc = f_close(&fil);
  26.         if (rc) die(rc);
  27.         printf("\nCreate a new file (hello.txt).\n");
  28.         rc = f_open(&fil, "HELLO.TXT", FA_WRITE | FA_CREATE_ALWAYS);
  29.         if (rc) die(rc);
  30.         printf("\nWrite a text data. (Hello world!)\n");
  31.         rc = f_write(&fil, "Hello world!\r\n", 14, &bw);
  32.         if (rc) die(rc);
  33.         printf("%u bytes written.\n", bw);
  34.         printf("\nClose the file.\n");
  35.         rc = f_close(&fil);
  36.         if (rc) die(rc);
  37.   
  38.   while (1)
  39.   {
  40.   }
  41. }
復制代碼

如果出現失敗的話,程序會進入die函數,該函數會輸出錯誤代碼,並進入一個無限循環。
通過串口的輸出結果如下所示。

<IGNORE_JS_OP> 接口 串口輸出.JPG


我再把SD卡從目標板上拿下,查看文件中的內容。的確hello.txt文件中寫了hello world字符(應該還有回車和換行符)。

<IGNORE_JS_OP> 結果 文件輸出.JPG


六         我的錯誤經歷
        再快要移植成功的時候,我一運行程序,程序就進入die函數,並顯示錯誤1,提示應該是SD卡操作錯誤。我通過斷點調試和printf輸出,把問題定位到發送cmd0處,返回的結果為一個非法的命令。我從CMD17命令入手,查閱了網上各位大神的經驗,有說是發送命令的延時時候不夠。但是照着這個修改之后問題存在,無奈之下在電腦面前苦苦思考。直到我的女朋友,在愚人節那天“玩”我,當時我正在仔細的檢查代碼,她和我說某某老師要找我並提醒我一定要拿手機,我收拾起我凌亂的思緒,立刻跑過去時,她卻打電話給我說愚人節快樂。我很無奈但也有點開心的回到電腦面前,一動鼠標就看到了某些異樣。
#define CMD17        (7)                        /* READ_SINGLE_BLOCK */
我把CMD17命令的宏定義寫成了7,而實際上是17。就這么一個簡答的錯誤,花費了我一天的時間。也非常感謝女朋友的這個愚人節玩笑,沒有她或許就無法發現這個問題。
        一個尚未解決的問題!
        還有一個比較特殊的地方請聰明的你注意一下,在generic中man函數中,把這些定義在了main函數里面。這些定義如下

  1.           FRESULT rc;                                /* Result code */
  2.         FATFS fatfs;                                /* File system object */
  3.         FIL fil;                                                /* File object */
  4.         DIR dir;                                        /* Directory object */
  5.         FILINFO fno;                                /* File information object */
  6.         UINT bw, br, i;
  7.         BYTE buff[128];
復制代碼

如果把這些變量的聲明都放在main函數中的話,系統將會運行到一個異常中,如下圖所示。這個錯誤會讓人非常的沮喪。雖然我沒有找到原因,但是我找到了解決的方法。把這些變量的聲明放在函數之外
<IGNORE_JS_OP>異常.JPG


IAR版本 V5.5

<IGNORE_JS_OP> STM32 FatFS.zip (563.81 KB, 下載次數: 1758)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM