FatFs文件系统

前言

    在芯片(如:STM32F103VET6单片机)使用过程中,为了方便数据的存储,我们加入了串行的外部Flash(SPI通信)。在使用存储的时候,如需要记录一个字符串“我学完FatFs文件系统觉得我现在强的可怕”,我们可以把这些文字转化成 ASCII 码,存储在数组中,然后调用写函数,把数组内容写入到Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以 ASCII 码的格式进行解读。
我们可以发现有几个问题:
(1)难以记录有效数据的位置;
(2)难以确定存储介质的剩余空间;
(3)不明确应该以何种格式来解读数据。
直接存储数据的方式对于小容量的存储介质如 EEPROM 还可以接受,但对于 SPI Flash 芯片或者 SD 卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。

补充小知识:ASCII表示一个字符需要占用七位二进制数,最左边为0,所以要占用一个字节。

一、文件系统

    文件系统是对数据进行管理的方式,使用文件系统可以有效的管理存储介质(Flash,EEPROM,SD卡,磁盘等);
    使用文件系统时,它为了存储和管理数据,在存储介质建立了一些组织结构这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化时会在存储介质上新建一个文件分配表目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。
    使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程,文件系统的存在使存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。

补充小知识:
关于内存扇区、块、簇的基本概念:
    扇区(sector):flash可操作的最小单元,通常指我们擦除的最小单元大小,以sdnand举例,通常最小为512Byte
    块(block) 以及簇(cluster):其实这是两个相同的概念,只是由于历史原因,在不同系统上的不同称呼,在windows中称簇,而在linux中称块。一个簇/块由多个扇区组成,由于一个扇区的空间较小,因此文件系统通过会将多个扇区组合在一起形成一个簇,并以簇为单位进行读写操作!一个簇通常可以由 2、4、8、… 、2的n次方个扇区组成。

目录和文件分配表的概念

目录:告诉我文件的各个属性,比如名字,大小,时间,读写属性等等;
文件分配表:告诉我们这个文件的存储地方,怎么读取这个文件,如下图从目录读完读第一个扇区,第一个读完读取第二个……等等。

文件系统的空间示意图:存储了A.TXT,B.TXT,C.TXT文件
在这里插入图片描述
目录示意图:记录了文件的开始簇位置、大小等信息
在这里插入图片描述
文件分配表:文件 a.txt 我们根据目录项中指定的 a.txt 的首簇为 2,然后找到文件分配表的第 2 簇记录,上面登记 的是 3,就能确定下一簇是 3。找到文件分配表的第 3 簇记录,上面登记的 是 4,就能确定下一簇是 4…直到指到第 11 簇,发现下一个指向是 FF,就是结束。文件便读取完毕。
在这里插入图片描述
删除B.TXT文件,创建D.TXT文件后的空间示意图
在这里插入图片描述
删除B.TXT文件,创建D.TXT文件后的目录示意图
在这里插入图片描述
删除B.TXT文件,创建D.TXT文件后的文件分配表示意图
在这里插入图片描述

以上图片及文字引用野火教程视频
以下是我对FatFs使用的学习所得,和大家分享一下。

二、FatFs文件系统

1.FatFs文件系统简介

    FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISIC语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32(现在主要用的)等格式。
利用前面写好的SPlFlash芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPIFlash芯片以“文件”格式进行读写操作了。

补充小知识
C语言中的文件操作
文件的打开操作 :
    fopen 打开一个文件 文件的关闭操作 :
    fclose 关闭一个文件 文件的读写操作:
    fgetc 从文件中读取一个字符
    fputc
写一个字符到文件中去
    fgets 从文件中读取一个字符串
    fputs 写一个字符串到文件中去
    fprintf 往文件中写格式化数据
    fscanf 格式化读取文件中数据
    fread 以二进制形式读取文件中的数据
    fwrite 以二进制形式写数据到文件中去
    getw 以二进制形式读取一个整数
    putw
以二进制形式存贮一个整数 文件状态检查函数:
    feof 文件结束
    ferror 文件读/写出错
    clearerr 清除文件错误标志
    ftel了解文件指针的当前位置
文件定位函数
    rewind 反绕
    fseek 随机定位

