文件系统FATFS的移植教程

最新FatFs:http://elm-chan.org/fsw/ff/00index_e.html

一、FATFS文件系统简介

        FATFS是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由C语言编写并且完全独立于底层I/O介质。支持的内核有:8051、PIC、AVR、SH、Z80、H8、ARM等。FATFS支持FAT12、FAT16、FAT32等格式。

1.1 FatFs的目录结构

从上述官方下载FatFs源码,如下图所示:
在这里插入图片描述
        上图所示的R0.14就是此次移植所使用的的源码版本。下载FatFs源码包解压后,在documents文件夹里面是一些使用的帮助文档;在source中是FatFs文件系统的源代码,source文件夹就是我们需要添加到工程中的文件。
在这里插入图片描述

1.2 FatFs帮助文档

在这里插入图片描述
        doc文件中是编译好的html文档,在res文件中主要是一些照片。对于整个document文件我们不需要关注。移植的时候只需要将source文件添加到工程中即可。

1.3 FATFS源码

在这里插入图片描述
        ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如果需要支持简体中文,需要把ffconf.h中的_CODE_PAGE的宏改成936。
        ff.h里面有针对文件系统所统一的字节类型:WORD、DWORD等类型,这是为了避免因内核不同而导致一些字节类型的定义不统一。所以,关乎到文件系统的字节定义都是要该文件系统中的字节类型进行定义就可以实现统一性。
        本次文章以串行FLASH文件系统FatFs为例进行讲解。一般我们只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。这些应用层函数的使用方法与标准C的文件操作函数类似。在移植时,我们只需要修改的函数如下图所示:
在这里插入图片描述
        上图中的六个函数位于diskio.c文件中,再加上我们需要适当的修改宏定义,位于ffconf.h中。所以实际上我们在进行文件系统移植的时候,只需要修改ffconf.h和diskio.c两个文件

二、文件系统移植流程

        移植顺序,打开diskio.c文件,从上到下依次进行

2.1 disk_status ()函数

在这里插入图片描述
        第①步,因为此次是对串行W25Qxx进行文件系统移植,所以首先就是添加w25qxx相关的头文件。
        第②是源码中定义的,就是将不同的设备定义为不同的编号,告诉底层要操作的介质是哪一个,以便后续函数进行根据编号的不同执行相应的函数。
        第③步,此处需要对我们自己需要的设备进行编号。所以就将②进行屏蔽。

#define SD_CARD      0 //SD卡设备,0就是SD卡的设备编号
#define SPI_FLASH    1 //本次操作的w25qxx设备,1就是w25qxx的设备编号

        【注意】:此处虽然对SD卡设备进行命名设备号,但是本次移植没有对SD卡进行移植。所以后期程序中关于SD卡部分的操作全部是空。
在这里插入图片描述
        上图是官方对disk_status()函数的解释。该函数只有一个形参,就是设备号用于标识不同的设备,在只有一个设备的情况下,设备号默认为0。
        该函数的返回值有:
        STA_NOINIT:标识该设备未初始化成功,未进入就绪状态。
        STA_NODISK:FatFs不引用此标志。
        STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效。
        0:表示该设备初始化成功。

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
//	int result;

	switch (pdrv) {
	case SD_CARD :


		return stat;

	case SPI_FLASH :
		  if(W25QXX_ReadID()!=W25Q128) //#define W25Q128	0XEF17							
				stat = STA_NOINIT ;  //检测不到W25Q128
		  else
				stat =  0;            //初始化成功
	}
	return stat;
}

        看上述代码可知:当W25Q128能够读取到自己的ID时,说明该设备就初始化成功,返回值为0;如果读取值不是自己的ID时,说明初始化失败,返回值为STA_NOINIT。由于此次移植没进行SD卡的移植,所以此处就没进行SD卡的处理(下面不再进行陈述)。

        在<ffconf.h>中有“#define FF_VOLUMES 1” 表示当前系统只能支持一个设备号,根据实际情况加以修改。本次虽说没进行SD卡移植,但是却有SD卡的设备号,也就是有两个设备号,所以需要将该宏改为2。

2.2 disk_initialize()函数

