【STM32进阶笔记】FATFS文件系统(上)

本文介绍了FATFS文件系统,它是一个与平台无关、免费开源的文件系统,适合嵌入式硬件如STM32。内容包括FATFS的引入、特点,以及移植过程中的关键步骤,如源码文件解析、重要配置选项和底层接口函数的编写。FATFS提供兼容Windows的FAT文件系统,并支持多卷、长文件名等功能,适合小型嵌入式系统。
摘要由CSDN通过智能技术生成

  本专栏争取每周三更新直到更新完成,期待大家的订阅关注,欢迎互相学习交流。

  本文需要一些SD卡的前置知识,后续文章会介绍,这里先介绍一下FATFS文件系统。关于FATFS的文章分为上下两篇,上篇主要介绍什么是FAT文件系统以及FATFS的移植,下篇主要介绍FATFS的一些API函数。

文章封面

一、FATFS文件系统简介

1.1 FATFS引入

  通常我们买来一张全新的SD卡时,如果我们想把它通过读卡器插入到电脑使用,我们需要先对其格式化,这是因为Windows系统使用的是FAT文件系统,我们格式化SD卡,实际也就是在SD卡中建立一个FAT文件系统,这样我们的电脑才能识别它。

  FAT是一个专门为小型的嵌入式系统设计的,完全用标准C语言编写,完全免费开源的文件系统。FAT具有良好的硬件平台独立性,可以移植到C51、PIC、ARM等单片机上,而且只需要做一些简单的修改,使用起来非常方便。

1.2 FATFS特点

  FATFS有以下特点

  • Windows 兼容的 FAT 文件系统
  • 与平台无关,移植简单
  • 代码量少、效率高
  • 支持多卷(物理驱动器或分区,最多 10 个卷)
  • 多个 ANSI/OEM 代码页包括 DBCS
  • 支持长文件名、ANSI/OEM 或 Unicode
  • 支持 RTOS
  • 支持多种扇区大小
  • 只读、最小化的 API 和 I/O 缓冲区等

  这里简单说明一下几个名词,仅供参考,大家可以自行搜索了解更多详细内容。

  上面提到的指的是是硬盘上的存储空间,一个硬盘包括好多卷,一卷也可以跨越许多磁盘。卷又分为很多种,比如启动卷、跨区卷、带区卷等。此外还有,指的是磁盘文件存储管理的最小单位。

  Unicode(Universal Multiple-Octet Coded Character Set)简称UCS,中文意思是统一码,或者叫万国码,可以简单理解为他就是一个数字映射表,每一个数字代表不同的字符或文字。

二、FATFS文件系统移植

2.1 FATFS源码文件简介

  下面我们介绍一下如何移植FATFS。首先我们需要下载到它的源码,有需要的友友可以私信获取。下载完成后打开其中的“src”文件夹,其中是我们需要的源码。我们在移植FATFS模块的时候,一般只需要修改ffconf.h 和 diskio.c这两个文件。

src文件夹

  • option文件夹是一些可选的外部C文件,包含了多语言支持需要的文件和转换函数,移植时我们通常会选择cc936.c文件,用来支持简体中文,它包含了简体中文GBK和Unicode互相转换的功能函数。
    option文件夹
  • diskio.c文件是FATFS移植的最关键文件,它为文件系统提供了最底层的接口返回问函数。
  • diskio.h里面包含了FATFS用到的宏定义以及diskio.c文件内与底层硬件接口相关的函数声明。
  • 00history.txt介绍了当前FATFS的版本更新情况。
  • 00readme.txt介绍了文件夹中各个文件的功能。
  • integer.h文件中是一些数据类型定义。
  • ff.c文件时FATFS的核心,里面包含了FATFS的各个模块程序,时文件管理的实现方法,我们在移植时通常不需要对这个文件进行修改。
  • ffconf.h是FATFS的配置文件,其中包含了对FATFS功能配置的宏定义。我们通过修改这些宏定义就可会议实现对FATFS功能的裁剪,达到自己想要的效果。

