一、FatFs簡(jiǎn)介
FatFs 是面向小型嵌入式系統(tǒng)的一種通用的 FAT 文件系統(tǒng)。它完全是由 ANSI C 語(yǔ)言編寫(xiě)并且完全獨(dú)立于底層的 I/O 介質(zhì)。因此它可以很容易地不加修改地移植到其他的處理器當(dāng)中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我們利用前面寫(xiě)好的 SPI Flash 芯片驅(qū)動(dòng),把 FatFs 文件系統(tǒng)代碼移植到工程之中,就可以利用文件系統(tǒng)的各種函數(shù),對(duì) SPI Flash 芯片以“文件”格式進(jìn)行讀寫(xiě)操作了。
FatFs 文件系統(tǒng)的源碼可以從 fatfs 官網(wǎng)下載:
http://elm-chan.org/fsw/ff/00index_e.html
1.1 FatFs文件系統(tǒng)布局

簇是文件存儲(chǔ)的最小單元,F(xiàn)AT32分區(qū)大小與對(duì)應(yīng)簇空間大小關(guān)系如下表示:
| 分區(qū)空間大小 | 簇空間大小 | 每個(gè)簇包含的扇區(qū)數(shù) |
|---|---|---|
| < 8GB | 4KB | 8 |
| [ 8GB, 16GB ) | 8KB | 16 |
| [ 16GB, 32GB ) | 16KB | 32 |
| >= 32GB | 32KB | 64 |
例如:創(chuàng)建一個(gè)50字節(jié)的test.txt文件,文件大小是50字節(jié),但是占用磁盤(pán)空間為4096字節(jié)(一個(gè)簇)

1.2 FatFs層次結(jié)構(gòu)

