STM32CubeMX教程26 FatFs 文件系统 - W25Q128读写

文章目录

  • 1、准备材料
  • 2、实验目标
  • 3、实验流程
    • 3.0、前提知识
    • 3.1、CubeMX相关配置
      • 3.1.0、工程基本配置
      • 3.1.1、时钟树配置
      • 3.1.2、外设参数配置
      • 3.1.3、外设中断配置
    • 3.2、生成代码
      • 3.2.0、配置Project Manager页面
      • 3.2.1、外设初始化调用流程
      • 3.2.2、外设中断调用流程
      • 3.2.3、添加其他必要代码
  • 4、烧录验证
  • 5、常用函数
  • 6、注释详解
  • 参考资料


1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0)

keil µVision5 IDE(MDK-Arm)

ST-LINK/V2驱动

野火DAP仿真器

XCOM V2.6串口助手

2、实验目标

使用STM32CubeMX软件配置STM32F407开发板使用FatFs中间件通过SPI通信协议对W25Q128芯片进行读写等操作

3、实验流程

3.0、前提知识

关于STM32F407使用SPI通信协议对W25Q128 FLASH芯片读写等操作涉及的SPI通信协议及W25Q128芯片相关知识请读者阅读STM32CubeMX教程20 SPI - W25Q128驱动实验,本实验不再过多介绍

对于容量较小的存储设备可以使用底层库函数直接根据内存地址对设备来进行读写,但是一旦存储设备容量稍大,直接根据地址对设备来进行读写将变得比较困难

这个时候使用文件系统来对存储设备进行各种操作将比较方便,FatFs是适用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块,它与磁盘I/O层完全分离,可以独立于硬件平台,因此非常方便移植

STM32CubeMX Version 6.10.0 中在中间件和软件包 Middleware and Software Packs 中集成了R0.12c 版本的FatFs文件系统模块,这个中间件支持Extemal SRAM、SD Card、USB Disk和User-defined四种模式,其中外部SRAM需要启用FSMC连接SRAM功能后才可以勾选,SD卡需要启用SDIO功能之后才可以勾选,USB Disk需要配置USB为大容量存储主机类功能后才可以勾选,User-defined则任何时候都可以勾选,将FatFs配置在User-defined模式下就可以利用FatFs使用除上述提到的三种存储之外的设备,比如SPI FLASH等

我们可以通过其官网FatFs历史版本记录找到 R0.12c 版本的FatFs源码,将其下载下来之后观察其源码目录结构如下图所示

请添加图片描述

其中ff.c/ff.h为FatF模块的源码;fconf.h文件为模块配置文件,可以通过宏定义来选择哪些功能开启,哪些功能关闭;integer.h文件为变量类型重命名文件,主要是为了兼容不同的变量类型命名;option文件夹中的文件为unicode编码文件和操作系统相关函数的文件;上面提到的这些文件用户一般无需修改

如果读者希望手动移植FatFs到自己的嵌入式系统上,则应重点关注源码中diskio.c/diskio.h两个文件,这两个文件中需要根据用户使用的RAM、MMC和USB这几个不同的内存类型来实现以下几个底层函数,函数如下列表所示,完成之后就可以直接通过FatFs提供的上层应用接口(eg:f_open())来对底层的存储设备进行操作

  1. 存储设备状态读取函数disk_status()
  2. 存储设备初始化函数disk_initialize()
  3. 存储设备读函数disk_read()
  4. 存储设备写函数disk_write()
  5. 存储设备IO控制操作函数disk_ioctl()

但是如果要使用 STM32CubeMX 配置的话就不需要自己下载和移植源码,通过配置 STM32CubeMX 的 FatFs ,在生成的工程代码中就已经将 FatFs 的框架准备好,用户只需在生成的 user_diskio.c 文件中添加底层驱动IO函数即可(仅仅对于 User-defined 模式需要自己添加,其他的模式底层代码会自动生成),具体请阅读本实验”3.2.3、添加其他必要代码“小节

在 FatFs 中,大多数的API都拥有一个名为 FRESULT 的结构体返回值,其包含了20个枚举对象,由于该返回值对于查找错误有很大帮助,因此笔者在这里列出来所有返回值并做了简单解释,具体如下源代码所示

