目录

一、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为168MHzPCLK1的频率为42MHzSPI2是挂在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编码页,编码页如果选择不正常,可能
导致打开文件失败。如果要支持中文,应该选择Simplified Chinese
(DBCS),对应的_CODE_PAGE参数值是936

USE_LFN

Disabled

是否使用长文件名(LFN)

MAX_LFN

255

设定值范围为12至255,是LFN的最大长度

LFN_UNICODE

ANSI/OEM

是否将FatFS API中的字符编码切换为Unicode。当USE_LFN设置为
Enabled时, LFN_UNICODE才可以设置为1(Unicode)。当USE_LFN
设置为Disabled时, LFN_UNICODE只能设置为ANSI/OEM

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字节,所
以设置为4096。当MAX_SS大于512时,在disk_ioctl()函数中需要
实现GET_SECTOR_SIZE指令

MIN_SS

512

最小扇区大小(字节数),只能设置为512、1024、2048或4096

MULTI_PARTITION

Disabled

设置为Disabled时,每个卷与相同编号的物理驱动器绑定,只会挂
载第一个分区。设置为Enabled时,每个卷与分区表VolToPart[]关联

USE_TRIM

Disabled

是否使用ATA_TRIM特性。要想使用Trim特性,需要在disk_ioctl()
函数中实现CTRL_TRIM指令

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)可减少
内存占用512字节

FS_EXFAT

Disabled

是否支持exFAT文件系统,当_USE_LFN设置为0时,这个参数只
能设置为Disabled

FS_NORTC

Dynamic
timestamp

如果系统有RTC提供实时的时间,就设置为Dynamic timestamp;如
果系统没有RTC提供实时的时间,就设置为Fixed timestamp

FS_REENTRANT

Disabled

设置FatFS的可重入性,在CubeMX里如果没有启用FreeRTOS,这
个参数只能设置为Disabled,如果启用了FreeRTOS这个参数只能
设置为Enabled

FS_TIMEOUT

1000

超时设置,单位是节拍数。FS_REENTRANT设置为Disabled时,这
个参数无效

FS_LOCK

2

如果要启用文件锁定功能,设定FS_LOCK的值大于或等于1表示
可同时打开的文件个数。设定值范围为0~255

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87624.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87624.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/87624.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu 22.04 安装部署logstash 7.10.0详细教程

安装部署logstash 7.10.0详细教程 一、下载并安装二、新建配置文件三、赋权文件权限四、检测文件grok语法是否异常五、启动服务六、安装启动常见问题 【背景】 整个elk安装是基于ubuntu 22.04和jdk 11环境。logstash采用 *.deb方式安装&#xff0c;需要服务器能联网。ubuntu 22…

JVM对象创建与内存分配机制深度剖析

对象创建的主要流程 类加载检查 在创建对象之前&#xff0c;JVM 首先会检查该类是否已经加载、解析并初始化&#xff1a; 如果没有&#xff0c;则会通过类加载机制加载类元信息&#xff08;Class Metadata&#xff09;到方法区。 这个过程包括&#xff1a;加载&#xff08;load…

Navicat 技术指引 | TiDB 的 AI 查询交互功能

目前&#xff0c;Navicat 两款工具支持对 TiDB 数据库的管理开发功能&#xff1a;一款是旗舰款 Navicat Premium&#xff0c;另一款是其轻量化功能的 Navicat Premium Lite&#xff08;官方轻量级免费版&#xff09;。Navicat 自版本 17.1 开始支持 TiDB 7。它支持的系统有 Win…

以list为输入条件,查询数据库表,java中的mapper层和mybatis层应该怎么写?

根据一个 List 中的两个字段 rangeCode 和 unitcd&#xff0c;查询数据库表 model_engineering_spatial_unit。这个需求在 Java MyBatis 项目中非常常见&#xff0c;下面我将为你详细写出 Mapper 接口&#xff08;Java&#xff09; 和 MyBatis XML 映射文件 的写法。 ✅ 前提…

pyspark 创建DataFrame