2.下载地址

    FatFs文件系统的源码可以从fatfs官网下载:http://elm-chan.org/fsw/ff/00index%20e.html
打开链接下拉到最下面可看到如下内容:
在这里插入图片描述

3.源码文件内容解释

解压之后会得以下内容:在documents文件夹里面是一些使用的帮助文档;在source中是FatFs文件系统的源代码,source文件夹就是我们需要添加到工程中的文件。
document:使用的帮助文档;对于整个document文件我们不需要关注,当然文件夹中有官网链接可以点进去看看,多了解一下。
source:FatFs文件系统的源代码,即需添加的文件;
在这里插入图片描述

source文件打开有以下文件:
在这里插入图片描述

diskio.c:包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。(我们主要操作的也是diskio.c和diskio.h
ff.c: FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。(我们不需要动它)
ff.h:文件中包含了一些数值类型定义:UINT、BYTE、WORD、DWORD等类型,为避免因内核不同而导致一些字节类型的定义不统一(屏蔽编译器差异)。
ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如需支持简体中文,需把ffconf.h中的_CODE_PAGE的宏改成936,这个字库在ffunicode.c中。(这其中的宏定义也是我们需要根据自己需求进行更改
下图是FatFs在程序中的关系网络:
在这里插入图片描述

下图是各文件解释:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/641d7e486022411aa060ad3be0523815.png

4.文件移植

首先加入source文件夹中的所有.c文件添加到工程当中:
在这里插入图片描述

之后,我们只需要修改diskio.c中下图所示的函数:
在这里插入图片描述
!!!注意:上方图片蓝色划线的函数,不要在应用层进行调用,即只能在diskio.c中修改,不能拿到除这个文件以外的文件中调用

具体修改内容(一)diskio.c

1.定义自己的逻辑设备号,并且添加存储介质所对应的相关头文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8667e004e70e43e0b7aee2c8d48e5a0f.png
2.修改disk_stataus函数官方对该函数解释
该函数作用是来查询当前驱动器(存储介质)状态。
DSTATUS disk_status (BYTE pdrv/* [IN] Physical drive number */);
传参:(BYTE pdrv)物理驱动号(即1中定义的逻辑设备号)
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)STA_NOINIT:标识该设备未初始化成功,未进入就绪状态;STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效;返回值的最低位为0:表示该设备初始化成功。
以下代码中,我把W25Q128内存分为两部分:一部分用来存储图片(之后用USB模拟U盘来用,直接可以把图片拖进存储中),一部分用来存储数据。以下代码没有写写保护判断,根据个人使用情况可自行添加。

/*-----------------------------------------------------------------------*/
/* 获取设备状态                                                          */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
	BYTE pdrv		/* 物理编号 */
)
{
    DSTATUS status = STA_NOINIT;
    switch (pdrv) 
    {
        case SD_CARD:      
        break;
        case FLASH_PICTURE:      
            /* SPI Flash状态检测:读取SPI Flash 设备ID */
            if(sFLASH_ID == SPI_FLASH_ReadID())//#define sFLASH_ID 0XEF4018 //这为W25Q128设备返回值
            {
                /* 设备ID读取结果正确 */
                status &= ~STA_NOINIT;
            }
            else
            {
                /* 设备ID读取结果错误 */
                status |= STA_NOINIT;
            }
        break;
		case FLASH_DATA:
			/* SPI Flash状态检测:读取SPI Flash 设备ID */
			 if(sFLASH_ID == SPI_FLASH_ReadID())//#define sFLASH_ID 0XEF4018 //这为W25Q128设备返回值
            {
                /* 设备ID读取结果正确 */
                status &= ~STA_NOINIT;
            }
            else
            {
                /* 设备ID读取结果错误 */
                status |= STA_NOINIT;
            }
        default:
            status = STA_NOINIT;
    }
    return status;
}

3.修改disk_initialize函数官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DSTATUS disk_initialize (BYTE pdrv /* [IN] Physical drive number */);
传参:(BYTE pdrv)物理驱动号(即1中定义的逻辑设备号)
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)STA_NOINIT:标识该设备未初始化成功,未进入就绪状态;STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效;返回值的最低位为0:表示该设备初始化成功。