typedef enum {FR_OK = 0,				/* (0) 成功 */FR_DISK_ERR,			/* (1) 在Disk IO层发生硬错误,检查user_diskio.c中代码 */FR_INT_ERR,				/* (2) 参数检查错误 */FR_NOT_READY,			/* (3) 物理驱动器不工作 */FR_NO_FILE,				/* (4) 找不到文件 */FR_NO_PATH,				/* (5) 找不到路径 */FR_INVALID_NAME,		/* (6) 路径名称格式无效,检查是否8.3格式/是否支持长文件名 */FR_DENIED,				/* (7) 因禁止访问或目录满导致无法访问 */FR_EXIST,				/* (8) 因禁止访问导致无法访问 */FR_INVALID_OBJECT,		/* (9) 文件/目录无效 */FR_WRITE_PROTECTED,		/* (10) 物理驱动器写保护 */FR_INVALID_DRIVE,		/* (11) 逻辑驱动器号无效 */FR_NOT_ENABLED,			/* (12) 卷无工作区 */FR_NO_FILESYSTEM,		/* (13) 无有效FAT卷 */FR_MKFS_ABORTED,		/* (14) 函数f_mkfs()因为问题终止 */FR_TIMEOUT,				/* (15) 不能在限定时间内获得访问卷的许可 */FR_LOCKED,				/* (16) 因为文件共享策略导致操作被拒绝 */FR_NOT_ENOUGH_CORE,		/* (17) 不能分配长文件名工作缓存区 */FR_TOO_MANY_OPEN_FILES,	/* (18) 打开文件个数大于_FS_LOCK */FR_INVALID_PARAMETER	/* (19) 无效参数 */
} FRESULT;

下图所示为带有 FatFs 模块的嵌入式系统的典型但非特定配置的依赖关系图,其中用户只需重点关注和实现"Low level disk I/O layer",实现之后在实际应用中只需使用 "User Application"中提供的上层应用接口即可 (注释1)

请添加图片描述

3.1、CubeMX相关配置

3.1.0、工程基本配置

打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示

开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示

在这里插入图片描述

详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”

3.1.1、时钟树配置

系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示

在这里插入图片描述

3.1.2、外设参数配置

本实验需要需要初始化开发板上WK_UP、KEY2、KEY1和KEY0用户按键,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应”

本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”

本实验需要以STM32CubeMX教程20 SPI - W25Q128驱动实验为基础,需要读者能够通过CubeMX软件配置STM32F407的SPI实现正常读写W25Q128芯片的功能,然后接下来只需要增加本实验需要的中间件FatFs即可

在Pinout & Configuration页面左边的功能分类栏中单击 Middleware and SoftwarePacks/FATFS,然后在右边的Mode下勾选 User-defined (目前只有该参数可以勾选),在下方Configuration/Set Defines对FatFs的功能进行配置,这个页面所有参数对应FatFs源码ffconf.h中的宏定义

这里我们将 CODE_PAGE(Code page on target) 参数修改为 Simplified Chinese (DBCS) ,然后将 MAX_SS (Maximum Sector Size) 参数修改为W25Q128芯片的扇区大小4096字节,其他所有参数不做修改,具体如下图所示

在这里插入图片描述

笔者将Set Defines页面的所有参数列为了一个表格,方便做简单介绍,具体如下图所示

3.1.3、外设中断配置

本实验无需配置任何中断

3.2、生成代码

3.2.0、配置Project Manager页面

单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,接着在链接设置中将最小栈大小修改为0x2000(8KB),之前所有实验该参数都为默认的0x0400(1KB),这是因为其他实验不需要占用太多的栈空间,但是本实验需要比较大的栈空间,不增加可能会导致FatFs读写文件失败卡死或者导致MCU复位的情况发生,读者可根据自己的情况自行设置栈大小

然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of ‘c/h’ files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示

请添加图片描述

详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节

3.2.1、外设初始化调用流程

关于SPI的初始化函数调用流程请读者阅读STM32CubeMX教程20 SPI - W25Q128驱动实验的”3.2.1、外设初始化调用流程“小节,在此不再赘述

重点来看看FatFs中间件是如何被初始化并与W25Q128芯片底层操作联系在一起的,首先在CubeMX中勾选启用FatFs中间件之后,会在生成的工程代码中增加MX_FATFS_Init()初始化函数,在该函数中只调用了FATFS_LinkDriver()一个函数

这个FATFS_LinkDriver()函数将一个名为 xxx_Driver(根据所选的存储设备不同,生成的该变量名称也会改变,比如User_Driver,SD_Driver等)的 Diskio_drvTypeDef 类结构体链接到了FatFs管理的驱动器列表中,并将卷路径赋值为"0:/"

