文件系统FATFS学习笔记二

本章主要讲解FatFs文件系统在STM32上的移植。FatFs文件系统的移植非常简单!!!

如果需要了解FatFs文件系统一些基础知识的,请参考我的另一篇文章:

文件系统FATFS学习笔记一

1、移植前的准备

本章适用STM32所有系列的芯片移植。以本章为例需要准备以下东西:

  1. 搭建一个点亮LED灯和串口正常打印的工程。
  2. 在工程中添加外部FLASH芯片W25QXX系列芯片的驱动,可正常读写。本章使用的是正点原子的W25QXX芯片的驱动。
  3. FatFs源文件
  4. 一个STM32系列的开发板。

2、移植过程

2.1、搭建一个含有FATFS文件的工程

  1. 先搭建好一个W25QXX芯片正常驱动的KEIL工程
    工程
  2. 在KEIL工程中添加FATFS文件夹。
    FATFS工程
  3. 将FatFs源文件的source文件夹下面所有的.c和.h全部拷贝到KEIL工程中FATFS文件夹下。
    FATFS文件夹
  4. 在KEIL工程中添加FATFS文件。


5. 在main函数文件头部添加

#include "ff.h"

如果现在编译工程,可以发现有两个错误,一个是来自 diskio.c 文件,提示有一些头文件没找, diskio.c 文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;

接下来要做的就是修改 diskio.c 文件和 ffconf.h 文件。

2.2、修改文件

仅需要修改两个文件:diskio.c 文件和 ffconf.h 文件。

2.2.1、修改diskio.c 文件

先给一个修改好的文件,后面再一个函数一个函数讲解。代码如下:

#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "w25qxx.h"
/* Definitions of physical drive number for each drive */
#define ATA			    0     // 预留SD卡使用
#define SPI_FLASH		1     // 外部SPI Flash


#define FLASH_SECTOR_SIZE 	512	
u16	    FLASH_SECTOR_COUNT=2048*12;	//W25Q1218,前12M字节给FATFS占用
#define FLASH_BLOCK_SIZE   	8     	//每个BLOCK有8个扇区
/*-----------------------------------------------------------------------*/
/* 获取设备状态                                                          */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;

	switch (pdrv) {
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:      
			/* SPI Flash状态检测:读取SPI Flash 设备ID */
			if(0xEF17 == W25QXX_ReadID())
			{
			/* 设备ID读取结果正确 */
				status &= ~STA_NOINIT;
			}
			else
			{
			/* 设备ID读取结果错误 */
				status = STA_NOINIT;;
			}
			break;

		default:
			status = STA_NOINIT;
	}
	return status;
}
/*-----------------------------------------------------------------------*/
/* 设备初始化                                                            */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) {
		case ATA:	         /* SD CARD */
			break;
    
		case SPI_FLASH:    /* SPI Flash */ 
			/* 初始化SPI Flash */
			W25QXX_Init();
			/* 获取SPI Flash芯片状态 */
			status=disk_status(SPI_FLASH);
			break;
      
		default:
			status = STA_NOINIT;
	}
	return status;
}
/*-----------------------------------------------------------------------*/
//读扇区:读取扇区内容到指定存储区
//drv:磁盘编号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 */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT status = RES_PARERR;
	if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误	
	switch (pdrv) 
	{
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:
			for(;count>0;count--)
			{
				W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			status = RES_OK;
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

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 status = RES_PARERR;
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) 
	{
		case ATA:	/* SD CARD */      
			break;

		case SPI_FLASH:
			for(;count>0;count--)
			{										    
				W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			status = RES_OK;
			break;

		default:
			status = RES_PARERR;
	}
	return status;
}

#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) 
	{
		case ATA:	/* SD CARD */
		break;

		case SPI_FLASH:
			switch (cmd) 
			{
				case CTRL_SYNC:
					break;	 
				case GET_SECTOR_SIZE:
					*(WORD*)buff = FLASH_SECTOR_SIZE;
					break;	 
				case GET_BLOCK_SIZE:
					*(WORD*)buff = FLASH_BLOCK_SIZE;
					break;	 
				case GET_SECTOR_COUNT:
					*(DWORD*)buff = FLASH_SECTOR_COUNT;
					break;       
			}
			status = RES_OK;
			break;

		default:
		status = RES_PARERR;
	}
	return status;
}

__weak DWORD get_fattime(void) {
	/* 返回当前时间戳 */
	return	  ((DWORD)(2015 - 1980) << 25)	/* Year 2015 */
			| ((DWORD)1 << 21)				/* Month 1 */
			| ((DWORD)1 << 16)				/* Mday 1 */
			| ((DWORD)0 << 11)				/* Hour 0 */
			| ((DWORD)0 << 5)				  /* Min 0 */
			| ((DWORD)0 >> 1);				/* Sec 0 */
}