/*-----------------------------------------------------------------------*/
/* 设备初始化                                                            */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
	BYTE pdrv				/* 物理编号 */
)
{
	uint16_t i;
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) 
	{
		case SD_CARD:      
       		break;
		case FLASH_PICTURE:    /* SPI Flash */ 
				/* 初始化SPI Flash */
				SPI_Flash_Init();//就是对存储介质所连接芯片的IO进行初始化,对IO口所对应的SPI进行初始化
				/* 延时一小段时间 */
				i=500;
				while(--i);	
				/* 唤醒SPI Flash */
				SPI_Flash_WAKEUP();
				/* 获取SPI Flash芯片状态 */
				status=disk_status(FLASH_PICTURE); //01
		break;
		case FLASH_DATA:    /* SPI Flash */ 
				/* 初始化SPI Flash */
				SPI_Flash_Init();
				/* 延时一小段时间 */
				i=500;
				while(--i);	
				/* 唤醒SPI Flash */
				SPI_Flash_WAKEUP();
				/* 获取SPI Flash芯片状态 */
				status=disk_status(FLASH_DATA);
		break;
		default:
		status = STA_NOINIT;
	}
	return status;
}

4.修改disk_read函数官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_read (
BYTE pdrv, // [IN] Physical drive number
BYTE* buff, // [OUT] Pointer to the read data buffer
LBA_t sector, // [IN] Start sector number
UINT count // [IN] Number of sectros to read
);

传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号)BYTE* buff:数据缓存区;DWORD sector:读取的扇区首地址32位;DWORD sector:扇区个数。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;RES_WRPRT:写保护;RES_NOTRDY:没准备好;RES_PARERR:无效参数。
注:FatFs支持的扇区大小为512到4096个字节,在FLASH中扇区是擦除的最小单位,而在FatFs中扇区(簇)是读/写都是最小的单位,在下面函数中,形参sector是FatFs中的扇区(簇)地址,代表第几个扇区(簇),则默认每个扇区设置大小为4096个字节,那么传到Flash读取的地址的时候需要给sector向左移动12位扩大4096倍

补充小知识:Flash——W25Q128 (128Mbit,16MByte),被组织为65536个可编程的页,每页256bytes。擦除方式分为16页一组(即一个扇区4kbytes),128页一组(即8个扇区32kbytes),256页一组(即16个扇区或1个块64kbytes),或整个芯片擦除。该芯片有4096个可擦除扇区,或256个可擦除块。

/*-----------------------------------------------------------------------*/
/* 读扇区:读取扇区内容到指定存储区                                              */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE pdrv,		/* 设备物理编号(0..) */
	BYTE *buff,		/* 数据缓存区 */
	DWORD sector,	/* 扇区首地址 */
	UINT count		/* 扇区个数(1..128) */
)
{
    DRESULT status = RES_PARERR;
    switch (pdrv) 
    {
    	case SD_CARD:      
       		break;
        case FLASH_PICTURE:  
			sector+=256;
			//一个数左移12位,就是扩大2的12次方,4096倍,即FatFs的最小单位设置成4096个字节
            SPI_Flash_Read(buff, sector <<12, count<<12);
            status = RES_OK;//在这默认全部读取正常
        break;
        case FLASH_DATA:
            sector+= 1536;
            SPI_Flash_Read(buff, sector <<12, count<<12);
            status = RES_OK;
        break;
        default:
            status = RES_PARERR;
    }
    return status;
}

5.修改disk_write函数官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_write (
BYTE pdrv, // [IN] Physical drive number
const BYTE* buff, // [IN] Pointer to the data to be written
LBA_t sector, // [IN] Sector number to write from
UINT count // [IN] Number of sectors to write
);

传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号) const BYTE* buff:欲写入数据的缓存区;LBA_t sector:写入扇区首地址32位;UINT count:扇区个数。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;RES_WRPRT:写保护;RES_NOTRDY:没准备好;RES_PARERR:无效参数。

