STM32Cube高效开发教程_高级篇.PDF

强烈建议阅读官方文档

在存储器容量不大,存储内容也不多的时候,我们通常就直接按照存储芯片的读写时序,进行读写操作,直接访问存储器的内存。

但是存储的数据量很大,或者需要分门别类的存储时,上面这种方式就很不方便了。这样就引入了文件系统,以文件的形式存储数据,就像用电脑,我们记录数据都是用的 word、txt、excel 等文件,十分方便管理。

FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。FatFs 模块是按照 ANSI C (C89) 编写的,它可以在 SD 卡、U 盘、Flash 芯片等存储介质上创建 FAT 或 exFAT 文件系统,它提供应用层接口函数,用户可以通过它直接对文件进行数据读写操作和管理。并且与磁盘 I/O 层完全分离。因此,它独立于平台。它可以集成到资源有限的小型微控制器中,如 8051、PIC、AVR、ARM、Z80、RX 等。(DOS/Windows 兼容 FAT/exFAT 文件系统、平台无关,容易移植)

image

  • 用户应用程序:调用FATFS的通用接口API函数进行文件系统的操作,比如f_open()、f_write()等
  • FATFS通用程序:与硬件无关的用于文件系统管理的一些API函数。比如文件系统的创建和挂载、目录操作等,这些操作需要调用底层的硬件访问操作
  • FATFS底层设备控制:FATFS与底层存储设备通信的一些函数,包括读写存储介质的函数disk_read()、disk_write()、获取时间戳get_fattime()等。比如调用f_write(),到这层就是调用disk_wtire()。FATFS移植主要就是自己实现这几个函数的功能

文件系统的一些基本概念

  1. 文件系统

FAT和exFAT都是微软定义的标准文件系统。FAT现在有FAT12、FAT16、FAT32。

FAT
FAT12过于古早、最大分区32MB
最大分区2G(有的4G)
FAT16
FAT32最大分区32G(有的2T、8T)
exFAT几乎无限制
  1. FAT卷

一个FAT文件系统称为一个逻辑卷(logic volume)​或者逻辑驱动器(logic driver)​,比如电脑上的C盘、D盘

每个FAT卷都包含如下的3或4个区域:

  • 保留区域:用于存储卷的配置数据
  • FAT区域:存储数据区的分配表
  • 根目录区域:FAT32卷上无
  • 数据区域:存储文件和目录的内容
  1. 扇区 sector

扇区是存储介质上读写数据的最小单元(物理层面上),一般扇区大小都是512byte​,FATFS支持512、1024、2048、4096byte等几种大小的扇区。每个扇区都有自己的一个扇区编号。

  1. 簇 cluster

一个卷的数据分区分为很多簇,一个簇包含一个或多个扇区,数据区以簇为单位进行管理。一个卷的FAT类型就是由其包含的簇的数量决定的。

  1. 数据存储形式

小端字节 little endian。字(word)数据也不一定是边界对齐的,所以尽量使用字节数组的形式进行读写

FATFS文件组成

先略过,一些底层的东西和移植的东西先不看。先看应用

FATFS的应用程序接口函数

在ff.h中定义。可以分为:卷管理和系统配置、文件和目录管理、目录访问、文件访问。强烈建议阅读官方文档

CubeMX 生成代码中某些函数的定义 与 FatFS官网上的原型定义 并不一样。这里以cubemx生成代码进行介绍

卷管理和系统配置

image

电脑上我们有好多分区(好多卷、好多盘),比如CDEF盘等,但是在嵌入式设备中,一个存储设备一般只有一个卷(盘),且卷的编号一般用字符串“0:" "1:" "2:"​这样表示的。所以一般不用f_fdisk()​这个函数

f_mkfs

用于在一个卷上创建文件系统,也就是格式化(清除所有数据,建立文件系统)。其函数原型如下:

image

path:卷号(逻辑驱动器号),比如”0:“或者”1:“这样。如果是空字符串,则表示默认卷

opt:要创建的文件系统类型,一般32G以下的,填FM_FAT;32G及其以上的填FM_FAT32。也有exFAT

au:簇的大小,单位是byte。

work:缓冲区的指针。

len:缓冲区work的大小。缓冲区越大,格式化越快。缓冲区大小必须是簇的一倍以上大小

f_mount

进行各种文件系统操作的第一步

FATFS要求每个驱动器都要有个文件系统对象作为工作区域。

image

//文件系统对象
FATFS fs;
FRESULT res=f mount(&fs,"0:",1); //立刻挂载文件系统
if(res==FR NO FILESYSTEM) //不存在文件系统,未格式化
{
	//工作缓冲区
	BYTE workBuffer[4096];
	res=f mkfs("0:",FM FAT,4096,workBuffer,4096); //簇大小=4096字节
	//格式化成功
	if(res ==FR OK)
	{
		//先卸载
		res=f mount(NULL"0:"1);
		//再次挂载文件系统
		res=f mount(&fs,"0:"1);
	}
}

f_getfree

返回一个卷上剩余的簇的个数

image

image