from pyspark.sql import SparkSession from pyspark.sql import StructType, StructField, IntegerType,StringType spark SparkSession.builder.appName(test).getOrCreate() 1、 从列表中创建DataFrame data [(1,"alice"),(2,Blob),(3,Charlie)] columns [&qu…

Vim:从入门到进阶的高效文本编辑器之旅

目录 一、Vim简介 二、Vim的基础操作 2.1 进入和退出Vim 2.2 Vim的三种模式 2.3 基础移动 三、Vim的高效编辑技巧 3.1 文本编辑 3.2 文本删除与修改 3.3 复制与粘贴 四、Vim的进阶使用 4.1 搜索与替换 4.2 寄存器与宏 4.3 插件与配置 五、结语 在编程界&#xff0…

Docker基础理论与阿里云Linux服务器安装指南

文章目录 一、Docker核心概念二、阿里云环境准备三、Docker安装与配置四、核心容器部署示例五、开发环境容器化六、运维管理技巧七、安全加固措施 一、Docker核心概念 容器化本质&#xff1a; 轻量级虚拟化技术&#xff0c;共享主机内核进程级隔离&#xff08;cgroups/namespac…

c#使用笔记之try catch和throw

一、try catch 一种报错的捕捉机制&#xff0c;try块里运行的代码出现错误的时候就会去执行catch块所以一般catch块里都是把错误打印出来或者保存到log日志里&#xff1b; 1.1、具体使用 catch可以用&#xff08;&#xff09;来选择捕捉什么类型的错误&#xff0c;一般用Exc…

(新手友好)MySQL学习笔记(9):索引(常见索引类型,查找结构的发展(二分查找法,二叉搜索树,平衡二叉树,B树,B+树))

目录 索引 常见索引类型 B树 二分查找法 二叉搜索树和平衡二叉树 B树和B树 索引 index&#xff0c;是存储引擎用于快速找到数据的一种数据结构。 MySQL默认使用InnoDB存储引擎&#xff0c;该存储引擎是最重要&#xff0c;使用最广泛的&#xff0c;除非有非常特别的原因需要使用…

进程间通信1(匿名管道)Linux

1 进程间通信的必要性 首先要明确进程间是相互独立的&#xff08;独享一份虚拟地址空间&#xff0c;页表&#xff0c;资源&#xff09;&#xff0c;那怎么样才能使得两个进程间实现资源的发送&#xff1f;所以&#xff0c;两个进程一定需要看到同一份资源&#xff0c;并且⼀个…

CAN2.0、DoIP、CAN-FD汽车协议详解与应用

一、CAN2.0 协议详解与应用示例 1. 技术原理与特性 协议架构&#xff1a;基于 ISO 11898 标准&#xff0c;采用载波监听多路访问 / 冲突检测&#xff08;CSMA/CD&#xff09;机制&#xff0c;支持 11 位&#xff08;CAN2.0A&#xff09;或 29 位&#xff08;CAN2.0B&#xff…

使用nvm管理npm和pnpm

1.使用nvm管理npm // 查看nvm版本 nvm -v // 查看可安装的 node 版本 nvm ls-remote // 安装指定 node 版本 nvm install 24.0.0 // 查看当前已安装的 node 版本及当前使用的版本 nvm list // 使用某个版本 node nvm use 24.0.0 // 卸载指定 node 版本 nvm uninstall 16.20.1…

YOLO11+QT6+Opencv+C++训练加载模型全过程讲解

实现效果&#xff1a; Yolov11环境搭建&#xff08;搭建好的可以直接跳过&#xff09; 最好使用Anconda进行包管理&#xff0c;安装可参考【文章】。下面简单过一下如何快速部署环境。如果搭建过或可以参考其他文章可以跳过Yolo11环境搭建这一章节。总体来说Yolov11环境搭建越…

Python 脚本,用于将 PDF 文件高质量地转换为 PNG 图像

import os import fitz # PyMuPDF from PIL import Image import argparse import logging from tqdm import tqdm# 配置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(PDF2PNG)def convert_pdf_…

【CUDA GPU 支持安装全攻略】PyTorch 深度学习开发者指南

PyTorch 的 CUDA GPU 支持 安装五条铁律&#xff08;最新版 2025 修订&#xff09;&#xff08;适用于所有用户&#xff09;-CSDN博客 是否需要预先安装 CUDA Toolkit&#xff1f;——按使用场景分级推荐及进阶说明-CSDN博客 “100% 成功的 PyTorch CUDA GPU 支持” 安装攻略…

Cyberith 运动模拟器Virtualizer2:提升虚拟现实沉浸体验

奥地利Cyberith公司是一家专注于虚拟现实&#xff08;VR&#xff09;互动解决方案的创新型科技企业&#xff0c;以其研发的Virtualizer虚拟现实步态模拟设备而闻名。该公司的核心技术体现在其设计和制造的全方位跑步机式VR交互平台上&#xff0c;使得用户能够在虚拟环境中实现自…

常见的数据处理方法有哪些?ETL中的数据处理怎么完成

在数字化转型纵深推进的背景下&#xff0c;数据作为新型生产要素已成为驱动企业战略决策、科研创新及智能化运营的核心战略资产。数据治理价值链中的处理环节作为关键价值节点&#xff0c;其本质是通过系统化处理流程将原始观测数据转化为结构化知识产物&#xff0c;以支撑预测…

WHAT - 为甲方做一个官网(二)- 快速版

文章目录 一、明确需求优先级&#xff08;快速决策&#xff09;二、推荐零代码/低代码工具&#xff08;附对比&#xff09;方案1&#xff1a;低代码建站平台&#xff08;适合无技术用户&#xff0c;拖拽式操作&#xff09;方案2&#xff1a;CMS系统&#xff08;适合内容更新频繁…

音视频之H.264视频编码传输及其在移动通信中的应用

系列文章&#xff1a; 1、音视频之视频压缩技术及数字视频综述 2、音视频之视频压缩编码的基本原理 3、音视频之H.264/AVC编码器原理 4、音视频之H.264的句法和语义 5、音视频之H.264/AVC解码器的原理和实现 6、音视频之H.264视频编码传输及其在移动通信中的应用 7、音视…

C#语言入门-task2 :C# 语言的基本语法结构

下面从四个方面对C#的基本语法进行简单介绍&#xff1a; 1. 数据类型 C#的类型可分为值类型和引用类型。值类型变量直接存储数据&#xff0c;引用类型变量则存储对象的引用。 值类型&#xff1a;涵盖整数类型&#xff08;像int、long&#xff09;、浮点类型&#xff08;例如…