最頂層是應(yīng)用層:使用者只需要調(diào)用FATFS模塊提供給用戶的一系列應(yīng)用接口函數(shù)(如f_open, f_read, f_write和f_close等),就可以像在PC上讀寫(xiě)文件那樣簡(jiǎn)單
中間層FATFS模塊:實(shí)現(xiàn)了FAT文件讀寫(xiě)協(xié)議;它提供了ff.c和ff.h文件,一般情況下不用修改,使用時(shí)將頭文件包含進(jìn)去即可
最底層是FATFS模塊的底層接口:包括存儲(chǔ)媒介讀寫(xiě)接口和供給文件創(chuàng)建修改時(shí)間的實(shí)時(shí)時(shí)鐘,需要在移植時(shí)編寫(xiě)對(duì)應(yīng)的代碼
FATFS源碼相關(guān)文件介紹如下表示;移植FATFS模塊時(shí),一般只需要修改2個(gè)文件(即ffconf.h和diskio.c)
與平臺(tái)無(wú)關(guān):
| 文件 | 說(shuō)明 |
|---|---|
| ffconf.h | FATFS模塊配置文件 |
| ff.h | FATFS和應(yīng)用模塊公用的包含文件 |
| ff.c | FATFS模塊 |
| diskio.h | FATFS和disk I/O模塊公用的包含文件 |
| interger.h | 數(shù)據(jù)類(lèi)型定義 |
| option | 可選的外部功能(比如支持中文) |
與平臺(tái)相關(guān):
| 文件 | 說(shuō)明 |
|---|---|
| diskio.c | FATFS和disk I/O模塊接口層文件 |
1.3 FatFs API
1.3.1 f_mount
| 功能 | 在FatFs模塊上注冊(cè)、注銷(xiāo)一個(gè)工作區(qū)(文件系統(tǒng)對(duì)象) |
|---|---|
| 函數(shù)定義 | FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt) |
| 參數(shù) | fs:工作區(qū)(文件系統(tǒng)對(duì)象)指針 path:注冊(cè)/注銷(xiāo)工作區(qū)的邏輯驅(qū)動(dòng)器號(hào) opt:注冊(cè)或注銷(xiāo)選項(xiàng) |
| 返回 | 操作結(jié)果 |
1.3.2 f_open
| 功能 | 創(chuàng)建/打開(kāi)一個(gè)文件對(duì)象 |
|---|---|
| 函數(shù)定義 | FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode) |
| 參數(shù) | fp:將被創(chuàng)建的文件對(duì)象結(jié)構(gòu)的指針 path:文件名指針,指定將創(chuàng)建或打開(kāi)的文件名 mode:訪問(wèn)類(lèi)型和打開(kāi)方法,由一下標(biāo)準(zhǔn)的一個(gè)組合指定的 |
| 返回 | 操作結(jié)果 |
| 模式 | 描述 |
|---|---|
| FA_READ | 指定讀訪問(wèn)對(duì)象??梢詮奈募凶x取數(shù)據(jù)。 與FA_WRITE 結(jié) 合可以進(jìn)行讀寫(xiě)訪問(wèn)。 |
| FA_WRITE | 指定寫(xiě)訪問(wèn)對(duì)象??梢韵蛭募袑?xiě)入數(shù)據(jù)。與FA_READ 結(jié)合 可以進(jìn)行讀寫(xiě)訪問(wèn)。 |
| FA_OPEN_EXISTING | 打開(kāi)文件。如果文件不存在,則打開(kāi)失敗。(默認(rèn)) |
| FA_OPEN_ALWAYS | 如果文件存在,則打開(kāi);否則,創(chuàng)建一個(gè)新文件。 |
| FA_CREATE_NEW | 創(chuàng)建一個(gè)新文件。如果文件已存在,則創(chuàng)建失敗。 |
| FA_CREATE_ALWAYS | 創(chuàng)建一個(gè)新文件。如果文件已存在,則它將被截?cái)嗖⒏采w。 |
1.3.3 f_close
| 功能 | 關(guān)閉一個(gè)打開(kāi)的文件 |
|---|---|
| 函數(shù)定義 | FRESULT f_close(FIL* fp) |
| 參數(shù) | fp:指向?qū)⒈魂P(guān)閉的已打開(kāi)的文件對(duì)象結(jié)構(gòu)的指針 |
| 返回 | 操作結(jié)果 |
1.3.4 f_read
| 功能 | 從一個(gè)打開(kāi)的文件中讀取數(shù)據(jù) |
|---|---|
| 函數(shù)定義 | FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br) |
| 參數(shù) | fp:指向?qū)⒈蛔x取的已打開(kāi)的文件對(duì)象結(jié)構(gòu)的指針 buff:指向存儲(chǔ)讀取數(shù)據(jù)的緩沖區(qū)的指針 btr:要讀取的字節(jié)數(shù) br:指向返回已讀取字節(jié)數(shù)的UINT變量的指針,返回為實(shí)際讀取的字節(jié)數(shù) |
| 返回 | 操作結(jié)果 |
1.3.5 f_write
| 功能 | 寫(xiě)入數(shù)據(jù)到一個(gè)已打開(kāi)的文件 |
|---|---|
| 函數(shù)定義 | FRESULT f_write(FIL* fp, void* buff, UINT btw, UINT* bw) |
| 參數(shù) | fp:指向?qū)⒈粚?xiě)入的已打開(kāi)的文件對(duì)象結(jié)構(gòu)的指針 buff:指向存儲(chǔ)寫(xiě)入數(shù)據(jù)的緩沖區(qū)的指針 btw:要寫(xiě)入的字節(jié)數(shù) bw:指向返回已寫(xiě)入字節(jié)數(shù)的UINT變量的指針,返回為實(shí)際寫(xiě)入的字節(jié)數(shù) |
| 返回 | 操作結(jié)果 |
另外FatFs還有很多API操作函數(shù),在這里不再作詳細(xì)的介紹,詳細(xì)信息請(qǐng)查看FatFs文件系統(tǒng)官網(wǎng)。
二、新建工程
1. 打開(kāi) STM32CubeMX 軟件,點(diǎn)擊“新建工程”

2. 選擇 MCU 和封裝

3. 配置時(shí)鐘
RCC 設(shè)置,選擇 HSE(外部高速時(shí)鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)

選擇 Clock Configuration,配置系統(tǒng)時(shí)鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車(chē),軟件會(huì)自動(dòng)修改所有配置

4. 配置調(diào)試模式
非常重要的一步,否則會(huì)造成第一次燒錄程序后續(xù)無(wú)法識(shí)別調(diào)試器
SYS 設(shè)置,選擇 Debug 為 Serial Wire

三、SDIO
STM32 控制器可以控制使用單線或 4 線傳輸,本開(kāi)發(fā)板設(shè)計(jì)使用 4 線傳輸。

3.1 參數(shù)配置
在 Connetivity 中選擇 SDIO 設(shè)置,并選擇 SD 4 bits Wide bus 四線SD模式

