Stm32文件系统FATFS(开始于2021-09-09)

Stm32文件系统FATFS

参考资料主要是原子和野火两家的讲解。

1.FATFS简介:

适合嵌入式小型单片机,是一个 独立 的软件层文件系统,我们只需要将底层硬件的读取函数移植到FATFS提供的向下的接口(Media Access Interface),完成之后,就可以像电脑一样使用文件的操作函数(FATFS提供的向上的供我们使用的API函数 (Application Interface) )。

FAFTS中的函数参数介绍中的,IN表示该参数是传入数值;OUT表示,该参数是介质用于存放需要传出数据的载体。

1)初始化磁盘:

  • 磁盘分区,将一整块(或多块)物理磁盘划分为多个逻辑上的磁盘(C盘/D盘……),或者是(0/1……)。

  • 初始化时在物理磁盘的内存上,最开始的空白区域,会建立一些信息结构(目录,查找转换代码*(把” ……/ …… / ……“的逻辑地址转换成物理地址(第几扇区第几个环道上……地址))* )。

2)系统结构:

  • 文件目录:

    主要存储文件的各种信息,包括地址,名字,大小,位置等等。

    Stm32文件系统FATFS(开始于2021-09-09)_第1张图片
  • 文件分配表:

    • 文件分配表中存储的是每个文件的各个部分的位置(目录只记录了开始位置)

    • 文件查找时,从目录寻找其初始地址,然后没读完一个块(或者一个部分单位)后,都要回文件分配表寻找它下一部分的地址,读完再回文件分配表查找;直到查询到读完整个文件为止。

    • 在文件较大时,分成多部分存放(因为文件分布不一定连续)时,文件分配表有极大作用。

2.FATFS文件系统

1)组要组成:(需要添加的文件)

  • integer.h: 数值类型的宏定义

  • ffconf.h: FATFS 模块配置文件( 可根据我们需要改变宏,进行对应的裁剪 )

  • ff.c : FATFS 模块的核心文件,我们不需要改动;

  • ff.h :FATFS 和应用模块公用的包含文件( 存放着函数的声明,移植成功后当我们使用后,只需要将其include即可

  • diskio.c : FATFS 和 disk I/O 模块接口层文件(就是 我们移植时重点要修改的东西 ,我们需要将底层的操作函数提供给它的接口)

  • cc936.c:主要存放了Unicode(国标码)与GBK相互转换的数组,用于支持中文。

    Stm32文件系统FATFS(开始于2021-09-09)_第2张图片

2)需要我们提供的底层接口:

要实现的函数有如下图:(但可根据自己所需要的东西选择性实现,但以下五个是一定要的,其中所谓的可选择,是指disk_ioctl函数中部分命令可不实现)

Stm32文件系统FATFS(开始于2021-09-09)_第3张图片

