STM32外部Flash移植FATFS笔记

FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051PICAVRSHZ80H8ARM等。 FatFs支持FAT12FAT16FAT32等格式。

目录

下载FatFs源码

认识FATFS源码结构

 FATFS文件系统移植步骤

1. 定义自己的设备类型编号

2. 修改DSTATUS disk_initialize 方法,初始化W25Q128设备

3. 完成DSTATUS disk_status 函数

4.对芯片的读取操作,完善DRESULT disk_read操作

5. 完成对芯片的写操作,完善DRESULT disk_write

6. 完成一些杂项操作DRESULT disk_ioctl

7. 完善系统要求的get_fattime方法

ffconf.h 的配置

应用层调用

验证结果 

参考代码


下载FatFs源码

FatFs - Generic FAT Filesystem Module

认识FATFS源码结构

文件

说明

备注

ffsystem.c

FatF用户提供的操作系统相关函数的示例代码

 

ffunicode.c

文件系统支持的语言编码

不需要修改

ffconf.h

文件系统配置项

根据需求修改

ff.c

FatFs核心文件,文件管理的实现
方法。该文件独立于底层介质操作文件
的函数,利用这些函数实现文件的读写。

不需要修改

diskio.c

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

集成驱动文件

函数

条件(ffconf.h)

备注

disk_status
disk_initialize
disk_read

总是需要

底层设备驱动函数

disk_write
get_fattime
disk_ioctl (CTRL_SYNC)

FF_FS_READONLY == 0

disk_ioctl (GET_SECTOR_COUNT)
disk_ioctl (GET_BLOCK_SIZE)

FF_USE_MKFS == 1

disk_ioctl (GET_SECTOR_SIZE)

FF_MAX_SS != _MIN_SS

disk_ioctl (CTRL_TRIM)

FF_USE_TRIM == 1

ff_convert
ff_wtoupper

FF_USE_LFN != 0

FF_CODE_PAGE 936 添加中文文件名支持

ff_cre_syncobj
ff_del_syncobj
ff_req_grant
ff_rel_grant

FF_FS_REENTRANT == 1

FatFs 统支持可重入配置,需要多任务系 (一般不需要)

ff_mem_alloc
ff_mem_free

FF_USE_LFN == 3

长文件名支持,缓冲区设置在堆 空间(一般设置_USE_LFN = 2)

 

图中的六个函数位于diskio.c文件中,再加上我们需要适当的修改宏定义,位于ffconf.h中。所以实际上我们在进行文件系统移植的时候,只需要修改ffconf.hdiskio.c两个文件

 FATFS文件系统移植步骤

1. 定义自己的设备类型编号

2. 修改DSTATUS disk_initialize 方法,初始化W25Q128设备

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

	switch (pdrv) {
	case DEV_FLASH :
			W25Q128_Init();
      // 延时一小段时间
	    i=500;
	    while(--i);	
      stat = disk_status(pdrv);
	    printf("init stat = %d\r\n",stat);
		return stat;

//	case DEV_MMC :
//		result = MMC_disk_initialize();

//		// translate the reslut code here

//		return stat;

//	case DEV_USB :
//		result = USB_disk_initialize();

//		// translate the reslut code here

//		return stat;
	}
	return STA_NOINIT;
}

这里调用W25Q128的初始化函数,之后调用 disk_status 检测设备是否初始化成功

3. 完成DSTATUS disk_status 函数

 上图是官方对disk_status()函数的解释。该函数只有一个形参,就是设备号用于标识不同的设备,在只有一个设备的情况下,设备号默认为0。
        该函数的返回值有:
        STA_NOINIT:标识该设备未初始化成功,未进入就绪状态。
        STA_NODISK:FatFs不引用此标志。
        STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效。
        0:表示该设备初始化成功。

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

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

  u16 FLASH_ID;
	switch (pdrv) {
		case DEV_FLASH:
	  
	   FLASH_ID = Read_Manufacturer();
		 if(FLASH_ID == NM25Q128) {
		   stat = 0;
		 } 
		 else
		 {
		   stat = STA_NOINIT;
		 }
	    
		
		return stat;

//	case DEV_MMC :
//		result = MMC_disk_status();

//		// translate the reslut code here

//		return stat;

//	case DEV_USB :
//		result = USB_disk_status();

		// translate the reslut code here

//		return stat;
	}
	return STA_NOINIT;
}

检测W25Q128是否能够读取芯片的厂商ID,这个ID是固定的,如果能够读取出来就返回0,代表成功,否则返回初始化失败。这样当系统层在调用的时候会给出对应的错误提示。

4.对芯片的读取操作,完善DRESULT disk_read操作

形参:
        第一个形参:要操作的设备号,在本次移植工程中,可选项是 DEV_FLASH(0)
        第二个形参:读取到的数据的缓冲区。
        第三个形参:要读取的扇区的个数。

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