那么 xxx_Driver 是什么,为什么要将这个Diskio_drvTypeDef 类结构体的变量链接给FatFs管理呢?

跳转到 xxx_Driver 的定义处,我们发现该结构体变量中保存了五个函数指针,刚刚好就是我们需要实现的对存储设备进行底层读写等操作的函数,具体xxx_Driver定义如下述代码所示

Diskio_drvTypeDef USER_Driver =
{USER_initialize,USER_status,USER_read,USER_write,USER_ioctl,
};

至此我们知道了FatFs初始化就是将用户重新实现的与存储设备底层进行读写等操作的函数链接到FatFs管理的驱动器列表中,将这些底层函数交给FatFs管理,用户直接使用FatFs提供的上层API函数来操作即可,对于为什么可以这样需要分析FatFs源码,本文就不涉及了

3.2.2、外设中断调用流程

本实验无配置任何中断

3.2.3、添加其他必要代码

打开整个工程之后观察其文件结构目录,在CubeMX中启用FatFs之后在生成的工程代码目录中会增加FatFs源码文件夹(该文件夹中文件无需用户修改),同时增加App和Target两个文件夹,在App文件夹中的fatfs.c文件需要用户实现获取RTC时间的函数①get_fattime(),在App文件夹中的user_diskio.c中需要用户实现②USER_initialize()、③USER_status()、④USER_read()、⑤USER_write()和⑥USER_ioctl()共计六个函数,其中USER_initialize(),USER_status(),USER_read()三个函数必须实现,其他函数按需实现,其文件结构目录如下图所示

请添加图片描述

对于配置为User-defined模式的FatFs来说,上面App文件夹和Target文件夹中的内容均需要用户自己实现,因为CubeMX并不知道用户想要使用的存储设备,所以也无法自动生成底层读写的IO驱动函数,但是对于Extemal SRAM、SD Card和USB Disk这三种固定类型的存储,则无需用户在App文件夹和Target文件夹中重新实现上面提到的一共六个函数,CubeMX生成的工程代码中会自动实现

接下来我们来实现上面提到的六个函数,注意FatFs获取RTC时间需要开启STM32F407的RTC功能,关于RTC的具体使用方法,读者可以阅读STM32CubeMX教程10 RTC 实时时钟 - 周期唤醒、闹钟AB事件和备份寄存器实验,如果不需要可以直接将函数体内容注释

对于Flash_ReadID()、Flash_ReadBytes()和Flash_WriteSector()三个函数是在STM32CubeMX教程20 SPI - W25Q128驱动实验中实现的,请读者自行查阅,重新实现的六个函数源代码如下所示

