STM32学习笔记--FATFS文件系统

介绍

FatFs是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 组件的编写遵循ANSI C(C89),完全分离于磁盘 I/O 层,因此不依赖于硬件平台。它可以嵌入到便宜的微控制器中,如 8051, PIC, AVR, ARM, Z80, RX等等,不需要做任何修改。
简单地说,FatFS将数据文件状态进行了处理封装,开发者只要调用对应api即可对文件进行便捷操作。

结构

整个文件系统包含doc和src两个文件夹,顾名思义,doc为开源系统文档,src为源码。这里我们重点分析src文件夹,如图。
STM32学习笔记--FATFS文件系统_第1张图片
如上图,为src文件夹的内容。其中option文件夹包含支持各语言所需的文件,如cc936.c文件可用于支持简体中文。00history.txt和00readme.txt文件用于说明版本更迭历史及系统介绍,这里不多赘述。其他还有6个源文件,下面详细说明。
integer.h: FatFS的数值类型定义,包括对常用的变量类型的重定义;
diskio.c: 包含底层存储介质的操作函数,为半完成状态,需要用户自行补足实现。主要是在各函数里调用用户实现的底层驱动函数,如SPI读写flash的操作函数;
diskio.h: diskio.c文件的函数原型声明及各个宏定义;
ff.c: FatFS核心文件,通过调用diskio.c的底层操作函数实现文件的读写等操作。该文件独立于底层介质操作文件,移植系统时不需要修改;
ff.h: 相关文件/文件夹的结构体定义,宏定义,及ff.c文件的函数原型声明等;
ffconf.h: 用于FatFS的功能配置,通过宏实现条件编译,修改相应宏定义的值即可实现功能的裁剪。如,需要支持简体中文,则将宏_CODE_PAGE的值修改为“936”,并把cc936.c文件添加到工程中即可。

系统工作流程

STM32学习笔记--FATFS文件系统_第2张图片
FatFs 提供下面的函数:
(用户函数,顶层)
f_mount - 注册/注销一个工作区域
f_open - 打开/创建一个文件
f_close - 关闭一个文件
f_read - 读文件
f_write - 写文件
f_lseek - 移动文件读/写指针
f_truncate - 截断文件
f_sync - 冲洗缓冲数据
f_opendir - 打开一个目录
f_readdir - 读取目录条目
f_getfree - 获取空闲簇
f_stat - 获取文件状态
f_mkdir - 创建一个目录
f_unlink - 删除一个文件或目录
f_chmod - 改变属性
f_utime - 改变时间戳
f_rename - 重命名/移动一个文件或文件夹
f_mkfs - 在驱动器上创建一个文件系统
f_forward - 直接转移文件数据到一个数据流
f_gets - 读一个字符串
f_putc - 写一个字符
f_puts - 写一个字符串
f_printf - 写一个格式化的字符磁盘I/O接口

因为FatFs模块完全与磁盘I/O层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘I/O模块并不是FatFs的一部分,并且必须由用户提供。资源文件中也包含有范例驱动。

(驱动函数,底层,控制存储器)
disk_initialize - 初始化磁盘驱动器
disk_status - 获取磁盘状态
disk_read - 读扇区
disk_write - 写扇区
disk_ioctl - 设备相关的控制特性
get_fattime - 获取当前时间

FATFS移植前的准备

1、需准备的文件:
FATFS文件:

在这里插入图片描述

储存器驱动文件(如SD卡):
在这里插入图片描述

将SD卡驱动封装成两个函数
在这里插入图片描述
通过修改diskio.c,使FATFS文件调用这两个函数,完成与内存驱动的连接,随后在其他地方调用ff.c的API函数,即可使用FATFS。

2、添加工程文件
将diskio.c\ff.c\cc936.c\sdio_sdcard.c添加入工程即可

3、注意
正点原子的STM32例程里添加了malloc.c\exfuns.c\fattester.c文件,这几个是辅助用的文件,其实可以不加,只是加了可以比较方便的实现一些功能,它其实是对ff.c文件的进一步封装。

FATFS移植

在SPI读写flash的实验基础上移植FatFS。
(例程不是我的,我用的是SD卡,排版很乱所以不放了)

将FatFS的“src”文件夹拷贝到项目USER文件夹下,并重命名;
在MDK里配置,将FatFS组件添加到工程中,需要添加的文件是ff.c、diskio.c及cc936.c三个文件;
添加FatFS组件的头文件包含路径。
接下来需要对diskio.c和ffconf.h文件进行修改及配置了。