函数值调用:

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

	switch (pdrv) {
	case DEV_FLASH :
		/* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面14MB空间 */
      sector+=512; 
		  W25Q128_Read((u8 *)buff, sector * FLASH_SECTOR_SIZE, count * FLASH_SECTOR_SIZE);
    res = RES_OK;
		return res;

//	case DEV_MMC :
//		// translate the arguments here

//		result = MMC_disk_read(buff, sector, count);

//		// translate the reslut code here

//		return res;

//	case DEV_USB :
//		// translate the arguments here

//		result = USB_disk_read(buff, sector, count);

//		// translate the reslut code here

//		return res;
	}

	return RES_PARERR;
}

这里前面sector+=512 是偏移2M字节,是根据秉火的教程,Flash前2M空间会放一些其他的内容,用户可以直接进行扇区的数据操作,不使用文件系统存放的一些数据。

w25q128 一个扇区4096byte,16个扇区一个Block,256个block是总共的存储空间。

所以2M( 2048 * 1024 / 4096 = 512 sectors) 每次系统在读的时候,进行这么多偏移,把前2M 的空间就不操作了。

5. 完成对芯片的写操作,完善DRESULT 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()

#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 res  = RES_PARERR;

	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}
	switch (pdrv) {
		case DEV_FLASH :
			
		    /* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面14MB空间 */
				sector+=512;
				W25Q128_Sector_Erase(sector * FLASH_SECTOR_SIZE);
				W25Q128_Write((u8 *)buff,sector * FLASH_SECTOR_SIZE,count * FLASH_SECTOR_SIZE);
				res = RES_OK;
			return res;

//	case DEV_MMC :
//		// translate the arguments here

//		result = MMC_disk_write(buff, sector, count);

//		// translate the reslut code here

//		return res;

//	case DEV_USB :
//		// translate the arguments here

//		result = USB_disk_write(buff, sector, count);

//		// translate the reslut code here

//		return res;
	}

	return RES_PARERR;
}

#endif

       这里 sectors+=512 也是对文件系统上层的的操作进行偏移,保留前面512字节地址,和写操作对应。

      另外,这里write 方法有个条件编译,我们需要设置对应的宏 FF_FS_READONLY == 0 才能使用write操作。

      在进行写操作之前一定要先进行擦除操作。在W25Q128_Write((u8*)buff,sector*4096,count*4096);中含有了擦除操作,最开始没有增加擦除方法的时候,默认提示格式化系统成功,但是后面用电脑进行USB读取Flash时候,提示未格式化,也就是没有格式化成功。所以这里还是把擦除操作加上了。可以屏蔽擦除扇区一行测试。

       这里默认我们给返回写成功。也可以对写操作进行判断,增加返回值。如果某次写错误,也可能会在系统层面提示成功。这里需要注意。根据实际情况来编码。

6. 完成一些杂项操作DRESULT disk_ioctl

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


	if (pdrv == DEV_FLASH) {
    switch (cmd) {
        /* 扇区数量:3584*4096/1024/1024=14(MB) */
        case GET_SECTOR_COUNT:
          *(DWORD * )buff = 3584; // 这个值来源于前面512扇区给了文件系统表,后面的才是可以使用的空间  w25q128 4096 扇区 - 512 扇区 =	3584
        break;
        /* 扇区大小  */
        case GET_SECTOR_SIZE :
          *(WORD * )buff = 4096;
        break;
        /* 同时擦除扇区个数 */
        case GET_BLOCK_SIZE :
          *(DWORD * )buff = 1;
        break;        
      }
      res = RES_OK;
	} 
	else 
  {
	   res = RES_PARERR;
	}

	return res;
}

怎么理解这里的cmd参数呢,有以下命令

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

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

这里我们只关心

GET_SECTOR_COUNT

告诉文件系统,我们的Flash 有多少个可以操作的扇区,这个值来源于前面512扇区给了文件系统,后面的才是可以使用的空间  w25q128 4096 扇区 - 512 扇区 =    3584 个扇区

GET_SECTOR_SIZE

获取每一个扇区的大小,一个扇区就是4096 字节。

GET_BLOCK_SIZE

以闪存介质扇区为单位的擦除块大小

该函数的使用必须首先开启宏,使得“FF_USE_MKFS == 1”

7. 完善系统要求的get_fattime方法

DWORD get_fattime (void)
{
    return (DWORD)(2022 - 80) << 25 |
           (DWORD)(10 + 1) << 21 |
           (DWORD)9 << 16 |
           (DWORD)20 << 11 |
           (DWORD)20 << 5 |
           (DWORD)0 >> 1;
}

这个方法是我们在对文件操作的时候,会产生一个时间戳,这里我写死了,可以使用RTC完善这里的具体时间参数。

OK diskio修改完成。


ffconf.h 的配置

#define FF_FS_READONLY    0

  要有写功能必须设置为0

#define FF_USE_MKFS        1

  如果存储芯片不存在文件系统,我们需要对其进行格式化,这里配置为1 使能格式化操作。对应的是f_mkfs()函数

#define FF_CODE_PAGE    936

  添加中文支持

#define FF_USE_LFN        2 

   长文件名存储空间设置,缓冲区设置在堆 空间(一般设置_USE_LFN = 2)

 #define FF_VOLUMES        1