/*fatfs.h文件中*/
/*添加RTC头文件*/
#include "rtc.h"/*fatfs.c文件中*/
/*FatFs获取RTC时间*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;//获取RTC时间if(HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){//获取RTC日期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;/* USER CODE END get_fattime */
}/*user_diskio.c文件中*/
/*存储设备初始化函数*/
DSTATUS USER_initialize (BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{/* USER CODE BEGIN INIT */Stat = STA_NOINIT;//获取驱动器状态Stat = USER_status(pdrv);    return Stat;/* USER CODE END INIT */
}/*获取存储设备状态*/
DSTATUS USER_status (BYTE pdrv       /* Physical drive number to identify the drive */
)
{/* USER CODE BEGIN STATUS */Stat = STA_NOINIT;		  //驱动器未初始化,Stat=0x01if(Flash_ReadID() != 0)   //读取Flash芯片的ID,只要不是0就表示En25Q128已初始化Stat &= ~STA_NOINIT;  //Stat=0x00return Stat;/* USER CODE END STATUS */
}/*底层读函数*/
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 *///扇区编号左移12位得绝对起始地址uint32_t globalAddr = sector << 12;  //字节个数,左移12位就是乘4096,每个扇有4096字节uint16_t byteCount = count << 12;   //读取数据Flash_ReadBytes(globalAddr, (uint8_t *)buff, byteCount);return RES_OK;/* USER CODE END READ */
}/*底层写函数*/
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;  //字节个数uint16_t byteCount  = count<<12;   Flash_WriteSector(globalAddr, (uint8_t*)buff, byteCount);return RES_OK;                                                                                                                       /* USER CODE END WRITE */
}/*底层控制操作函数*/
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){/*以下四个命令都是按照FatFs默认参数配置时必须需要的*///完成挂起的写入过程(在_FS_READONLY == 0时需要)case CTRL_SYNC:   break;//获取存储介质容量(在_USE_MKFS == 1时需要)case GET_SECTOR_COUNT:  //W25Q128总的扇区个数,4096*(DWORD *)buff = FLASH_SECTOR_COUNT;  break;//获取扇区大小(_MAX_SS != _MIN_SS时需要)case GET_SECTOR_SIZE:  //W25Q128每个扇区的大小,4096字节*(DWORD *)buff = FLASH_SECTOR_SIZE;  break;//获取擦除块的大小(_USE_MKFS == 1时需要)case GET_BLOCK_SIZE:  //W25Q128每个块拥有16个扇区,按块擦除*(DWORD *)buff = 16;  break;default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}

然后增加使用FatFs库中API进行文件操作的函数,包括挂载文件系统、显示SD卡信息、读/写TXT文件、获取文件信息、扫描文件列表和删除文件等函数,笔者将其封装在了file_operate.c/file_operate.h文件中,具体的源代码如下所示

file_operate.c文件

#include "file_operate.h"//定义用于格式化的工作区缓存
BYTE workBuffer[4*User_Sector];/*挂载FatFs文件系统*/
void Mount_FatFs(void)
{//挂载文件系统FRESULT retUSER = f_mount(&User_FatFs, User_SDPath, 1);//发生错误if(retUSER != FR_OK){//没有文件系统,需要格式化if(retUSER == FR_NO_FILESYSTEM){printf("\r\n没有文件系统,开始格式化\r\n");//创建文件系统retUSER = f_mkfs(User_SDPath, FM_FAT32, 0, workBuffer, 4*User_Sector);//格式化失败if(retUSER != FR_OK){printf("格式化失败,错误代码 = %d\r\n", retUSER);}//格式化成功else{printf("格式化成功,开始重新挂载\r\n");//有文件系统后重新挂载retUSER = f_mount(&User_FatFs, User_SDPath, 1);//挂载失败if(retUSER != FR_OK){printf("发生错误,错误代码 = %d\r\n", retUSER);}//挂载成功else{printf("*** 文件系统挂载成功 ***\r\n");}}}//不是没有文件系统,而是发生其他错误else{printf("发生其他错误,错误代码 = %d\r\n", retUSER);}}//有文件系统直接挂在成功else{printf("文件系统挂载成功\r\n");}
}/*获取磁盘信息并在LCD上显示*/
void FatFs_GetDiskInfo(void)
{FATFS *fs;//定义剩余簇个数变量DWORD fre_clust; //获取剩余簇个数FRESULT res = f_getfree("0:", &fre_clust, &fs); //获取失败if(res != FR_OK){printf("f_getfree() error\r\n");return;}printf("\r\n*** FAT disk info ***\r\n");//总的扇区个数DWORD tot_sect = (fs->n_fatent - 2) * fs->csize;  //剩余的扇区个数 = 剩余簇个数 * 每个簇的扇区个数DWORD fre_sect = fre_clust * fs->csize;    //对于SD卡和U盘, _MIN_SS=512字节
#if  _MAX_SS == _MIN_SS  //SD卡的_MIN_SS固定为512,右移11位相当于除以2048//剩余空间大小,单位:MB,用于SD卡,U盘DWORD freespace= (fre_sect>>11); //总空间大小,单位:MB,用于SD卡,U盘		DWORD totalSpace= (tot_sect>>11);  
#else//Flash存储器,小容量//剩余空间大小,单位:KBDWORD freespace= (fre_sect*fs->ssize)>>10;   //总空间大小,单位:KBDWORD totalSpace= (tot_sect*fs->ssize)>>10;  
#endif//FAT类型printf("FAT type = %d\r\n",fs->fs_type);printf("[1=FAT12,2=FAT16,3=FAT32,4=exFAT]\r\n");//扇区大小,单位字节printf("Sector size(bytes) = ");//SD卡固定512字节
#if  _MAX_SS == _MIN_SS printf("%d\r\n", _MIN_SS);
#else//FLASH存储器printf("%d\r\n", fs->ssize);
#endifprintf("Cluster size(sectors) = %d\r\n", fs->csize);printf("Total cluster count = %ld\r\n", fs->n_fatent-2);printf("Total sector count = %ld\r\n", tot_sect);//总空间
#if  _MAX_SS == _MIN_SS printf("Total space(MB) = %ld\r\n", totalSpace);
#elseprintf("Total space(KB) = %ld\r\n", totalSpace);
#endif//空闲簇数量printf("Free cluster count = %ld\r\n",fre_clust);//空闲扇区数量printf("Free sector count = %ld\r\n", fre_sect);//空闲空间
#if  _MAX_SS == _MIN_SS printf("Free space(MB) = %ld\r\n", freespace);
#elseprintf("Free space(KB) = %ld\r\n", freespace);
#endifprintf("Get FAT disk info OK\r\n");
}/*创建文本文件*/
void FatFs_WriteTXTFile(TCHAR *filename,uint16_t year, uint8_t month, uint8_t day)
{FIL	file;printf("\r\n*** Creating TXT file: %s ***\r\n", filename);FRESULT res = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);//打开/创建文件成功if(res == FR_OK){//字符串必须有换行符"\n"TCHAR str[]="Line1: Hello, FatFs***\n";  //不会写入结束符"\0"f_puts(str, &file); printf("Write file OK: %s\r\n", filename);}else{printf("Open file error,error code: %d\r\n", res);}//使用完毕关闭文件f_close(&file);
}/*读取一个文本文件的内容*/
void FatFs_ReadTXTFile(TCHAR *filename)
{printf("\r\n*** Reading TXT file: %s ***\r\n", filename);FIL	file;//以只读方式打开文件FRESULT res = f_open(&file, filename, FA_READ);  //打开成功if(res == FR_OK){//读取缓存TCHAR str[100];//没有读到文件内容末尾while(!f_eof(&file)){//读取1个字符串,自动加上结束符”\0”f_gets(str,100, &file);	printf("%s", str);}printf("\r\n");}//如果没有该文件else if(res == FR_NO_FILE)printf("File does not exist\r\n");//打开失败elseprintf("f_open() error,error code: %d\r\n", res);//关闭文件f_close(&file);
}/*扫描和显示指定目录下的文件和目录*/
void FatFs_ScanDir(const TCHAR* PathName)
{DIR dir;					//目录对象FILINFO fno;				//文件信息//打开目录FRESULT res = f_opendir(&dir, PathName);//打开失败if(res != FR_OK){//关闭目录,直接退出函数f_closedir(&dir);printf("\r\nf_opendir() error,error code: %d\r\n", res);return;}printf("\r\n*** All entries in dir: %s ***\r\n", PathName);//顺序读取目录中的文件while(1){//读取目录下的一个项res = f_readdir(&dir, &fno);    //文件名为空表示没有多的项可读了if(res != FR_OK || fno.fname[0] == 0)break;  //如果是一个目录if(fno.fattrib & AM_DIR)  		{printf("DIR: %s\r\n", fno.fname);}//如果是一个文件else  		{printf("FILE: %s\r\n",fno.fname);}}//扫描完毕,关闭目录printf("Scan dir OK\r\n");f_closedir(&dir);
}/*获取一个文件的文件信息*/
void FatFs_GetFileInfo(TCHAR *filename)
{printf("\r\n*** File info of: %s ***\r\n", filename);FILINFO fno;//检查文件或子目录是否存在FRESULT fr = f_stat(filename, &fno);//如果存在从fno中读取文件信息if(fr == FR_OK){printf("File size(bytes) = %ld\r\n", fno.fsize);printf("File attribute = 0x%x\r\n", fno.fattrib);printf("File Name = %s\r\n", fno.fname);//输出创建/修改文件时的时间戳FatFs_PrintfFileDate(fno.fdate, fno.ftime);}//如果没有该文件else if (fr == FR_NO_FILE)printf("File does not exist\r\n");//发生其他错误elseprintf("f_stat() error,error code: %d\r\n", fr);
}/*删除文件*/
void FatFs_DeleteFile(TCHAR *filename)
{printf("\r\n*** Delete File: %s ***\r\n", filename);FIL	file;//打开文件FRESULT res = f_open(&file, filename, FA_OPEN_EXISTING);  if(res == FR_OK){//关闭文件f_close(&file);printf("open successfully!\r\n");}//删除文件res = f_unlink(filename);//删除成功if(res == FR_OK){printf("The file was deleted successfully!\r\n");}//删除失败else{printf("File deletion failed, error code:%d\r\n", res);}
}/*打印输出文件日期*/
void FatFs_PrintfFileDate(WORD date, WORD time)
{printf("File data = %d/%d/%d\r\n", ((date>>9)&0x7F)+1980, (date>>5)&0xF, date&0x1F);printf("File time = %d:%d:%d\r\n", (time>>11)&0x1F, (time>>5)&0x3F, time&0x1F);
}