diskio.c
diskio.c文件里的函数会被ff.c的函数调用,用于控制底层存储介质flash的读写操作。文件里有5个半完成状态的函数,其调用用户实现的底层驱动函数。在补足5个函数之前,需要先定义好各个物理设备的编号。

#define SPI_FLASH    1     // SPI FLASH的编号

通过传入不同物理设备的宏,分别对相应设备进行不同的操作。

#define sFLASH_ID  0XEF4017   // w25q64的ID

接下来逐个分析函数。

 /*
      * @brief  获取设备状态
      * @param  pdrv:物理设备编号
      * @retval 0:设备ID读取成功 STA_NOINIT:设备ID读取失败
      */
    DSTATUS disk_status(BYTE pdrv)
    {
        DSTATUS status = STA_NOINIT;   // 设备状态   
    
        switch(pdrv)
        {
    	case SPI_FLASH:
    	    if(SPI_FLASH_ReadID() == sFLASH_ID)
    	    {
    	        status = 0;     // 设备ID读取成功
    	    }
    	    else
    	    {
    		status = STA_NOINIT;// 设备ID读取失败
    	    }
    	    break;
    
    	default:
    	    status = STA_NOINIT;
        }
    
        return status;
    } 

disk_status ()函数很简单,传入需要操作的物理设备的编号pdrv,在switch里根据编号的不同执行对应设备的代码。这里通过获取设备ID的方式返回设备状态。

/**
  * @brief  设备初始化
  * @param  pdrv:物理设备编号
  * @retval 0:设备初始化成功 STA_NOINIT:设备初始化失败
  */
DSTATUS disk_initialize(BYTE pdrv)
{
    DSTATUS status = STA_NOINIT;

    uint16_t i = 500;

    switch(pdrv)
    {
        case SPI_FLASH:
	    SPI_FLASH_Init();
	    while(i--);
	    SPI_Flash_WAKEUP(); // 唤醒SPI FLASH
	    status = disk_status(SPI_FLASH);
	    break;

	default: 
	    status = STA_NOINIT;
    }

    return status;
}

通过调用SPI_FLASH_Init()函数进行设备初始化,之后调用disk_status()函数获取设备ID,进而判断是否初始化完成。

/**
  * @brief  扇区读取
  * @param  
  *   @arg  pdrv:物理设备编号
  *   @arg  buff:数据缓冲区指针
  *   @arg  sector:扇区首地址
  *   @arg  count:即将读取的扇区数量
  * @retval RES_OK:读取成功 RES_PARERR:无效值
  */
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
    DRESULT status = RES_PARERR;

    switch(pdrv)
    {
	case SPI_FLASH:
            sector += 512; // 偏离512个扇区
            SPI_FLASH_BufferRead(buff, sector<<12, count<<12); // 左移12位,即乘以2^12
            status = RES_OK;
	    break;

	default:
	    status = RES_PARERR;
	}

	return status;
}

这里用的flash芯片有8M字节,每个扇区4096字节,我们把前面2M字节空出用于其他,后面6M字节给FatFS使用。那么,需要将所有的地址都偏移2M,即512个扇区的空间。

/**
  * @brief  扇区读取
  * @param  
  *   @arg  pdrv:物理设备编号
  *   @arg  buff:待写入数据的缓冲区指针
  *   @arg  sector:扇区首地址
  *   @arg  count:写入的扇区数量
  * @retval RES_OK:写入成功 RES_PARERR:无效值
  */
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
    DRESULT status = RES_PARERR;
    uint32_t write_addr;

    switch(pdrv)
    {
	case SPI_FLASH:
            sector += 512;
	    write_addr = sector<<12;
	    SPI_FLASH_SectorErase(write_addr);                 //写入前先擦除扇区
	    SPI_FLASH_BufferWrite((u8 *)buff, sector<<12, count<<12);
	    status = RES_OK;
	    break;

	default:
	    status = RES_PARERR;
    }

    return status;
}

和读取函数类似,需要偏移512扇区再进行擦除扇区及写入。