在这里插入图片描述
        上图是官方对disk_status()函数的解释。该函数只有一个形参,就是设备号用于标识不同的设备,在只有一个设备的情况下,设备号默认为0。
        该函数的返回值有:就是返回disk_status()。也就是说,当给设备进行初始化后,通过返回读取自己ID的结果就可以判断出该设备是否初始化成功。
        下图就是修改后的代码。

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive  在官方文档中[in]表示为输入;[out]表示为输出       */
/*读参考手册会发现在disk_initialize函数后,告诉该函数最后是由f_mount调用的*/
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
//	int result;

	switch (pdrv) {
	case SD_CARD :


		return stat;

	case SPI_FLASH :
			W25QXX_Init();     //此处就是对FLASH进行初始化,该函数需要返回初始化是否成功的标志位
			return disk_status(SPI_FLASH);

	}
	return STA_NOINIT; //此语句可以省略的,因为所有的结果,通过disk_status读取就可以返回了。
}

        通过阅读官方对该函数的介绍可知:在用户应用层对设备进行初始化,需要用的函数是f_mount()函数。也就是说函数调用关系是:f_mount------>disk_initialize----->disk_status。此处既然提到了f_mount函数,那就先进行讲述次函数。

2.3 f_mount()函数

在这里插入图片描述
        该函数用户只需要调用即可,不需要修改该函数的作用是:挂载文件系统
        第一个形参是一个句柄,是一个指向要注册和清除的文件系统对象的指针。如果该指针是空指针,那就是取消挂载。该变量所占空间很大,所以一般都是定义为全局变量。
        第二个形参是存储器路径,就类似于电脑中的C盘、D盘还是E盘。在FATFS中就是SD_CARD还是SPI_FLASH,输入形式为“设备号:(冒号一定不能少)”。
        最后一个形参:0表示稍后挂载;1表示立即挂载。我们选择1。
        使用方法如下:

FATFS fsobject;   //一定是一个全局变量
BYTE work[FF_MAX_SS]; //一定是一个全局变量

int main(void)
{
	FRESULT  res ;   //局部变量
	res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统 , "1:"就是挂载的设备号为1的设备

	if(res == FR_NO_FILESYSTEM)  //FR_NO_FILESYSTEM值为13,表示没有有效的设备
	{
		// printf("res = %d\r\n",res);
		res = f_mkfs("1:",0,work,sizeof(work));
		// printf("f_mkfs  is  over\r\n");
		// printf("res = %d\r\n",res);
		res = f_mount(NULL,  "1:",  1);   //取消文件系统
		res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
 	}
	 // printf("res = %d\r\n",res);
}
//上述所屏蔽掉的printf是为了调试使用的

格式化时常见错误:
        1、在<ffconf.h>中的“#define FF_VOLUMES 1”设置不对;
        2、在<ffconf.h>中的“#define FF_MAX_SS 512”设置不对,此处的数值表示所挂的所有设备中,设备扇区的最大值。如w25qxx的扇区是4096,如果此处还是设置成512,就会造成栈溢出而陷入HardFault_Handler的死循环中。
        同时还有一个变量“#define FF_MIN_SS 512”表示所挂的所有设备中,设备扇区的最小值,一般设置为512即可,因为扇区最小值就是512。
        3、出现设备上不存在文件系统的错误,即“res == FR_NO_FILESYSTEM”。这是因为我们还没有对设备进行格式化,如果格式化后就会在设备上存储一些文件信息。
        就是因为可能有错误3的存在,所以才会有下述程序:

if(res == FR_NO_FILESYSTEM)  //FR_NO_FILESYSTEM值为13,表示没有有效的设备
{
	// printf("res = %d\r\n",res);
	res = f_mkfs("1:",0,work,sizeof(work));
	// printf("res = %d\r\n",res);    
	res = f_mount(NULL,  "1:",  1);   //取消文件系统
	res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
 }
 // printf("res = %d\r\n",res);

        上述程序只会执行一次,因为一旦设备被格式化成功,就不会出现“没有有效设备”的错误。格式化时所调用的API是f_mkfs()函数,格式化后一定要再次挂载该设备。也即先取消文件系统,然后再挂载文件系统。
        如果格式化成功,也就是f_mkfs()函数返回值是0;但是经再次挂载后显示的依旧是“没有有效设备”的错误。那只能说明在串行FLASH进行写操作之前没有进行擦除操作。
        函数调用关系:f_mkfs()---->disk_write()---->W25QXX_Write(),所以说如果在写操作之前不进行擦除,那就不会写到FLASH中的,而又因为此处disk_write()默认串行FLASH是写成功的,即便实际上没有将数据写进FLASH中,也会返回0。所以后期再次挂起的时候,依旧是“没有有效设备”的错误。<此话若不懂,当讲到disk_write()函数时会详细解释>。此处既然说到了f_mkfs()函数,那就接下来先进行讲解f_mkfs()函数。