file_operate.h文件

#ifndef FILE_OPERATE_H
#define FILE_OPERATE_H#include "main.h"
#include "FatFs.h"
#include "stdio.h"/*定义自己的存储设备*/
/*用户存储设备扇区字节数*/
#define User_Sector 4096
/*用户存储设备FatFS对象*/
#define User_FatFs 	USERFatFS
/*用户存储设备卷路径*/
#define User_SDPath USERPath/*函数声明*/
void Mount_FatFs(void);
void FatFs_GetDiskInfo(void);
void FatFs_ScanDir(const TCHAR* PathName);
void FatFs_ReadTXTFile(TCHAR *filename);
void FatFs_WriteTXTFile(TCHAR *filename,uint16_t year, uint8_t month, uint8_t day);
void FatFs_GetFileInfo(TCHAR *filename);
void FatFs_DeleteFile(TCHAR *filename);
void FatFs_PrintfFileDate(WORD date, WORD time);#endif

向工程中添加.c/.h文件的步骤请阅读“STM32CubeMX教程19 I2C - MPU6050驱动”实验3.2.3小节

最后在main.c文件中添加 ”file_operate.h“ 头文件,然后在主函数 main() 中调用文件系统挂载函数,实现按键控制逻辑程序,具体源代码如下所示

/*main.c中添加头文件*/
#include "file_operate.h" /*外输初始化完进入主循环前*/
//检测SPI与W25Q128通信是否正常
printf("Reset,ID:0x%x\r\n", Flash_ReadID());
//挂载文件系统
Mount_FatFs();
//获取磁盘信息
FatFs_GetDiskInfo();/*主循环中按键逻辑*/
/*按键WK_UP被按下*/
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET)
{HAL_Delay(50);if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == GPIO_PIN_SET){FatFs_ScanDir("0:/");while(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin));}
}/*按键KEY2被按下*/
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{HAL_Delay(50);if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET){FatFs_WriteTXTFile("test.txt",2016,11,15);while(!HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin));}
}/*按键KEY1被按下*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{HAL_Delay(50);if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET){FatFs_ReadTXTFile("test.txt");FatFs_GetFileInfo("test.txt");while(!HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin));}
}/*按键KEY0被按下*/
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{HAL_Delay(50);if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET){FatFs_DeleteFile("test.txt");while(!HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin));}
}

