6、Fatfs系统移植

文章详细介绍了如何在STM32上配置Fatfs文件系统,用于挂载SPI连接的W25Qxx闪存芯片。强调了使用DMA避免中断干扰的重要性,以及在CubeMx中配置HAL库和Fatfs组件的步骤。此外,还涉及到驱动文件的移植和修改,以及Fatfs系统关键函数的使用,包括f_mount和f_mkfs等。
摘要由CSDN通过智能技术生成

注意:挂载Fatfs笔记
Fatfs系统读写文件的时间是不固定的,随机性
搭载Fatfs的外设通信方式建议开启DMA方式,否则应避免中断打断时序,导致Fatfs出现FR_DISK_ERR(A hard error occurred in the low level disk I/O layer)错误,尤其是在读写文件时
参考野火教程

一、W25Qxx上挂载Fatfs系统 HAL库 CubeMx配置

说明:通过CubeMx快速配置W25Qxx 芯片SPI通信,并配置Fatfs组件,实现文件读写操作

1、CubeMx配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面配置Fatfs
在这里插入图片描述
在这里插入图片描述
生成工程代码…

2、准备工作,拷贝编写好的W25Qxx驱动文件

驱动文件来自我的5、HAL库驱动W25Qxx,里面会有一些介绍
编写好的驱动文件下载:
链接:https://pan.baidu.com/s/1r0JCrUNAHt6sGJ6D_tT0lg
提取码:fxzn

在这里插入图片描述
W25Qxx驱动文件(SPI通信 SCK MOSI MISO CS)
使用方法:
1、添加文件到工程:Common_Driver.c、spi_Driver.c、W25Qxx_Driver.c
2、spi_Driver.h文件
修改外设SPI1相应挂载的外设片选引脚
3、spi_Driver.c文件
修改SPI1相应通信线(CLK、MOSI、MISO)的引脚宏
4、W25Qxx_Driver.h文件
修改W25Qxx通信时使用的SPI句柄和外设宏:SPIx、SPIxHandle
修改W25Qxx型号对应的宏:sFLASH_ID
5、W25Qxx_Driver.h文件
开启调试宏(芯片是否正常,会进行擦除数据写入读出对比数据),调试完毕后屏蔽:_W25Qxx_Debug
6、调用SPI初始化函数MX_SPI1_Init()(此时SPI进入空闲,CS引脚空闲)
7、调用W25Qxx_InitCheck()函数检查芯片(执行完后芯片进入省电模式)
8、完毕

时序说明:全双工SPI 线束:SCK|MOSI|MISO CS
SPI模式 CPOL CPHA 空闲时 SCK 时钟 采样时刻
0 0 0 低电平 奇数边沿(W25Qxx支持)
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿(W25Qxx支持)当前使用

数据长度8
高位在前
速度配置为PCLK2/2分频 = 42MHz

注意:W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1);
以此使能外设SPI,使用HAL库自带的收发函数不需要此使能
此驱动文件只适合W25Qxx 16M及以下型号,因为访问地址位数不同
W25Qxx_Driver.c文件中的所有函数需要先初始化SPI后才可以调用

3、移植W25Qxx驱动

添加驱动文件,勾选Use MicroLIB库在这里插入图片描述
修改外设SPI1相应挂载的外设片选引脚
在这里插入图片描述
修改SPI1相应通信线(CLK、MOSI、MISO)的引脚宏

在这里插入图片描述
修改W25Qxx通信时使用的SPI句柄和外设宏:SPIx、SPIxHandle
修改W25Qxx型号对应的宏:sFLASH_ID
开启调试宏(芯片是否正常,会进行擦除数据写入读出对比数据)在这里插入图片描述
调用SPI初始化函数MX_SPI1_Init()(此时SPI进入空闲,CS引脚空闲)
CubeMx配置生成的代码会在主函数自动生成调用此函数
注意:此函数会和驱动文件按W25Qxx_Driver.c中的出现冲突,选用1个即可,两个都是一样的函数名,另一个修改名字处理
选用自动生成的应—>>>>>增加外设使能语句

本次选用CubeMx自动生成的
在这里插入图片描述

添加外设使能语句以及W25Qxx检测
调用W25Qxx_InitCheck()函数检查芯片(执行完后芯片进入省电模式)
在这里插入图片描述
在这里插入图片描述
延时方便看调试信息
在这里插入图片描述
在这里插入图片描述
编译下载:
在这里插入图片描述
屏蔽调试宏
在这里插入图片描述
W25Qxx通信移植完成
在这里插入图片描述

