目录
一、SPI-Flash芯片硬件电路
二、CubeMX项目基础设置
1、RCC、SYS、Code Generator、USART6、NVIC
2、RTC
3、SPI2
4、GPIO
5、FatFS模式
6、FatFS参数设置概述
(1)Version组
(2)Function Parameters组
1)参数FS_READONLY
2)参数S_MINIMIZE
3)参数USE_STRFUNC
(3)Locale and Namespace Parameters组
1)参数USE_LFN
2)参数STRF_ENCODE
3)参数FS_RPATH
(4)Physical Driver Parameters组
(5)System Parameters组
1)参数FS_NORTC
2)参数FS_REENTRANT
三、项目FatFS的文件组成及详细分析
1、main()的初始化代码
2、文件fatfs.h和fatfs.c
3、文件user_diskio.h和user_diskio.c
4、文件ff_gen_drv.h和ff_gen_drv.c
5、文件diskio.h和diskio.c
本文以开发板上的SPI-Flash芯片W25Q16为媒介详细介绍CubeMX中FatFS各个参数的意义和设置,分析生成的代码中FatFS各个文件的作用和关联,完成针对W25Q16的硬件访问层的移植。
继续使用旺宝红龙开发板STM32F407ZGT6 KITV1.0。相关原理图如下:
一、SPI-Flash芯片硬件电路
开发板上有一个SPI-Flash存储芯片W25Q16,它与STM32F407的SPI2接口连接,W25Q16总容量是2M字节,存储空间参数如下。
- 总共32个块(block),每个块64KB。
- 每个块又分为16个扇区,共512个扇区,每个扇区4KB。
- 每个扇区又分为16个页(page),共8192个页,每个页256B。
二、CubeMX项目基础设置
本文创建一个示例针对SPI-Flash存储芯片W25Q16进行FatFS移植,使用FatFS进行文件读写等操作。此外,使用RTC为FatFS提供时间戳数据。
1、RCC、SYS、Code Generator、USART6、NVIC
参数设置可以本文作者发布的其他文章文章。
2、RTC
- 启用RTC的时钟源和日历,设置初始日期和时间,其他功能无须开启。在示例中读取RTC的当前日期和时间。
- 在RCC组件中启用LSE,在时钟树上,使用LSE作为RTC的时钟源。
3、SPI2
- 设置HCLK为168MHz,PCLK1的频率为42MHz,SPI2是挂在APB1总线上的,这样是为方便计算SPI2的波特率。
- SPI2的模式设置为Full-Duplex Master,不使用硬件NSS信号,分频系数(Prescaler)设置为8,波特率为5.25Mbit/s。数据传输是MSB先行,CPOL和CPHA的组合是SPI时序模式3。
4、GPIO
5、FatFS模式
模式设置就是选择FatFS应用的存储介质,有以下4个选项。
- External SRAM,外部SRAM存储器。例如,开发板上有一个外部SRAM芯片IS62WV51216,可以将此芯片的一部分或全部存储区域用作文件系统,使用FatFS管理SRAM上的文件,实现内存上的高速文件读写。
- SD Card,SD卡。此项需要启用SDIO接口后才可以选择。
- USB Disk,U盘。需要将USB-OTG-FS或USB-OTG-HS组件的模式设置为Host-Only(仅作为主机),并且将Middleware分组中的USB_HOST组件的IP类型设置为MassStorage Host Class(大容量存储主机类)之后,此项才可以选择。
- User-defined,用户定义器件。除以上3项之外的其他存储介质,例如,开发板上连接在SPI2接口上的Flash存储芯片W25Q16。
使用前3种存储介质时,CubeMX生成的代码里有FatFS完整的移植程序,因此用户无须再编程实现硬件层的Disk IO函数。使用用户定义器件时,CubeMX生成的代码里有FatFS移植代码框架,用户需要自己编程实现器件的Disk IO函数。本文的示例使用的存储介质是连接在SPI2接口上的Flash存储芯片W25Q16属于用户定义器件。通过这个示例,详细地了解FatFS程序移植的原理。
6、FatFS参数设置概述
设置FatFS参数。这些参数分为多个组,大多数与FatFS配置文件ffconf.h中的宏定义对应,用于设置FatFS的一些参数,以及进行功能裁剪。
(1)Version组
只有一个参数,显示了FatFS的版本。图中显示的版本为R0.12c。
(2)Function Parameters组
用于配置是否包含某些函数,就是FatFS的功能裁剪。
Function Parameters组参数,第一列是参数名称,对应于源文件中的宏,宏的名称就是参数名称前加“_”,例如,参数FS_READONLY对应的宏是_FS_READONLY,参数USE_FIND对应的宏是_USE_FIND。第二列是参数值,这些参数一般是逻辑值,Disabled表示设置为0,Enabled表示设置为1。
这些参数有的用于定义系统的特性,如FS_MINIMIZE定义系统最小化级别,会影响多个函数;有的用于裁剪某个功能,可同时影响多个函数,如USE_FIND影响函数f_findfirst()和f_findnext();有的参数只影响一个函数,如USE_CHMOD只控制是否使用函数f_chmod()。
参数 | 默认值 | 可设置内容和影响的函数 |
FS_READONLY | Disabled | 只能设置为Disabled,表示不使用只读功能 |
FS_MINIMIZE | Disabled | 最小化级别,可设置为0、1、2、3等4种级别 |
USE_STRFUNC | 2 | 是否使用字符串函数,可设置为0、1或2 |
USE_FIND | Disabled | 是否使用查找函数f_findfirst()和f_findnext() |
USE_MKFS | Enabled | 是否使用函数f_mkfs() |
USE_FASTSEEK | Enabled | 是否使用快速寻找功能,无具体影响函数 |
USE_EXPAND | Disabled | 是否使用函数f_expand() |
USE_CHMOD | Disabled | 是否使用函数f_chmod() |
USE_LABEL | Disabled | 是否使用获取和设置卷标签的函数f_getlable()和f_setlabel() |
USE_FORWARD | Disabled | 是否使用函数f_forward() |
1)参数FS_READONLY
在CubeMX中,参数FS_READONLY只能设置为Disabled,表示不使用只读功能。若在代码中设置_FS_READONLY为1,表示系统为只读系统,将移除用于写操作的函数,包括f_write()、f_sync()、f_unlink()、f_mkdir()、f_chmod()、f_rename()和f_truncate(),并且函数f_getfree()将变得无用。
2)参数S_MINIMIZE
参数S_MINIMIZE设置系统的最小化级别,有如下4种选项。
- Disabled:对应级别0,启用所有基本函数。
- Enabled with 6 functions removed:对应级别1,移除函数f_stat()、f_getfree()、f_unlink()、f_mkdir()、f_truncate()和f_rename()。
- Enabled with 9 functions removed:对应级别2,在级别1的基础上,再移除函数f_opendir()、f_readdir()和f_closedir()。
- Enabled with 10 functions removed:对应级别3,在级别2的基础上,再移除函数f_lseek()。
3)参数USE_STRFUNC
参数USE_STRFUNC设置是否使用字符串相关函数,以及如何使用,有如下3种选项。
- Disabled:对应值0,不使用字符串相关的函数,如f_gets()、f_putc()、f_puts()等。
- Enabled without LF->CRLF conversion:对应值1,使用字符串相关函数,但不使用LF->CRLF转换。
- Enabled with LF->CRLF conversion:对应值2,使用字符串相关函数,并且使用LF->CRLF转换,也就是字符串中的'\n'会被转换为\r¹+'n'。
(3)Locale and Namespace Parameters组
本地化和名称空间参数,例如,设置代码页、是否使用长文件名等。
参数 | 默认值 | 可设置内容和功能描述 |
CODE_PAGE | Latin 1 | 设置目标系统上使用的OEM编码页,编码页如果选择不正常,可能 |
USE_LFN | Disabled | 是否使用长文件名(LFN) |
MAX_LFN | 255 | 设定值范围为12至255,是LFN的最大长度 |
LFN_UNICODE | ANSI/OEM | 是否将FatFS API中的字符编码切换为Unicode。当USE_LFN设置为 |
STRF_ENCODE | UTF-8 | 启用Unicode后,FatFSAPI中的字符编码都需要转换为Unicode。这 |
FS_RPATH | Disabled | 是否使用相对路径,以及使用相对路径时的特性 |
1)参数USE_LFN
参数USE_LFN控制是否使用LFN(Long File Name,长文件名),有如下4种选项。
- 0=Disabled:不使用LFN,参数MAX_LFN无影响。
- 1=Enable LFN with static working buffer on the BSS:使用LFN,且使用BSS段的静态工作缓冲区。这种情况下,LFN工作缓冲区是BSS段上的静态变量,总是不可重入的,即不是线程安全的。
- 2=Enable LFN with dynamic working buffer on the STACK:使用LFN,且在栈空间为LFN分配动态工作缓冲区。
- 3=Enable LFN with dynamic working buffer on the HEAP:使用LFN,且在堆空间为LFN分配动态工作缓冲区。
要使用LFN功能,请务必将处理Unicode的函数ff_convert()和f_wtoupper()添加到项目中。LFN工作缓冲区占用(MAX_LFN+1)×2字节。当使用栈空间作为LFN工作缓冲区时,要注意栈溢出问题。使用堆空间作为LFN工作缓冲区时,需要将内存管理函数ff_memalloc()和ff_memfree()添加到项目中。
如果不使用LFN,即参数USE_LFN设置为Disabled时,不含后缀的文件名的长度不能超过8个ASCII字符,后缀为3个ASCII字符。如果在嵌入式设备上使用LFN,最好也不要在文件名中使用汉字,即LFN_UNICODE不要设置为Unicode,而是设置为ANSI/OEM。本示例暂时不使用LFN,所以将USE_LFN设置为Disabled。但是FatFS需要能支持中文,所以CODE_PAGE设置为Simplified Chinese(DBCS)。
2)参数STRF_ENCODE
参数STRF_ENCODE用于设置字符串的编码。当LFN_UNICODE设置为0(ANSI/OEM)时,这个参数无效。当LFN_UNICODE设置为1(Unicode)时,FatFS API中的字符编码都需要转换为Unicode。这个参数用于选择字符串操作相关函数,如f_gets()、f_putc()、f_puts()、f_printf()等,在读写文件时使用的编码,有如下几种选项。
- 0=ANSI/OEM。
- 1=UTF-16LE。
- 2=UTF-16BE。
- 3=UTF-8。
其中,UTF-8是最常用的汉字编码,需要使用汉字时,就将此参数设置为UTF-8。
3)参数FS_RPATH
参数FS_RPATH用于设置是否使用相对路径,以及使用相对路径时的特性,有如下3种选项。
- 0=Disabled,不使用相对路径,移除相关函数。
- 1=Enabled without f_getcwd,使用相对路径,可使用函数f_chdrive()和f_chdir()。
- 2=Enabled with f_getcwd,在选项1的基础上,增加可使用函数f_getcwd()。
读取目录的函数f_readdir()的返回结果与此选项有关。
(4)Physical Driver Parameters组
物理驱动器参数,包括卷的个数、扇区大小等。
参数 | 默认值 | 可设置内容和功能描述 |
VOLUMES | 1 | 使用的逻辑驱动器的个数,设置范围为1~9 |
MAX_SS | 512 | 最大扇区大小(字节数),只能设置为512、1024、2048或4096,比如使用的Flash存储芯片W25Q128的扇区大小为4096字节,所 |
MIN_SS | 512 | 最小扇区大小(字节数),只能设置为512、1024、2048或4096 |
MULTI_PARTITION | Disabled | 设置为Disabled时,每个卷与相同编号的物理驱动器绑定,只会挂 |
USE_TRIM | Disabled | 是否使用ATA_TRIM特性。要想使用Trim特性,需要在disk_ioctl() |
FS_NOFSINFO | 0 | 参数取值为0、1、2或3,它设置了函数f_getfree()的运行特性 |
FatFS可以支持多个卷,这多个卷可以是多个不同的存储介质,例如,一个嵌入式设备上同时有Flash存储芯片和SD卡。存储介质的扇区大小是固定的,但不同存储介质的扇区大小不一样,例如,SD卡的扇区大小是512字节,而Flash存储芯片W25Q16的扇区大小是4096字节。
参数FS_NOFSINFO是两位二进制的数[bit1,bit0],组成的参数值是0、1、2或3,用于设置函数f_getfree()的运行特性。函数f_getfree()用于获取一个卷的剩余簇个数。如果bit0=0,在卷挂载后,首次执行函数f_getfree()时,会强制进行完整的FAT扫描,得到的剩余簇个数会记录到返回的FATFS对象的free_clst变量中,bit1根据bit0的设置控制最后分配的簇编号,也就是结构体FATFS中的成员变量last_clst。结构体FATFS中的成员变量free_clst和last_clst称为FSINFO,也就是剩余空间信息(free space information)。
- bit0=0:使用FSINFO中的剩余簇个数,即成员变量free_clst。
- bit0=1:不要相信FSINFO中的剩余簇个数,因为不会进行完全的FAT扫描。
- bit1=0:使用FSINFO中的最后分配簇编号,即成员变量last_clst。
- bit1=1:不要相信FSINFO中的最后分配簇编号。
参数FS_NOFSINFO影响f_getfree()的运行效果。在后面的示例中,我们会讲到如何用函数f_getfree()获取存储介质空间信息。
(5)System Parameters组
系统参数,一些系统级的参数定义,如是否支持exFAT文件系统。在设置参数时,用户可以单击参数设置界面右上方的显示描述信息的小按钮,这样就可以让每个参数的描述信息显示出来,例如参数设置的数值范围等。
System Parameters组参数用于设置FatFS的一些系统级别信息。
参数 | 默认值 | 可设置内容和功能描述 |
FS_TINY | Disabled | 微小缓冲区模式,如果设置为Enabled,每个文件对象(FIL)可减少 |
FS_EXFAT | Disabled | 是否支持exFAT文件系统,当_USE_LFN设置为0时,这个参数只 |
FS_NORTC | Dynamic | 如果系统有RTC提供实时的时间,就设置为Dynamic timestamp;如 |
FS_REENTRANT | Disabled | 设置FatFS的可重入性,在CubeMX里如果没有启用FreeRTOS,这 |
FS_TIMEOUT | 1000 | 超时设置,单位是节拍数。FS_REENTRANT设置为Disabled时,这 |
FS_LOCK | 2 | 如果要启用文件锁定功能,设定FS_LOCK的值大于或等于1,表示 |
1)参数FS_NORTC
参数FS_NORTC用于设置是否有RTC为FatFS提供时间戳。如果系统有RTC提供实时的时间,就设置为Dynamic timestamp,在移植时,实现硬件层访问函数get_fattime(),读取RTC的当前时间作为文件的时间戳。如果系统没有RTC提供实时的时间,就设置为Fixedtimestamp,会出现NORTC_YEAR(年)、NORTC_MON(月)和NORTC_MDAY(日)这3个参数,用于设置一个固定的时间戳数据。
2)参数FS_REENTRANT
参数FS_REENTRANT用于设置FatFS的可重入性。可重入性表示是否线程安全,在使用RTOS时,才有可重入性问题。所以,在CubeMX里如果没有启用FreeRTOS,这个参数只能设置为Disabled;如果启用了FreeRTOS,这个参数只能设置为Enabled。
如果FS_REENTRANT设置为Enabled,会出现参数SYNC_t和USE_MUTEX。其中,SYNC_t是用于同步的对象类型。当USE_MUTEX设置为Disabled时,SYNC_t固定为oSSemaphoreld_t,即使用信号量;当USE_MUTEX设置为Enabled时,SYNC_t固定为osMutexId_t,即使用互斥量。
如果设置为可重入的,还需要在FatFS中移植函数ff_req_grant()、ff_rel_grant()、ff_del_syncobj()和ff_cre_syncobj()。
三、项目FatFS的文件组成及详细分析
完成设置后,CubeMX会自动生成代码。在CubeIDE中打开项目,将KEY_LED驱动程序目录添加到项目搜索路径。在本示例中,用到W25Q16芯片,其驱动程序FLASH目录也添加到项目搜索路径。
项目中FatFS相关的文件是自动加入的。使用CubeMX生成的FatFS的文件组成(对比:全手工移植的FatFS文件组成,两者区别很大)。Middlewares目录下加入的FatFS的源代码文件,这些是不允许用户修改的FatFS源程序文件,各个文件的作用描述如下。
- 文件integer.h:包含FatFS中用到的各种基础数据类型的定义。
- 文件ff.h和ff.c:这是FatFS应用程序接口API函数所在的文件,是与具体硬件无关的软件模块。
- 文件diskio.h和diskio.c:这是存储介质Disk IO访问通用接口函数所在的文件。在CubeMX生成的项目代码中,与具体存储介质相关的Disk IO函数在另外的文件里实现,例如,本示例使用的存储介质是User-defined,会自动生成用户程序文件user_diskio.h和user_diskio.c。文件diskio.c中的函数只是调用文件user_diskio.c中定义的具体Disk IO函数。
- 文件ff_gen_drv.h和ff_gen_drv.c:实现驱动器列表管理功能的文件,这些功能包括链接一个驱动器,或解除一个驱动器的链接。FatFS初始化时,就调用其中的函数FATFS_LinkDriver()链接驱动器。
- option子目录下的文件syscall.c:包含在使用RTOS系统时需要实现的一些函数的示例代码,如果使用FreeRTOS,需要重新实现这些函数。
与具体存储介质相关,可以由用户修改的FatFS相关文件在目录\FATFS下。这些文件的功能描述如下。
- 文件fatfs.h和fatfs.c:是用户的FatFS初始化程序文件。其中有FatFS初始化函数MX_FATFS_Init()、几个全局变量的定义,以及需要重新实现的获取RTC时间作为文件系统时间戳的函数get_fattime()。
- 文件ffconf.h,FatFS的配置文件,包含很多的宏定义,与CubeMX里的FatFS设置对应。
- 文件user_diskio.h和user_diskio.c,是user-defined存储介质的Disk IO函数的程序文件,自动生成了各个函数的框架,只需针对SPI-Flash芯片编写具体的函数代码即可。
FatFS相关文件的层次和关系如下图所示:
- 文件fatfs.h中包含CubeMX生成的FatFS初始化函数MX_FATFS_Init(),在主程序中进行外设初始化时,会调用这个函数。
- 函数MX_FATFS_Init()会调用文件ff_gen_drv.h中的函数FATFS_LinkDriver(),将文件user_diskio.h中定义的驱动器对象USER_Driver链接到FatFS管理的驱动器列表里,相当于完成了驱动器的注册。
- 文件user_diskio.c中实现了针对W25Q16芯片的Disk IO访问函数。文件user_diskio.h中定义了驱动器对象USER_Driver,并且使用函数指针将diskio.h中的Disk IO通用函数指向文件user_diskio.c中实现的针对W25Q16芯片的Diok IO函数。
- 在文件user_diskio.c中实现W25Q16芯片的Disk IO访问函数时,需要用到W25Q16芯片的驱动程序文件w25flash.b/.c,而这个驱动程序使用SPI接口的HAL驱动程序实现对W25Q16芯片的访问。
1、main()的初始化代码
在CubeMX生成的CubeIDE项目代码中,main()函数的初始代码如下:
/*** @brief The application entry point.* @retval int*/
int main(void)
{/* 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_FATFS_Init();MX_RTC_Init();MX_SPI2_Init();MX_USART6_UART_Init();//以下代码先省略
}
在外设初始化部分,函数MX_FATFS_Init()是FatFS的初始化函数,在文件fatfs.h中定义。
2、文件fatfs.h和fatfs.c
文件fatfs.h定义了FatFS初始化函数MX_FATFS_Init(),还定义了几个变量。由于在CubeMX里FatFS的模式被设置为User-defined,因此称这个存储介质为USER逻辑驱动器。文件fatfs.h的完整代码如下:
/* USER CODE BEGIN Header */
/********************************************************************************* @file fatfs.h* @brief Header for fatfs applications******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __fatfs_H
#define __fatfs_H
#ifdef __cplusplusextern "C" {
#endif#include "ff.h"
#include "ff_gen_drv.h"
#include "user_diskio.h" /* defines USER_Driver as external *//* USER CODE BEGIN Includes *//* USER CODE END Includes */extern uint8_t retUSER; /* Return value for USER */
extern char USERPath[4]; /* USER logical drive path */
extern FATFS USERFatFS; /* File system object for USER logical drive */
extern FIL USERFile; /* File object for USER */void MX_FATFS_Init(void);/* USER CODE BEGIN Prototypes *//* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__fatfs_H */
其中,用extern声明的4个变量是在文件fatfs.c中定义的。USERPath用于表示逻辑驱动器的路径,如“0:/”;USERFatFS是一个FATFS结构体变量,用于表示USER逻辑驱动器上的文件系统;USERFile是一个FIL结构体类型变量,表示文件对象,在文件操作时可以使用这个文件对象。
/* USER CODE BEGIN Header */
/********************************************************************************* @file fatfs.c* @brief Code for fatfs applications******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
#include "fatfs.h"uint8_t retUSER; /* Return value for USER */
char USERPath[4]; /* USER logical drive path */
FATFS USERFatFS; /* File system object for USER logical drive */
FIL USERFile; /* File object for USER *//* USER CODE BEGIN Variables */
#include "file_opera.h"
/* USER CODE END Variables */void MX_FATFS_Init(void)
{/*## FatFS: Link the USER driver ###########################*/retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);/* USER CODE BEGIN Init *//* additional user code for init *//* USER CODE END Init */
}/*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return fat_GetFatTimeFromRTC();/* USER CODE END get_fattime */
}/* USER CODE BEGIN Application *//* USER CODE END Application */
函数MX_FATFS_Init()用于FatFS的初始化,只有一行代码,即
retUSER=FATFS_LinkDriver(&USER_Driver,USERPath);
函数FATFS_LinkDriver()是在文件ff_gen_drv.h中定义的,USER_Driver是在文件user_diskio.c中定义的一个Diskio_drvTypeDef结构体类型的变量。执行这行代码的作用是将USER_Driver链接到FatFS管理的驱动器列表,将USERPath赋值为“0:”。
3、文件user_diskio.h和user_diskio.c
文件user_diskio.h中只有一行有效语句,就是声明了变量USER_Driver:
extern Diskio_drvTypeDef USER_Driver;
变量USER_Driver是在文件user_diskio.c中定义的,这个文件包含SPI-Flash芯片的Disk IO函数框架——用户需要自己编写代码实现这些函数。文件user_diskio.c的完整代码如下。为使程序结构更清晰,这里删除了对预编译条件_USE_WRITE和_USE_IOCTL的判断(默认情况下,这两个参数都是1),删除了代码沙箱段的定义,删除了函数参数的注释说明。这些代码都是CubeMX生成的初始代码。
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2017 */
/* */
/* Portions COPYRIGHT 2017 STMicroelectronics */
/* Portions Copyright (C) 2017, ChaN, all right reserved */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various existing */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff_gen_drv.h"#if defined ( __GNUC__ )
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#endif/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern Disk_drvTypeDef disk;/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @brief Gets Disk Status* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_status (BYTE pdrv /* Physical drive number to identify the drive */
)
{DSTATUS stat;stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);return stat;
}/*** @brief Initializes a Drive* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{DSTATUS stat = RES_OK;if(disk.is_initialized[pdrv] == 0){stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);if(stat == RES_OK){disk.is_initialized[pdrv] = 1;}}return stat;
}/*** @brief Reads Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data buffer to store read data* @param sector: Sector address (LBA)* @param count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT disk_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to read */
)
{DRESULT res;res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);return res;
}/*** @brief Writes Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data to be written* @param sector: Sector address (LBA)* @param count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT disk_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to write */
)
{DRESULT res;res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);return res;
}
#endif /* _USE_WRITE == 1 *//*** @brief I/O control operation* @param pdrv: Physical drive number (0..)* @param cmd: Control code* @param *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res;res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);return res;
}
#endif /* _USE_IOCTL == 1 *//*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
__weak DWORD get_fattime (void)
{return 0;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED#ifdef __cplusplus
extern "C" {
#endif#define _USE_WRITE 1 /* 1: Enable disk_write function */
#define _USE_IOCTL 1 /* 1: Enable disk_ioctl function */#include "integer.h"/* Status of Disk Functions */
typedef BYTE DSTATUS;/* Results of Disk Functions */
typedef enum {RES_OK = 0, /* 0: Successful */RES_ERROR, /* 1: R/W Error */RES_WRPRT, /* 2: Write Protected */RES_NOTRDY, /* 3: Not Ready */RES_PARERR /* 4: Invalid Parameter */
} DRESULT;/*---------------------------------------*/
/* Prototypes for disk control functions */DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DWORD get_fattime (void);/* Disk Status Bits (DSTATUS) */#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected *//* Command code for disk_ioctrl fucntion *//* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) *//* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media *//* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status *//* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */#ifdef __cplusplus
}
#endif#endif
这个文件定义了几个以“USER_”为前缀的函数,用于实现具体的Disk IO访问功能。这些函数在文件diskio.h中定义了,用作FatFS的通用Disk IO函数。
文件user_diskio.c定义了一个结构体Diskio_drvTypeDef类型的变量USER_Driver,用于表示USER驱动器访问接口。结构体Diskio_drvTypeDef在文件ff_gen_drv.h中定义,其成员变量就是5个函数指针,在定义USER_Driver时就为其成员变量赋值了,也就是指向本文件内定义的USER驱动器的Disk IO函数。
针对SPI-Flash芯片的FatFS移植,主要就是在文件user_diskio.c中完善这几个以“USER_”为前缀的函数,实现存储介质初始化、读取介质状态信息、以扇区为基本单位写入数据或读取数据。
4、文件ff_gen_drv.h和ff_gen_drv.c
文件ff_gen_drv.h定义了单个驱动器的硬件访问接口结构体类型Diskio_drvTypeDef,还定义了驱动器组的硬件访问结构体类型Disk_drvTypeDef。这个文件还定义了函数FATFS_LinkDriver(),就是MX_FATFS_Init()内调用的函数,用于将文件user_diskio.h中定义的驱动器对象USER_Driver链接到FatFS管理的驱动器列表里。文件ff_gen_drv.h的完整代码如下:
/********************************************************************************* @file ff_gen_drv.h* @author MCD Application Team* @brief Header for ff_gen_drv.c module.****************************************************************************** @attention** Copyright (c) 2017 STMicroelectronics. All rights reserved.** This software component is licensed by ST under BSD 3-Clause license,* the "License"; You may not use this file except in compliance with the* License. You may obtain a copy of the License at:* opensource.org/licenses/BSD-3-Clause*******************************************************************************
**//* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __FF_GEN_DRV_H
#define __FF_GEN_DRV_H#ifdef __cplusplusextern "C" {
#endif/* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff.h"
#include "stdint.h"/* Exported types ------------------------------------------------------------*//*** @brief Disk IO Driver structure definition*/
typedef struct
{DSTATUS (*disk_initialize) (BYTE); /*!< Initialize Disk Drive */DSTATUS (*disk_status) (BYTE); /*!< Get Disk Status */DRESULT (*disk_read) (BYTE, BYTE*, DWORD, UINT); /*!< Read Sector(s) */
#if _USE_WRITE == 1DRESULT (*disk_write) (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0 */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT (*disk_ioctl) (BYTE, BYTE, void*); /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */}Diskio_drvTypeDef;/*** @brief Global Disk IO Drivers structure definition*/
typedef struct
{uint8_t is_initialized[_VOLUMES];const Diskio_drvTypeDef *drv[_VOLUMES];uint8_t lun[_VOLUMES];volatile uint8_t nbr;}Disk_drvTypeDef;/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path);
uint8_t FATFS_UnLinkDriver(char *path);
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, BYTE lun);
uint8_t FATFS_UnLinkDriverEx(char *path, BYTE lun);
uint8_t FATFS_GetAttachedDriversNbr(void);#ifdef __cplusplus
}
#endif#endif /* __FF_GEN_DRV_H *//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
结构体Diskio_drvTypeDef是单个驱动器的Disk IO接口定义,其成员就是5个函数指针,用于指向具体存储介质的Disk IO函数。文件user_diskio.c定义的Diskio_drvTypeDef类型变量USER_Driver就是User-defined介质的Disk IO接口定义,在定义USER_Driver时就为其各个函数指针赋值了,指向user_diskio.c中定义的各个“USER_”函数。
文件ff_gen_drv.h还定义了一个结构体类型Disk_drvTypeDef,这是全局的驱动器的硬件接口定义,成员变量nbr表示物理驱动器个数,其他成员变量都是数组,数组长度是全局参数_VOLUMES,也就是卷的个数。其中Diskio_drvTypeDef*drv[_VOLUMES]是各个驱动器的硬件访问接口指针数组。所以,在FatFS中可以管理多个存储介质,例如,同时使用SPI-Flash芯片和SD卡,它们的硬件访问层接口函数可以分别管理。
文件ff_gen_drv.h定义了几个函数,FATFS_LinkDriver()和FATFS_LinkDriverEx()用于连接驱动器,FATFS_GetAttachedDriversNbr()用于返回FatFS当前连接的驱动器的个数,其他两个函数用于解除驱动器的连接。文件ff_gen_drv.c的代码如下:
/********************************************************************************* @file ff_gen_drv.c* @author MCD Application Team* @brief FatFs generic low level driver.****************************************************************************** @attention** Copyright (c) 2017 STMicroelectronics. All rights reserved.** This software component is licensed by ST under BSD 3-Clause license,* the "License"; You may not use this file except in compliance with the* License. You may obtain a copy of the License at:* opensource.org/licenses/BSD-3-Clause*******************************************************************************
**/
/* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
Disk_drvTypeDef disk = {{0},{0},{0},0};/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @brief Links a compatible diskio driver/lun id and increments the number of active* linked drivers.* @note The number of linked drivers (volumes) is up to 10 due to FatFs limits.* @param drv: pointer to the disk IO Driver structure* @param path: pointer to the logical drive path* @param lun : only used for USB Key Disk to add multi-lun managementelse the parameter must be equal to 0* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)
{uint8_t ret = 1;uint8_t DiskNum = 0;if(disk.nbr < _VOLUMES){disk.is_initialized[disk.nbr] = 0;disk.drv[disk.nbr] = drv;disk.lun[disk.nbr] = lun;DiskNum = disk.nbr++;path[0] = DiskNum + '0';path[1] = ':';path[2] = '/';path[3] = 0;ret = 0;}return ret;
}/*** @brief Links a compatible diskio driver and increments the number of active* linked drivers.* @note The number of linked drivers (volumes) is up to 10 due to FatFs limits* @param drv: pointer to the disk IO Driver structure* @param path: pointer to the logical drive path* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path)
{return FATFS_LinkDriverEx(drv, path, 0);
}/*** @brief Unlinks a diskio driver and decrements the number of active linked* drivers.* @param path: pointer to the logical drive path* @param lun : not used* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_UnLinkDriverEx(char *path, uint8_t lun)
{uint8_t DiskNum = 0;uint8_t ret = 1;if(disk.nbr >= 1){DiskNum = path[0] - '0';if(disk.drv[DiskNum] != 0){disk.drv[DiskNum] = 0;disk.lun[DiskNum] = 0;disk.nbr--;ret = 0;}}return ret;
}/*** @brief Unlinks a diskio driver and decrements the number of active linked* drivers.* @param path: pointer to the logical drive path* @retval Returns 0 in case of success, otherwise 1.*/
uint8_t FATFS_UnLinkDriver(char *path)
{return FATFS_UnLinkDriverEx(path, 0);
}/*** @brief Gets number of linked drivers to the FatFs module.* @param None* @retval Number of attached drivers.*/
uint8_t FATFS_GetAttachedDriversNbr(void)
{return disk.nbr;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*返回FatFS模块连接的驱动器的个数*/
uint8_t FATFS_GetAttachedDriversNbr(void)
{return disk.nbr; //FatFS模块连接的驱动器的个数
}
文件ff_gen_drv.c定义了一个Disk_drvTypeDef类型的全局变量disk,用于管理FatFS连接的所有驱动器的Disk IO驱动。在定义时就进行了初始化赋值,各数组成员变量只有一个元素,因为本示例中参数_VOLUMES为1。
Disk_drvTypeDef disk =({0),{0},{0},0}; //全局变量,FatFS连接的驱动器
在main()函数中调用的FatFS初始化函数MX_FATFS_Init()的代码如下:
void MX_FATFS_Init(void)
{/*##FatFs:连接USER驱动器###########################*/retUSER = FATFS_LinkDriver(&USER_Driver,USERPath);
}
其功能就是调用函数FATFS_LinkDriver(),将文件user_diskio.c中定义的USER驱动器的Disk IO驱动接口USER_Driver连接到FatFS。通过观察FATFS_LinkDriver()和FATFS_LinkDriverEx()的代码,读者就会发现,执行MX_FATFS_Init()后,USER驱动器的驱动器路径USERPath被赋值为“0:/”,USER_Driver被添加到了全局的驱动器管理变量disk中,特别是设置了Disk IO驱动器,即
disk.drv[disk.nbr]=drv;//驱动器的Disk IO驱动结构体
所以,disk.drv[disk.nbr]就是一个Diskio_drvTypeDef类型的驱动器Disk IO访问结构体指针,文件diskio.c中的各个Disk IO访问通用函数中会用到它。
5、文件diskio.h和diskio.c
文件diskio.h是Disk IO访问的通用定义文件,其中定义了基本的结构体、宏定义和DiskIO访问通用函数。在CubeMX生成的代码中,diskio.h和diskio.c是作为Disk IO访问的通用文件,具体器件的Disk IO访问函数的实现放在另外的文件里,例如,本示例User-defined介质的Disk IO访问函数是在文件user_diskio.c中实现的。
文件diskio.h的完整代码如下:
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED#ifdef __cplusplus
extern "C" {
#endif#define _USE_WRITE 1 /* 1: Enable disk_write function */
#define _USE_IOCTL 1 /* 1: Enable disk_ioctl function */#include "integer.h"/* Status of Disk Functions */
typedef BYTE DSTATUS;/* Results of Disk Functions */
typedef enum {RES_OK = 0, /* 0: Successful */RES_ERROR, /* 1: R/W Error */RES_WRPRT, /* 2: Write Protected */RES_NOTRDY, /* 3: Not Ready */RES_PARERR /* 4: Invalid Parameter */
} DRESULT;/*---------------------------------------*/
/* Prototypes for disk control functions */DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DWORD get_fattime (void);/* Disk Status Bits (DSTATUS) */#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected *//* Command code for disk_ioctrl fucntion *//* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) *//* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media *//* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status *//* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */#ifdef __cplusplus
}
#endif#endif
上述代码定义了Disk IO访问的6个基本函数,其中还有一些宏定义,主要是函数disk_ioctrl()里需要用到的一些指令码的定义。
对应的源程序文件diskio.c的完整代码如下:
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2017 */
/* */
/* Portions COPYRIGHT 2017 STMicroelectronics */
/* Portions Copyright (C) 2017, ChaN, all right reserved */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various existing */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
#include "diskio.h"
#include "ff_gen_drv.h"#if defined ( __GNUC__ )
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#endif/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern Disk_drvTypeDef disk;/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*** @brief Gets Disk Status* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_status (BYTE pdrv /* Physical drive number to identify the drive */
)
{DSTATUS stat;stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);return stat;
}/*** @brief Initializes a Drive* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS disk_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{DSTATUS stat = RES_OK;if(disk.is_initialized[pdrv] == 0){stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);if(stat == RES_OK){disk.is_initialized[pdrv] = 1;}}return stat;
}/*** @brief Reads Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data buffer to store read data* @param sector: Sector address (LBA)* @param count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT disk_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to read */
)
{DRESULT res;res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);return res;
}/*** @brief Writes Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data to be written* @param sector: Sector address (LBA)* @param count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT disk_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to write */
)
{DRESULT res;res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);return res;
}
#endif /* _USE_WRITE == 1 *//*** @brief I/O control operation* @param pdrv: Physical drive number (0..)* @param cmd: Control code* @param *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res;res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);return res;
}
#endif /* _USE_IOCTL == 1 *//*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
__weak DWORD get_fattime (void)
{return 0;
}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
从文件diskio.c的各函数代码可以看到,几个Disk IO通用函数的代码实质上就是执行了变量disk中物理驱动器的Disk IO函数。对于本示例来说,只有一个驱动器,disk.drv[pdrv]就是USER_Driver,也就是User-defined驱动器,它的Disk IO函数就是文件user_diskio.c中前缀为“USER_”的几个函数。
在文件diskio.c中,函数get_fattime()使用了编译修饰符_weak,是一个弱函数。这个函数在任何文件里都可以重新实现,其框架在文件fatfs.c中重新定义。函数get_fatime()用于从RTC获取日期时间作为文件系统的时间戳数据。
所以,要针对SPI-Flash芯片W25Q16进行移植,只需实现文件user_diskio.c中前缀为“USER_”的几个Disk IO访问函数,以及文件fatfs.c中的函数get_fattime(),其他的工作都由CubeMX自动生成的代码完成了。
四、Disk IO函数的实现
要实现SPI-Flash芯片W25Q16的FatFS移植,只需实现文件user_diskio.c中前缀为“USER_”的几个Disk IO函数,以及文件fatfs.c中的函数get_fattime()。
1、获取驱动器状态的函数USER_status()
文件diskio.h中有如下3个驱动器状态位宏定义:
#define STA_NOINIT0x01 //驱动器未初始化
#define STA_NODISK0x02 //驱动器中无存储介质
#define STA_PROTECT0x04 //写保护
函数USER_status()用于返回驱动器的状态,如果存在以上的状态,就将相应的状态位置1,否则,返回0x00即可。对于开发板上的W25Q16芯片来说,没有写保护问题,只存在是否已初始化的问题。所以,完成后的USER_status()函数代码以及user_diskio.c文件头的一些定义如下:
/* USER CODE BEGIN Header */
/********************************************************************************* @file user_diskio.c* @brief This file includes a diskio driver skeleton to be completed by the user.******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************//* USER CODE END Header */#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/** Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)* To be suppressed in the future.* Kept to ensure backward compatibility with previous CubeMx versions when* migrating projects.* User code previously added there should be copied in the new user sections before* the section contents can be deleted.*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif/* USER CODE BEGIN DECL *//* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*//* Private variables ---------------------------------------------------------*/
#include "w25flash.h"
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;/* USER CODE END DECL *//* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */Diskio_drvTypeDef USER_Driver =
{USER_initialize,USER_status,USER_read,
#if _USE_WRITEUSER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};/* Private functions ---------------------------------------------------------*//*** @brief Initializes a Drive* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{/* USER CODE BEGIN INIT */Stat =USER_status(pdrv); /* Get the drive status */return Stat;/* USER CODE END INIT */
}/*** @brief Gets Disk Status* @param pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_status (BYTE pdrv /* Physical drive number to identify the drive */
)
{/* USER CODE BEGIN STATUS */Stat = STA_NOINIT; // Drive not initialized, Stat=0x01if (0 != Flash_ReadID()) // Read the ID of the Flash chip. As long as it is not 0, it means that En25Q16 has been initialized.Stat &= ~STA_NOINIT; // Stat=0x00return Stat;/* USER CODE END STATUS */
}/*** @brief Reads Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data buffer to store read data* @param sector: Sector address (LBA)* @param count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT USER_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to read */
)
{/* USER CODE BEGIN READ */uint32_t globalAddr= sector<<12; //The sector number is shifted left by 12 bits to get the absolute starting addressuint16_t byteCount = count<<12; //The number of bytes, shifting left by 12 bits is multiplied by 4096, and each sector is 4096 bytes.Flash_ReadBytes(globalAddr, buff, byteCount);//Read datareturn RES_OK;/* USER CODE END READ */
}/*** @brief Writes Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data to be written* @param sector: Sector address (LBA)* @param count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to write */
)
{/* USER CODE BEGIN WRITE *//* USER CODE HERE */uint32_t globalAddr = sector<<12; // Absolute addressuint16_t byteCount = count<<12; // Number of bytesFlash_WriteSector(globalAddr, buff, byteCount);return RES_OK;/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 *//*** @brief I/O control operation* @param pdrv: Physical drive number (0..)* @param cmd: Control code* @param *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{/* USER CODE BEGIN IOCTL */DRESULT res = RES_OK;switch(cmd){case CTRL_SYNC: /* Complete the pending write operation process when _FS_READONLY == 0 */break;case GET_SECTOR_COUNT: /* Obtain storage media capacity when _USE_MKFS == 1 */*(DWORD *)buff=FLASH_SECTOR_COUNT; /* Total number of sectors is 4096 */break;case GET_SECTOR_SIZE: /* Get sector size when _MAX_SS != _MIN_SS */*(DWORD *)buff=FLASH_SECTOR_SIZE; /* each sector is 4096 bytes */break;case GET_BLOCK_SIZE: /* When _USE_MKFS == 1, get the size of the erase block */*(DWORD *)buff=16; /* W25Q16锟斤拷32 Block锟斤拷16*32=512 sector */break;default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
上述代码包含了头文件w25flash.h,这是Flash存储芯片W25Q16的驱动程序文件,在实现这些Disk IO函数时,要用到W25Q16的驱动函数。Stat是文件user_diskio.c中定义的一个表示驱动器状态的私有变量,在多个函数中都会用到。
函数USER_status()的输入参数pdrv是驱动器编号,如果系统中有多个驱动器,就需要通过参数pdrv区分不同的驱动器。本例只有一个驱动器,pdrv为0,也就无须区分驱动器。
使用W25Q16驱动程序文件w25flash.h中定义的函数Flash_ReadID()读取芯片ID,只要读取的ID不为0,就说明连接芯片W25Q16的SPI2接口已经初始化。
函数USER_status()的返回值类型为DRESULT,这是文件diskio.h中定义的枚举类型,详见前面文件diskio.h的完整代码。返回值为0x00(即枚举值RES_OK),表示驱动器状态正常;否则,返回STA_NOINIT,表示驱动器未初始化。
2、驱动器初始化函数USER_initialize()
函数USER_initialize()用于驱动器硬件接口的初始化,对于W25Q16来说,就是与其连接的SPI2接口的初始化。在手工移植FatFS的代码时,一般要在函数disk_initialize()里进行存储介质的硬件接口初始化,但是在本示例中,SPI2接口的初始化是由CubeMX自动生成的函数MX_SPI2_Init()完成的,在执行MX_FATFS_Init()之前就已经执行了MX_SPI2_Init()。所以,这里无须再对SPI2接口进行初始化。
完成后USER_initialize()函数的代码如下,这里调用函数USER_status()获取驱动器状态,而函数USER_status()的返回值总是0x00,所以表示初始化成功。
DSTATUS USER_initialize(BYTE pdrv)
{/*USER CODE BEGIN INIT*/Stat =USER_status(pdrv); //获取驱动器状态return Stat;/*USER CODE END INIT*/
}
3、驱动器IO控制函数USER_ioctl()
函数USER_ioctl()用于执行Disk IO访问时的一些操作,如获取总的扇区个数、获取扇区大小等。只有当宏定义_USE_IOCTL等于1时才有这个函数,这个宏在文件diskio.h中定义,默认值为1。完成后函数USER_ioctl()的代码如下:
/*** @brief I/O control operation* @param pdrv: Physical drive number (0..)* @param cmd: Control code* @param *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{/* USER CODE BEGIN IOCTL */DRESULT res = RES_OK;switch(cmd){case CTRL_SYNC: /* Complete the pending write operation process when _FS_READONLY == 0 */break;case GET_SECTOR_COUNT: /* Obtain storage media capacity when _USE_MKFS == 1 */*(DWORD *)buff=FLASH_SECTOR_COUNT; /* Total number of sectors is 4096 */break;case GET_SECTOR_SIZE: /* Get sector size when _MAX_SS != _MIN_SS */*(DWORD *)buff=FLASH_SECTOR_SIZE; /* each sector is 4096 bytes */break;case GET_BLOCK_SIZE: /* When _USE_MKFS == 1, get the size of the erase block */*(DWORD *)buff=16; /* W25Q16锟斤拷32 Block锟斤拷16*32=512 sector */break;default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */
其中,参数cmd是操作指令,这些指令是一些宏定义常数,在文件diskio.h中定义;buff是用于接收或发送数据的缓冲区指针。函数USER_ioctl()实现了如下4个通用指令的处理。
- 指令CTRL_SYNC用于完成挂起的写操作过程,只要_FS_READONLY==0就需要响应这个指令。这是指存储介质写入数据时是否有缓存操作:如果有缓存,就需要将缓存数据写入介质,如果写操作都是直接写入存储介质的,直接返回RES_OK即可。
- 指令GET_SECTOR_COUNT用于获取存储介质的扇区个数,如果_USE_MKFS==1,则需要响应此指令。在使用函数f_mkfs()和f_fdisk()时,这个指令决定卷的大小是需要用到的。芯片W25Q16共有512个扇区。
- 指令GET_SECTOR_SIZE用于获取扇区大小,如果_MAX_SS不等于_MIN_SS,则要用到这个指令。芯片W25Q16的扇区大小是4096字节(4KB)。
- 指令GET_BLOCK_SIZE用于获取擦除块的大小(以扇区为单位),必须是1和32768之间的2的幂次数。如果返回值是1,则表示擦除块的大小是未知的,或没有Flash存储介质。这个指令只有函数f_mkfs()使用,在_USE_MKFS ==1时需要响应此指令。W25Q16的一个块有16个扇区,所以这里设置为16。
4、读取扇区数据的函数USER_read()
函数USER_read()用于从W25Q16芯片读取一个或多个扇区的数据,完成后的代码如下:
/*** @brief Reads Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data buffer to store read data* @param sector: Sector address (LBA)* @param count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT USER_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to read */
)
{/* USER CODE BEGIN READ */uint32_t globalAddr= sector<<12; //The sector number is shifted left by 12 bits to get the absolute starting addressuint16_t byteCount = count<<12; //The number of bytes, shifting left by 12 bits is multiplied by 4096, and each sector is 4096 bytes.Flash_ReadBytes(globalAddr, buff, byteCount);//Read datareturn RES_OK;/* USER CODE END READ */
}
其中,参数buff是用来存储读出数据的缓冲区,sector是读取数据的起始扇区编号,count是要读出数据的扇区个数。
上述程序使用W25Q16的驱动函数Flash_ReadBytes()读出数据,这个函数需要数据绝对起始地址作为输入参数。对于W25Q16来说,将扇区编号sector左移12位得到的就是这个扇区的绝对起始地址。每个扇区是4096字节,将扇区个数count左移12位就等于乘以4096,也就是总的字节数。
函数Flash_ReadBytes()是文件w25flash.c中的W25Q16驱动程序函数,其代码如下。
//从任何地址开始读取指定长度的数据
//globalAddr:开始读取的地址(24bit), pBuffer:数据存储区指针,byteCount:要读取的字节数
void Flash_ReadBytes(uint32_t globalAddr,uint8_t* pBuffer,uint16_t byteCount)
{ uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr,&byte2,&byte3,&byte4);//24位地址分解为3个字节__Select_Flash(); //CS=0SPI_TransmitOneByte(0x03); //Command=0x03, read dataSPI_TransmitOneByte(byte2); //发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);SPI_ReceiveBytes(pBuffer,byteCount);//接收byteCount个字节数据__Deselect_Flash(); //CS=1
}
5、将数据写入扇区的函数USER_write()
函数USER_write()用于将一个缓冲区内的数据写入Flash芯片W25Q16,完成后的代码如下:
/*** @brief Writes Sector(s)* @param pdrv: Physical drive number (0..)* @param *buff: Data to be written* @param sector: Sector address (LBA)* @param count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */DWORD sector, /* Sector address in LBA */UINT count /* Number of sectors to write */
)
{/* USER CODE BEGIN WRITE *//* USER CODE HERE */uint32_t globalAddr = sector<<12; // Absolute addressuint16_t byteCount = count<<12; // Number of bytesFlash_WriteSector(globalAddr, buff, byteCount);return RES_OK;/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */
其中,buff是待写入Flash芯片的数据缓冲区的指针,sector是起始扇区编号,count是需要写入的扇区个数。
上述程序使用W25Q16的驱动函数Flash_WriteSector()写入数据,同样,先通过扇区号得到绝对地址,通过扇区个数得到字节个数。函数Flash_WriteSector()的代码如下:
// 从某个Sector的起始位置开始写数据,数据可能跨越多个Page,甚至跨越Sector,不必提前擦除
// globalAddr是写入初始地址,全局地址,是扇区的起始地址,
// pBuffer是要写入数据缓冲区指针
// byteCount是需要写入的数据字节数,byteCount不能超过64K,也就是一个Block(16个扇区)的大小,但是可以超过一个Sector(4K字节)
// 如果数据超过一个Page,自动分成多个Page,调用EN25Q_WriteInPage分别写入
void Flash_WriteSector(uint32_t globalAddr,const uint8_t* pBuffer,uint16_t byteCount)
{
//需要先擦除扇区,可能是重复写文件uint8_t secCount = (byteCount/FLASH_SECTOR_SIZE);//数据覆盖的扇区个数if ((byteCount%FLASH_SECTOR_SIZE) > 0)secCount++;uint32_t startAddr = globalAddr;for (uint8_t k=0;k<secCount;k++){Flash_EraseSector(startAddr); //擦除扇区startAddr += FLASH_SECTOR_SIZE; //移到下一个扇区}//分成Page写入数据,写入数据的最小单位是Pageuint16_t leftBytes = byteCount%FLASH_PAGE_SIZE; //非整数个Page剩余的字节数,即最后一个Page写入的数据uint16_t pgCount = byteCount/FLASH_PAGE_SIZE; //前面整数个Pageuint8_t* buff = (uint8_t*)pBuffer;for(uint16_t i=0;i<pgCount;i++) //写入前面pgCount个Page的数据,{Flash_WriteInPage(globalAddr,buff,FLASH_PAGE_SIZE);//写一整个Page的数据globalAddr += FLASH_PAGE_SIZE; //地址移动一个Pagebuff += FLASH_PAGE_SIZE; //数据指针移动一个Page大小}if (leftBytes > 0)Flash_WriteInPage(globalAddr,buff,leftBytes); //最后一个Page,不是一整个Page的数据
}
W25Q16擦除操作的最小单位是扇区,写入数据操作的基本单位是页。在写入数据之前,程序需要调用Flash_EraseSector()擦除要用到的扇区,因为可能是已有文件的重复写入。然后,调用函数Flash_WriteInPage()将数据分解为多个页写入Flash芯片。
6、获取RTC时间的函数get_fattime()
函数get_fattime()用于获取RTC时间,作为创建文件或修改文件的时间戳数据。文件diskio.c中的函数get_fattime()是用编译修饰符_weak定义的弱函数,在文件fatfs.c中重新实现这个函数。完成后的函数代码如下:
/*** @brief Gets Time from RTC* @param None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */return fat_GetFatTimeFromRTC();/* USER CODE END get_fattime */
}
其中at_GetFatTimeFromRTC(),在用户程序file_opera.c中定义:
//Get time from RTC as the file system time stamp data
DWORD fat_GetFatTimeFromRTC()
{RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);WORD date=(2000+sDate.Year-1980)<<9;date = date |(sDate.Month<<5) |sDate.Date;WORD time=sTime.Hours<<11;time = time | (sTime.Minutes<<5) | (sTime.Seconds>1);DWORD dt=(date<<16) | time;return dt;}elsereturn 0;
}
函数get_fattime()需要返回一个DWORD类型的数,这个数的高16位是日期,低16位是时间。其中秒的数据是实际秒时间的一半。
在保存文件时,FatFS会自动调用函数get_fattime()获取RTC时间,然后作为文件的时间戳信息写入FAT里。在使用函数f_stat()获取文件信息时,返回的是一个FILINFO结构体变量,其成员变量fdate和ftime就是文件的修改时间。
针对SPI-Flash芯片和RTC完成以上的6个函数后,就完成了FatFS针对硬件层的移植,后面就可以使用FatFS的应用层API函数在SPI-Flash芯片上创建FAT文件系统,管理文件了。
五、在SPI-Flash芯片上使用文件系统
1、主程序功能
完成硬件层移植后,我们就可以使用FatFS的API函数在W25Q16芯片上创建FAT文件系统,进行文件和目录的管理,以及文件读写操作。
在首次使用一个存储介质时,我们需要先执行函数f_mkfs()将存储介质格式化,也就是创建FAT或exFAT文件系统。在格式化之后,存储介质就成了一个驱动器。在嵌入式系统中,一般不会在一个存储介质上进行分区,所以一个物理驱动器上只有一个卷,也就是一个逻辑驱动器。
要使用一个驱动器,需要先使用函数f_mount()将其挂载到文件系统对象,然后才可以进行文件管理和文件读写操作。如果需要在程序运行期间弹出一个驱动器,还可以使用函数f_mount()卸载它,只需将文件系统指针参数设置为NULL即可。
本示例演示FAT文件系统的一些基本操作,包括磁盘格式化、创建文件、读取文件、获取磁盘信息、获取文件信息等。主程序代码如下:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "rtc.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ff.h"
#include "keyled.h"
#include "w25flash.h"
#include "file_opera.h"
#include <stdio.h>
/* USER CODE END Includes *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* 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_FATFS_Init();MX_RTC_Init();MX_SPI2_Init();MX_USART6_UART_Init();/* USER CODE BEGIN 2 */// Start Menuuint8_t startstr[] = "Demo12_1: FatFS on SPI-Flash chip.\r\n";HAL_UART_Transmit(&huart6,startstr,sizeof(startstr),0xFFFF);FRESULT res=f_mount(&USERFatFS, "0:", 1); //Mount the driveif (res==FR_OK) //Mounted successfullyprintf("FatFS is mounted, OK.\r\n");elseprintf("No file system.\r\n");//The menu item start line is used to clear the area when drawing the second group of menus.//Group 1 Menuprintf("[1][S2]KeyUp = Format chip.\r\n");printf("[2][S4]KeyLeft = FAT disk info.\r\n");printf("[3][S5]KeyRight = List all entries.\r\n");printf("[4][S3]KeyDown = Next menu page.\r\n");KEYS waitKey; //Key inputwhile(1){waitKey=ScanPressedKey(KEY_WAIT_ALWAYS); //Waiting for the button//Format Flash chips and create file systemif (waitKey == KEY_UP){BYTE workBuffer[FLASH_SECTOR_SIZE]; //FLASH_SECTOR_SIZE=4096DWORD clusterSize=2*FLASH_SECTOR_SIZE;//The cluster must be greater than or equal to 1 sectorprintf("Formatting the chip...\r\n");FRESULT res=f_mkfs("0:", FM_FAT, clusterSize, workBuffer, FLASH_SECTOR_SIZE);//Create a file system. The cluster size must be greater than or equal to 1 sector.//The workBuffer size should be an integer multiple of the sector size.if (res ==FR_OK)printf("Format OK.\r\n");elseprintf("Format fail.\r\n");}else if(waitKey == KEY_LEFT)fatTest_GetDiskInfo(); //Get and display disk informationelse if (waitKey == KEY_RIGHT)fatTest_ScanDir("0:/"); //Scan the files and directories in the root directoryelse if (waitKey == KEY_DOWN)break; //Scan the files and directories in the root directoryprintf("Reselect menu item or reset.\r\n");HAL_Delay(500); //Delay 500 to eliminate the impact of key jitter}//Group 2 Menuprintf("[5][S2]KeyUp = Write files.\r\n");printf("[6][S4]KeyLeft = Read a TXT file.\r\n");printf("[7][S5]KeyRight = Read a BIN file.\r\n");printf("[8][S3]KeyDown = Get a file info.\r\n");HAL_Delay(500); //Delay 500 to eliminate the impact of key jitterwhile(2){//Waiting for the buttonwaitKey=ScanPressedKey(KEY_WAIT_ALWAYS);if (waitKey==KEY_UP ) //Write a file test{fatTest_WriteTXTFile("readme.txt",2019,3,5);fatTest_WriteTXTFile("help.txt",2016,11,15);fatTest_WriteBinFile("ADC500.dat",20,500);fatTest_WriteBinFile("ADC1000.dat",50,1000);f_mkdir("0:/SubDir1"); //Create a directoryf_mkdir("0:/MyDocs"); //Create a directory}else if (waitKey==KEY_LEFT )fatTest_ReadTXTFile("readme.txt"); //Test reading text fileselse if (waitKey==KEY_RIGHT)fatTest_ReadBinFile("ADC500.dat"); //Test reading binary fileselse if (waitKey==KEY_DOWN)fatTest_GetFileInfo("ADC1000.dat"); //Test to obtain file informationprintf("Reselect menu item or reset.\r\n");HAL_Delay(500); //Delay, eliminate the impact of key jitter}/* USER CODE END 2 */// 省略/* USER CODE BEGIN 4 */
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END 4 */// 省略以下代码
其中,include部分包含了一个文件file_opera.h,是本示例创建的用于测试文件操作的程序文件。因为这些文件操作测试函数与硬件无关,所以也可以在后面用于SD卡、U盘的文件操作测试。
在外设初始化部分,函数MX_SPI2_Init()对与W25Q16连接的SPI2接口进行初始化,函数MX_FATFS_Init()用于FatFS的初始化。硬件初始化完成后,执行函数f_mount()立即挂载文件系统,即
FRESULT res=f_mount(&USERFatFS,"0:",1); //挂载文件系统
其中,USERFatFS是在文件fatfs.c中定义的FATFS类型变量,表示文件系统。系统中只有一个驱动器,驱动器号是“0:”。这样挂载后,USERFatFS就表示逻辑驱动器0上的文件系统。
如果函数f_mount()的返回值为FR_OK,就表示驱动器挂载成功,可以进行文件系统的操作了;否则,就是没有文件系统,需要先执行函数f_mkfs()进行格式化操作。
不管函数f_mount()的返回值是什么,程序会在串口助手上显示一组菜单,内容如下:
[1][S2]KeyUp =Format chip
[2][S4]KeyLeft =FAT disk info
[3][S5]KeyRight=List all entries
[4][S3]KeyDown =Next menu page
使用开发板上的4个按键进行选择操作,函数ScanPressedKey()是文件keyled.h中定义的轮询方式检测按键的函数。按下KeyDown后会显示第2组菜单,内容如下:
[5][S2]KeyUp=Write files
[6][S4]KeyLeft =Read a TXT file
[7][S5]KeyRight=Read a BIN file
[8][S3]KeyDown =Get a file info