底层设备驱动函数是存放在 diskio.c 文件,我们的目的就是把 diskio.c 中的函数接口与SPI Flash 芯片驱动连接起来。总共有五个函数,分别为:

  1. 设备状态获取(disk_status)
  2. 设备初始化(disk_initialize)
  3. 扇区读取(disk_read)
  4. 扇区写入(disk_write)
  5. 其他控制(disk_ioctl)。

接下来,我们对每个函数结合 SPI Flash 芯片驱动做详细讲解。

(1)、物理编号宏定义

#define ATA			    0     // 预留SD卡使用
#define SPI_FLASH		1     // 外部SPI Flash

这两个宏定义在 FatFs 中非常重要, FatFs 是支持多物理设备的,必须为每个物理设备定义一个不同的编号。SD 卡是预留接口,在讲解 SDIO 接口相关章节后会用到,可以实现使用读写 SD 卡内文件。

(2)、设备状态获取

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;
	switch (pdrv) {
		case ATA:	/* SD CARD */
			break;
		case SPI_FLASH:      
			/* SPI Flash状态检测:读取SPI Flash 设备ID */
			if(0xEF17 == W25QXX_ReadID())
			{
			/* 设备ID读取结果正确 */
				status &= ~STA_NOINIT;
			}
			else
			{
			/* 设备ID读取结果错误 */
				status = STA_NOINIT;;
			}
			break;
		default:
			status = STA_NOINIT;
	}
	return status;
}

disk_status 函数只有一个参数 pdrv,表示物理编号。一般我们都是使用 switch 函数实现对 pdrv 的分支判断。对于 SD 卡只是预留接口,留空即可。对于 SPI Flash 芯片,我们直接调用在W25QXX_ReadID()获取设备 ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。

(3)、设备初始化

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) {
		case ATA:	         /* SD CARD */
			break;
    
		case SPI_FLASH:    /* SPI Flash */ 
			/* 初始化SPI Flash */
			W25QXX_Init();
			/* 获取SPI Flash芯片状态 */
			status=disk_status(SPI_FLASH);
			break;
      
		default:
			status = STA_NOINIT;
	}
	return status;
}

disk_initialize 函数也是有一个参数 pdrv,用来指定设备物理编号。对于 SPI Flash 芯片我们调用 W25QXX_Init()函数实现对 SPI Flash 芯片引脚 GPIO 初始化配置以及 SPI 通信参数配置。 最后调用 disk_status 函数获取 SPI Flash 芯片状态,并返回状态值。

(4)、读取扇区

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 status = RES_PARERR;
	if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误	
	switch (pdrv) 
	{
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:
			for(;count>0;count--)
			{
				W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			status = RES_OK;
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}

disk_read 函数有四个形参。 pdrv 为设备物理编号。 buff 是一个 BYTE 类型指针变量,buff 指向用来存放读取到数据的存储区首地址。 sector 是一个 DWORD 类型变量,指定要读取数据的扇区首地址。 count 是一个 UINT 类型变量,指定扇区数量。

(5)、扇区写入

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 status = RES_PARERR;
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) 
	{
		case ATA:	/* SD CARD */      
			break;

		case SPI_FLASH:
			for(;count>0;count--)
			{										    
				W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
				sector++;
				buff+=FLASH_SECTOR_SIZE;
			}
			status = RES_OK;
			break;

		default:
			status = RES_PARERR;
	}
	return status;
}

disk_write 函数有四个形参, pdrv 为设备物理编号。 buff 指向待写入扇区数据的首地址。sector,指定要读取数据的扇区首地址。 count 指定扇区数量。对于 SPI Flash 芯片,调用数据写入函数(W25QXX_Write)把数据写入到指定位置内。

(6)、其它控制

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) 
	{
		case ATA:	/* SD CARD */
		break;

		case SPI_FLASH:
			switch (cmd) 
			{
				case CTRL_SYNC:
					break;	 
				case GET_SECTOR_SIZE:
					*(WORD*)buff = FLASH_SECTOR_SIZE;
					break;	 
				case GET_BLOCK_SIZE:
					*(WORD*)buff = FLASH_BLOCK_SIZE;
					break;	 
				case GET_SECTOR_COUNT:
					*(DWORD*)buff = FLASH_SECTOR_COUNT;
					break;       
			}
			status = RES_OK;
			break;

		default:
		status = RES_PARERR;
	}
	return status;
}

disk_ioctl 函数有三个形参, pdrv 为设备物理编号, cmd 为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令, buff 为指令对应的数据指针。

(7)、时间戳获取

__weak DWORD get_fattime(void) {
	/* 返回当前时间戳 */
	return	  ((DWORD)(2015 - 1980) << 25)	/* Year 2015 */
			| ((DWORD)1 << 21)				/* Month 1 */
			| ((DWORD)1 << 16)				/* Mday 1 */
			| ((DWORD)0 << 11)				/* Hour 0 */
			| ((DWORD)0 << 5)				  /* Min 0 */
			| ((DWORD)0 >> 1);				/* Sec 0 */
}

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