2.2 FATFS重要配置选项

  上面在介绍源码文件时提到,FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们这里再介绍一些重要的配置选项。

  • _FS_TINY该宏定义时设置使用的是标准模式还是微小模式,我们通常设置为0,使用标准模式。
  • _FS_READONLY该宏定义是选择是否使用只读模式,通常我们选择可读可写,设置为0。
  • _FS_MINIMIZE该宏定义是选择是否裁剪掉一些函数,通常选择不裁剪,设置为0。
  • _USE_STRFUNC该宏定义是设置是否支持字符串类操作,通常选择支持,设置为1。
  • _USE_MKFS该宏定义是设置是否使用格式化,我们选择使用,设置为1。
  • _USE_FASTSEEK该宏定义用来设定是否是能快速定位,通常选择使用,设置为1。
  • _USE_LABEL该宏定义用来设置是否支持磁盘盘符读取与设置,通常选择支持,设置为1。使能后我们就可以通过相关函数来读取或者设置磁盘的名字。
  • _CODE_PAGE该宏定义用来设置语言类型,通常我们选择简体中文,设置为936(也就是c936.c文件)。
  • _USE_LFN该宏定义用来设置是否支持长文件名,取值范围是0到3。0表示不支持长文件名,1~3表示支持长文件名,但是存储的地方不一样。通常设置为3,可以通过ff_memalloc函数来动态分配长文件名的存储区域。
  • _MAX_LFN该宏定义用来设置允许的最大文件名长度,我们设置为最大值255。
  • _LFN_UNICODE该宏定义用来设置是否使用FATFS的字符编码,通常设置为不使用,设置为0。
  • _VOLUMES该宏定义用来设置FATFS支持的逻辑涉笔数量。
  • _MAX_SS该宏定义用来设置山区缓冲的最大值,一般设置为512。

2.3 FATFS移植步骤

  想要将FATFS移植到STM32实际并不复杂,主要分为三步

  • 下载FATFS源码
  • 将源码添加到Keil工程
  • 编写一些底层的接口函数

  通常最后一步我们需要编写六个底层的接口函数,下面我们来详细地介绍一下它们。

2.3.1 disk_initialize函数

  • 函数功能:初始化磁盘。
  • 函数原型:DSTATUS disk_initialize(BYTE pdrv);
  • 输入参数:prdrv是要初始化的逻辑驱动器号,也就是盘符,取值范围是0~9。
  • 返回值:返回一个盘符状态作为结果。
  • 注意事项:应用程序不应该在FATFS活动时调用此函数,否则卷上的FAT结构可能会损坏。
//初始化磁盘
DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	u8 res=0;	    
	switch(pdrv)
	{
		case SD_CARD://SD卡
			res=SD_Init();//SD卡初始化 
  			break;
		case EX_FLASH://外部flash
			W25QXX_Init();
			FLASH_SECTOR_COUNT=2048*12;//W25Q1218,前12M字节给FATFS占用 
 			break;
		default:
			res=1; 
	}		 
	if(res)return  STA_NOINIT;
	else return 0; //初始化成功 
} 

2.3.2 disk_status函数

  • 函数功能:返回当前磁盘驱动器的状态。
  • 函数原型:DSTATUS disk_status (BYTE pdrv);
  • 输入参数:pdrv是要确认的逻辑驱动器号,也就是盘符,取值范围是0~9。
  • 返回值:有如下几个返回值
#define STA_NOINIT		0x01	/* Drive not initialized */
#define STA_NODISK		0x02	/* No medium in the drive */
#define STA_PROTECT		0x04	/* Write protected */

  FATFS只使用前两个返回值。

//获得磁盘状态
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{ 
	return RES_OK;
} 

2.3.3 disk_read函数

  • 函数功能:从磁盘上读取扇区。
  • 函数原型:DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
  • 输入参数:pdrv:驱动器逻辑号;
    buff:指向存储读取数据字节数组的指针;
    sector:指定起始扇区的逻辑块上的地址;
    count:指定要读取的扇区数;
  • 返回值:返回值结构体如下