/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上                                      */
/*-----------------------------------------------------------------------*/
//下方宏定义可能因为版本不同宏定义不同
#if FF_FS_READONLY == 0 //根据这个宏定义判断这个函数要不要加进去,是个写保护,是0加进去,1不加进去
DRESULT disk_write (
	BYTE pdrv,			  /* 设备物理编号(0..) */
	const BYTE *buff,	/* 欲写入数据的缓存区 */
	DWORD sector,		  /* 扇区首地址 */
	UINT count			  /* 扇区个数(1..128) */
)
{
    uint32_t write_addr; 
    DRESULT status = RES_PARERR;
    if (!count) {
    return RES_PARERR;		/* Check parameter */
    }

    switch (pdrv)
    {
    	case SD_CARD:      
       		break;
        case FLASH_PICTURE:
			sector+=256;
			while(count--)
			{
				write_addr = sector<<12;    
	            SPI_Flash_Erase_Sector(write_addr);//擦除
	            SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
	            sector++;
	            buff+=4096;//默认FatFs的一个扇区(簇)4096个大小
			}
			/*
			//一般使用过程中都操作一个,可以直接按照下面这样写,但按照上面这样写更严谨
			write_addr = sector<<12;    
            SPI_Flash_Erase_Sector(write_addr);//擦除
            SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
			*/
            status = RES_OK;
        break;
        case FLASH_DATA:
            sector+=1536;
            {
				write_addr = sector<<12;    
	            SPI_Flash_Erase_Sector(write_addr);//擦除
	            SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
	            sector++;
	            buff+=4096;
			}
            status = RES_OK;
        break;
        default: 
            status = RES_PARERR;
    }
    return status;
}
#endif

因为FatFs文件系统会考虑到存储介质,需要擦除或者同步的时候需要等待。我们可以把这种等待、擦除放到后面移植的disk_ioctl函数中,但是一般在Flash的SPI_Flash_Write函数中直接把等待的写进去了,所以也就没往disk_ioctl函数放写完的等待函数。
6.修改disk_ioctl函数官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_ioctl (
BYTE pdrv, // [IN] Drive number
BYTE cmd, // [IN] Control command code
void* buff // [I/O] Parameter and data buffer
);

传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号)BYTE cmd:命令(具体有哪些命令可以点进”官方对该函数的解释“查看);void* buff:数据缓冲区,既有可能输出也有可能输入,根据命令不同,可能会有缓存区。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;;RES_NOTRDY:没准备好;RES_PARERR:无效参数。

主要以下五个命令:(主要是红色)
CTRL_SYNC: 确保设备已完成挂起的写入过程。 如果磁盘I / O层或存储设备具有回写缓存,则脏缓存数据必须立即提交到介质。 如果在disk_write函数中完成了对介质的每次写入操作,则此命令将不执行任何操作。
GET_SECTOR_COUNT: 获取设备上可用于文件系统的容量大小。比如设备容量是16MB,但我只用6MB做文件系统使用,那么此时该命令返回的值应该是6MB。根据变量类型的定义可以看出是32位 ,也就是说容量不能超出4GB。
GET_SECTOR_SIZE: 获取一个扇区大小的命令。
GET_BLOCK_SIZE: 获取设备中一个块大小的命令。
CTRL_TRIM: 通知设备不再需要扇区块上的数据,可以将其擦除。 如果不支持此功能或闪存设备不执行此命令,则无需执行任何操作。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/85cb8f676f004b4e87deff46adbb2d9d.png

主要是这三个:
GET_SECTOR_COUNT、GET_SECTOR_SIZE 、GET_BLOCK_SIZE

DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理编号 */
	BYTE cmd,		  /* 控制指令 */
	void *buff		/* 写入或者读取数据地址指针 */
)
{
    DRESULT status = RES_PARERR;
    switch (pdrv)
    {
    	case SD_CARD:      
       		break;
        case FLASH_PICTURE:
            switch (cmd) 
            {
            	case CTRL_SYNC://写入同步在disk_write函数已经完成,这里默认返回ok
       				 status = RES_OK;
                break; 
                case GET_SECTOR_COUNT:
                	//大小:1280*4096/1024/1024=5MB
                    *(DWORD * )buff = 1280;//存储空间大小,有多少个FatFs扇区(sector)	
                    //总共16MB,分给这个区5MB,5*1024(KB)*1024(Byte)/4096=1280,按照最大扇区设置的4096个字节,分了1280个扇区给它
                    status = RES_OK;
                break;
                /* 扇区大小  */
                case GET_SECTOR_SIZE :
                    *(WORD * )buff = 4096;//每个FatFs扇区(sector)的大小,有多少个字节
                    status = RES_OK;
                break;
                /* 同时擦除扇区个数 */
                case GET_BLOCK_SIZE ://获取擦除的最小个数,以FatFs扇区(sector)为单位,注意:FatFs的扇区和Flash的扇区不是一个概念
                    *(DWORD * )buff = 1;
                    status = RES_OK;
                break;     
                case CTRL_TRIM:
       				 status = RES_OK;
                break; 
                default:
            		status = RES_PARERR;
            	break;   
            }
        break;
        case FLASH_DATA:
        {
            switch (cmd) 
            {
            	case CTRL_SYNC:
       				 status = RES_OK;
                break; 
                case GET_SECTOR_COUNT:
                    *(DWORD * )buff = 2560;	
                    status = RES_OK;	
                break;
                /* 扇区大小  */
                case GET_SECTOR_SIZE :
                    *(WORD * )buff = 4096;
                    status = RES_OK;
                break;
                /* 同时擦除扇区个数 */
                case GET_BLOCK_SIZE :
                    *(DWORD * )buff = 1;
                    status = RES_OK;
                break;  
                case CTRL_TRIM:
       				 status = RES_OK;
                break;   
                default:
            		status = RES_PARERR;
            	break;     
            }
            break;            
        }
        break;
        default:
            status = RES_PARERR;
    }
    return status;
}
#endif

7.添加DWORD get_fattime 函数官方对该函数解释
作用就是获取时间,就是你创建或者修改文件的时间。

DWORD get_fattime (void)//返回
{/*
    time_t t;
    struct tm *stm;
    t = time(0);
    stm = localtime(&t);
*//*
    return (DWORD)(stm->tm_year - 80) << 25 |
           (DWORD)(stm->tm_mon + 1) << 21 |
           (DWORD)stm->tm_mday << 16 |
           (DWORD)stm->tm_hour << 11 |
           (DWORD)stm->tm_min << 5 |
           (DWORD)stm->tm_sec >> 1;*/
    return      
			((DWORD)(2024- 1980) << 25)|	// Year 2024 
			((DWORD)5 << 21)|				// Month 5
			((DWORD)20 << 16)|				// Mday 20 
			((DWORD)0 << 11)| 				// Hour 0 
			((DWORD)0 << 5)|				// Min 0 
			((DWORD)0 >> 1);				// Sec 0 秒要除以2
}

具体修改内容(二)ffconf.h

(1)修改FF_MAX_SS为4096,理由:调用函数的时候会传入一个buff(即数据),这个buff可以跳入FATFS这个结构体,在最后一个定义的数组可查到,这个数组的长度按照我们的定义应为4096个字节;
在这里插入图片描述
(2)修改FF_VOLUMES为3(修改范围在1-10),理由:这个宏定义的是我们用了几个存储介质,从上面看到我们用了3个,如果使用人增加了存储介质,可以自行再增加;
在这里插入图片描述
(3)修改FF_USE_MKFS为1,理由:这个宏为了使用f_mkfs进行创建卷,所以要把它打开。
在这里插入图片描述
(4)修改FF_USE_LFN为1,理由:为了支持长文件名
在这里插入图片描述
(5)将FF_CODE_PAGE按照下面根据自己使用情况进行修改
在这里插入图片描述

5.函数调用

总共有以下函数:

文件访问

f_open:打开/创建文件;f_closf_close:关闭打开的文件;f_read:从文件中读取数据;f_write:将数据写入文件;f_lseek:移动读/写指针,扩展大小;f_truncate:截断文件大小;sync:刷新缓存的数据;f_forward:将数据转发到流;f_expand:为文件分配一个连续的块;f_gets:读取字符串;f_putc:写一个字符;f_puts:写一个字符串;f_printf:编写格式化字符串;f_tell:获取当前读/写指针;f_eof:测试文件结束;f_size:获取尺寸;f_error:测试错误;

目录访问

f_opendir:打开目录;f_closedir :关闭打开的目录;f_readdir:读取目录项;f_findfirst打开目录并且读取匹配的第一个项目;f_findnext:读取下一个匹配的项目。

文件和目录管理

