如何利用tm32cubemx對(duì)fatfs文件系統(tǒng)進(jìn)行移植
掃描二維碼
隨時(shí)隨地手機(jī)看文章
今天要給大家分享的是使用FatFs這個(gè)庫(kù)來(lái)讀寫(xiě)SD卡上面的文件。工程的初始化函數(shù)和FatFs都是通過(guò)STM32CubeMX配置生成的,不需要我們手動(dòng)添加庫(kù)。
今天分享的內(nèi)容和我的上一篇帖子SDIO讀取SD卡的扇區(qū)有關(guān),最好掌握了SDIO讀取SD卡扇區(qū)的基礎(chǔ)之后再來(lái)看這一篇帖子。
寫(xiě)在前面的話
上一次發(fā)表了關(guān)于SDIO讀取SD卡的一篇開(kāi)發(fā)分享的帖子,今天呢就更進(jìn)一步使用了FatFs文件系統(tǒng)。同樣我們完全使用ST公司提供的STM32CubeMX軟件和HAL庫(kù)來(lái)進(jìn)行開(kāi)發(fā)。如果喜歡我的帖子請(qǐng)多多回復(fù)我會(huì)努力更新的。
我使用的工具
開(kāi)發(fā)平臺(tái):正點(diǎn)原子探索者STM32F407開(kāi)發(fā)板
硬件:使用了NUCLEO-F446RE開(kāi)發(fā)板的ST-Link作為調(diào)試器、SD卡、數(shù)據(jù)線、開(kāi)發(fā)板的電源適配器、DELL一體機(jī)
軟件:STM32CubeMX、Keil V5、串口助手
學(xué)習(xí)的知識(shí)點(diǎn)
1、使用STM32CubeMX配置SDIO
2、在Keil中初始化SDIO
3、修改HEAP內(nèi)存塊的大小
4、malloc函數(shù)的使用
5、如何使用FatFs提供的一些庫(kù)函數(shù)
共享的資源
完整的工程文件FatFs.zip
STM32Cube中FatFs的中文幫助文檔
準(zhǔn)備工作
參考SDIO讀取SD卡扇區(qū)這篇帖子,這里不再贅述。
目錄
一、在STM32CubeMX中配置好我們的工程
1、配置FatFs
2、配置SDIO
3、配置SYS
4、配置USART
二、在Keil中做初始化
1、調(diào)用BSP_SD_Init()函數(shù)
2、修改MX_FATFS_Init(void)函數(shù)
三、修改啟動(dòng)文件
1、malloc函數(shù)的介紹
2、FatFs對(duì)malloc函數(shù)的調(diào)用
3、改變heap的內(nèi)存大小
四、使用FatFs
1、定義一些變量
2、使用f_open
3、使用f_read
4、使用f_lseek
5、使用f_write
6、使用f_close
7、再次讀取文件內(nèi)容
五、上電測(cè)試
一、在STM32CubeMX中配置好我們的工程
1、配置FatFs
在STM32CubeMX引腳配置中,找到Configuration->MiddleWares->FatFs,勾選下面的SD Card。
2、配置SDIO
STM32CubeMX引腳配置中Peripherals->SDIO,下拉框里面選擇SD 4 bit Wide bus。
3、配置SYS
STM32CubeMX引腳配置中Peripherals->SYS,下拉框里面選擇SWD and Asynchronous Trace。
4、配置USART
STM32CubeMX引腳配置中Peripherals->USART,下拉框里面選擇Asynchronous。
下面是我的工程配置界面的截圖。
二、在Keil中做初始化
1、調(diào)用BSP_SD_Init()函數(shù)
在main函數(shù)開(kāi)頭的部分調(diào)用了各種初始化函數(shù),這些函數(shù)完成了硬件的初始化工作。在我的前一篇帖子 SDIO讀取SD卡扇區(qū) 中,SDIO的初始化也是要添加一點(diǎn)代碼才可以正常的工作。這里也是一樣,不過(guò)更加的簡(jiǎn)單。我們只需在MX_FATFS_Init();之前調(diào)用BSP_SD_Init();函數(shù)就可以了,我在下面貼出了這段初始化的代碼。
這里介紹一下BSP_SD_Init()函數(shù)。BSP的意思是板級(jí)支持包的意思,也就是這個(gè)函數(shù)是專門(mén)針對(duì)某一類芯片支持的。這個(gè)函數(shù)在文件bsp_driver_sd.c文件中,在這個(gè)文件里面還有很多的函數(shù),這些函數(shù)完成了對(duì)讀寫(xiě)SD卡、查詢SD卡狀態(tài)、初始化SD卡等一系列操作。這些函數(shù)原先都是要用戶來(lái)編寫(xiě),提供給FatFs調(diào)用作為驅(qū)動(dòng)支持的。但是這里幫我們都定義好了只需要調(diào)用即可,如果你要用FatFs來(lái)連接自己的設(shè)備的話,那你就需要自己定義這些底層的IO驅(qū)動(dòng)函數(shù)了。
關(guān)于FatFs底層驅(qū)動(dòng)支持和驅(qū)動(dòng)架構(gòu)的關(guān)系請(qǐng)查看附件UM1721_DM00105259_CN.pdf,這里面有ST官方對(duì)這個(gè)的詳細(xì)說(shuō)明。
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SDIO_SD_Init();
MX_USART1_UART_Init();
BSP_SD_Init();
MX_FATFS_Init();
復(fù)制代碼
2、修改MX_FATFS_Init(void)函數(shù)
除了上面對(duì)初始化函數(shù)的調(diào)用以外,我們還需要一些別的修改。在這里,我希望調(diào)用BSP_SD_Init()初始化好了SD卡之后,在調(diào)用MX_FATFS_Init()初始化的時(shí)候就掛載SD卡。所以我們就在FatFs的初始化函數(shù)里面用戶代碼的部分加入了我們的f_mount函數(shù)。下面貼出MX_FATFS_Init()函數(shù)的代碼,這個(gè)函數(shù)位于文件Application/User->fatfs.c中。
我還設(shè)置了一個(gè)簡(jiǎn)單的條件語(yǔ)句,如果掛載SD卡成功的話就發(fā)送一個(gè)成功的消息。
void MX_FATFS_Init(void)
{
/*## FatFS: Link the SD driver ###########################*/
retSD = FATFS_LinkDriver(&SD_Driver, SD_Path);
/* USER CODE BEGIN Init */
/* additional user code for init */
if(f_mount(&SDCard, SD_Path,0) == 0)
{
HAL_UART_Transmit(&huart1, (uint8_t *)"Success!n", 9, 500);
}
/* USER CODE END Init */
}
復(fù)制代碼
OK,到了這里我們對(duì)文件的修改就基本上完成了,但是僅僅如此只能讓SD卡正常驅(qū)動(dòng)和掛載,并不能夠打開(kāi)文件和讀寫(xiě)操作。接下來(lái)的內(nèi)容就是來(lái)解決這個(gè)問(wèn)題的。
三、修改啟動(dòng)文件
1、malloc函數(shù)的介紹
原型
extern void *malloc(unsigned int num_bytes);
頭文件
#include
函數(shù)聲明
void *malloc(size_t size);
備注:void* 表示未確定類型的指針,void *可以指向任何類型的數(shù)據(jù),更明確的說(shuō)是指申請(qǐng)內(nèi)存空間時(shí)還不知道用戶是用這段空間來(lái)存儲(chǔ)什么類型的數(shù)據(jù)(比如是char還是int或者其他數(shù)據(jù)類型)。
功能
分配長(zhǎng)度為num_bytes字節(jié)的內(nèi)存塊
返回值
如果分配成功則返回指向被分配內(nèi)存的指針(此存儲(chǔ)區(qū)中的初始值不確定),否則返回空指針NULL。當(dāng)內(nèi)存不再使用時(shí),應(yīng)使用free()函數(shù)將內(nèi)存塊釋放。函數(shù)返回的指針一定要適當(dāng)對(duì)齊,使其可以用于任何數(shù)據(jù)對(duì)象。
以上是百度百科對(duì)malloc函數(shù)的一些簡(jiǎn)介,在Keil中要使用這個(gè)函數(shù)需要包含stdlib.h頭文件。同時(shí)在Keil的編譯器設(shè)置中要選中microlib選項(xiàng),不過(guò)使用STM32CubeMX生成的工程默認(rèn)都選中了這一選項(xiàng)。這個(gè)函數(shù)是放在標(biāo)準(zhǔn)庫(kù)里面的,所以不能找到這個(gè)的定義代碼在哪里,編譯的時(shí)候鏈接所屬的庫(kù)就可以了。
2、FatFs對(duì)malloc函數(shù)的調(diào)用
為了討論FatFs和malloc函數(shù)的關(guān)系,我們要先打開(kāi)MiddleWares/FatFs->syscall.c文件。在這個(gè)文件里面,我們可以看到有一些函數(shù)是分配內(nèi)存的函數(shù)。滾動(dòng)頁(yè)面到最下面,可以看到最后兩個(gè)函數(shù)都和分配內(nèi)存有關(guān)。這些函數(shù)都調(diào)用了malloc函數(shù)來(lái)動(dòng)態(tài)分配內(nèi)存,在ST的手冊(cè)中沒(méi)有提到這個(gè)malloc。但是如果沒(méi)有這個(gè)系統(tǒng)提供的malloc函數(shù)我們就需要自己來(lái)定義一個(gè)動(dòng)態(tài)內(nèi)存分配函數(shù),同時(shí)把原來(lái)的malloc調(diào)用替換為我們提供的函數(shù)接口。
這樣我們就知道了FatFs對(duì)malloc函數(shù)有調(diào)用。但是僅僅如此嗎?不是這么簡(jiǎn)單。FatFs默認(rèn)是開(kāi)啟了對(duì)長(zhǎng)文件名支持的,這樣就需要很多的內(nèi)存來(lái)存儲(chǔ)文件名這些信息。如果malloc內(nèi)存初始化的時(shí)候內(nèi)存設(shè)置的比較小的話,就不能成功分配內(nèi)存了不是嗎?這樣一來(lái)調(diào)用FatFs的f_open函數(shù)的時(shí)候就會(huì)返回17,查詢這個(gè)枚舉值的定義就知道是內(nèi)存分配不足的錯(cuò)誤。感興趣的話可以不進(jìn)行下一步修改heap的大小來(lái)調(diào)用f_open同時(shí)把返回值發(fā)送到串口來(lái)看一看。
3、改變heap的內(nèi)存大小
malloc函數(shù)的內(nèi)存是從堆(heap)里面分配的,如果使用了對(duì)長(zhǎng)文件名的支持我們就需要malloc為FatFs提供內(nèi)存分配的支持。在使用malloc之前會(huì)對(duì)malloc的內(nèi)存池進(jìn)行初始化,而這個(gè)初始化的操作是由我們的庫(kù)提供的啟動(dòng)代碼完成的。
下面貼出匯編語(yǔ)言的啟動(dòng)代碼的一部分。
Stack_Size EQU 0x00000400
復(fù)制代碼
這一段代碼是初始化內(nèi)存池的匯編語(yǔ)言代碼,在這里我把Heap_Size設(shè)置為0x00000800,也就是2KB。默認(rèn)的數(shù)值是512Bytes,這樣的話分配給我們的FatFs使用會(huì)不足,分配內(nèi)存會(huì)失敗。只有這樣子修改了之后,后面的f_open這些函數(shù)才可以正常使用。
進(jìn)行了以上的步驟之后,我們就可以輕松愉快地使用FatFs提供的各種函數(shù)了。
四、使用FatFs
1、定義一些變量
在我們代碼開(kāi)始的部分,先定義一些變量供我們使用。這里選擇幾個(gè)來(lái)解析一下。
第一個(gè)FIL file;這個(gè)變量是文件的結(jié)構(gòu)體變量,記錄了我們打開(kāi)的文件的信息。使用f_open等函數(shù)的時(shí)候都要用到。
第二個(gè)Words變量是一個(gè)字符串指針,我用這個(gè)指針來(lái)存儲(chǔ)讀取的字符信息。這里我們就使用了malloc函數(shù)來(lái)分配內(nèi)存,我們通過(guò)修改啟動(dòng)代碼提供了更多的堆內(nèi)存所以這里就可以分配一些給我們使用。
第三個(gè)是Path這個(gè)字符串,這里保存的是文件所在的路徑。關(guān)于文件路徑的寫(xiě)法,請(qǐng)參考FatFs的官方文檔網(wǎng)站。
FIL file;
復(fù)制代碼
2、使用f_open
關(guān)于f_open函數(shù)的具體用法和詳細(xì)說(shuō)明,參見(jiàn)FatFs官方文檔網(wǎng)站。這里可以看看我在下方貼出的代碼作為參考來(lái)使用。
3、使用f_read
關(guān)于f_read沒(méi)有特別的說(shuō)明,詳細(xì)的用法去看看FatFs官方文檔網(wǎng)站。這里只想說(shuō)一下第三個(gè)和第四個(gè)參數(shù)。這里的第三個(gè)參數(shù)是給定的要讀取的字節(jié)數(shù),而第四個(gè)參數(shù)是最后讀取了的字節(jié)數(shù)的存儲(chǔ)指針。這里要說(shuō)明的是,不一定你要讀取128個(gè)字節(jié)就一定會(huì)讀取這么多,如果遇到了文件尾符號(hào)就會(huì)停止讀取。停止讀取的時(shí)候就會(huì)把讀取的字節(jié)數(shù)寫(xiě)入到第四個(gè)參數(shù)指定的存儲(chǔ)空間里。所以我們可以通過(guò)返回的讀取字節(jié)數(shù)來(lái)發(fā)送到串口,不需要自己數(shù)讀取了多少個(gè)字節(jié)。
if(!f_open(&file, Path, FA_READ | FA_OPEN_EXISTING | FA_WRITE))
復(fù)制代碼
4、使用f_lseek
我在調(diào)用f_write函數(shù)之前,調(diào)用了一下f_lseek函數(shù)來(lái)移動(dòng)文件指針。不過(guò)在這里并不需要,你們參考我的完整工程文件就知道。在這之前我已經(jīng)進(jìn)行了文件讀取的操作,文件指針已經(jīng)定位在了文件的最后一個(gè)字節(jié)這里。不過(guò)我還是調(diào)用了一下,因?yàn)橛袝r(shí)候需要用到這個(gè)函數(shù)。這個(gè)函數(shù)的詳細(xì)用法請(qǐng)參考FatFs的官方文檔網(wǎng)站。
5、使用f_write
f_write的用法和f_read非常相似,這里就不在贅述。第三個(gè)參數(shù)和第四個(gè)參數(shù)的含義也是大同小異。想了解這個(gè)函數(shù)的詳細(xì)說(shuō)明,請(qǐng)參考FatFs的官方文檔網(wǎng)站。
f_lseek(&file, Number);
復(fù)制代碼
6、使用f_close
f_close這個(gè)函數(shù)的用法就非常的簡(jiǎn)單了,這里在寫(xiě)入操作之后要調(diào)用一次這個(gè)函數(shù)。調(diào)用了這個(gè)函數(shù)之后,寫(xiě)入的信息才可以保存在SD卡的上面,下一次才可以讀取出來(lái)。如果沒(méi)有使用這個(gè)函數(shù)的話,文件就會(huì)存儲(chǔ)在緩沖區(qū)中,只有這一次讀取可以成功。到了下一次復(fù)位的時(shí)候,讀取的還是原先的文件內(nèi)容。
f_close(&file);
復(fù)制代碼
7、再次讀取文件內(nèi)容
現(xiàn)在我們有寫(xiě)入了一些信息到了我們的文件里面,再次讀取一下看看寫(xiě)入成功了嗎。下面的代碼實(shí)現(xiàn)了這一功能,大家參考一下。
f_open(&file, Path, FA_READ | FA_OPEN_EXISTING);
復(fù)制代碼
五、上電測(cè)試
代碼我們都準(zhǔn)備好了,只要編譯通過(guò)下載到我們的開(kāi)發(fā)板上就行了。如果你的硬件連接都非常的正確,而且按照前面的步驟來(lái)的話就可以開(kāi)始測(cè)試讀取SD卡了。
我在SD卡的根目錄下建立了/File/test.txt
初始的文件內(nèi)容如下:
This is a test file to confirm the library of FatFs.
Don't support Chinese in this file.
Wish you a good luck!
測(cè)試結(jié)果
觀察兩次的輸出結(jié)果可以看得出來(lái),第二次復(fù)位的時(shí)候文件的末尾又多了一行文字。這一現(xiàn)象符合我們的程序邏輯。大家可以對(duì)比一下這里的結(jié)果,自己做一個(gè)參考。
寫(xiě)在后面的話
注意!注意!注意!本人不是什么工程師,只不過(guò)是愛(ài)好嵌入式開(kāi)發(fā)的學(xué)生一枚,如果你發(fā)現(xiàn)在這個(gè)帖子中的錯(cuò)誤請(qǐng)及時(shí)提醒我。如果對(duì)本帖的內(nèi)容有什么疑問(wèn)請(qǐng)?jiān)谙路搅粞?,我?huì)經(jīng)常過(guò)來(lái)逛論壇的。
這次開(kāi)發(fā)FatFs讀寫(xiě)確實(shí)走了很多的彎路啊,結(jié)果最后發(fā)現(xiàn)不過(guò)是堆內(nèi)存空間不夠,不能分配的錯(cuò)誤而已。為了發(fā)現(xiàn)這個(gè)錯(cuò)誤我也是找了很多的資料什么的,不過(guò)這就是學(xué)習(xí)的過(guò)程吧。我覺(jué)得只有這樣才能夠鍛煉我們發(fā)現(xiàn)問(wèn)題和解決問(wèn)題的能力。相信在這一次又一次的坎坷和曲折之中我一定會(huì)進(jìn)步越來(lái)越大的。大家一起加油!
下一次給大家?guī)?lái)的是什么呢?我現(xiàn)在貼出一張照片,大家一起猜一猜。
下一次要更新的東西會(huì)比較復(fù)雜的吧,所以時(shí)間可能會(huì)隔得比較久,希望期待下一篇帖子的社區(qū)成員多多關(guān)注。