STM32F429入门(二十二):SPI-FatFs文件系统

本文详细介绍了如何在STM32嵌入式系统上移植和使用FatFs文件系统,从理解文件系统的基本概念到FatFs的配置、驱动编写、错误排查,再到文件的读写操作和目录管理。通过FatFs,可以方便地对SPI Flash进行文件操作,实现类似于在电脑上的文件管理功能。
摘要由CSDN通过智能技术生成

使用文件系统可以更好地管理FLASH的存储空间。小型存储器如EEPROM可以直接存储,但是对于FLASH、SD卡等这些大容量的设备时,就需要文件系统来管理。

一、初识文件系统

当需要记录字符时,可以把这些文字转换成ASCII码,存储在数据中,然后调用SPI_FLASH_BufferWrite函数,把数组内容写入到SPI FLASH芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

缺陷:

  • 难以记录有效数据的位置

  • 难以确定存储介质的剩余空间

  • 不明确应以何种格式来解读数据

文件系统,就是对数据进行管理的方式。使用文件系统可以有效地管理存储介质。

使用文件系统时,它为了存储和管理数据,在存储介质建立了一些组织机构,这些结构包括操作系统引导区(windows、Linux等才有)、目录和文件。常见的windows下的文件系统格式包括FAT32\NTFS\exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化时会在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

磁盘分区表:也就是我们电脑中将硬盘分为好几个磁盘。Windows操作系统为了便于用户对磁盘的管理。加入了对磁盘分区的概念,即将一块磁盘逻辑划分为几块,它会把磁盘的分区信息记录到硬盘分区表中。在硬盘分区表中,描述了各个逻辑分区的属性,如分区的开始和结束位置所在的物理地址(柱面号、扇区号),空间大小等信息。

 

文件系统的结构与特性:

使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。

文件系统的存在使存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整地文件可能被分开成多端存储不连续的物理地址,使用目录链表的方式来获取下一段的位置。

文件系统的空间示意图

 目录:记录了文件的开始簇位置、大小等信息。

 文件分配表:(在我看来它更像一个链表的形式)文件a.txt我们根据目录项中指定的a.txt的首簇为2,然后找到文件分配表的第2簇记录,上面登记的是3,就能确定下一簇是3.....直到指向第11簇,发现下一个指向是FF,就是结束。文件便读取完毕。

 如果我们要删除B.txt文件,创建D.txt文件:

 目录将变为:

 文件分配表变为:由于D.txt文件大于B.txt,所以它在后面扩展了新的区域。

 

二、FatFs简介

以上的适用于Windows系统,接下来学习适合在嵌入式领域的文件系统:

由于STM32芯片上面没有系统,所以c语言标准库中操作文件的方式无法实现。所以我们需要FatFs文件系统,c语言标准库如下:c标准库 通过系统接口实现文件操作。

文件的打开操作 fopen 打开一个文件

文件的关闭操作 fclose 关闭一个文件

文件的读写操作 fgetc 从文件中读取一个字符         

                          fputc 写一个字符到文件中去         

                          fgets 从文件中读取一个字符串         

                         fputs 写一个字符串到文件中去        

                         fprintf 往文件中写格式化数据         

                         fscanf 格式化读取文件中数据         

                         fread 以二进制形式读取文件中的数据         

                         fwrite 以二进制形式写数据到文件中去         

                        getw 以二进制形式读取一个整数         

                       putw 以二进制形式存贮一个整数

文件状态检查函数 feof 文件结束          

                             ferror 文件读/写出错          

                            clearerr 清除文件错误标志          

                            ftell 了解文件指针的当前位置

文件定位函数 rewind 反绕        

                      fseek 随机定位

  • FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32等格式。(也就是说,这个文件系统它跟底层的存储介质是没有关系的,它会提供接口)

我们可以利用前面写好的SPI Flash芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPI Flash芯片以”文件“格式进行读写操作。

从FatFs的官网可以下载其源码:FatFs - Generic FAT Filesystem Module (elm-chan.org)