FATES *fs;
DWORD fre_clust;
FRESULT res =fgetfree("0:",&fre_clust,&fs);
if(res == FR OK)
{
	DWORD tot_sect = (fs->nfatent - 2) * fs->csize; //总的扇区个数
	DWORD fre_sect = fre_clust * fs->csize; //剩余的扇区个数 = 剩余簇个数*每个簇的扇区个数
	DWORD freespace = (fre_sect * fs->ssize) / 1024: //剩余空间大小,单位:KB
}

文件和目录管理

只记录几个项目上可能用到的

image

f_utime

改变一个文件或者目录的时间戳数据

image

// FILINFO结构体
typedef struct 
{
	FSIZE_t	fsize;			/* 文件大小,字节数 */
	WORD	fdate;			/* 最后修改日期 Modified date */
	WORD	ftime;			/* 最后修改时间 Modified time */
	BYTE	fattrib;		/* 文件属性 File attribute */
#if _USE_LFN != 0
	TCHAR	altname[13];			/* Alternative file name */
	TCHAR	fname[_MAX_LFN + 1];	/* Primary file name */
#else
	TCHAR	fname[13];		/* 文件名 */
#endif
} FILINFO;

image

image

FILINFO fno;
//日期2019-12-13
WORD date = (2019-1980)<<9;
WORD month = 12<<5;
fno.fdate = date | month | 13;
//时间 14:32:15
WORD time = 14<<11;
WORD minute = 32<<5;
WORD sec=15>>1; //除以2
fno.ftime =time | minute | sec、;
f_utime("readme.txt", &fno);//修改文件的时间戳

目录访问

image

f_readdir

读取目录的下个项(文件或目录)

image

dp:已经open的目录对象

fno:存储着读取到的项的信息1

连续调用f_readdir可以依次读取目录下的项,不包括"."和".."项​。当一个目录下所有项都被读取完之后,fno的成员变量fname1变成以NULL​结尾的空字符串

官方有读取某目录下所有项的例程2

f_findfirst、f_findnext

image

建议阅读官方文档

文件访问

image

f_write、f_read

image

相对一个文件进行操作,首先要f_open​。操作结束后一定要f_close​,否则对文件的修改可能不会保存到存储介质上,导致文件损坏

文件对象结构体FIL​里有个DWORD​类型成员变量fptr​,称为文件读写位置指针,用于表示文件内当前读写位置。文件open​后,该指针指向文件最前面。f_write​写入数据后,该指针会自动向后移动。

close后再open,指针回到最前面

fwriteO)和fread()是通用的数据读写函数,可以读写任何类型的数据,但不太适合于读写字符串数据。

f_puts、f_gets

这两个函数专门用于字符串的读写

字符串最后要有\n或者\r\n,取决于宏USE_STRFUNC的值

image

这两个函数,写入的时候不会把字符串的结束符'\0'​写入。读取操作持续进行,直到存储'\n'​、到达文件末尾、缓冲区已填充len - 1 个字符,自动添加字符串的结束符'\0'

文件内读写指针的移动

image

FTP断点续传的时候需要,先问服务器已经存了多少了,指针移动到相应的地方,继续发送。

f_sync

函数f_sync​用于在写入文件时,将缓存的数据保存到物理文件里。与f_close​类似,只是文件继续处于可以继续写入开状态。

就相当于我们写文档的时候按一下Ctrl+S​保存文件,这样如果突然电脑死机了,已经写入的东西不会丢失。f_close​相当于关闭文档时弹出的是否保存,我们点了保存。

Footnotes

  1. // FILINFO结构体
    typedef struct 
    {
    	FSIZE_t	fsize;			/* 文件大小,字节数 */
    	WORD	fdate;			/* 最后修改日期 Modified date */
    	WORD	ftime;			/* 最后修改时间 Modified time */
    	BYTE	fattrib;		/* 文件属性 File attribute */
    #if _USE_LFN != 0
    	TCHAR	altname[13];			/* Alternative file name */
    	TCHAR	fname[_MAX_LFN + 1];	/* Primary file name */
    #else
    	TCHAR	fname[13];		/* 文件名 */
    #endif
    } FILINFO;
    2
  2. /* Recursive scan of all items in the directory */
     
    FRESULT scan_files (
        char* path        /* Start node to be scanned (***also used as work area***) */
    )
    {
        FRESULT res;
        DIR dir;
        UINT i;
        static FILINFO fno;
     
     
        res = f_opendir(&dir, path);                       /* Open the directory */
        if (res == FR_OK) {
            for (;;) {
                res = f_readdir(&dir, &fno);                   /* Read a directory item */
                if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
                if (fno.fattrib & AM_DIR) {                    /* It is a directory */
                    i = strlen(path);
                    sprintf(&path[i], "/%s", fno.fname);
                    res = scan_files(path);                    /* Enter the directory */
                    if (res != FR_OK) break;
                    path[i] = 0;
                } else {                                       /* It is a file. */
                    printf("%s/%s\n", path, fno.fname);
                }
            }
            f_closedir(&dir);
        }
     
        return res;
    }
     
     
    int main (void)
    {
        FATFS fs;
        FRESULT res;
        char buff[256];
     
     
        res = f_mount(&fs, "", 1);
        if (res == FR_OK) {
            strcpy(buff, "/");
            res = scan_files(buff);
        }
     
        return res;
    }