2.2.2、修改ffconf.h 文件

ffconf.h 对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采用默认即可。

#define FF_USE_MKFS		1
#define FF_CODE_PAGE	936
#define FF_USE_LFN		2
#define FF_LFN_UNICODE	2
#define FF_STRF_ENCODE	3
#define FF_VOLUMES		2
#define FF_MIN_SS		512
#define FF_MAX_SS		4096
  1. FF_USE_MKFS :格式化功能选择,为使用 FatFs 格式化功能,需要把它设置为 1。
  2. FF_CODE_PAGE :语言功能选择,并要求把相关语言文件添加到工程宏。为支持简 体中文文件名需要使用“936”。
  3. FF_USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名, 并指定使用栈空间为缓冲区。
  4. FF_LFN_UNICODE:文件名使用UTF-8格式。
  5. FF_STRF_ENCODE:文件使用UTF-8格式。
  6. FF_VOLUMES:指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPIFlash 芯片。
  7. FF_MIN_SS、 FF_MAX_SS :指定扇区大小的最小值和最大值。 SD 卡扇区大小一般都 为 512字节, SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把_MAX_SS 改为 4096。

文件修改完毕。接下来就是进行测试。

3、FatFas移植测试

测试代码如下:

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "system_event.h"//里面包含的是usart.h
#include "w25qxx.h" 
#include "ff.h"

FATFS fs;													/* FatFs文件系统对象 */
FIL fnew;													/* 文件对象 */
FRESULT res_flash;                /* 文件操作结果 */
UINT fnum;            					  /* 文件成功读写数量 */
char fpath[100];                  /* 保存当前扫描路径 */
char readbuffer[512];             /*  */
BYTE work[FF_MAX_SS];

char write[] = "欢迎关注redeemer奇,测试!";

int main(void)
{
	DIR dir;
	FATFS *pfs;
	DWORD fre_clust, fre_sect, tot_sect;
	
	uint16_t temp;
	
	delay_init(168);
	led_init();
	system_event_init();//里面是串口初始化
	W25QXX_Init();

	printf("\r\n=======开始测试=====================\r\n");
	//挂载文件系统
	res_flash = f_mount(&fs,"1:",1);
	if (res_flash == FR_NO_FILESYSTEM) 
	{
		printf("》 FLASH 还没有文件系统,即将进行格式化...\r\n");
		
		res_flash=f_mkfs("1:",0,work,sizeof(work));
		
		if (res_flash == FR_OK) 
		{
			printf("》 FLASH 已成功格式化文件系统。 \r\n");
			res_flash = f_mount(NULL,"1:",1);
			res_flash = f_mount(&fs,"1:",1);
		}
		else
		{
			printf("《《格式化失败。%d》》 \r\n",res_flash);
			while (1);
		}
		
	}
	else if(res_flash!=FR_OK)
	{
		printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
		printf("!!可能原因:SPI Flash初始化不成功。\r\n");
		while(1);
	}
	else
	{
		printf("外部Flash挂载文件系统成功。(%d)\r\n",res_flash);
	}
	
	/*---------------------- 文件系统测试:写测试 ----------------------*/
	/* 打开文件,如果文件不存在则创建它 */
	printf("\r\n****** 即将进行文件写入测试... ******\r\n");
	
	res_flash = f_open(&fnew, "1:data.txt",FA_CREATE_ALWAYS | FA_WRITE );	

	if ( res_flash == FR_OK ) 
	{
		printf("\r\n》打开/创建 文件成功,向文件写入数据。 \r\n");
		
		res_flash=f_write(&fnew,write,sizeof(write),&fnum);
		
		if (res_flash==FR_OK)
		{
			printf("》文件写入成功,写入字节数据: %d\n",fnum);
			printf("》向文件写入的数据为: \r\n%s\r\n",write);
		}
		else
		{
			printf("!!文件写入失败: (%d)\n",res_flash);
		}
		f_close(&fnew);
	}
	else
	{
		printf("!!打开/创建文件失败。 %d\r\n",res_flash);
		while(1);
	}
	
	/*---------------------- 文件系统测试:读测试 ----------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	res_flash = f_open(&fnew, "1:data.txt",FA_OPEN_EXISTING | FA_READ);
	if (res_flash == FR_OK)
	{
		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
	{
		printf("!!打开文件失败。 \r\n");
	}
	f_close(&fnew);
	
	
	while(1)
	{

	}
}

结束

欢迎大家关注我的微信号:redeemer奇
里面惊喜,有大量的学习资料持续更新。各种开源项目!
一起交流!一起努力!

软件下载 | 学习视频 | 嵌入式书籍 | 项目资料 |公众号中持续更新…

redeemer奇

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

redeemer奇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值