3、W25Qxx搭载Fatfs系统

修改user_diskio.c文件接口
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于挂载系统后容易出现硬错误,应增加HardFault_Handler()显示
在这里插入图片描述

此时就可以测试使用了
fatfs.c中添加文件读写测试函数

/**
  ******************************************************************************
  * @file   fatfs.c
  * @brief  Code for fatfs applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

#include "fatfs.h"
#include "Common_Driver.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 */
void SDFileTestWrite(void);
void SDFileTestRead(void);
/* 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 */
    BYTE work[_MAX_SS];

    retUSER = f_mount(&USERFatFS,USERPath,1);//挂载盘符A
    if(retUSER == FR_NO_FILESYSTEM)//没有文件系统就格式化创建创建文件系统
    {
        retUSER = f_mkfs(USERPath,FM_FAT,4096,work,sizeof(work));
        if(retUSER == FR_OK)
        {
            retUSER = f_mount(NULL,USERPath,1);//格式化后,先取消挂载
            retUSER = f_mount(&USERFatFS,USERPath,1);//挂载
            printf("格式化成功retUSER=%d\r\n",retUSER);
        }
        else{printf("格式化失败retUSER=%d\r\n",retUSER);}//格式化失败
    }
    else if(retUSER == FR_OK){printf("挂载成功retUSER=%d\r\n",retUSER);}
    else{printf("挂载失败retUSER=%d\r\n",retUSER);}//挂载失败
    SDFileTestWrite();
    SDFileTestRead();
    while(1);
  /* 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 0;
  /* USER CODE END get_fattime */
}

/* USER CODE BEGIN Application */
FIL  fpSD;
void SDFileTestWrite(void)
{
    FRESULT res_sd;
    UINT fnum;/* 文件成功读写数量 */
    char string[100];
    signed int ByteNum = 0;

    memset(string,0,sizeof(string));
    sprintf(string,"%s%s.xls",USERPath,"Test");
    res_sd = f_open(&fpSD, string,FA_CREATE_ALWAYS | FA_WRITE );
    if(res_sd != FR_OK){printf("Failed to create file! %d\r\n",res_sd);}
    sprintf(string,"Vreal\tA1\tA2\n");
    ByteNum = strlen(string);
    res_sd=f_write(&fpSD,string,ByteNum,&fnum);
    res_sd = f_close(&fpSD);
    if(res_sd != FR_OK){printf("Error:File closure Exception!\r\n");}
    else{printf("SDFileTestWrite ok!\r\n");}
}

void SDFileTestRead(void)
{
    FRESULT res_sd;
    char string[100];
    uint32_t line = 0;

    memset(string,0,sizeof(string));
    sprintf(string,"%s%s.xls",USERPath,"Test");
    res_sd = f_open(&fpSD, string, FA_OPEN_EXISTING | FA_READ);
    if(res_sd != FR_OK){goto LoadFail;}
    line = 0;

    while(!(f_eof(&fpSD)))
    {
        memset(string,0,sizeof(string));
        f_gets(string,sizeof(string),&fpSD);
        if(strlen(string) == 0){break;}
        ++line;
        printf("line:%d %s\r\n",line,string);
        //sscanf(string,"%f\t%f\t%f\n",&Vreal[*pNum],&Va1[*pNum],&Va2[*pNum]);//按格式提取字符串函数
    }
    res_sd = f_close(&fpSD);
    if(res_sd != FR_OK){printf("Error:Load File closure Exception!\r\n");}
    printf("SDFileTestRead ok\r\n");
    return;
    LoadFail:
    {
      printf("Load Fail:%s\r\n",string);
    }
}
/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

下载至单片机中

在这里插入图片描述
第一次下载会出现格式化信息,我这个已经格式化过了,所以没有这个信息
此时已经可以看出,W25Qxx已经成功挂载Fatfs系统

4、完成工程

链接:https://pan.baidu.com/s/1Bd5bLzQqoByEzPkpSYKxuw
提取码:p0ez

二、Fatfs系统 CubeMx配置的与官方源码大致区别

Fatfs官网:http://elm-chan.org/fsw/ff/00index_e.html
在这里插入图片描述

1、Fatfs官方源码下载

官网底部
在这里插入图片描述

源码文件对比