下载下来后,目录结构由帮助文档(doc)和文件系统源码(src)构成:

 

 打开源码我们会看到如下几个文件:

  • integer.h:文件中包含了一些数值类型定义。

  • diskio.c:包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。

  • ff.c:FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。(相当于C语言中的stdio.h)

  • cc936.c:本文在option目录下,是简体中文支持所需要添加的文件,包含了简体志文的GBK和Unicode相互转换功能函数。

  • ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如需要支持简体中文,需要把ffconf.h中的_CODE_PAGE的宏改成936并把上面的cc936.c文件加入到工程之中。

三、FatFs在程序中的关系网络

  • 用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。操作与C语言操作类似。

  • FatFs组件是FatFs的主体,文件都在源码src文件中,其中ff.c、ff.h、integer.h以及diskio.h四个文件我们不需要改动,只需要修改ffconf.h和diskio.c两个文件。

  • 底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。

四、FatFs移植

移植需要用户支持函数:

函数条件(ffconf.h)备注
disk_status disk_initialize disk_read总是需要底层设备驱动函数
disk_write get_fattime disk_ioctl (CTRL_SYNC)_FS_READONLY == 0底层设备驱动函数
disk_ioctl (GET_SECTOR_COUNT) disk_ioctl (GET_BLOCK_SIZE)_USE_MKFS == 1 (格式化)底层设备驱动函数
disk_ioctl (GET_SECTOR_SIZE)_MAX_SS != _MIN_SS底层设备驱动函数
disk_ioctl (CTRL_TRIM)_USE_TRIM == 1底层设备驱动函数
ff_convert ff_wtoupper_USE_LFN != 0Unicode支持,为支持简体中文,添加cc936.c到工程即可
ff_cre_syncobj ff_del_syncobj ff_req_grant ff_rel_grant_FS_REENTRANT == 1FatFs可重入配置,需要多任务系统支持(一般不需要)
ff_mem_alloc ff_mem_free_USE_LFN == 3长文件名支持,缓冲区设置在堆空间(一般设置_USE_LFN = 2 )

虽然说最后实现的效果跟上一篇文章中的使用SPI读写Flash操作差不多,但是它在应用层跟上一篇的是完全不一样的,它的流程就是相当于我们在电脑新建文档,编辑文档的样子,我们在SPI读取FLASH的基础上进行扩展:

(1)添加FatFs文件并修改文件

我们打开下载的FatFs的开源库,将下列三个文件添加进来:

 

 之后它会报错,它报错的原因是在于没有中带一些头文件,其实那些头文件都是用于例程的,我们把它删掉,把宏定义中的ATA、USB等介质删掉,留下我们想要读写的FLASH:

 之后就会报出当前不需要这个cc936.c文件的错误。

它的意思就是说,在这个环境下面不需要这个文件,需要移除。

我们打开ffconf.h,找到如下,并改为我们所需要的中文文件,之后将它屏蔽,不需要编译:

 之后修改寻找介质的函数,将其改为如下,当我们有多个设备时,可以使用宏来区分,在检测设备的同时也要检测FLASH的ID是否对的上:以下函数均在diskio.c

//选择设备
DSTATUS disk_status (
	BYTE pdrv		/* 物理编号 */
)
{
	DSTATUS stat;
    
	switch (pdrv) {
	case ATA :

		return stat;

	case SPI_FLASH :
	 
		if(SPI_FLASH_ReadID()==sFLASH_ID)
		{
		    return 0;
		}
		else
		{
			return STA_NOINIT;
		}
	}
    
	return STA_NOINIT;
}

对于 SPI Flash 芯片,我们直 接调用在 SPI_FLASH_ReadID()获取设备 ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。

之后修改初始化函数如下:

DSTATUS disk_initialize (
	BYTE pdrv				/* 物理编号 */
)
{
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case ATA :
 
		return stat;

	case SPI_FLASH :
		
		SPI_FLASH_Init();
	
		SPI_Flash_WAKEUP(); //唤醒
	 
		if(SPI_FLASH_ReadID()==sFLASH_ID)
		{
		    return 0;
		}
		else
		{
			return STA_NOINIT;
		}

		return stat;
	}
	return STA_NOINIT;
}

 接下来我们要进行统一单位,在FatFs中,是以扇区为单位,而我们上一节写入FLASH是以字节为单位,所以需要统一单位。修改后的函数如下:

DRESULT disk_read (
	BYTE pdrv,		/* 设置物理编号 */
	BYTE *buff,		/* 数据缓存区 */
	DWORD sector,	/* 扇区首地址 */
	UINT count		/* 扇区个数 */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
	case ATA :

		 return res;
 
	case SPI_FLASH:
		
    sector += 1563;
            
	SPI_FLASH_BufferRead(buff,sector*4096,count*4096);

	    return RES_OK;
	}

	return RES_PARERR;
}

disk_read 函数有四个形参。pdrv 为设备物理编号。buff 是一个 BYTE 类型指针变量, buff 指向用来存放读取到数据的存储区首地址。sector 是一个 DWORD 类型变量,指定要 读取数据的扇区首地址。count 是一个 UINT 类型变量,指定扇区数量。 BYTE 类型实际是 unsigned char 类型,DWORD 类型实际是 unsigned long 类型,UINT 类型实际是 unsigned int 类型,类型定义在 integer.h 文件中。 开发板使用的 SPI Flash 芯片型号为 W25Q256FV,每个扇区大小为 4096 个字节(4KB), 总共有 16M 字节空间,为兼容后面实验程序,我们只将后部分16MB 空间分配给 FatFs 使用,前部分 6MB 空间用于其他实验需要,即 FatFs 是从 6MB 空间开始,为实现这个效果需要将所有的读写地址都偏移 1563=6 * 1024 * 1024 / 4069个扇区空间。

将写入函数修改为如下:

#if _USE_WRITE
DRESULT disk_write (
	BYTE pdrv,			/* 设备物理编号 */
	const BYTE *buff,	/* 欲写入数据的缓存区 */
	DWORD sector,		/* 扇区首地址 */
	UINT count			/* 扇区个数 */
)
{
	DRESULT res;

	switch (pdrv) {
	case ATA :
		return res;

	case SPI_FLASH :

    sector += 1536; //扇区偏移6MB
  
    SPI_FLASH_SectorErase(sector*4096);
            
	SPI_FLASH_BufferWrite((u8 *)buff,sector*4096, count*4096);

	return RES_OK;
	}

	return RES_PARERR;
}

 接下来是处理其他控制:

#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理编号 */
	BYTE cmd,		/* 控制指令 */
	void *buff		/* 写入或读取数据地址指针 */
)
{
	DRESULT res;
	
	switch (pdrv) {
	case ATA :

		return res;

  case SPI_FLASH:
		
	     switch(cmd){
			 
				 //扇区数量 4096*4096/1024/1024=16MB 我们需要偏移6MB 6*1024*1024/4096=1536
				 case GET_SECTOR_COUNT:
						*(DWORD *)buff = 4096 - 1536;
				 break;
				 //扇区大小
				 case GET_SECTOR_SIZE:
					 *(WORD *)buff = 4096;
				 break;
				 //同时擦除扇区个数
				 case GET_BLOCK_SIZE:
					 *(DWORD *)buff = 1;
				 break;
			 }
		
	}

	return RES_PARERR;
}

最后编译后还剩下这个错误:

....\Output\FLASH.axf: Error: L6218E: Undefined symbol get_fattime (referred from ff.o).

出现这个问题是因为ff.c文件中没有时间戳这个函数,我们只需要将下面这个函数加进去即可:

__weak DWORD get_fattime(void)
{
		/*返回当前时间戳*/
	 return ((DWORD)(2021 - 1980)<<25)  //年
		     | ((DWORD)1<<21)			//月
	         | ((DWORD)1<<16) 			//日
			 | ((DWORD)0<<11) 			//时
	         | ((DWORD)0<<5)			//分
			 | ((DWORD)0>>1);			//秒
}