/* 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;
//读扇区
//pdrv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
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 */
)
{
	u8 res=0; 
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误		 	 
	switch(pdrv)
	{
		case SD_CARD://SD卡
			res=SD_ReadDisk(buff,sector,count);	 
			while(res)//读出错
			{
				SD_Init();	//重新初始化SD卡
				res=SD_ReadDisk(buff,sector,count);	
				//printf("sd rd error:%d\r\n",res);
			}
			break;
		case EX_FLASH://外部flash
			for(;count>0;count--)
			{
				W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			res=0;
			break;
		default:
			res=1; 
	}
   //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
    if(res==0x00)return RES_OK;	 
    else return RES_ERROR;	   
}

2.3.4 disk_write函数

  • 函数功能:向磁盘写入一个或多个扇区。
  • 函数原型:DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
  • 输入参数:pdrv:要操作的逻辑驱动器号;
    buff:指向要写入的数组的指针;
    sector:指定起始扇区逻辑块上的地址;
    count:指定要写入的扇区数,取值范围是1~128;
  • 返回值:与上面的读函数为同一个结构体。
//写扇区
//pdrv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _USE_WRITE
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 */
)
{
	u8 res=0;  
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误		 	 
	switch(pdrv)
	{
		case SD_CARD://SD卡
			res=SD_WriteDisk((u8*)buff,sector,count);
			while(res)//写出错
			{
				SD_Init();	//重新初始化SD卡
				res=SD_WriteDisk((u8*)buff,sector,count);	
				//printf("sd wr error:%d\r\n",res);
			}
			break;
		case EX_FLASH://外部flash
			for(;count>0;count--)
			{										    
				W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			res=0;
			break;
		default:
			res=1; 
	}
    //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
    if(res == 0x00)return RES_OK;	 
    else return RES_ERROR;	
}
#endif

2.3.5 disk_ioctl函数

  • 函数功能:控制设备指定特性和一些除了读写外的杂项功能。
  • 函数原型:DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
  • 输入参数:pdrv:要操作的逻辑驱动器号;
    cmd:命令代码;
    buff:指向参数缓冲区的指针,取决于命令代码,不使用时指向一个NULL指针;
  • 返回值:与上面的读/写函数为同一个结构体。
  • 注意事项

  这里贴一下一些命令的宏定义

/* 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 */
//其他表参数的获得
//pdrv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
DRESULT res;						  			     
	if(pdrv==SD_CARD)//SD卡
	{
	    switch(cmd)
	    {
		    case CTRL_SYNC:
				res = RES_OK; 
		        break;	 
		    case GET_SECTOR_SIZE:
				*(DWORD*)buff = 512; 
		        res = RES_OK;
		        break;	 
		    case GET_BLOCK_SIZE:
				*(WORD*)buff = SDCardInfo.CardBlockSize;
		        res = RES_OK;
		        break;	 
		    case GET_SECTOR_COUNT:
		        *(DWORD*)buff = SDCardInfo.CardCapacity/512;
		        res = RES_OK;
		        break;
		    default:
		        res = RES_PARERR;
		        break;
	    }
	}else if(pdrv==EX_FLASH)	//外部FLASH  
	{
	    switch(cmd)
	    {
		    case CTRL_SYNC:
				res = RES_OK; 
		        break;	 
		    case GET_SECTOR_SIZE:
		        *(WORD*)buff = FLASH_SECTOR_SIZE;
		        res = RES_OK;
		        break;	 
		    case GET_BLOCK_SIZE:
		        *(WORD*)buff = FLASH_BLOCK_SIZE;
		        res = RES_OK;
		        break;	 
		    case GET_SECTOR_COUNT:
		        *(DWORD*)buff = FLASH_SECTOR_COUNT;
		        res = RES_OK;
		        break;
		    default:
		        res = RES_PARERR;
		        break;
	    }
	}else res=RES_ERROR;//其他的不支持
    return res;
}
#endif

2.3.6 get_fattime函数

  • 函数功能:获取当前时间。
  • 返回值:返回以双字封装的当前时间,具体格式这里不再详细描述了。
  • 注意事项:get_fattime函数必须返回一个合法的时间,如果系统不支持实时时钟,可以返回0。

  通过上述步骤,我们就完成了对于FATFS的移植,FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续 API 的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二土电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值