在这里插入图片描述
diskio.c 文件是 FatFs 移植最关键的文件,它为文件系统提供了最底层的访问 SPI Flash 芯片的方法,FatFs 有且仅有它需要用到与 SPI Flash 芯片相关的函数。diskio.h 定义了 FatFs 用到的宏,以及 diskio.c 文件内与底层硬件接口相关的函数声明。包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。
ff.c:FatFs 核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。
integer.h:文件中包含了一些数值类型定义。
cc936.c:本文件在 option 目录下,是简体中文支持所需要添加的文件,包含了简体中文的GBK 和 Unicode 相互转换功能函数。
ffconf.h: 这个头文件包含了对 FatFs 功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs 的功能。如需要支持简体中文,需要把 ffconf.h 中的 _CODE_PAGE 的宏改成 936 并把上面的 cc936.c 文件加入到工程之中。
在这里插入图片描述
CubeMx是对官方源码进行了部分修改,在编写底层驱动接口时不再直接修改文件diskio.c,而是修改文件use_diskio.c,内部会通过指针调用,fatfs.c是用来编写应用层代码的,注意的是使用FATFS_LinkDriver()函数进行设备结构体初始化关联。

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 == 1
  DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT (*disk_ioctl)      (BYTE, BYTE, void*);              /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */

}Diskio_drvTypeDef;

FatFs 移植需要用户支持函数
在这里插入图片描述

三、Fatfs系统 函数介绍

1、f_mount() 挂载|卸载物理设备

FRESULT f_mount (
	FATFS* fs,			/* Pointer to the file system object (NULL:unmount)*/
	const TCHAR* path,	/* Logical drive number to be mounted/unmounted */
	BYTE opt			/* Mode option 0:Do not mount (delayed mount), 1:Mount immediately */
)

fs:指向 FATFS 变量指针,如果赋值为 NULL 可以取消物理设备挂载
path:要挂载/卸载的逻辑驱动器号;使用设备根路径表示,与物理设备编号挂钩,上面代码中由于定义 SPI Flash 芯片物理编
号为 0(见FATFS_LinkDriverEx()函数),所以这里使用“0:”
opt:模式选项0:不挂载(延迟挂载),1:立即挂载
返回: FRESULT 类型值,指示运行情况

2、f_mkfs() 创建一个FAT/exFAT卷,系统格式化

FRESULT f_mkfs (
	const TCHAR* path,	/* Logical drive number */
	BYTE opt,			/* Format option */
	DWORD au,			/* Size of allocation unit (cluster) [byte] */
	void* work,			/* Pointer to working buffer */
	UINT len			/* Size of working buffer */
)

**path:**要挂载/卸载的逻辑驱动器号;使用设备根路径表示
**opt:**系统格式,Format options (2nd argument of f_mkfs)

/* Format options (2nd argument of f_mkfs) */
#define FM_FAT		0x01
#define FM_FAT32	0x02
#define FM_EXFAT	0x04
#define FM_ANY		0x07
#define FM_SFD		0x08

**au:**分配单元大小(集群)[字节],扇区大小;0–默认
格式化成功后需要先取消挂载原来设备,再重新挂载设备

四、Fatfs系统常用功能

1、FatFs多项功能测试

static FRESULT miscellaneous(void)
{
  DIR dir;
  FATFS *pfs;
  DWORD fre_clust, fre_sect, tot_sect;
  
  printf("\n*************** 设备信息获取 ***************\r\n");
  /* 获取设备信息和空簇大小 */
  res_flash = f_getfree("1:", &fre_clust, &pfs);

  /* 计算得到总的扇区个数和空扇区个数 */
  tot_sect = (pfs->n_fatent - 2) * pfs->csize;
  fre_sect = fre_clust * pfs->csize;

  /* 打印信息(4096 字节/扇区) */
  printf("》设备总空间:%10lu KB。\n》可用空间:  %10lu KB。\n", tot_sect *4, fre_sect *4);
  
  printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");
  res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",
                            FA_OPEN_ALWAYS|FA_WRITE|FA_READ );
	if ( res_flash == FR_OK )
	{
    /*  文件定位 */
    res_flash = f_lseek(&fnew,f_size(&fnew));
    if (res_flash == FR_OK)
    {
      /* 格式化写入,参数格式类似printf函数 */
      f_printf(&fnew,"\n在原来文件新添加一行内容\n");
      f_printf(&fnew,"》设备总空间:%10lu KB。\n》可用空间:  %10lu KB。\n", tot_sect *4, fre_sect *4);
      /*  文件定位到文件起始位置 */
      res_flash = f_lseek(&fnew,0);
      /* 读取文件所有内容到缓存区 */
      res_flash = f_read(&fnew,readbuffer,f_size(&fnew),&fnum);
      if(res_flash == FR_OK)
      {
        printf("》文件内容:\n%s\n",readbuffer);
      }
    }
    f_close(&fnew);    
    
    printf("\n********** 目录创建和重命名功能测试 **********\r\n");
    /* 尝试打开目录 */
    res_flash=f_opendir(&dir,"1:TestDir");
    if(res_flash!=FR_OK)
    {
      /* 打开目录失败,就创建目录 */
      res_flash=f_mkdir("1:TestDir");
    }
    else
    {
      /* 如果目录已经存在,关闭它 */
      res_flash=f_closedir(&dir);
      /* 删除文件 */
      f_unlink("1:TestDir/testdir.txt");
    }
    if(res_flash==FR_OK)
    {
      /* 重命名并移动文件 */
      res_flash=f_rename("1:FatFs读写测试文件.txt","1:TestDir/testdir.txt");      
    } 
	}
  else
  {
    printf("!! 打开文件失败:%d\n",res_flash);
    printf("!! 或许需要再次运行“FatFs移植与读写测试”工程\n");
  }
  return res_flash;
}