get_fattime 函数用于获取当前时间戳,在 ff.c 文件中被调用。FatFs 在文件创建、被修 改时会记录时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器 RTC 功能,具体要求返回格式为(32位的返回值):

  • bit31:25 ——从 1980 至今是多少年,范围是 (0..127) ;

  • bit24:21 ——月份,范围为 (1..12) ;

  • bit20:16 ——该月份中的第几日,范围为(1..31) ;

  • bit15:11——时,范围为 (0..23);  bit10:5 ——分,范围为 (0..59);

  • bit4:0 ——秒/ 2,范围为 (0..29) 。

如果不想以上操作这么麻烦,那可以加入一个空函数:

__DWORD get_fattime(void)
{
		return 0; //返回时间,默认
}

(2)使用FatFs

FATFS 是在 ff.h 文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号扇区大小等等信息,一般我们都需要为每个物理设备定义一个 FATFS 变量。之后再使用下面的函数进行模块注册,才可以使用fopen()等函数:

 它的具体使用如下:

 该函数有返回值,我们可以判断它的返回值看它是否格式化成功,我们可以打开存储着各种返回值的结构体,如果配置成功,则是返回0:

 之后我使用串口将配置的结果打印出来:

int main(void)
{ 	
	FRESULT res;
	
	LED_GPIO_Config();
	LED_BLUE;
	
	/* 配置串口1为:115200 8-N-1 */
	Debug_USART_Config();
  //立即挂载
    res =	f_mount(&flash_fs,"1:",1);
	
	printf("The reslut is %d\n",res);
	
	while(1);  
}

 最后的输出是:

代表的逻辑ID无效,于是我就找到了下面的原因:

打开文件ffconf.h,找到下面这个宏定义,改为2,这个宏定义的意思是,当前有几个外挂设备,我们定义了一个ATA(学到SD卡的时候用),另外一个是SPI_FLASH:

 

 修改完上面的错误后,又出现了新的bug:

 问题是因为我们将函数进行修改后,函数disk_ioctl忘记返回一个参数RES_OK:

 解决了这个问题后又出现了新的问题 T T:

 

如果 f_mount 函数返回值为 FR_NO_FILESYSTEM,说明没有 FAT 文件系统,比如新出厂的 SPI Flash 芯片就没有 FAT 文件系统。我们就必须对物理设备进行格式化处理。使 用 f_mkfs 函数可以实现格式化操作

在格式化前,先把这个宏定义置1:

 之后调用如下的函数:

res = f_mkfs("1:",0,0);           //格式化,会擦除源文件系统的内容
res = f_mount(NULL,"1:",1);       //取消挂载
res = f_mount(&flash_fs,"1:",1);  //重新挂载

就可以正常读写了。

在此文件找到下面的宏定义,这个宏定义USE_LFN,将它置1为支持长文件名的意思,这个长文件名不是指我们所看到的文件名,而是文件的路径。下面的那个宏定义是可以配置最高支持多少个字节的文件名,如果是1,则为全局变量;如果是2,则是存储在栈里边;如果是3,则存储在堆里面。

 

 之后,我们找到配置扇区大小范围,如果不配置则会溢出:

最后,我们将程序下载进芯片中,会发现程序会卡在硬件上访的函数中,主要原因是我们的变量定义在函数内的问题,导致内存超出了设定的值:

当它定义为局部变量时:

我们所定义的变量内存只有1024个字节,可我们在操作的时候都是以4096个字节来处理的,所以会导致硬件上访。所以我们将它从函数移除,作为全局变量。

之后内存就变为如下:

 

 栈的大小如下,换算过来为1024:

 之后我们再调用fopen函数看看:

FATFS flash_fs;
FTL testfp;