2.4 f_mkfs()函数

        此函数只需要调用,无需进行修改。
在这里插入图片描述
在这里插入图片描述

        第一个形参:存储器路径,在本次移植工程中,可选项是SD_CARD("0:")或SPI_FLASH("1:"),只不过后续程序处理部分SD_CARD没进行处理。
        第二个形参:指定保存格式选项的结构。 如果给出了空指针,它将为函数提供默认值的所有选项。该配置有五个选项:
        BYTE fmt:指定FAT类型标志FM_FAT,FM_FAT32,FM_EXFAT和按位或这三个标志FM_ANY的组合。 未启用exFAT时,将忽略FM_EXFAT。 这些标志指定要在存储设备上创建哪种FAT类型。 如果指定了两种或多种类型,则将根据存储设备的大小au_size选择其中一种。 FM_SFD标志指定以SFD格式在驱动器上创建卷。 默认值为FM_ANY
        DWORD au_size:以字节为单位指定分配单位(簇)的大小。 对于FAT / FAT32卷,有效值是2的幂,在扇区大小和128 *扇区大小之间(包括FAT / FAT32卷),最大为16 MB(对于exFAT卷)。 如果给出零(默认值)或任何无效值,则默认分配单位大小取决于所使用的卷大小
        UINT n_align:以扇区为单位指定卷数据区域(文件分配池,通常擦除闪存介质的块边界)的对齐方式。 该成员的有效值在1到32768之间(包括2的幂),如果给出零(默认值)或任何无效值,则该函数使用disk_ioctl函数从较低层获取块大小。
        BYTE n_fat:指定FAT / FAT32卷上的FAT副本数。 该成员的有效值为1或2。默认值(0),任何非法值均为1。如果FAT类型为exFAT,则此成员无效。
        BYTE n_fat:指定FAT卷上的根目录条目数。该成员的有效值最大为32768,并与扇区大小/ 32对齐。默认值(0)和任何无效值都为512。如果FAT类型为FAT32或exFAT, 该成员无效。

一般此参数直接赋值0,使用默认值即可。

        第三个形参:指向用于格式化过程的工作缓冲区的指针。该工作缓冲区越大,格式化过程就越快。
        第四个形参:工作缓冲区的大小,以字节为单位。 至少需要为FF_MAX_SS。 大量的工作缓冲区减少了写入驱动器的事务数量,格式化过程将很快完成。

使用方法见下所示:

FATFS fsobject;   //一定是一个全局变量
BYTE work[FF_MAX_SS]; //一定是一个全局变量,否则可能导致无法读写FLASH

int main(void)
{
	FRESULT  res ;   //局部变量
	res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统 , "1:"就是挂载的设备号为1的设备

	if(res == FR_NO_FILESYSTEM)  //FR_NO_FILESYSTEM值为13,表示没有有效的设备
	{
		// printf("res = %d\r\n",res);
		res = f_mkfs("1:",0,work,sizeof(work));
		// printf("f_mkfs  is  over\r\n");
		// printf("res = %d\r\n",res);
		res = f_mount(NULL,  "1:",  1);   //取消文件系统
		res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
 	}
	 // printf("res = %d\r\n",res);
}
//上述所屏蔽掉的printf是为了调试使用的

如果使用这个函数,必须将<ffconf.h> 中的“#define FF_USE_MKFS 0”配置为1。

2.5 disk_read()函数

在这里插入图片描述
形参:
        第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(0)或SPI_FLASH(1),只不过后续程序处理部分SD_CARD没进行处理。
        第二个形参:读取到的数据的缓冲区

        第三个形参:要读取的扇区的个数

返回值:
        RES_OK (0):     读数据正常
        RES_ERROR:   读取操作期间发生了不可恢复的硬错误。
        RES_PARERR: 输入了无效参数
        RES_NOTRDY: 设备还没初始化

函数值调用:

      f_read()---->disk_read()