4、烧录验证

烧录程序,开发板上电后会尝试在W25Q128 FLASH芯片上挂载文件系统,挂载成功后会输出读取到的 FLASH芯片的信息,接下来按照下面几个步骤使用FatFs文件系统对 FLASH芯片进行读写等测试

  1. 按下开发板上的WK_UP按键,扫描FLASH芯片根目录下所有文件,并通过串口将文件列表输出
  2. 按下开发板上的KEY2按键,在 FLASH芯片根目录创建一个”test.txt“文件,将一个字符串 ”Hello,OSnotes“ 写入该文件中,该字符串大小为15个字节(该字符串中末尾包括了一个’\n‘和一个‘\0’)
  3. 按下开发板上的KEY1按键,读取FLASH芯片根目录下名为”test.txt“的文件,将其中的内容通过串口输出,然后读取该文件的信息(大小,属性,名称),并通过串口输出
  4. 按下开发板上的KEY0按键,删除FLASH芯片根目录下名为”test.txt“的文件

整个实验过程串口具体的输出情况如下图所示

请添加图片描述

5、常用函数

FatFs的所有API函数详细介绍请参看FatFs官网 FatFs - Generic FAT Filesystem Module,如下所示为笔者对其常用应用接口及其功能做简单介绍

/*注册/取消注册卷的工作区域*/
FRESULT f_mount(FatFs* fs, const TCHAR* path, BYTE opt)
/*在逻辑驱动器上创建FAT卷*/
FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len)
/*获取卷上的可用空间*/
FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FatFs** FatFs)
/*打开/创建文件*/
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode)
/*写入一个字符串*/
int f_puts(const TCHAR* str, FIL* fp)
/*写入格式化字符串*/
int f_printf(FIL* fp, const TCHAR* fmt, ...)
/*关闭打开的文件*/
FRESULT f_close(FIL* fp)
/*读取字符串*/
TCHAR* f_gets(TCHAR* buff, int len, FIL* fp)
/*打开目录*/
FRESULT f_opendir(DIR* dp, const TCHAR* path)
/*读取目录项*/
FRESULT f_readdir(DIR* dp, FILINFO* fno)
/*关闭打开的目录*/
FRESULT f_closedir(DIR *dp)
/*检查文件或子目录是否存在*/
FRESULT f_stat(const TCHAR* path, FILINFO* fno)
/*删除文件或子目录*/
FRESULT f_unlink(const TCHAR* path)
/*重命名/移动文件或子目录*/
FRESULT f_rename(const TCHAR* path_old, const TCHAR* path_new)
/*创建子目录*/
FRESULT f_mkdir(const TCHAR* path)

