一、FatFs简介
FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统。它完全是由 ANSI C 语言编写并且完全独立于底层的 I/O 介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对 SPI Flash 芯片以“文件”格式进行读写操作了。
FatFs 文件系统的源码可以从 fatfs 官网下载:
http://elm-chan.org/fsw/ff/00index_e.html
1.1 FatFs文件系统布局
簇是文件存储的最小单元,FAT32分区大小与对应簇空间大小关系如下表示:
分区空间大小 | 簇空间大小 | 每个簇包含的扇区数 |
---|---|---|
< 8GB | 4KB | 8 |
[ 8GB, 16GB ) | 8KB | 16 |
[ 16GB, 32GB ) | 16KB | 32 |
>= 32GB | 32KB | 64 |
例如:创建一个50字节的test.txt文件,文件大小是50字节,但是占用磁盘空间为4096字节(一个簇)
1.2 FatFs层次结构
最顶层是应用层:使用者只需要调用FATFS模块提供给用户的一系列应用接口函数(如f_open, f_read, f_write和f_close等),就可以像在PC上读写文件那样简单
中间层FATFS模块:实现了FAT文件读写协议;它提供了ff.c和ff.h文件,一般情况下不用修改,使用时将头文件包含进去即可
最底层是FATFS模块的底层接口:包括存储媒介读写接口和供给文件创建修改时间的实时时钟,需要在移植时编写对应的代码
FATFS源码相关文件介绍如下表示;移植FATFS模块时,一般只需要修改2个文件(即ffconf.h和diskio.c)
与平台无关:
文件 | 说明 |
---|---|
ffconf.h | FATFS模块配置文件 |
ff.h | FATFS和应用模块公用的包含文件 |
ff.c | FATFS模块 |
diskio.h | FATFS和disk I/O模块公用的包含文件 |
interger.h | 数据类型定义 |
option | 可选的外部功能(比如支持中文) |
与平台相关:
文件 | 说明 |
---|---|
diskio.c | FATFS和disk I/O模块接口层文件 |
1.3 FatFs API
1.3.1 f_mount
功能 | 在FatFs模块上注册、注销一个工作区(文件系统对象) |
---|---|
函数定义 | FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt) |
参数 | fs:工作区(文件系统对象)指针 path:注册/注销工作区的逻辑驱动器号 opt:注册或注销选项 |
返回 | 操作结果 |
1.3.2 f_open
功能 | 创建/打开一个文件对象 |
---|---|
函数定义 | FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode) |
参数 | fp:将被创建的文件对象结构的指针 path:文件名指针,指定将创建或打开的文件名 mode:访问类型和打开方法,由一下标准的一个组合指定的 |
返回 | 操作结果 |
模式 | 描述 |
---|---|
FA_READ | 指定读访问对象。可以从文件中读取数据。 与FA_WRITE 结 合可以进行读写访问。 |
FA_WRITE | 指定写访问对象。可以向文件中写入数据。与FA_READ 结合 可以进行读写访问。 |
FA_OPEN_EXISTING | 打开文件。如果文件不存在,则打开失败。(默认) |
FA_OPEN_ALWAYS | 如果文件存在,则打开;否则,创建一个新文件。 |
FA_CREATE_NEW | 创建一个新文件。如果文件已存在,则创建失败。 |
FA_CREATE_ALWAYS | 创建一个新文件。如果文件已存在,则它将被截断并覆盖。 |
1.3.3 f_close
功能 | 关闭一个打开的文件 |
---|---|
函数定义 | FRESULT f_close(FIL* fp) |
参数 | fp:指向将被关闭的已打开的文件对象结构的指针 |
返回 | 操作结果 |
1.3.4 f_read
功能 | 从一个打开的文件中读取数据 |
---|---|
函数定义 | FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br) |
参数 | fp:指向将被读取的已打开的文件对象结构的指针 buff:指向存储读取数据的缓冲区的指针 btr:要读取的字节数 br:指向返回已读取字节数的UINT变量的指针,返回为实际读取的字节数 |
返回 | 操作结果 |
1.3.5 f_write
功能 | 写入数据到一个已打开的文件 |
---|---|
函数定义 | FRESULT f_write(FIL* fp, void* buff, UINT btw, UINT* bw) |
参数 | fp:指向将被写入的已打开的文件对象结构的指针 buff:指向存储写入数据的缓冲区的指针 btw:要写入的字节数 bw:指向返回已写入字节数的UINT变量的指针,返回为实际写入的字节数 |
返回 | 操作结果 |
另外FatFs还有很多API操作函数,在这里不再作详细的介绍,详细信息请查看FatFs文件系统官网。
二、新建工程
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
三、SDIO
STM32 控制器可以控制使用单线或 4 线传输,本开发板设计使用 4 线传输。
3.1 参数配置
在 Connetivity
中选择 SDIO
设置,并选择 SD 4 bits Wide bus
四线SD模式
此时 SDIO 对应的管脚也被选中。
在
Parameter Settings
进行具体参数配置。
Clock transition on which the bit capture is made: Rising transition
。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿
。
SDIO Clock divider bypass: Disable
。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路
。
SDIO Clock output enable when the bus is idle: Disable the power save for the clock
。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。
SDIO hardware flow control: The hardware control flow is disabled
。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。
SDIOCLK clock divide factor: 6
。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。
SDIO_CK 引脚的时钟信号在卡识别模式时要求不超过 400KHz,而在识别后的数据传输模式时则希望有更高的速度(最大不超过 25MHz),所以会针对这两种模式配置 SDIOCLK 的时钟。
这里参数描述建议将SDIOCLK clock divede factor 参数使用默认值为0,SDIOCLK为72MHz,可以得到最大频率36MHz,但请注意,有些型号的SD卡可能不支持36MHz这么高的频率,所以还是要以实际情况而定。
3.2 配置DMA
SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式或轮询模式,ST 标准库提供 SDIO 示例中针对这两个模式做了区分处理。
应用中一般都使用DMA 传输模式。
点击 DMA Settings
添加 SDIO 对应 DMA2 的通道4。DMA模式选择循环模式,方向选为内存到外设。
- Priority:
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。 - Mode:
Normal
表示单次传输,传输一次后终止传输。
Circular
表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。 - Increment Address:
Peripheral
表示外设地址自增。
Memory
表示内存地址自增。 - Data Width:
Byte
一个字节。
Half Word
半个字,等于两字节。
Word
一个字,等于四字节。
3.3 配置NVIC
DMA及SDIO中断设置,原则是全局中断优先级高于DMA中断
四、FATFS
4.1 参数配置
在 Middleware
中选择 FATFS
设置,并勾选 SD Card
配置为SD卡
Function Parameters
跳过
-
Locale and Namespace Parameters:
- CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
支持简体中文编码
- USE_LFN(Use Long Filename): Enabled with dynamic working buffer on the STACK
支持长文件名,并指定使用栈空间为缓冲区
- CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows)
缓存工作区为什么放在栈?其实fatfs提供了三个选项:BSS,STACK , HEAP,根据个人情况选一个。
在BSS上启用带有静态工作缓冲区的LFN,不能动态分配。
如果选择了HEAP(堆)且自己有属于自己的malloc就去重写ff_memalloc ff_memfree函数。如果是库的malloc就不需要。
一般都选择使用STACK(栈),能动态分配。
当使用堆栈作为工作缓冲区时,请注意堆栈溢出。
- Physical Drive Parameters:
- VOLUMES(Logical drivers): 2
指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPI Flash 芯片
- MAX_SS(Maximum Sector Size): 512
指定扇区大小的最大值。SD 卡扇区大小一般都为 512 字节,SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 512
- MIN_SS(Minimum Sector Size): 512
指定扇区大小的最小值
- VOLUMES(Logical drivers): 2
4.2 配置SD卡检测引脚
SD卡插入检测引脚,如果不配置一个引脚生成文件时会报错,所以这里即使没有硬件连接,也可以任意设置一引脚使用,生成工程后注释代码。
4.3 增大栈空间
将最小栈空间改到 0x1000
注意:由于刚才设置长文件名动态缓存存储在堆中,故需要增大堆大小,如果不修改则程序运行时堆会生成溢出,程序进入硬件错误中断(HardFault),死循环。
4.4 生成代码
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的
’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
五、屏蔽SD卡插入检测代码
由于没有检测SD卡插入的硬件引脚,打开 bsp_driver_sd.c
文件,修改 BSP_SD_IsDetected()
函数,始终返回SD_PRESENT:
六、修改main函数
定义相关变量:
FATFS fs; /* FatFs 文件系统对象 */
FIL file; /* 文件对象 */
FRESULT f_res; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024] = {0}; /* 读缓冲区 */
BYTE WriteBuffer[] = /* 写缓冲区 */
"This is STM32 working with FatFs\r\n";
修改main函数:
/**
* @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 卡挂载文件系统,文件系统挂载时会对 SD 卡初始化
f_res = f_mount(&fs, "0:", 1);
/*----------------------- 格式化测试 ---------------------------*/
printf("\r\n****** Register the file system object to the FatFs module ******\r\n");
/* 如果没有文件系统就格式化创建创建文件系统 */
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");
}
/*----------------------- 文件系统测试:写测试 -----------------------------*/
/* 打开文件,如果文件不存在则创建它 */
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");
/* 将指定存储区内容写入到文件内 */
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);
}
/* 不再读写,关闭文件 */
f_close(&file);
}
else
{
printf(" open file error : %d\r\n", f_res);
}
/*------------------- 文件系统测试:读测试 ------------------------------------*/
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);
}
/* 不再读写,关闭文件 */
f_close(&file);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL, "0:", 1);
/* 操作完成,停机 */
/* 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学习笔记(6)——USART串口使用
八、工程代码
链接:https://pan.baidu.com/s/1kgJ7AmbnW89yHcrgBzD_1w 提取码:kg5a
九、注意事项
f_open、f_write、f_read如果偶尔有问题;f_mkfs报错 FS_DISK_ERR,可以加上了SDIO硬件流使能试试。
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2021 年 12 月 7 日
• 参考:STM32CubeMX系列教程18:文件系统FATFS
STM32CubeMX系列|FATFS文件系统
1.6 Cubemx_STM32F103_NOOS SDIO_DMA_FATFS基于SD卡的FATFS测试(一)
【STM32Cube_20】在SD卡上移植FATFS文件系统
STM32CubeMX之SD卡+FatFs