移植disk_read()函数如下:

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :
		W25QXX_Read(buff , sector*4096 , count*4096);//该函数没有返回值,所以默认返回正常;每个扇区的大小是4096,所以传给底层的起始地址就是sector*4096
                                                  //要读取多少个字节,就是读取的扇区个数count*4096
		res = RES_OK;
		return res;

	}

	return RES_PARERR;
}

2.6 disk_write()函数

在这里插入图片描述
形参:
        第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(0)或SPI_FLASH(1),只不过后续程序处理部分SD_CARD没进行处理。
        第二个形参:写数据的缓冲区。 要写入的数据大小为扇区大小 × 计数字节。

        第三个形参:写操作的起始扇区。比如要从第三个扇区写操作,那么该参数值就是3。
        第四个形参:写操作的扇区个数。也就是写操作要进行操作几个扇区。

返回值:
        RES_OK (0):     写数据正常
        RES_ERROR:   写操作期间发生了不可恢复的硬错误。
        RES_WRPRT:   设备进行了写保护。
        RES_PARERR: 输入了无效参数
        RES_NOTRDY: 设备还没初始化

函数值调用:

      f_write()---->disk_write()

移植disk_write()函数如下:

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :
		// translate the arguments here
		W25QXX_Write((u8*)buff,sector*4096,count*4096);  //buff是const BYTE类型,而传入下一层需要的是uint8_t类型,所以此处进行强制转化
		res = RES_OK ;
		// translate the reslut code here
     
		return res;

	}

	return RES_PARERR;
}

        在之前讲过,在进行写操作之前一定要先进行擦除操作。在W25QXX_Write((u8*)buff,sector4096,count4096);中含有了擦除操作,所以就没有再单独在该函数之前调用擦除函数进行擦除。(根据实际情况进行编写)
        W25QXX_Write()函数是没有返回值的,但是disk_write()函数只需要带有返回值的。所以此处我们永久性返回写入正常。这也就是我们之前在2.3节讲到的“disk_write()默认串行FLASH是写成功的,即便实际上没有将数据写进FLASH中,也会返回0”。所以才会出现在我们格式化后显示格式化是成功的,但是再次挂起设备时,就还是显示“没有有效设备”的错误提醒。[注意]:可以根据自己的实际情况,在编写disk_write函数时,底层的设备读写函数(此处是W25QXX_Write)自带准确的返回值。这样就可以避免格式化成功,但实际上再次挂起的时候还是“无有效设备”的错误,即格式化失败。也就是说不需要通过挂起是否成功(返回值为0)来判断格式化是否成功,而是直接可以通过 格式化函数返回的值进行判断是否格式化成功。

2.7 disk_ioctl()函数

在这里插入图片描述
        调用disk_ioctl函数以控制设备的特定功能和除通用读/写之外的其他功能。比如通过发送命令来获取该设备的扇区大小、内存大小等相关信息。
形参:
        第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(0)或SPI_FLASH(1),只不过后续程序处理部分SD_CARD没进行处理。
        第二个形参:控制命令号。命令号,通过发送命令控制flash;比如查询每个扇区的字节数等等

        第三个形参:数据缓冲区。既有可能输出也有可能输入。因为输入的命令可能会带有参数;发送命令后,需要接收返回来的数据信息。

返回值:
        RES_OK (0):     此次 操作正常
        RES_ERROR:   有错误产生
        RES_PARERR: 输入的命令或参数是无效的
        RES_NOTRDY: 设备还没初始化

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

      f_write()---->disk_write()

移植disk_ioctl()函数如下:

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :

			switch(cmd)
			{
				//返回扇区个数
				case GET_SECTOR_COUNT:
						*(DWORD *)buff = 16777216; //此次试验是w25q128,全部用于文件系统,共16MB字节,即16777216字节
				break;
				
				//返回扇区大小
				case GET_SECTOR_SIZE:
						*(WORD *)buff = 4096 ;
				break;
				//返回擦除扇区的最小个数
				case GET_BLOCK_SIZE:
						*(WORD *)buff = 1 ;   //每次擦除一个扇区
				break;
			}
		res = RES_OK;         //由此看出,此处的数据返回值不是通过return返回的,如之前的讲解是通过指针返回
		return res;

	}

	return RES_PARERR;
}

上述参数的讲解参考下图所示的官方所给的注释:
在这里插入图片描述
该函数的使用必须首先开启宏,使得“FF_USE_MKFS == 1”

2.8 get_fattime()函数