6、注释详解

注释1:图片来源 FatFs Module Application Note (elm-chan.org)

参考资料

STM32Cube高效开发教程(高级篇)

更多内容请浏览 STM32CubeMX+STM32F4系列教程文章汇总贴

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

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

相关文章

C#winform上位机开发学习笔记7-串口助手的波特率参数设置功能添加

1.功能描述 上位机与下位机进行通讯时需要用到波特率设置功能&#xff0c;以及尝试与下位机实体进行通讯。 2.代码部分 步骤1&#xff1a;串口开启按钮事件中添加代码 serialPort1.BaudRate Convert.ToInt32(comboBox14.Text, 10);//将十进制的文本转换为32位整型赋值给串…

揭秘真相!成都力寰璨泓科技有限公司抖音小店究竟是否可靠?

在互联网电商繁荣发展的今天&#xff0c;抖音小店作为新兴的电商平台&#xff0c;吸引了众多商家和消费者的目光。在这其中&#xff0c;成都力寰璨泓科技有限公司的抖音小店尤为引人注目。那么&#xff0c;这家公司在抖音小店的运营是否可靠呢&#xff1f;本文将为你揭开真相。…

Raspbian安装云台

Raspbian安装云台 1. 源由2. 选型3. 组装4. 调试4.1 python3-print问题4.2 python函数入参类型错误4.3 缺少mjpg-streamer可执行文件4.4 缺失编译头文件和库4.5 python库缺失4.6 图像无法显示&#xff0c;但libcamera-jpeg测试正常4.7 异常IOCTL报错4.8 Git问题 5. 效果5.1 WEB…

制作高端的电子杂志神器推荐

根据市场调查数据显示&#xff0c;越来越多的消费者开始青睐电子杂志这种阅读方式。相比传统纸质杂志&#xff0c;电子杂志具有更高的阅读体验、更便捷的分享和传播方式以及更环保的阅读方式。此外&#xff0c;越来越多的企业也开始重视电子杂志的宣传作用&#xff0c;将其作为…

安裝火狐和穀歌流覽器插件FoxyProxy管理海外動態IP代理

代理生態系統擁有大量有用的實用程式&#xff0c;使海外代理IP代理設置的使用變得簡單起來。其中一種類型叫做代理管理工具&#xff0c;像FoxyProxy就是該工具集比較受歡迎的。 本文將全面解析FoxyProxy擴展的功能和特性、Foxyproxy怎麼下載、以及如何在穀歌流覽器和火狐流覽器…

14、Kafka ------ kafka 核心API 之 流API(就是把一个主题的消息 导流 到另一个主题里面去)

目录 kafka 核心API 之 流APIKafka流API的作用&#xff1a;流API的核心API&#xff1a;使用流API编程的大致步骤如下&#xff1a;代码演示 流API 用法MessageStream 流API 代码演示消息从 test1主题 导流到 test2主题演示使用匿名内部类对消息进行处理Topology 拓扑结构 讲解 代…

Linux之快速入门(CentOS 7)

文章目录 一、Linux目录结构二、常用命令2.1 切换用户2.2查看ip地址2.3 cd2.4 目录查看2.5 查看文件内容2.6 创建目录及文件2.72.82.93.0 一、Linux目录结构 目录作用/bin是 Binaries (二进制文件) 的缩写,这个目录存放着最经常使用的命令/dev是 Device(设备) 的缩写,该目录下存…

【办公类-22-01】20240123 UIBOT逐一提取CSDN质量分