2、FatFs文件信息获取测试

FILINFO fno;
/**
  * 文件信息获取
  */
static FRESULT file_check(void)
{

  
  /* 获取文件信息 */
  res_flash=f_stat("1:TestDir/testdir.txt",&fno);
  if(res_flash==FR_OK)
  {
    printf("“testdir.txt”文件信息:\n");
    printf("》文件大小: %ld(字节)\n", fno.fsize);
    printf("》时间戳: %u/%02u/%02u, %02u:%02u\n",
           (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,fno.ftime >> 11, fno.ftime >> 5 & 63);
    printf("》属性: %c%c%c%c%c\n\n",
           (fno.fattrib & AM_DIR) ? 'D' : '-',      // 是一个目录
           (fno.fattrib & AM_RDO) ? 'R' : '-',      // 只读文件
           (fno.fattrib & AM_HID) ? 'H' : '-',      // 隐藏文件
           (fno.fattrib & AM_SYS) ? 'S' : '-',      // 系统文件
           (fno.fattrib & AM_ARC) ? 'A' : '-');     // 档案文件
  }
  return res_flash;
}

3、FatFs文件扫描测试

char fpath[100];                  /* 保存当前扫描路径 */
  printf("***************** 文件扫描测试 ****************\r\n");
  strcpy(fpath,"1:");
  scan_files(fpath);
/**
  * @brief  scan_files 递归扫描FatFs内的文件
  * @param  path:初始扫描路径
  * @retval result:文件系统的返回值
  */
static FRESULT scan_files (char* path) 
{ 
  FRESULT res; 		//部分在递归过程被修改的变量,不用全局变量	
  FILINFO fno; 
  DIR dir; 
  int i;            
  char *fn;        // 文件名	
	
#if _USE_LFN 
  /* 长文件名支持 */
  /* 简体中文需要2个字节保存一个“字”*/
  static char lfn[_MAX_LFN*2 + 1]; 	
  fno.lfname = lfn; 
  fno.lfsize = sizeof(lfn); 
#endif 
  //打开目录
  res = f_opendir(&dir, path); 
  if (res == FR_OK) 
	{ 
    i = strlen(path); 
    for (;;) 
		{ 
      //读取目录下的内容,再读会自动读下一个文件
      res = f_readdir(&dir, &fno); 								
      //为空时表示所有项目读取完毕,跳出
      if (res != FR_OK || fno.fname[0] == 0) break; 	
#if _USE_LFN 
      fn = *fno.lfname ? fno.lfname : fno.fname; 
#else 
      fn = fno.fname; 
#endif 
      //点表示当前目录,跳过			
      if (*fn == '.') continue; 	
      //目录,递归读取      
      if (fno.fattrib & AM_DIR)         
			{ 			
        //合成完整目录名        
        sprintf(&path[i], "/%s", fn); 		
        //递归遍历         
        res = scan_files(path);	
        path[i] = 0;         
        //打开失败,跳出循环        
        if (res != FR_OK) 
					break; 
      } 
			else 
			{ 
				printf("%s/%s\r\n", path, fn);								//输出文件名	
        /* 可以在这里提取特定格式的文件路径 */        
      }//else
    } //for
  } 
  return res; 
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值