int main(void)
{
	FRESULT res;
	LED_GPI0_Config();
    LED BLUE;
	/*配置串口1为:1152008-N-1*/ 
    Debug_USART_Config0;
	printf("\r\n这是一个文件系统移植实验\r\n");
	res = f mount(&flash_fs,"1:",1); 
    printf("f_mount res =%d ",res);

	res = f open(&testfp,"1:/testfatfs.txt",FA_OPEN_ALWAYS|FA_WRITE|FA_Read);
	printf("f_open res =%d ",res);

	while(1)
}

fopen最后一个形参的使用如下:

我们将宏定义_USE_LFN修改为1,他才可以支持长文件名,但是编译器会报错,这个时候把我们上面屏蔽掉的文件cc936.c取消屏蔽就可以了,原因是他缺少了两个文件。

之后我们再向我们打开的文件中写入信息:

UINT bw;  //写入多少个字节

res = f_write(&testfp,&w_buff,sizeof(w_buff),&bw);
printf("f_write res = %d",res);

之后,我们再将写入的信息读出,我们需要f_lseek函数:

UINT br;  //读出多少个字节

res = f_lseek(&testfp,0);
printf("\r\n f_lseek res = %d\r\n",res);
res = f_read(&testfp,r_buff,1024。&br);
printf("\r\nf_read res = %d\r\n",res);
printf("\r\nr_buff = %s\r\n",r_buff);

 最后,一定要关闭文件:

f_close(&testfp);

五、升级函数

先总结一下上面的操作:

  1. 修改diskio.c驱动接口

  2. 修改ffconf.h文件的配置

  3. 编写测试

以上程序的bug有:不支持中文格式的文件名,中文的文件名会产生乱码

所以我们需要改为中文的代码页,改为936,并将936加入其中:

 

 之后就有了中文支持,将其转为u盘格式的时候,会在电脑里看到新建的文件,以及文件的文字。

//将变量定义为全局变量,防止栈的溢出
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0}; /* 读缓冲区 */
BYTE WriteBuffer[] = "内容";/* 写缓冲区*/

格式化是一个破坏性很大的操作!!它可能会将存储介质格式化为ntfs格式,而不是现在在使用的介质,会导致介质初始化失败,所以格式化需要谨慎。具体库函数如下:

int main(void)
{
	/* 初始化LED */
	LED_GPIO_Config();	
	LED_BLUE;
	
	/* 初始化调试串口,一般为串口1 */
	Debug_USART_Config();	
  printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
  
	//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
	res_flash = f_mount(&fs,"1:",1);
	
/*----------------------- 格式化测试 ---------------------------*/  
	/* 如果没有文件系统就格式化创建创建文件系统 */
	if(res_flash == FR_NO_FILESYSTEM)
	{
		printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    /* 格式化 */
		res_flash=f_mkfs("1:",0,0);							
		
		if(res_flash == FR_OK)
		{
			printf("》FLASH已成功格式化文件系统。\r\n");
      /* 格式化后,先取消挂载 */
			res_flash = f_mount(NULL,"1:",1);			
      /* 重新挂载	*/			
			res_flash = f_mount(&fs,"1:",1);
		}
		else
		{
			LED_RED;
			printf("《《格式化失败。》》\r\n");
			while(1);
		}
	}
  else if(res_flash!=FR_OK)
  {
    printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
    printf("!!可能原因:SPI Flash初始化不成功。\r\n");
		while(1);
  }
  else
  {
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
  }
  
/*----------------------- 文件系统测试:写测试 -----------------------------*/
	/* 打开文件,如果文件不存在则创建它 */
	printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
	res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
	if ( res_flash == FR_OK )
	{
		printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
    /* 将指定存储区内容写入到文件内 */
		res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
    if(res_flash==FR_OK)
    {
      printf("》文件写入成功,写入字节数据:%d\n",fnum);
      printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
    }
    else
    {
      printf("!!文件写入失败:(%d)\n",res_flash);
    }    
		/* 不再读写,关闭文件 */
    f_close(&fnew);
	}
	else
	{	
		LED_RED;
		printf("!!打开/创建文件失败。\r\n");
	}
	
/*------------------- 文件系统测试:读测试 ------------------------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt", FA_OPEN_EXISTING | FA_READ); 	 
	if(res_flash == FR_OK)
	{
		LED_GREEN;
		printf("》打开文件成功。\r\n");
		res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum); 
    if(res_flash==FR_OK)
    {
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
    else
    {
      printf("!!文件读取失败:(%d)\n",res_flash);
    }		
	}
	else
	{
		LED_RED;
		printf("!!打开文件失败。\r\n");
	}
	/* 不再读写,关闭文件 */
	f_close(&fnew);	
  
	/* 不再使用文件系统,取消挂载文件系统 */
	f_mount(NULL,"1:",1);
  
  /* 操作完成,停机 */
	while(1)
	{
	}
}