/**
  * @brief  其他控制
  * @param  
  *   @arg  pdrv:物理设备编号
  *   @arg  cmd:控制指令
  *   @arg  buff:写入或读取的数据地址指针
  * @retval RES_OK:操作成功 RES_PARERR:无效值
  */
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{
    DRESULT status = RES_PARERR;

    switch (pdrv) 
    {
	case SPI_FLASH:
	    switch(cmd)
            {
	        /* 扇区数量:6MB*1024*1024/4096=1536 */
		case GET_SECTOR_COUNT:
		    *(DWORD *)buff = 1536;
		    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;
}

ffconf.h
这个文件主要是FatFS的功能配置的宏定义,下面几个宏是需要修改的。

#define _USE_MKFS   1                         // 格式化选择 
#define _CODE_PAGE  936                       // 语言选择
#define _USE_LFN    2                         // 长文件名支持 
#define _VOLUMES    2                         // 物理设备数量 
#define _MIN_SS     512                       // 指定扇区的最小值 
#define _MAX_SS     4096                      // 指定扇区的最大值

相关的宏定义配置在每个宏之后都有做注释,可以阅读源文件。

移植后测试

移植至此,基本上就完成了,可以执行编译,不会有错误了。接下来可以尝试对其进行测试。在main.c文件里测试。
需要先格式化设备并申请文件信息存储区,然后挂载FatFS文件系统,最后分别进行读写测试。

FATFS fs;// FATFS文件系统结构体对象
FIL fnew;// FIL文件结构体对象
FRESULT res_flash;// 文件操作结果结构体对象
UINT fnum;// 文件读写数量
BYTE ReadBuffer[1024] = {0}; // 读缓冲区
BYTE WriteBuffer[] = "stm32专栏-FatFS测试专用"; 
// 写缓冲区

挂载文件系统
挂载之前需要先对设备进行格式化操作,用到了f_mount()和f_mkfs()两个函数。

res_flash = f_mount(&fs, "1:", 1);// 挂载时会对SPI设备初始化
if(res_flash == FR_NO_FILESYSTEM)                      // 判断是否有文件系统,没有则格式化并创建
{
    printf("没有检测到文件系统...开始进行格式化...\r\n");
    res_flash = f_mkfs("1:", 0, 0);                    // 格式化
    if(res_flash == FR_OK)                             // 格式化成功
    {
        printf("已成功格式化\r\n");
		res_flash = f_mount(NULL, "1:", 1);// 格式化后先取消挂载
        res_flash = f_mount(&fs, "1:", 1);             // 重新挂载
    }
    else
    {
        printf("格式化失败\r\n");
		while(1);
    }
}
else if(res_flash != FR_OK)
{
    printf("FatFS文件系统挂载失败(%d)\r\n", res_flash);
    while(1);
}
else
{
    printf("FatFS文件系统挂载成功,可以开始读写测试。\r\n");
}

f_mount()和f_mkfs()函数都在ff.c文件里定义的,用于挂载系统及格式化。

写测试
需要用到f_open()、f_write()、f_close()函数。

/* 打开文件,每次都以新的形式打开,属性为可写 */	
res_flash = f_open(&fnew, "1:FatFS读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);
if(res_flash == FR_OK)
{
    printf("打开/创建 FatFS读写测试文件.txt 成功。\r\n\r\n");
    res_flash = f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum); // 将写缓冲区内容写入到文件内
    if(res_flash == FR_OK)
    {
        printf("写入的字节数据大小:%d\n", fnum);
	printf("写入的内容为:%s\r\n\r\n", WriteBuffer);
    }
    else
    {
        printf("写入失败(%d)\n", res_flash);
    }
}
else
{
printf("打开/创建文件失败!!!\r\n");
}
f_close(&fnew);                                        // 关闭文件

这三个函数也都在ff.c文件中,至于参数如何也都有注释,这里不再赘述。

读测试
读测试除了用到f_open()、f_close()函数,还用到了f_read()函数。

res_flash = f_open(&fnew, "1:FatFS读写测试文件.txt", FA_OPEN_EXISTING | FA_READ);
if(res_flash == FR_OK)
{
    printf("打开 FatFS读写测试文件.txt 成功。开始读取数据。\\r\n\r\n");
    res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
    if(res_flash == FR_OK)
    {
        printf("读到的字节数据大小:%d\n", fnum);
	printf("读到的内容为:%s\r\n\r\n\r\n", ReadBuffer);
    }
    else
    {
        printf("文件读取失败:(%d)", res_flash);
    }
}
else
{
    printf("打开文件失败!!!\r\n");
}

f_close(&fnew);

f_mount(NULL, "1:", 1);
printf("文件系统已被卸载");

读文件的测试和写文件大体相似,参考代码很容易读懂。至此,FatFS文件系统移植并测试完成了。

你可能感兴趣的:(软件代码,STM32笔记)