f_stat:检查文件或者子目录是否存在;f_unlink:删除文件或者子目录;f_rename:重命名/移动文件或者子目录;f_chmod:更改文件或者子目录的属性;f_utime:更改文件或者子目录的时间戳;f_mkdir:创建子目录;f_chdir:更改当前目录;f_chdrive:更改电流驱动器;f_getcwd:检索当前目录和驱动器;

卷管理和系统配置

f_mount:注册/注销卷的工作区;f_mkfs:在逻辑驱动器上创建FAT卷;f_fdisk:在物理驱动器上创建分区;f_getfree:获取卷上的可用空间;f_getfree:获取卷上的可用空间;f_getlabel:获取卷标;f_setlabel:设置卷标;f_setcp:设置活动代码页;
以上函数大多都会有一个FRESULT类型的返回值:FatFs学习(1)——枚举:返回值FRESULT

typedef enum {
	FR_OK = 0,				/* (0) Succeeded *///函数执行成功,无错误。
	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer *///磁盘I/O层硬错误。
	FR_INT_ERR,				/* (2) Assertion failed *///断言错误。
	FR_NOT_READY,			/* (3) The physical drive cannot work *///物理驱动器无法工作。
	FR_NO_FILE,				/* (4) Could not find the file *///找不到文件。
	FR_NO_PATH,				/* (5) Could not find the path *///找不到路径。
	FR_INVALID_NAME,		/* (6) The path name format is invalid *///路径名格式无效。
	FR_DENIED,				/* (7) Access denied due to prohibited access or directory full *///拒绝访问。
	FR_EXIST,				/* (8) Access denied due to prohibited access *///名称冲突。
	FR_INVALID_OBJECT,		/* (9) The file/directory object is invalid *///文件/目录对象无效。
	FR_WRITE_PROTECTED,		/* (10) The physical drive is write protected *///写保护。
	FR_INVALID_DRIVE,		/* (11) The logical drive number is invalid *///逻辑驱动器号无效。
	FR_NOT_ENABLED,			/* (12) The volume has no work area *///当前卷无工作区。
	FR_NO_FILESYSTEM,		/* (13) There is no valid FAT volume *///没有有效的FAT卷。
	FR_MKFS_ABORTED,		/* (14) The f_mkfs() aborted due to any parameter error *///MKFS终止。
	FR_TIMEOUT,				/* (15) Could not get a grant to access the volume within defined period *///超时
	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy *///根据文件共享策略拒绝操作。
	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated *///无法分配 LFN 工作缓冲区。
	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > _FS_SHARE *///打开的文件数目> FF_FS_LOCK。
	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid *///参数无效。
} FRESULT;

我们使用文件系统的时候首先要使用f_mount挂在文件系统,其次使用f_mkfs创建一个卷积并且格式化,最后可以创建一个文件。具体我们写一个例子来说明:我们使用2:FLASH_DATA这个存储空间举例,在该存储介质中,我们创建1个文件夹,以时间命名