六、其他函数的应用

(1)设备信息以及空簇大小获取: f_getfree()

 

  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);  

其中总的扇区个数:Number of FAT entries, = number of clusters + 2,算出簇的大小,再乘以每个簇的大小。

通过以上函数就可以实现查找出当前空间的大小以及剩余空间的大小,就像电脑中查看磁盘的容量。

(2)文件定位和格式化写入功能测试: f_open()

使用f_open函数打开的文件,下面的函数都是处理其产生的函数句柄。也就是&fnew。

  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);    

文件定位在一些场合非常有用, 比如我们需要记录多项数据,但每项数据长度不确定,但有个最长长度,使用我们就可以 使用文件定位 lseek 函数功能把数据存放在规定好的地址空间上。当我们需要读取文件内容时就使用文件定位函数定位到对应地址读取

使用文件读写操作之前都必须使用 f_open 函数打开文件,开始文件是读写指针是在文件起始位置的,马上写入数据的话会覆盖原来文件内容的。这里,我们使用 f_lseek 函数定 位到文件末尾位置,再写入内容。

 调用f_size函数,得知需要偏移的地址。(得到文件的大小)

写入调用f_printf()函数:f_printf 函数是格式化写入函数,需要把 ffconf.h 文件中的_USE_STRFUNC 配置为 1 才 支持。

(3)目录创建和重命名功能测试:fopendir()

用函数打开一个执行目录,并为后续f_readdir创建目录对象。使用 f_opendir 函数可以打开路径(这里不 区分目录和路径概念,下同),如果路径不存在则返回错误,使用 f_closedir 函数关闭已经 打开的路径。新版的 FatFs 支持相对路径功能,使路径操作更加灵活。f_opendir 函数有两个形参,第一个参数为指向路径对象的指针,第二个参数为路径。f_closedir 函数只需要指 向路径对象的指针一个形参。

     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");      
    } 

目录使用如下的数据结构:

 f_mkdir 函数用于创建路径,如果指定的路径不存在就创建它,创建的路径存在形式就 是文件夹。f_mkdir 函数只要一个形参,就是指定路径。

 

 

打开目录后,到最后也需要关闭目录。

删除文件:f_unlink()。上面函数要生成文件后再删除文件,目的是为了下面的重命名文件:

 

 f_rename 函数是带有移动功能的重命名函数,它有两个形参,第一个参数为源文件名 称,第二个参数为目标名称。目标名称可附带路径,如果路径与源文件路径不同见移动文件到目标路径下:

(4)文件信息获取测试:f_stat()

相当于我们电脑右键,查看电脑属性一样的操作:

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;
}

 FILINFO结构体如下:

 (5)路径扫描:递归

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);	 //输出文件名	
        /* 可以在这里提取特定格式的文件路径 */        
      }
    } 
  } 
  return res; 
}

文件系统总算学完啦。接下来要忙电赛跟广工的比赛,有点忙,那就更新的慢一些。最后,希望自己比赛顺利!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

郑烯烃快去学习

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

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

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

打赏作者

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

抵扣说明:

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

余额充值