此時(shí) SDIO 對(duì)應(yīng)的管腳也被選中。

在
Parameter Settings 進(jìn)行具體參數(shù)配置。
Clock transition on which the bit capture is made: Rising transition。主時(shí)鐘 SDIOCLK 產(chǎn)生 CLK 引腳時(shí)鐘有效沿選擇,可選上升沿或下降沿,它設(shè)定 SDIO 時(shí)鐘控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般選擇設(shè)置為上升沿。
SDIO Clock divider bypass: Disable。時(shí)鐘分頻旁路使用,可選使能或禁用,它設(shè)定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驅(qū)動(dòng) CLK 線輸出時(shí)鐘;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分頻 SDIOCLK,然后輸出到 CLK 線。一般選擇禁用時(shí)鐘分頻旁路。
SDIO Clock output enable when the bus is idle: Disable the power save for the clock。節(jié)能模式選擇,可選使能或禁用,它設(shè)定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能節(jié)能模式,CLK 線只有在總線激活時(shí)才有時(shí)鐘輸出;如果禁用節(jié)能模式,始終使能 CLK 線輸出時(shí)鐘。
SDIO hardware flow control: The hardware control flow is disabled。硬件流控制選擇,可選使能或禁用,它設(shè)定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 發(fā)送上溢和下溢錯(cuò)誤。
SDIOCLK clock divide factor: 6。時(shí)鐘分頻系數(shù),它設(shè)定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,設(shè)置 SDIOCLK 與 CLK 線輸出時(shí)鐘分頻系數(shù):CLK 線時(shí)鐘頻率=SDIOCLK/([CLKDIV+2])。
SDIO_CK 引腳的時(shí)鐘信號(hào)在卡識(shí)別模式時(shí)要求不超過(guò) 400KHz,而在識(shí)別后的數(shù)據(jù)傳輸模式時(shí)則希望有更高的速度(最大不超過(guò) 25MHz),所以會(huì)針對(duì)這兩種模式配置 SDIOCLK 的時(shí)鐘。
這里參數(shù)描述建議將SDIOCLK clock divede factor 參數(shù)使用默認(rèn)值為0,SDIOCLK為72MHz,可以得到最大頻率36MHz,但請(qǐng)注意,有些型號(hào)的SD卡可能不支持36MHz這么高的頻率,所以還是要以實(shí)際情況而定。
3.2 配置DMA
SDIO 外設(shè)支持生成 DMA 請(qǐng)求,使用 DMA 傳輸可以提高數(shù)據(jù)傳輸效率,因此在 SDIO 的控制代碼中,可以把它設(shè)置為 DMA 傳輸模式或輪詢(xún)模式,ST 標(biāo)準(zhǔn)庫(kù)提供 SDIO 示例中針對(duì)這兩個(gè)模式做了區(qū)分處理。
應(yīng)用中一般都使用DMA 傳輸模式。

點(diǎn)擊 DMA Settings 添加 SDIO 對(duì)應(yīng) DMA2 的通道4。DMA模式選擇循環(huán)模式,方向選為內(nèi)存到外設(shè)。
-
Priority:
當(dāng)發(fā)生多個(gè) DMA 通道請(qǐng)求時(shí),就意味著有先后響應(yīng)處理的順序問(wèn)題,這個(gè)就由仲裁器也管理。仲裁器管理 DMA 通道請(qǐng)求分為兩個(gè)階段。第一階段屬于軟件階段,可以在 DMA_CCRx 寄存器中設(shè)置,有 4 個(gè)等級(jí):非常高、高、中和低四個(gè)優(yōu)先級(jí)。第二階段屬于硬件階段,如果兩個(gè)或以上的 DMA 通道請(qǐng)求設(shè)置的優(yōu)先級(jí)一樣,則他們優(yōu)先級(jí)取決于通 道編號(hào),編號(hào)越低優(yōu)先權(quán)越高,比如通道 0 高于通道 1。在大容量產(chǎn)品和互聯(lián)型產(chǎn)品中,DMA1 控制器擁有高于 DMA2 控制器的優(yōu)先級(jí)。 -
Mode:
Normal表示單次傳輸,傳輸一次后終止傳輸。
Circular表示循環(huán)傳輸,傳輸完成后又重新開(kāi)始繼續(xù)傳輸,不斷循環(huán)永不停止。 -
Increment Address:
Peripheral表示外設(shè)地址自增。
Memory表示內(nèi)存地址自增。 -
Data Width:
Byte一個(gè)字節(jié)。
Half Word半個(gè)字,等于兩字節(jié)。
Word一個(gè)字,等于四字節(jié)。
3.3 配置NVIC
DMA及SDIO中斷設(shè)置,原則是全局中斷優(yōu)先級(jí)高于DMA中斷