在这里插入图片描述
        本次移植工程中未对该函数进行移植,此处也就不再叙述,待日后补充…

三、使用API调用函数

        FatFs文件系统对文件的操作流程和C语言中对文件的操作流程是一样的。对于接下来的函数我们只是直接使用,而不需要进行修改的。

3.1 f_open()函数

在这里插入图片描述
在这里插入图片描述

形参:
        第一个形参:指向空白文件对象结构的指针。该结构体是FIL,由于结构体所占空间较大,所以一定要是全局变量而不能是局部变量:FIL fp;是在全局变量中定义的。当打开一个文件后,就会返回一个文件句柄赋给该变量,以后的操作就是使用这个文件句柄对该文件进行各种操作。
        第二个形参:文件名。使用方法:“1:abs”,意思就是说在设备号为1下的abs文件。

        第三个形参:打开文件的模式。就是以什么样的模式打开这个文件。
模式有:
        FA_READ:     以只读模式打开
        FA_WRITE:   以写模式打开
        FA_OPEN_EXISTING: 如果文件存在就打开,否则就不打开
        FA_CREATE_NEW: 创建一个新文件。 如果该文件存在,则函数失败并显示FR_EXIST。
        FA_CREATE_ALWAYS: 创建一个新文件。 如果文件已存在,它将被新文件覆盖。
        FA_OPEN_ALWAYS: 如果文件存在就打开文件。 否则,将创建一个新文件并打开。
        FA_OPEN_APPEND: 与FA_OPEN_ALWAYS相同,不同之处在于将读/写指针设置为文件的结尾。
返回值:
        此处的返回值类型较多 ,不再一一叙述。如果遇到,直接查官方手册或者相关参数后面的注释进行学习 。但是如果返回0,说明打开正常。
该函数的用法如下:

FIL fp ;  //一定是一个全局变量

int main()
{
	res = f_open(&fp , "1:abcd" , FA_OPEN_ALWAYS|FA_READ |FA_WRITE );
//在设备号为1的设备上打开一个文件名叫做abcd的文件,可对其进行读写操作
	...
}

3.2 f_write()函数

在这里插入图片描述
形参:
        第一个形参:指向文件对象结构的指针,该结构体是FIL。
        第二个形参:指向要写入的数据的指针。也就是准备写入数据的首地址。

        第三个形参:写入数据的大小。是UINT型
        第四个形参:指向变量的指针以返回写入的字节数。是(UINT*)型,保存的数据是写入的字节数。

返回值:
        该函数也有返回值,返回值的具体含义详见工程中对应参数的后面的注释

文件系统个函数的返回值参照表:

typedef enum {
	FR_OK = 0,				/* (0) Succeeded */
	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer */
	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 */
	FR_MKFS_ABORTED,		/* (14) The f_mkfs() aborted due to any problem */
	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 */
	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > FF_FS_LOCK */
	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
} FRESULT;

3.3 f_lseek()函数

在这里插入图片描述
        f_lseek函数移动打开的文件对象的文件读/写指针到

形参:
        第一个形参:指向文件对象结构的指针,该结构体是FIL。
        第二个形参:设置读取/写入指针的字节偏移量

返回值:
        该函数也有返回值,返回值的具体含义详见工程中对应参数的后面的注释

        当打开文件,并对文件进行了写操作后,文件的指针就已经指向了该文件的尾部。那么此时如果不将文件指针偏移到文件的开头(也就是说偏移量为0),而直接读取文件,将会什么也读取不到。

3.4 f_read()函数

在这里插入图片描述
形参:
        第一个形参:指向文件对象结构的指针,该结构体是FIL。
        第二个形参:指向要读入的数据的指针。也就是准备保存读入数据的首地址。

        第三个形参:读入数据的大小。是UINT型
        第四个形参:指向变量的指针以返回读入的字节数。是(UINT*)型,保存的数据是写入的字节数。

返回值:
        该函数也有返回值,返回值的具体含义详见工程中对应参数的后面的注释

读写文件的简单操作历程如下:

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	 
#include "ff.h"	
#include "diskio.h"	

FATFS fsobject;
BYTE work[FF_MAX_SS]; 