主要是在diskio.c中的五个函数的实现:

  • disk_status :

    • 主要是用于查看底层硬件的状态函数,可用读取芯片ID的函数来检测底层的硬件是否正常;

    • 该函数的返回值是DSTATES类(宏定义的底层状态 0x00 正常; 0x01 STA_NOINT; 0x02 STA_NODISK; 0x04 STA_PROTECT)。

    Stm32文件系统FATFS(开始于2021-09-09)_第4张图片
  • disk_initialize :

    • 主要是使用对应底层的初始化函数;对应的API函数是 f_mount() 函数。

    • 该函数需要返回值是各种定义好的宏,如果对应的初始化函数没有返回值的话,可以采取读取ID芯片来校验是否初始化成功(经常这么干的,像使用mpu6050时就是)。( 也可以直接调用上面的disk_status函数来检测 )

    • 注意:如果底层介质含有低功耗省电模式的话,我们需要同时在这儿函数中加入唤醒函数。

    Stm32文件系统FATFS(开始于2021-09-09)_第5张图片
  • disk_read :

    • 主要是对底层介质的读取多个扇区函数,注意 要提供好对应它的参数的接口函数 ,返回值是RES类(也是宏定义好的类型 0x00 RES_OK ; 0x01 RES_ERROE; …… ),对应的API函数是 f_read() 函数。
    • 移植时,主要是提供底层介质的读取函数,注意其所需参数,因为上层对介质的读写操作最小单位是扇区,所以对底层介质的读取时注意读取单位的转换。
    Stm32文件系统FATFS(开始于2021-09-09)_第6张图片
  • disk_write

    • disk_write()的移植与disk_read() 一致,注意移植时的对应的参数即可。

    • 需要注意一点: 在ffconf.h的只读宏 _FS_READONLY 应设置为0, 即关闭只读

    Stm32文件系统FATFS(开始于2021-09-09)_第7张图片
  • disk_ioctl :

    • 这个函数主要用于实现FATFS中的一些其他的功能。(注意移植时函数参数的类型,必要时进行强制转换)

    • 通过不同的命令参数(宏定义)实现不同的函数功能:([参考上图](#### 2)需要我们提供的底层接口:))

      需要注意:其中GET_BOLCK_SIZE:该函数使用改命令是告诉上层函数能够擦除的最小的最小块大小(单位是扇区,多少扇区)。

  • get_fattime 获取实时(文件修改)时间所用的,我们可以不需要提供。

3.ffconf.h的配置

  • _VOLUMES : 用于设置 FATFS 支持的逻辑设备数目.

  • _MAX_SS:扇区缓冲的最大值,一般设置为 512(SD卡中) ,FLASH (常用4096);

    注意:_MAX_SS的大小,要与diskio_ioctl(GET_SECTOR_SIZE)中单次读取扇区大小一致,若不一致,极其容易导致内存溢出,HARDFAULT.

  • _USE_MKFS:这个用来定时是否使能格式化,设置为 1,允许格式化。

  • _FS_MINIMISE :对文件系统裁剪的宏.

  • _FS_TINY :是否使用微系统。

  • _FS_READONLY :是否只读.

  • _USE_STRFUNC :是否使能格式化操作。

  • _USE_FASTSEEK :是否使能快速定位功能。

  • _USE_LABEL : 置是否支持磁盘盘符(磁盘名字)读取与设置。

  • _CODE_PAGE :设置哪种语言类型,936GBK简体中文,932日文等。

4.FATFS系统API的调用

1)FATFS系统的对象(定义的结构体)

(对于结构体的成员细节,简单看看就好)

  • FATFS文件系统对象:

    typedef struct {
    	BYTE	fs_type;		/* FAT sub-type (0:Not mounted) 文件类型*/
    	BYTE	drv;			/* Physical drive number 即是宏定义的盘符0-9*/
    	BYTE	csize;			/* Sectors per cluster (1,2,4...128) 扇区/簇*/
    	BYTE	n_fats;			/* Number of FAT copies (1 or 2) */
    	BYTE	wflag;			/* win[] flag (b0:dirty) */
    	BYTE	fsi_flag;		/* FSINFO flags (b7:disabled, b0:dirty) */
    	WORD	id;				/* File system mount ID */
    	WORD	n_rootdir;		/* Number of root directory entries (FAT12/16) */
    #if _MAX_SS != _MIN_SS
    	WORD	ssize;			/* Bytes per sector (512, 1024, 2048 or 4096) */
    #endif
    #if _FS_REENTRANT
    	_SYNC_t	sobj;			/* Identifier of sync object */
    #endif
    #if !_FS_READONLY
    	DWORD	last_clust;		/* Last allocated cluster */
    	DWORD	free_clust;		/* Number of free clusters */
    #endif
    #if _FS_RPATH
    	DWORD	cdir;			/* Current directory start cluster (0:root) */
    #endif
    	DWORD	n_fatent;		/* Number of FAT entries, = number of clusters + 2 */
    	DWORD	fsize;			/* Sectors per FAT */
    	DWORD	volbase;		/* Volume start sector */
    	DWORD	fatbase;		/* FAT start sector */
    	DWORD	dirbase;		/* Root directory start sector (FAT32:Cluster#) */
    	DWORD	database;		/* Data start sector */
    	DWORD	winsect;		/* Current sector appearing in the win[] */
    	BYTE	win[_MAX_SS];	/* Disk access window for Directory, FAT (and file data at tiny cfg) */
    } FATFS;
    

关于该结构体比较多,也比价复杂;需要注意的是其中最后一项定义了一个缓存数组 BYTE win[_MAX_SS] ,如果是512 ( MAX_SS )字节一个扇区的话,这个数组非常大。

若定义为本地变量的话,极其容易导致栈溢出;所以通常定义为全局变量(该结构体是物理磁盘的文件结构的结构体,通常一个物理磁盘只需一个即可),原子的exfuns.c 中使用的是动太内存分配的方式。

  • FRESULT 多个函数的返回类型(ff.h定义的一个枚举变量,实际是u8的枚举)我们常使用来判断API函数是否成功运行并实现预期效果,该枚举如下如下:

    typedef enum {
    	FR_OK = 0,				/* (0) Succeeded */
    	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer */
    	FR_INT_ERR,				/* (2) Assertion failed */
    	FR_NOT_READY,			/* (3) The physical drive cannot work */
    	FR_NO_FILE,				/* (4) Could not find the file */
    	FR_NO_PATH,				/* (5) Could not find the path */
    	FR_INVALID_NAME,		/* (6) The path name format is invalid */
    	FR_DENIED,				/* (7) Access denied due to prohibited access or directory full */
    	FR_EXIST,				/* (8) Access denied due to prohibited access */
    	FR_INVALID_OBJECT,		/* (9) The file/directory object is invalid */
    	FR_WRITE_PROTECTED,		/* (10) The physical drive is write protected */
    	FR_INVALID_DRIVE,		/* (11) The logical drive number is invalid */
    	FR_NOT_ENABLED,			/* (12) The volume has no work area */
    	FR_NO_FILESYSTEM,		/* (13) There is no valid FAT volume */
    	FR_MKFS_ABORTED,		/* (14) The f_mkfs() aborted due to any parameter error */
    	FR_TIMEOUT,				/* (15) Could not get a grant to access the volume within defined period */
    	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy */
    	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated */
    	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > _FS_SHARE */
    	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
    } FRESULT;
    
  • FIL 文件对象:(同样,建议为全局或者是动态内存分配)

    typedef struct {
    	FATFS*	fs;				/* Pointer to the related file system object (**do not change order**) */
    	WORD	id;				/* Owner file system mount ID (**do not change order**) */
    	BYTE	flag;			/* Status flags */
    	BYTE	err;			/* Abort flag (error code) */
    	DWORD	fptr;			/* File read/write pointer (Zeroed on file open) */
    	DWORD	fsize;			/* File size */
    	DWORD	sclust;			/* File start cluster (0:no cluster chain, always 0 when fsize is 0) */
    	DWORD	clust;			/* Current cluster of fpter (not valid when fprt is 0) */
    	DWORD	dsect;			/* Sector number appearing in buf[] (0:invalid) */
    #if !_FS_READONLY
    	DWORD	dir_sect;		/* Sector number containing the directory entry */
    	BYTE*	dir_ptr;		/* Pointer to the directory entry in the win[] */
    #endif
    #if _USE_FASTSEEK
    	DWORD*	cltbl;			/* Pointer to the cluster link map table (Nulled on file open) */
    #endif
    #if _FS_LOCK
    	UINT	lockid;			/* File lock ID origin from 1 (index of file semaphore table Files[]) */
    #endif
    #if !_FS_TINY
    	BYTE	buf[_MAX_SS];	/* File private data read/write window */
    #endif
    } FIL;
    
  • FILINFO 文件信息对象(与文件对象不同,是另外定义的一个结构体)

    同样,建议全局或者是动态内存分配。

    typedef struct {
    	DWORD	fsize;			/* File size */
    	WORD	fdate;			/* Last modified date */
    	WORD	ftime;			/* Last modified time */
    	BYTE	fattrib;		/* Attribute属性(被打开文件的权限,类型等) */
    	TCHAR	fname[13];		/* Short file name (8.3 format) */
    #if _USE_LFN
    	TCHAR*	lfname;			/* Pointer to the LFN buffer */
    	UINT 	lfsize;			/* Size of LFN buffer in TCHAR */
    #endif
    } FILINFO;
    

2)API函数使用:

  • 磁盘驱动器初始化(磁盘挂载函数):初始化0盘,1盘等等,取决于path(自己决定,不过常与ff内定义的驱动器的宏一致,0-9).

    FRESULT f_mount(FATFS*  fs /*(文件结构指针,全局定义一个即可)*/,	
                    const TCHAR * /*path(字符串类型,表示其路径,如”0:“)*/,   
                    BYTE  opt  /*(选项,0/1,通常是1表示立即挂载,0表示稍后挂载。)*/  
                     ) 
    
  • 格式化磁盘,在物理磁盘上建立FAFTS文件系统的架构(目录、文件分配表等)

    FRESULT f_mkfs (const TCHAR* path,	/* 逻辑驱动器的路径 如:”0:“ */
    				BYTE sfd,/* 通常为0,初始化磁盘类型 0:FDISK, 1:SFD */
      				UINT au/* 格式化的每个扇区大小,0的话,系统会自动分配 */
                   )
    

    注意:格式化后需要重新挂载磁盘,所以需要执行两步:

    1️⃣ 取消挂载 ​f_monut​(​NULL,“0:”,1),即将空设备挂到我们的逻辑磁盘上(相当于清空逻辑磁盘)2️⃣ 重新挂载设备​ f_mount( &fs ,"0: " ,1).

  • 打开文件函数:与c语言相同,注意第三个参数,表示以什么方式(权限)打开。

    当我们需要多种权限时,可以将第三个参数以多个可选参数相| (与)的形式写入

    FRESULT f_open (
    FIL* fp,/* Pointer to the blank file object 文件对象指针,全局定义一个就好 */
    const TCHAR* path,	/* Pointer to the file name,路径 */
    BYTE mode/* Access mode and file open mode flags 权限(打开方式)*/ 
    )
    
  • 写文件函数

    FRESULT f_write (
    	FIL* fp,			/* Pointer to the file object */
    	const void *buff,	/* Pointer to the data to be written */
    	UINT btw,			/* Number of bytes to write */
    	UINT* bw			/* Pointer to number of bytes written 写数据的索引,用于查看是否写到文件结束*/
    )
    
  • 读文件函数

    FRESULT f_read (
    	FIL* fp, 		/* Pointer to the file object */
    	void* buff,		/* Pointer to data buffer【OUT】用于存放输出的字符串 */
    	UINT btr,		/* Number of bytes to read 读取字节数*/
    	UINT* br		/* Pointer to number of bytes read 【OUT】读了几个字节,可用于查看是否读到文件结束(br
    )
    
  • 光标重定位函数(常在读写函数后使用,因为读写时光标会移动)

    FRESULT f_lseek (
    	FIL* fp,		/* Pointer to the file object操作的文件对象 */
    	DWORD ofs		/* File pointer from top of file重定位到的位置(离文件开头多远) */
    )
    
  • 文件关闭函数

    (注意:f_open使用后一定要f_close,因为在有些系统中,只有在f_close时才将文件缓冲区的数据写到物理磁盘中)

    FRESULT f_close (FIL *fp/* Pointer to the file object to be closed */)
    
  • 获取文件大小的函数

    FSIZE_t f_size (FIL* fp   /* [IN] File object文件对象,返回其大小(字节数) */);
    
  • 指定格式写入文件函数(与printf函数相同,转义字符也相同,注意第一个参数为写入文件对象)

    int f_printf (
    	FIL* fp,			/* Pointer to the file object */
    	const TCHAR* fmt,	/* Pointer to the format string */
    	...					/* Optional arguments... */
    )
    
  • 删测文件(文件夹)函数

    FRESULT f_unlink (	const TCHAR* path/* Pointer to the file or directory path */)
    
  • 有关文件夹的打开,创建,关闭函数(与上面操作文件没有什么不同,注意参数类型,为dir类(宏定义的结构体))

    FRESULT f_opendir (
      DIR* dp,           /* [OUT] Pointer to the directory object structure */
      const TCHAR* path  /* [IN] Directory name */
    );
    
    FRESULT f_mkdir (
      const TCHAR* path /* [IN] Directory name */
    );
    
    FRESULT f_closedir (
      DIR* dp     /* [IN] Pointer to the directory object */
    );
    
  • 值得注意的是读取文件夹的函数 (注意:参数类型FILFINO)

    注意它每一次调用会返回该文件夹中下一个文件的FILFINO

    FRESULT f_readdir (
      DIR* dp,      /* [IN] Directory object */
      FILINFO* fno  /* [OUT] File information structure */
    );
    

    可以使用该函数实现对一个文件夹扫描所有文件的函数:(可以参考官网的递归函数进行扫描的方法)[参考](D:\typora\local notebook\stm32\stm32正点原子图片解码使用简介.md)

5.支持长(中文)文件名

  • 1️⃣ 将cc936.c文件加入FATFS文件分组中(加入工程)

  • 2️⃣ 将文件ffconf.h 中的三个宏:

    #define	_USE_LFN	3//(改为非0,使用长文件名,1/2/3是储存的地方不同)
    					//1.BSS全局静态变量区,2.Stack,
    					//3.heap,但需要我们提供动态内存分配的接口: ff_memalloc()  ff_memfree(),
    #define	_MAX_LFN	255//文件名允许的最长文件名(_MAX_LFN + 1) * 2个 bytes
    #define _CODE_PAGE	936	//采用中文GBK编码(936代表中文编码)
    

    但cc936.c中有两个大的转换数组,将会使代码烧录变慢,可以使用像原子一样,将cc936.c写在磁盘中读出。

你可能感兴趣的:(stm32自学笔记(欢迎指正),stm32)