我的代码中只移植了Flash的文件系统,所以只有一个设备DEV_FLASH,所以这里定义为1 ,如果还有其他的设备定义,这可以修改。

#define FF_MAX_SS        4096

这里是可以操作的扇区的大小,因为Flash的一个扇区是4096个字节,所以一次可以操作最大设置为4096.这里和 disk_ioctl() 方法里的 GET_SECTOR_SIZE 对应。

最基本的配置就是这些了。


应用层调用

首先挂载文件系统,如果没有文件系统则对底层设备进行格式化,就会创建一个文件系统 ,挂在的“0:”代表DEV_FLASH 定义的数值,: 是必须要写的。

printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
   
	//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
	//初始化函数调用流程如下
	//f_mount()->find_volume()->disk_initialize->SPI_FLASH_Init()
	res_flash = f_mount(&fs,"0:",1);
	printf("res_flash = %d\r\n",res_flash);
	if(res_flash == FR_NO_FILESYSTEM)
	{
		printf("FLASH还没有文件系统,即将进行格式化...\r\n");
    /* 格式化 */
		res_flash=f_mkfs("0:",0,work,sizeof(work));
		if(res_flash == FR_OK)
		{
			printf("》FLASH已成功格式化文件系统。\r\n");
      /* 格式化后,先取消挂载 */
			res_flash = f_mount(NULL,"0:",1);			
      /* 重新挂载	*/			
			res_flash = f_mount(&fs,"0:",1);
		}
		else
		{
			LED_RED = 0;
			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");
  }	

 写操作,这里使用了长文件名,并且文件名是中文。并且开启宏 FF_FS_READONLY == 0 

/*------------------- 文件系统测试:写测试 --------------------------*/
printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
res_flash = f_open(&fnew, "0:新建文本文档abc.txt",FA_CREATE_ALWAYS | FA_WRITE);
if(res_flash == FR_OK )
{
	printf("》打开/创建新建文本文档abc.txt文件成功,向文件写入数据。\r\n");
    /* 将指定存储区内容写入到文件内 */
    res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
    if(res_flash == FR_OK)
    {
      printf("文件写入成功,写入字节数据:%d\r\n",fnum);
			delay_ms(1);
      printf("向文件写入的数据为:\r\n%s\r\n",WriteBuffer);		
      delay_ms(1);			
    }
    else
    {
      printf("!!文件写入失败:(%d)\n",res_flash);
    }    
		/* 不再读写,关闭文件 */
    f_close(&fnew);
}
else
{	
    LED_RED = 0;
	printf("!!打开/创建文件失败。\r\n");
}

 文件读测试。

/*------------------- 文件系统测试:读测试 --------------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	res_flash = f_open(&fnew, "0:新建文本文档abc.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(res_flash == FR_OK)
	{
		LED_GRREN = 0;
		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)\r\n",res_flash);
    }		
	}
	else
	{
		LED_RED = 0;
		printf("!!打开文件失败(%d)。\r\n",res_flash);
	}
	/* 不再读写,关闭文件 */
	f_close(&fnew);	
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL,"0:",1);

第一个参数NULL,代表取消挂载文件系统。
第二个参数“0:”代表取消挂载的设备
第三个参数 1 立即取消挂载

验证结果 

备注,以上是在原子F103精英板子上进行测试

参考代码

参考代码地址

通过USB验证Flash文件

  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在STM32F407上移植FATFS文件系统,首先需要了解FATFS是一个开源的文件系统,它可以在嵌入式设备上实现存储器的文件读写操作。 在移植FATFS之前,需要准备一些基本的材料,如软件开发环境、相关的文档和资料。可以从FATFS的官方网站下载最新版本的库文件和相关的示例代码。 首先,创建一个新的工程,并在工程中添加所需的库文件和头文件。将fatfs.c和diskio.c两个源文件添加到工程,并在工程设置中包含相关的头文件目录。 接下来,在工程中添加适配底层硬件的代码,在这里是针对STM32F407的。需要编写适配FATFS的底层I/O读写操作的代码,包括初始化SD卡、读取扇区数据和写入扇区数据。 然后,根据具体需求配置FATFS的参数。可以在fatfs_conf.h文件中修改相关的参数,如簇大小、扇区大小和最大文件数等。这些参数根据实际应用需求进行调整。 最后,编写应用程序代码。可以利用FATFS提供的API来实现文件的打开、读取和写入等操作。需要注意的是在使用FATFS API之前,需要先调用f_mount函数挂载文件系统。 完成以上步骤后,就可以进行编译和烧录操作,将程序下载到STM32F407上进行测试。可以通过读取和写入文件来验证FATFS的正常工作。 总结来说,STM32F407上移植FATFS文件系统需要准备相关的材料,包括库文件和文档。然后添加相关的源文件和头文件到工程中,并编写适配底层硬件的代码。之后根据需求配置FATFS参数,并编写应用程序代码。最后进行编译和烧录操作,进行测试。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值