STM32串行FLASH W25Q64文件系统FatFs移植详解(二)

引言

  • 简述本篇文章目标:介绍如何将FatFs文件系统移植到STM32平台上的W25Q64串行FLASH。
  • 回顾上篇内容:软件模拟SPI驱动的实现。
  • 一、FatFs文件系统简介

    文件系统相对庞大且复杂,需要根据具体应用的文件系统格式进行编写。通常,文件系统与底层驱动分离,便于移植。因此,在实际工程应用中,常常直接移植现成的文件系统源码,以提高开发效率。

    FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。FatFs 模块按照 ANSI C (C89) 编写,并与磁盘 I/O 层完全分离。因此,它独立于平台。它可以集成到资源有限的小型微控制器中,如8051、PIC、AVR、ARM、Z80、RX等。
    FatFs
    支持
    FAT12

    FAT16

    FAT32
    等格式,所以我们利用上一篇文章
    写好的 W25Q64的 
    芯片驱动,将 
    FatFs
    文件系统的代码移植到工程之中,就可以利用文件系统的各种函数,对 Flash
    芯片以“文件”格式进行读写操作了。

    移植所用到的FatFs文件系统的源码可以从官网进行下载:
    FatFs官网

    1.1 源码下载

    目前官网最新的版本为R0.15版本,我移植的时R0.14b版本,所以点击这里的Previous Releases,到之前的版本中选择需要的版本进行下载。

    1.2 源码的目录结构

    下载好的源码解压后,包括两个文件夹和一个txt文件。

    1.3 帮助文档

    其中documents文件夹中是一些帮助文档,00index_e.html是关于FatFs的简介,双击后就会进入到官网,在官网中可以查看FatFs文件系统的函数和用法,或者点击doc文件夹中各个以函数名作为名称的html文件了解函数的介绍及用法,利用这些函数我们就可以操作FLASH芯片,res文件夹中包括一些00index_e.html会用到的图片,还有6个名为app.c的文件,文件中都是一些FatFs具体的应用例程。

    1.4 FatFs源码

    source文件夹中是FatFs文件系统的源码,我们在移植的时候要将source文件夹中文件添加到工程中。

  • 00readme.txt:对文件夹中各个文件的简单介绍。
  • 00history.txt:介绍的是FatFs的版本更新情况。
  • ff.c:FatFs的核心文件,文件管理的实现函数都在该文件中,该文件独立于底层操作文件的函数,利用这些函数可以实现对文件的读写。                 
  • ffconf.h:该头文件中包含了对FatFs功能配置的宏定义,通过修改这些宏定义可以裁剪FatFs的功能。
  • ff.h:常见的包括FatFs和应用程序模块的文件。
  • diskio.h:diskio.c文件的函数声明以及diskio.c文件中用到的定义。
  • diskio.c:包含底层存储截止的操作函数,这些函数要自己实现,主要添加底层驱动函数。
  • ffunicode.c:可选的Unicode实用程序函数。
  • ffsystem.c:可选O/S相关功能的示例。
  • 这些文件中,diskio.c中的底层操作函数需要我们自己实现,ffconf.h文件中的宏定义需要做相应的修改,所以在移植过程中,需要我们自己修改的就只有diskio.c和ffconf.h两个文件。

      二、FatFs的移植步骤 

    在工程目录中创建FatFs文件夹,将源码中source文件夹下的文件复制到该文件夹中。

    复制完成后在工程中添加FatFs分组,并将diskio.c,ff.c,ffunicode.c添加到分组中,并且要将路径添加到工程中去。

    完成后点击编译会出现13个错误和17个警告,其中包括头文件无法找到等各个错误,这里我们对diskio.c和ffconf.h文件进行修改,修改完成后就可以消除掉这些错误和警告。

    2.1 修改ffconf.h中的宏定义值

  • 将FF_USE_MKFS的值修改为1,因为后续要用到f_mkfs()函数来进行格式化操作。
  • 这里将FF_CODE_PAGE的值修改为437,为了用于支持后续创建文件名称和读写文件时对英文的支持,修改之后必须将ffunicode.c添加到工程中,否则会报错。
  • 将FF_USE_LFN设置为2,代表启用长文件名功能,并且长文件名所需的工作区是动态分配的,这样只有在需要处理长文件名时才会占用内存,从而节省了系统资源。
  • 这里将FF_MAX_SS修改为4096,表示文件系统能够支持从512字节到4096字节的扇区大小范围。
  • FF_VOLUMES 用于配置文件系统同时支持的逻辑卷(卷)的数量。将 FF_VOLUMES 设置为 1 表示该文件系统仅支持一个逻辑卷,若要再安装SD卡等需将该值修改为所有的逻辑卷的数量。
  • 2.2 diskio.c文件修改

    2.2.1 添加头文件并修改宏定义

    只有SPI FLASH左右逻辑卷,暂时没有其他的,所以将其他进行屏蔽,只为SPI_FLASH这一个驱动设置编号即可。

    2.2.2 disk_status()  设备状态获取函数

    disk_status函数只有一个参数pdrv,表示物理盘号。一般使用switch来进行分支判断,因为我们目前只有SPI FLASH芯片,所以将其他分支都删除掉,只留下SPI FLASH芯片这一个分支,在这个分支下直接调用FLASH芯片驱动函数中的W25QXX_ReadID()函数来获取设备ID并判断是否正确,正确的话函数返回正常的标志,错误的话返回错误标志。

    DSTATUS disk_status (
    	BYTE pdrv		/* Physical drive nmuber to identify the drive */
    )
    {
    	DSTATUS status = STA_NOINIT;
    	
    	switch (pdrv) {
    		case SPI_FLASH :
    			if (W25Q64 == W25QXX_ReadID())
    			{
    				status &= ~STA_NOINIT;
    			}
    			else
    			{
    				status = STA_NOINIT;
    			}
    			break;
    			
    		default:
    			status = STA_NOINIT;
    	}
    	return status;
    }
    
     2.2.3 disk_initialize()  设备初始化函数

    disk_initialize函数也只有一个参数pdrv,用于指定物理盘号。在该函数中,我们调用FLASH驱动函数中的W25QXX_Init()函数来对FLASH芯片进行初始化,然后做一个短暂的延时,确保初始化完成,在调用disk_status函数来获取SPI FLASH芯片的状态,并返回状态值。

    DSTATUS disk_initialize (
    	BYTE pdrv				/* Physical drive nmuber to identify the drive */
    )
    {
    	uint16_t i;
    	DSTATUS status = STA_NOINIT;
    
    	switch (pdrv) {
    		
    		case SPI_FLASH :
    			W25QXX_Init();
    			i = 500;
    			while(--i);    //进行短暂的延时
    			status = disk_status(SPI_FLASH);    
    			break;
    		
    		default:
    			status = STA_NOINIT;
    	}
    	return status;
    }
    
    2.2.4 disk_read()  读取扇区函数

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

    对于
    SPI Flash
    芯片,主要是使用 W25QXX_Read() 函数来实现在指定地址读取指定长度的数据,它包括三个参数,第一个参数用于指定数据存放地址指针,第二个参数用于指定数据读取的起始地址,这里使用左移运算符,左移 12
    位实际是乘以
    4096
    ,因为W25Q64芯片的每个扇区大小是4096个字节,第三个参数为要读取的扇区数,也需要使用左移运算符来换算成需要读取的字节数。

    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;
    	
    	switch (pdrv) {
    		case SPI_FLASH :
    			/* 左移12位也就是乘以4096,因为一个扇区4096个字节 */
    			W25QXX_Read(buff, sector<<12, count<<12);
    			status = RES_OK;
    			break;
    		
    		default:
    			status = RES_PARERR;
    	}
    
    	return status;
    }
    
    2.2.5 disk_write()  扇区写入函数

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

    对于 SPI Flash 芯片,主要是使用 W25QXX_Write() 函数来实现在指定地址写入指定长度的数据,在写入数据之前,要使用W25QXX_Erase_Sector()函数将要写入的扇区的数据擦除,否则会写入失败。

    W25QXX_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 */
    )
    {
    	uint32_t write_addr;
    	DRESULT status = RES_PARERR;
    	
    	if (!count){
    		return RES_PARERR;		/* Check parameter */
    	}
    
    	switch (pdrv) {
    		case SPI_FLASH :
    			write_addr = sector << 12;
    			W25QXX_Erase_Sector(write_addr);
    			W25QXX_Write((u8 *)buff, write_addr, count<<12);
    			status = RES_OK;
    			break;
    		
    		default:
    			status = RES_PARERR;
    	}
    
    	return status;
    }
    
    #endif
    2.2.6 disk_ioctl()  其他控制函数

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

    对于
    SPI Flash
    芯片,为了支持
    FatFs
    格式化功能,需要用到获取扇区数量
    (GET_SECTOR_COUNT) 指令和获取擦除块数量 (GET_BLOCK_SIZE)
    指令。另外,
    SPI Flash芯片一般设置扇区大小为 4096
    字节,所以需要用到获取扇区大小
    (GET_SECTOR_SIZE)
    指令。

    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 SPI_FLASH:
    			switch(cmd)
    			{
    				/* 扇区数量 2048*4096/1024/1024=8MB */
    				case GET_SECTOR_COUNT:
    					*(DWORD *)buff = 2048;
    					break;
    				
    				/* 扇区大小 */ 
    				case GET_SECTOR_SIZE:
    					*(WORD *)buff = 4096;
    					break;
    				
    				/* 同时擦除扇区个数 */
    				case GET_BLOCK_SIZE:
    					*(DWORD *)buff = 1;
    					break;
    			}
    			status = RES_OK;
    			break;
    			
    		default:
    			status = RES_PARERR;
    	}
    
    	return status;
    }
    

    三、FatFs功能测试

    测试内容参照野火霸道例程中,适当做了一些修改,在main.c文件中实现,具体代码如下:

    #include "stm32f10x.h"
    #include "ff.h"
    #include "ffconf.h"
    #include "./usart/bsp_usart.h"
    #include "./led/bsp_led.h"
    #include "./flash/bsp_spi_flash.h"
    
    
    FATFS fs;						/* FatFs文件系统对象 */
    FIL fnew;						/* 文件对象 */
    FRESULT res_flash;              /* 文件操作结果 */
    UINT fnum;            			/* 文件成功读写数量 */
    BYTE ReadBuffer[1024]={0};      /* 读缓冲区 */
    BYTE WriteBuffer[] =            /* 写缓冲区*/
    "welcome to use YeHuo board, this is a file system test.\r\n"; 
    
    
    /*
     * 函数名:main
     * 描述  :主函数
     * 输入  :无
     * 输出  :无
     */
    int main(void)
    { 	
    	BYTE work[FF_MAX_SS];  // 工作缓冲区
        MKFS_PARM opt = {
            .fmt = FM_FAT|FM_SFD,
            .n_fat = 1,
            .align = 0,
            .n_root = 0,
            .au_size = 0
    	};
    	
    	LED_GPIO_Config();
    	LED_BLUE;
    	
    	USART_Config();
    	printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
    	
    	res_flash = f_mount(&fs, "0:", 1);
    //	res_flash = FR_NO_FILESYSTEM;
    	if (res_flash == FR_NO_FILESYSTEM)
    	{
    		printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    		
    		res_flash = f_mkfs("0:", &opt, 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);
    			if (res_flash == FR_OK)
    			{
    				printf("格式化后挂载成功\r\n");
    			}
    			else
    			{
    				printf("格式化后挂载失败, err = %d\r\n", res_flash);
    			}
    		}
    		else
    		{
    			LED_RED;
    			printf("《《格式化失败。》》\r\n");
    			printf("errcode = %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");
    		printf("请下载 SPI—读写串行FLASH 例程测试,如果正常,在该例程f_mount语句下if语句前临时多添加一句 res_flash = FR_NO_FILESYSTEM; 让重新直接执行格式化流程\r\n");
    		while(1);
    	}
    	else
    	{
    		LED_GREEN; 
    		printf("》文件系统挂载成功,可以进行读写测试\r\n");
    	}
    	
    	
    	/*----------------------- 文件系统测试:写测试 -------------------*/
    	/* 打开文件,每次都以新建的形式打开,属性为可写 */
    	printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
    	res_flash = f_open(&fnew, "0:/test1.txt",FA_CREATE_ALWAYS | FA_WRITE );
    	printf("/******************************************************/\r\n");
    	if ( res_flash == FR_OK )
    	{
    		printf("》打开/创建test1.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****** 即将进行文件读取测试... ******\r\n");	
    	res_flash = f_open(&fnew, "0:/test1.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, "0:", 1);
    	
    	while(1);  
    }
    

    测试串口打印的结果如下:

    若想创建中文名称的文件名,写入中文数据的话,需要将ffconf.h文件中FF_CODE_PAGE的值修改为936。

    如果在调用FatFs文件系统的函数创建文件时跳转到HardFault_Handler硬件错误中断中的情况,是因为栈空间不足导致,将启动文件中Stack_Size的值修改大一些即可。

    结语

    本文对FatFs文件系统进行了简要介绍,并实现了FatFs文件系统的移植以及移植完成后对文件系统基础功能的测试。


    相关阅读

  • STM32官方文档
  • FatFs文件系统官方文档
  • W25Q64数据手册
  • 如果你有任何问题或建议,请在评论区留言。感谢阅读!

    作者:小仲努力ing

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32串行FLASH W25Q64文件系统FatFs移植详解(二)

    发表回复