背景需求&#xff1a; 最近每天传2份Python&#xff0c;发现平均分从73.5降到了72.7。网上搜索一下原因&#xff0c;发现每篇CSDN都有一个评分&#xff08;以下是查分网站&#xff09; https://www.csdn.net/qchttps://www.csdn.net/qc 但是一篇一篇查询&#xff0c;显然太繁…

Java 数据结构篇-实现红黑树的核心方法

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 红黑树的说明 2.0 红黑树的特性 3.0 红黑树的成员变量及其构造方法 4.0 实现红黑树的核心方法 4.1 红黑树内部类的核心方法 &#xff08;1&#xff09;判断当前…

软件工程实验报告(完整)

博主介绍&#xff1a;✌全网粉丝喜爱、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦&#xff01; &#x1f345;附上相关C语言版源码讲解&#x1f345; &#x1f44…

openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错

文章目录 openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错205.1 业务运行时整数转换错205.1.1 问题现象205.1.2 原因分析205.1.3 处理办法 openGauss学习笔记-205 openGauss 数据库运维-常见故障定位案例-业务运行时整数转换错 205.1 业务…

Java21 + SpringBoot3集成easy-captcha实现验证码显示和登录校验

文章目录 前言相关技术简介easy-captcha 实现步骤引入maven依赖定义实体类定义登录服务类定义登录控制器前端登录页面实现测试和验证 总结附录使用Session缓存验证码前端登录页面实现代码 前言 近日心血来潮想做一个开源项目&#xff0c;目标是做一款可以适配多端、功能完备的…

虚拟机下载docker

一&#xff0c;Docker简介 百科说&#xff1a;Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化&#xff0c;容器是完全使用沙箱机制&#xff…

CentOS 7安装全解析

目录 一.centos安装1.1 下载镜像文件1.2 安装 二.远程连接&#xff0c;换源2.1 下载并且使用MobaXterm2.2 远程连接2.3 换源 一.centos安装 1.1 下载镜像文件 https://mirrors.aliyun.com/centos/7/isos/x86_64/ 下载即可 1.2 安装 二.远程连接&#xff0c;换源 2.1 下载并…

租幻兽帕鲁Palworld服务器多少钱?

使用腾讯云服务器搭建搭建幻兽帕鲁Palworld如何选择服务器配置&#xff1f;腾讯云百科txybk.com建议幻兽帕鲁选择腾讯云轻量应用服务器4核16G14M带宽&#xff0c;Ubuntu/Debian系统。如何收费&#xff1f; 腾讯云幻兽帕鲁服务器活动 https://curl.qcloud.com/oRMoSucP 轻量应用…

C#,入门教程(28)——文件夹(目录)、文件读(Read)与写(Write)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(27)——应用程序&#xff08;Application&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/125094837 C#知识比你的预期简单的多&#xff0c;但也远远超乎你的想象&#xff01; 与文件相关的知识&#xf…

记一次低级且重大的Presto运维事故

本文纯属虚构&#xff0c;旨在提醒各位别犯类似低级错误。 如有雷同&#xff0c;说的就是你&#xff01; 文章目录 前言事件回顾后续总结 前言 首先&#xff0c;要重视运维工作和离职人员的交接工作&#xff0c;这个不必多说。一将无能&#xff0c;累死三军&#xff01; 接下来…

目标检测难题 | 小目标检测策略汇总

大家好&#xff0c;在计算机视觉中&#xff0c;检测小目标是最有挑战的问题之一&#xff0c;本文给出了一些有效的策略。 从无人机上看到的小目标 为了提高模型在小目标上的性能&#xff0c;本文推荐以下技术&#xff1a; 提高图像采集的分辨率 增加模型的输入分辨率 tile你…

3DMAX初级小白班第一课:菜单栏介绍

基本介绍 这里不可能一个一个选项全部教给大家&#xff08;毕竟之后靠实操慢慢就记住了&#xff09;&#xff0c;只说一些相对需要注意的设置。 自定义-热键编辑器-热键设置 这里有你所需要的全部快捷键 自定义-自定义UI启动布局 将UI布局还原到启动的位置 自定义-通用单…

【Linux配置yum源以及基本yum指令】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、yum是什么&#xff1f; 二、什么是软件包&#xff1f; 三、三种安装软件包的方式 四、yum的相关操作 4.1、搜索软件 4.2、安装软件 4.3、卸载软件 4.4、那…