四、FATFS
4.1 參數(shù)配置
在 Middleware 中選擇 FATFS 設(shè)置,并勾選 SD Card 配置為SD卡

Function Parameters
跳過(guò)-
Locale and Namespace Parameters:
-
CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
支持簡(jiǎn)體中文編碼 -
USE_LFN(Use Long Filename): Enabled with dynamic working buffer on the STACK
支持長(zhǎng)文件名,并指定使用??臻g為緩沖區(qū)
-
CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
緩存工作區(qū)為什么放在棧?其實(shí)fatfs提供了三個(gè)選項(xiàng):BSS,STACK , HEAP,根據(jù)個(gè)人情況選一個(gè)。
在BSS上啟用帶有靜態(tài)工作緩沖區(qū)的LFN,不能動(dòng)態(tài)分配。
如果選擇了HEAP(堆)且自己有屬于自己的malloc就去重寫(xiě)ff_memalloc ff_memfree函數(shù)。如果是庫(kù)的malloc就不需要。
一般都選擇使用STACK(棧),能動(dòng)態(tài)分配。
當(dāng)使用堆棧作為工作緩沖區(qū)時(shí),請(qǐng)注意堆棧溢出。
-
Physical Drive Parameters:
-
VOLUMES(Logical drivers): 2
指定物理設(shè)備數(shù)量,這里設(shè)置為 2,包括預(yù)留 SD 卡和 SPI Flash 芯片 -
MAX_SS(Maximum Sector Size): 512
指定扇區(qū)大小的最大值。SD 卡扇區(qū)大小一般都為 512 字節(jié),SPI Flash 芯片扇區(qū)大小一般設(shè)置為 4096 字節(jié),所以需要把 _MAX_SS 改為 512 -
MIN_SS(Minimum Sector Size): 512
指定扇區(qū)大小的最小值
-
VOLUMES(Logical drivers): 2

4.2 配置SD卡檢測(cè)引腳
SD卡插入檢測(cè)引腳,如果不配置一個(gè)引腳生成文件時(shí)會(huì)報(bào)錯(cuò),所以這里即使沒(méi)有硬件連接,也可以任意設(shè)置一引腳使用,生成工程后注釋代碼。


4.3 增大??臻g
將最小棧空間改到 0x1000
注意:由于剛才設(shè)置長(zhǎng)文件名動(dòng)態(tài)緩存存儲(chǔ)在堆中,故需要增大堆大小,如果不修改則程序運(yùn)行時(shí)堆會(huì)生成溢出,程序進(jìn)入硬件錯(cuò)誤中斷(HardFault),死循環(huán)。

4.4 生成代碼
輸入項(xiàng)目名和項(xiàng)目路徑

選擇應(yīng)用的 IDE 開(kāi)發(fā)環(huán)境 MDK-ARM V5

每個(gè)外設(shè)生成獨(dú)立的
’.c/.h’ 文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對(duì)應(yīng)的外設(shè)文件。 如 GPIO 初始化代碼生成在 gpio.c 中。

點(diǎn)擊 GENERATE CODE 生成代碼

五、屏蔽SD卡插入檢測(cè)代碼
由于沒(méi)有檢測(cè)SD卡插入的硬件引腳,打開(kāi) bsp_driver_sd.c 文件,修改 BSP_SD_IsDetected() 函數(shù),始終返回SD_PRESENT:


六、修改main函數(shù)
定義相關(guān)變量:
FATFS fs; /* FatFs 文件系統(tǒng)對(duì)象 */
FIL file; /* 文件對(duì)象 */
FRESULT f_res; /* 文件操作結(jié)果 */
UINT fnum; /* 文件成功讀寫(xiě)數(shù)量 */
BYTE ReadBuffer[1024] = {0}; /* 讀緩沖區(qū) */
BYTE WriteBuffer[] = /* 寫(xiě)緩沖區(qū) */
"This is STM32 working with FatFs\r\n";
修改main函數(shù):
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_SDIO_SD_Init();
MX_FATFS_Init();
/* USER CODE BEGIN 2 */
printf("\r\n****** FatFs Example ******\r\n\r\n");
//在外部 SD 卡掛載文件系統(tǒng),文件系統(tǒng)掛載時(shí)會(huì)對(duì) SD 卡初始化
f_res = f_mount(&fs, "0:", 1);
/*----------------------- 格式化測(cè)試 ---------------------------*/
printf("\r\n****** Register the file system object to the FatFs module ******\r\n");
/* 如果沒(méi)有文件系統(tǒng)就格式化創(chuàng)建創(chuàng)建文件系統(tǒng) */
if(f_res == FR_NO_FILESYSTEM)
{
printf("The SD card does not yet have a file system and is about to be formatted...\r\n");
/* 格式化 */
f_res = f_mkfs("0:", 0, 0);
if(f_res == FR_OK)
{
printf("The SD card successfully formatted the file system\r\n");
/* 格式化后,先取消掛載 */
f_res = f_mount(NULL, "0:", 1);
/* 重新掛載 */
f_res = f_mount(&fs, "0:", 1);
}
else
{
printf("The format failed\r\n");
while(1);
}
}
else if(f_res != FR_OK)
{
printf(" mount error : %d \r\n", f_res);
while(1);
}
else
{
printf(" mount sucess!!! \r\n");
}
/*----------------------- 文件系統(tǒng)測(cè)試:寫(xiě)測(cè)試 -----------------------------*/
/* 打開(kāi)文件,如果文件不存在則創(chuàng)建它 */
printf("\r\n****** Create and Open new text file objects with write access ******\r\n");
f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(f_res == FR_OK)
{
printf(" open file sucess!!! \r\n");
/* 將指定存儲(chǔ)區(qū)內(nèi)容寫(xiě)入到文件內(nèi) */
printf("\r\n****** Write data to the text files ******\r\n");
f_res = f_write(&file, WriteBuffer, sizeof(WriteBuffer), &fnum);
if(f_res == FR_OK)
{
printf(" write file sucess!!! (%d)\n", fnum);
printf(" write Data : %s\r\n", WriteBuffer);
}
else
{
printf(" write file error : %d\r\n", f_res);
}
/* 不再讀寫(xiě),關(guān)閉文件 */
f_close(&file);
}
else
{
printf(" open file error : %d\r\n", f_res);
}
/*------------------- 文件系統(tǒng)測(cè)試:讀測(cè)試 ------------------------------------*/
printf("\r\n****** Read data from the text files ******\r\n");
f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_OPEN_EXISTING | FA_READ);
if(f_res == FR_OK)
{
printf(" open file sucess!!! \r\n");
f_res = f_read(&file, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(f_res == FR_OK)
{
printf("read sucess!!! (%d)\n", fnum);
printf("read Data : %s\r\n", ReadBuffer);
}
else
{
printf(" read error!!! %d\r\n", f_res);
}
}
else
{
printf(" open file error : %d\r\n", f_res);
}
/* 不再讀寫(xiě),關(guān)閉文件 */
f_close(&file);
/* 不再使用文件系統(tǒng),取消掛載文件系統(tǒng) */
f_mount(NULL, "0:", 1);
/* 操作完成,停機(jī) */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
七、查看打印
串口打印功能查看 STM32CubeMX學(xué)習(xí)筆記(6)——USART串口使用

八、工程代碼
鏈接:https://pan.baidu.com/s/1kgJ7AmbnW89yHcrgBzD_1w 提取碼:kg5a
九、注意事項(xiàng)
f_open、f_write、f_read如果偶爾有問(wèn)題;f_mkfs報(bào)錯(cuò) FS_DISK_ERR,可以加上了SDIO硬件流使能試試。

用戶代碼要加在 USER CODE BEGIN N 和 USER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后,會(huì)被刪除。

? 由 Leung 寫(xiě)于 2021 年 12 月 7 日
? 參考:STM32CubeMX系列教程18:文件系統(tǒng)FATFS
STM32CubeMX系列|FATFS文件系統(tǒng)
1.6 Cubemx_STM32F103_NOOS SDIO_DMA_FATFS基于SD卡的FATFS測(cè)試(一)
【STM32Cube_20】在SD卡上移植FATFS文件系統(tǒng)
STM32CubeMX之SD卡+FatFs