#include "ff.h" //要包含此头文件
FATFS fs;		/* FatFs文件系统对象 */
FRESULT res_csv;/* 文件操作结果 */
FIL fnew_csv;	/* 文件对象 */
UINT fnum_csv;  /* 文件成功读写的数量,是一个返回值*/
char filename[14] = {'0',':','2','0'};//文件名
char *fileheader = "Time,,Alarm value,,Alarm Way\r\n";//写入文件内容
void Filesystem_init(void)
{
    uint16_t i;
    char buffer[30];
    res_csv = f_mount(&fs_csv,"2:",1);
    if(res_csv == FR_NO_FILESYSTEM)
    {
        /* 格式化,操作FLASH_DATA存储介质,并且操作硬盘:0;默认自行分配大小:0*/
        res_csv=f_mkfs("2:",0,0);
        if(res_csv == FR_OK)
        {
            /* 格式化后,先取消挂载 */
            res_csv = f_mount(NULL,"2:",1);
            /* 重新挂载	*/
            res_csv = f_mount(&fs_csv,"2:",1);
            /*每天建立一个文件*/
            filename[4] = system.time.year / 0x10 + 0x30;filename[5] = system.time.year % 0x10 + 0x30;
            filename[6] = system.time.month / 0x10 + 0x30;filename[7] = system.time.month % 0x10 + 0x30;
            filename[8] = system.time.day / 0x10 + 0x30;filename[9] = system.time.day % 0x10 + 0x30;
            filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
            /*建立当天记录文件并写入起始目录*/
            res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);//文件存在则覆盖重新创建,不存在就创建创建|可写
            if(res_csv == FR_OK)
            {
                res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
            }
            f_close(&fnew_csv);
        }
        else
        {
            res_csv=f_mkfs("2:",0,0);
            if(res_csv == FR_OK)
            {
                /* 格式化后,先取消挂载 */
                res_csv = f_mount(NULL,"2:",1);
                res_csv = f_mount(&fs_csv,"2:",1);
                /*每天建立一个文件*/
                filename[4] = system.time.year / 0x10 + 0x30;system[5] = system.time.year % 0x10 + 0x30;
                filename[6] = system.time.month / 0x10 + 0x30;system[7] = system.time.month % 0x10 + 0x30;
                filename[8] = system.time.day / 0x10 + 0x30;system[9] = system.time.day % 0x10 + 0x30;
                filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
                /*建立当天记录文件并写入起始目录*/
                res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);
                if(res_csv == FR_OK)
                {
                    res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
                }
                f_close(&fnew_csv);
            }
        }
    }
	else if(res_csv == FR_OK)
	{
		/*每天建立一个文件*/
		filename[4] = system.time.year / 10 + 0x30;filename[5] = system.time.year % 10 + 0x30;
		filename[6] = system.time.month / 10 + 0x30;filename[7] = system.time.month % 10 + 0x30;
		filename[8] = system.time.day / 10 + 0x30;filename[9] = system.time.day % 10 + 0x30;
		filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
		/*查看今天的数据文件是否存在,如果没有就建立文件并写入起始数据(分类信息)*/
		res_csv = f_open(&fnew_csv,filename,FA_OPEN_EXISTING | FA_READ | FA_WRITE);
		if(res_csv != FR_OK)
		{
				res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);
				if(res_csv == FR_OK)
				{
						res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
				}
		}	
		/*检查文件头是否正确*/
		else
		{
				f_read(&fnew_csv,buffer,30,&fnum_csv);
				for(i = 0;i < 30;i ++)
				{
						if(buffer[i] != fileheader[i])
						{//和我要写的内容不一样的话我重新写
								f_write(&fnew_csv,fileheader,30,&fnum_csv);
						}
				}
		}
	}
    f_close(&fnew_csv);
}
int main(void)
{
	Filesystem_init();
	while(1);
}

如果读者您感觉文字看着有点难理解,并且有足够的时间学习,可以点解此链接,进行视频学习:FatFs视频教程

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FATFS文件系统是一种在FAT文件系统基础上进行了封装和简化的文件系统FATFS文件系统的原理是通过使用FAT表来索引和定位磁盘中的文件数据。FAT表是一种链式结构,类似于一本书的目录,用于记录文件的存储位置和状态。FAT文件系统将目录也抽象为文件,以简化对数据的管理。 在FATFS文件系统中,用户无需了解FATFS的内部结构和复杂的FAT协议,只需要调用提供的应用接口函数(如f_open,f_read,f_write和f_close等),就可以像在PC上读写文件一样简单地进行操作。FATFS文件系统将文件的存储组织为簇链式数据结构,文件被分成一系列的数据簇进行存储。 FAT文件系统的目录结构是一颗从根到叶的有向树,根目录是整个目录结构的入口。跟目录的位置在格式化时就已经确定,通常紧随FAT表之后,大小为32个扇区。根据根目录的位置,FAT文件系统可以寻址其他文件和文件夹。 FAT文件系统将目录(文件夹)当作一个特殊的文件来处理,在FAT16中,根目录的组织形式和普通的目录并没有不同,而在FAT32中,根目录也被当作文件处理。目录文件实际上是一个存放其他文件和文件夹入口参数的数据表,其占用空间的大小并不等同于其下所有数据的大小,但也不为0。目录文件以32个字节为单位进行簇的分配,每个字节偏移定义一个文件或文件夹的属性,形成一个简单的二维表。 总的来说,FATFS文件系统通过使用FAT表和目录文件来管理文件的索引和定位,为用户提供了简单而方便的文件操作接口。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雩风廿二YJ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值