FIL fp ;
const char write_buf[] = "欢迎使用";
char read_buf[4096] = "";
UINT bw;
UINT br;
/************************************************
 ALIENTEK战舰STM32开发板实验24
 SPI 实验   
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/

 int main(void)
 {	 
	FRESULT  res ;
	
  LED_Init();	    	 //延时函数初始化	  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
	printf("这是一个FATFS文件系统移植实验\r\n");
	printf("res = %d\r\n",res);
	if(res == FR_NO_FILESYSTEM)
	{
		printf("res = %d\r\n",res);
		res = f_mkfs("1:",0,work,sizeof(work));
		printf("f_mkfs  is  over\r\n");
		printf("res = %d\r\n",res);
		res = f_mount(NULL,  "1:",  1);   //取消文件系统
	    res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
  }
		printf("res = %d\r\n",res);
	printf("hello word......\r\n");
	
	res = f_open(&fp , "1:abcd" , FA_OPEN_ALWAYS|FA_READ |FA_WRITE );//????&fp/fp
  	printf("res = %d\r\n",res);
	
	if(res == RES_OK)
	{
	   res = f_write(&fp,write_buf,sizeof(write_buf),&bw); //bw写入成功的
		 printf("bw=%d\r\n",bw);
		 if(res == RES_OK)
	   {
			  f_lseek(&fp,0);
			  res = f_read(&fp ,read_buf,f_size(&fp),&br);
			  if(res == RES_OK)
				{
				   printf("文件内容为:%s ,br=%d",read_buf,br);
				}
		 }
	}
	while(1)
	{
	}
}

3.5 函数移植需要的宏定义总结

在这里插入图片描述

四、细节调整

上述移植过程的讲解已经可以实现简单的文件读写操作,但是并不支持中文的文件名字以及较长的文件名字。

4.1 支持中文目录

        首先需要在工程中加入“ffunicode.c”文件,如下图所示:
在这里插入图片描述
        如果想要使用该文件首先需要开启宏,“#if FF_USE_LFN ”,在<ffconf.h>文件下,配置为“#define FF_USE_LFN ”。

        配置为“#define FF_USE_LFN 1”就是将文件名存储在BSS段(数据段),也就是将其作为全局变量进行存储;

        配置为“#define FF_USE_LFN 2”就是将 文件名存储在STACK区(栈区);

        配置为“#define FF_USE_LFN 3”就是将文件名存储在HEAP(堆区)。

        一般配置为配置为“#define FF_USE_LFN 1”。如果存储在栈空间,防止某处操作不当而溢出。

        “#define FF_MAX_LFN 255”就是允许文件名的最长的长度

        接下来就是配置FF_CODE_PAGE的参数,如下图所示:

在这里插入图片描述
        只需要将上述所讲的宏进行开启和配置,就可以实现文件名字可以是中文的效果。

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"	 
#include "ff.h"	
#include "diskio.h"	

FATFS fsobject;
BYTE work[FF_MAX_SS]; 

FIL fp ;
const char write_buf[] = "欢迎使用";
char read_buf[4096] = "";
UINT bw;
UINT br;
/************************************************
 ALIENTEK战舰STM32开发板实验24
 SPI 实验   
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/

 int main(void)
 {	 
	FRESULT  res ;
	
  LED_Init();	    	 //延时函数初始化	  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
	printf("这是一个FATFS文件系统移植实验\r\n");
	printf("res = %d\r\n",res);
	if(res == FR_NO_FILESYSTEM)
	{
		printf("res = %d\r\n",res);
		res = f_mkfs("1:",0,work,sizeof(work));
		printf("f_mkfs  is  over\r\n");
		printf("res = %d\r\n",res);
		res = f_mount(NULL,  "1:",  1);   //取消文件系统
	  res = f_mount(&fsobject,  "1:",  1);   //挂载文件系统
  }
		printf("res = %d\r\n",res);
	printf("hello word......\r\n");
	
	res = f_open(&fp , "1:移植文件系统" , FA_OPEN_ALWAYS|FA_READ |FA_WRITE );//中文文件名字
  	printf("res = %d\r\n",res);
	
	if(res == RES_OK)
	{
	   res = f_write(&fp,write_buf,sizeof(write_buf),&bw); //bw写入成功的
		 printf("bw=%d\r\n",bw);
		 if(res == RES_OK)
	   {
			  f_lseek(&fp,0);
			  res = f_read(&fp ,read_buf,f_size(&fp),&br);
			  if(res == RES_OK)
				{
				   printf("文件内容为:%s ,br=%d",read_buf,br);
				}
		 }
	}
	while(1)
	{
	}
}

4.2 指定位置进行文件系统移植

        效果:此次移植的FLASH是W25Q128,实现的效果是预留前10M的空间,将最后的6M空间进行格式化为文件系统。
        只需要修改disk_write()、disk_read()和disk_ioctl()函数

4.2.1 disk_read()函数

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :
		//扇区偏移10MB,外部FLASH文件系统空间放在SPI FLASH后面6MB空间
	    sector += 2560 ; //扇区加2560,作为起始地址  10*1024*1024/4096
		W25QXX_Read(buff , sector*4096 , count*4096);
		res = RES_OK;
		return res;

	}

	return RES_PARERR;
}

4.2.2 disk_write()函数

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :
		//扇区偏移10MB,外部FLASH文件系统空间放在SPI FLASH后面6MB空间
	  sector += 2560;
		W25QXX_Write((u8*)buff,sector*4096,count*4096);  //buff是const BYTE类型,而传入下一层需要的是uint8_t类型,所以此处进行强制转化
		res = RES_OK ;
		// translate the reslut code here
     
		return res;

	}

	return RES_PARERR;
}

4.2.3 disk_ioctl()函数

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res;
//	int result;

	switch (pdrv) {
	case SD_CARD :

		return res;

	case SPI_FLASH :  //w25q128一共有256个块,每个块有16个扇区,每个扇区4K,共16M

			switch(cmd)
			{
				//返回扇区个数
				case GET_SECTOR_COUNT:
					//所使用的扇区数量,1536*4096/1024/1024 = 6(MB)
						*(DWORD *)buff = 1536 ;   
				//就是对于整片FLASH,我们只取最后的6M字节做文件系统,6*1024*1024/4096 = 1536,即6M字节占1536个扇区
				break;
				
				//返回扇区大小
				case GET_SECTOR_SIZE:
						*(WORD *)buff = 4096 ;
				break;
				//返回擦除扇区的最小个数
				case GET_BLOCK_SIZE:
						*(WORD *)buff = 1 ;   //每次擦除一个扇区
				break;
			}
		res = RES_OK;         //由此看出,此处的数据返回值不是通过return返回的,如之前的讲解是通过指针返回
		return res;

	}

	return RES_PARERR;
}

4.3 中间层函数讲解

        实际上STM32内部的FLASH也是可以一直文件系统的,但一般不进行这样的移植。那是因为,单纯的对于文件格式化后的文件系统的结构信息就需要占据300多KB 的空间,而对于 STM32F103ZET6来说,也就只有512KB的FLASH空间,所以这样就太浪费内部 FLASH空间了。

4.3.1 f_getfree()函数

        f_getfree()函数是获取整个文件系统的空余簇(扇区)的大小
在这里插入图片描述
        第一个形参:要操作的设备号,在本次移植工程中,可选项是SD_CARD(“0:”)或SPI_FLASH(“1:”),只不过后续程序处理部分SD_CARD没进行处理。
        第二个形参:数据的指针。用于保存返回来的空余簇(扇区)的数量。

        第三个形参:指向指针的指针,用于存储指向相应文件系统对象的指针。是FATFS*型

返回值:
        该函数也有返回值,返回值的具体含义详见工程中对应参数的后面的注释

        在FATFS的这一结构体中, n_fatent就是簇的数目加2,csize就是说一个簇有csize个扇区这些我们直接调用就可以。其余 结构体成员变量,用到可以自行查阅。

4.3.2 f_printf()函数

        f_printf函数是将格式化的字符串写入文件
在这里插入图片描述
在这里插入图片描述
参数:
        第一个形参:指向文件对象结构的指针,该结构体是FIL。
        第二个形参:格式字符串
返回值:
        成功写入字符串后,它将返回写入文件的字符编码单元数。 当功能由于磁盘已满或任何错误而失败时,将返回EOF(-1)。

格式:
        %[flag][width][prefix][type]

        flag: 填充选项。 “ 0”指定填充为零。 “-”表示左对齐。

        width: 字段的最小宽度,“ 1”-“ 99”或“ *”。 如果生成的字符串的宽度小于指定的值,则用空白或零填充rest字段。 *表示值来自int类型的参数。 默认值为零。

        prefix: 长整数参数的大小前缀“ l”。 如果sizeof(long)== sizeof(int)(这是32位系统的典型值),则可以省略大小前缀。

        type: ‘c’,‘s’,‘d’,‘u’,‘o’,‘x’,‘b’,指定参数的类型和输出格式,字符,字符串,带小数的带符号整数,带小数的无符号整数 ,八进制的无符号整数,十六进制的无符号整数和二进制的无符号整数。 除“ x”外,这些字符不区分大小写。 “ X”以大写形式生成十六进制。

示例如下:

    f_printf(&fil, "%d", 1234);            /* "1234" */
    f_printf(&fil, "%6d,%3d%%", -200, 5);  /* "  -200,  5%" */
    f_printf(&fil, "%ld", 12345L);         /* "12345" */
    f_printf(&fil, "%06d", 25);            /* "000025" */
    f_printf(&fil, "%06d", -25);           /* "000-25" */
    f_printf(&fil, "%*d", 5, 100);         /* "  100" */
    f_printf(&fil, "%-6d", 25);            /* "25    " */
    f_printf(&fil, "%u", -1);              /* "65535" or "4294967295" */
    f_printf(&fil, "%04x", 0xAB3);         /* "0ab3" */
    f_printf(&fil, "%08lX", 0x123ABCL);    /* "00123ABC" */
    f_printf(&fil, "%04o", 255);           /* "0377" */
    f_printf(&fil, "%016b", 0x550F);       /* "0101010100001111" */
    f_printf(&fil, "%s", "String");        /* "String" */
    f_printf(&fil, "%8s", "abc");          /* "     abc" */
    f_printf(&fil, "%-8s", "abc");         /* "abc     " */
    f_printf(&fil, "%c", 'a');             /* "a" */
    f_printf(&fil, "%f", 10.0);            /* f_printf lacks floating point support */

         这是f_write函数的包装函数。 当FF_FS_READONLY == 0且FF_USE_STRFUNC> = 1时可用。当FF_USE_STRFUNC == 2时,在生成的字符串中的’\ n’分别写为’\ r’+’\ n’

1. 硬件平台选择 首先,需要选择一个适合自己应用的硬件平台,可以根据自己的需求选择不同的平台,比如STM32、GD32、ESP32、Arduino等等。 在本文中,我们选择了GD32F303VCT6作为硬件平台。 2. FatFs库介绍 FatFs是一个轻量级的文件系统,支持FAT12、FAT16、FAT32等多种文件系统格式,可以在各种嵌入式系统中运行。FatFs提供了一组API函数,可以方便地对文件系统进行读写操作。 3. 移植步骤 步骤一:创建工程 首先,需要创建一个GD32的Keil工程,选择自己的芯片型号。然后,在工程目录下新建一个fatfs文件夹,用于存放FatFs库和相关的驱动代码。 步骤二:添加FatFs库 将FatFs库添加到工程中。可以在官网上下载最新的FatFs库,然后将其解压到fatfs文件夹下。 步骤三:配置FatFsfatfs文件夹下创建一个diskio.h文件,用于定义磁盘驱动接口。然后,在fatfs文件夹下创建一个ffconf.h文件,用于配置FatFs的一些参数,比如支持的文件系统格式、簇大小等等。 步骤四:实现磁盘驱动接口 在diskio.h文件中,定义以下磁盘驱动接口: ``` DSTATUS disk_initialize(BYTE pdrv); DSTATUS disk_status(BYTE pdrv); DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count); DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count); DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff); ``` 这些接口函数需要在磁盘驱动文件中实现,比如SD卡驱动文件sdio_sd.c中就实现了这些接口函数。 步骤五:初始化FatFs 在main函数中,调用以下代码初始化FatFs: ``` FATFS fs; FRESULT res; res = f_mount(&fs, "", 1); if (res != FR_OK) { // mount failed } ``` 这段代码将FatFs挂载到默认的磁盘上,如果挂载失败,则说明磁盘不存在或者格式不支持。 步骤六:使用FatFs 使用FatFs的API函数对文件系统进行读写操作,比如: ``` FIL file; FRESULT res; res = f_open(&file, "file.txt", FA_READ); if (res != FR_OK) { // open failed } char buf[128]; UINT read_bytes; res = f_read(&file, buf, sizeof(buf), &read_bytes); if (res != FR_OK) { // read failed } f_close(&file); ``` 这段代码打开一个名为file.txt的文件,并读取其中的内容。 至此,FatFs文件系统移植工作完成。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值