【STM32进阶笔记】FATFS文件系统(上)

07-21 1449阅读

  本专栏争取每周三更新直到更新完成,期待大家的订阅关注,欢迎互相学习交流。

  本文需要一些SD卡的前置知识,后续文章会介绍,这里先介绍一下FATFS文件系统。关于FATFS的文章分为上下两篇,上篇主要介绍什么是FAT文件系统以及FATFS的移植,下篇主要介绍FATFS的一些API函数。

【STM32进阶笔记】FATFS文件系统(上)

目录

  • 一、FATFS文件系统简介
    • 1.1 FATFS引入
    • 1.2 FATFS特点
    • 二、FATFS文件系统移植
      • 2.1 FATFS源码文件简介
      • 2.2 FATFS重要配置选项
      • 2.3 FATFS移植步骤
        • 2.3.1 disk_initialize函数
        • 2.3.2 disk_status函数
        • 2.3.3 disk_read函数
        • 2.3.4 disk_write函数
        • 2.3.5 disk_ioctl函数
        • 2.3.6 get_fattime函数

          一、FATFS文件系统简介

          1.1 FATFS引入

            通常我们买来一张全新的SD卡时,如果我们想把它通过读卡器插入到电脑使用,我们需要先对其格式化,这是因为Windows系统使用的是FAT文件系统,我们格式化SD卡,实际也就是在SD卡中建立一个FAT文件系统,这样我们的电脑才能识别它。

            FAT是一个专门为小型的嵌入式系统设计的,完全用标准C语言编写,完全免费开源的文件系统。FAT具有良好的硬件平台独立性,可以移植到C51、PIC、ARM等单片机上,而且只需要做一些简单的修改,使用起来非常方便。

          1.2 FATFS特点

            FATFS有以下特点

          • Windows 兼容的 FAT 文件系统
          • 与平台无关,移植简单
          • 代码量少、效率高
          • 支持多卷(物理驱动器或分区,最多 10 个卷)
          • 多个 ANSI/OEM 代码页包括 DBCS
          • 支持长文件名、ANSI/OEM 或 Unicode
          • 支持 RTOS
          • 支持多种扇区大小
          • 只读、最小化的 API 和 I/O 缓冲区等

              这里简单说明一下几个名词,仅供参考,大家可以自行搜索了解更多详细内容。

              上面提到的卷指的是是硬盘上的存储空间,一个硬盘包括好多卷,一卷也可以跨越许多磁盘。卷又分为很多种,比如启动卷、跨区卷、带区卷等。此外还有簇,指的是磁盘文件存储管理的最小单位。

              Unicode(Universal Multiple-Octet Coded Character Set)简称UCS,中文意思是统一码,或者叫万国码,可以简单理解为他就是一个数字映射表,每一个数字代表不同的字符或文字。

            二、FATFS文件系统移植

            2.1 FATFS源码文件简介

              下面我们介绍一下如何移植FATFS。首先我们需要下载到它的源码,有需要的友友可以私信获取。下载完成后打开其中的“src”文件夹,其中是我们需要的源码。我们在移植FATFS模块的时候,一般只需要修改ffconf.h 和 diskio.c这两个文件。

            【STM32进阶笔记】FATFS文件系统(上)

            • option文件夹是一些可选的外部C文件,包含了多语言支持需要的文件和转换函数,移植时我们通常会选择cc936.c文件,用来支持简体中文,它包含了简体中文GBK和Unicode互相转换的功能函数。

              【STM32进阶笔记】FATFS文件系统(上)

            • diskio.c文件是FATFS移植的最关键文件,它为文件系统提供了最底层的接口返回问函数。
            • diskio.h里面包含了FATFS用到的宏定义以及diskio.c文件内与底层硬件接口相关的函数声明。
            • 00history.txt介绍了当前FATFS的版本更新情况。
            • 00readme.txt介绍了文件夹中各个文件的功能。
            • integer.h文件中是一些数据类型定义。
            • ff.c文件时FATFS的核心,里面包含了FATFS的各个模块程序,时文件管理的实现方法,我们在移植时通常不需要对这个文件进行修改。
            • ffconf.h是FATFS的配置文件,其中包含了对FATFS功能配置的宏定义。我们通过修改这些宏定义就可会议实现对FATFS功能的裁剪,达到自己想要的效果。

              2.2 FATFS重要配置选项

                上面在介绍源码文件时提到,FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们这里再介绍一些重要的配置选项。

              • _FS_TINY该宏定义时设置使用的是标准模式还是微小模式,我们通常设置为0,使用标准模式。
              • _FS_READONLY该宏定义是选择是否使用只读模式,通常我们选择可读可写,设置为0。
              • _FS_MINIMIZE该宏定义是选择是否裁剪掉一些函数,通常选择不裁剪,设置为0。
              • _USE_STRFUNC该宏定义是设置是否支持字符串类操作,通常选择支持,设置为1。
              • _USE_MKFS该宏定义是设置是否使用格式化,我们选择使用,设置为1。
              • _USE_FASTSEEK该宏定义用来设定是否是能快速定位,通常选择使用,设置为1。
              • _USE_LABEL该宏定义用来设置是否支持磁盘盘符读取与设置,通常选择支持,设置为1。使能后我们就可以通过相关函数来读取或者设置磁盘的名字。
              • _CODE_PAGE该宏定义用来设置语言类型,通常我们选择简体中文,设置为936(也就是c936.c文件)。
              • _USE_LFN该宏定义用来设置是否支持长文件名,取值范围是0到3。0表示不支持长文件名,1~3表示支持长文件名,但是存储的地方不一样。通常设置为3,可以通过ff_memalloc函数来动态分配长文件名的存储区域。
              • _MAX_LFN该宏定义用来设置允许的最大文件名长度,我们设置为最大值255。
              • _LFN_UNICODE该宏定义用来设置是否使用FATFS的字符编码,通常设置为不使用,设置为0。
              • _VOLUMES该宏定义用来设置FATFS支持的逻辑涉笔数量。
              • _MAX_SS该宏定义用来设置山区缓冲的最大值,一般设置为512。

                2.3 FATFS移植步骤

                  想要将FATFS移植到STM32实际并不复杂,主要分为三步

                • 下载FATFS源码
                • 将源码添加到Keil工程
                • 编写一些底层的接口函数

                    通常最后一步我们需要编写六个底层的接口函数,下面我们来详细地介绍一下它们。

                  2.3.1 disk_initialize函数

                  • 函数功能:初始化磁盘。
                  • 函数原型:DSTATUS disk_initialize(BYTE pdrv);
                  • 输入参数:prdrv是要初始化的逻辑驱动器号,也就是盘符,取值范围是0~9。
                  • 返回值:返回一个盘符状态作为结果。
                  • 注意事项:应用程序不应该在FATFS活动时调用此函数,否则卷上的FAT结构可能会损坏。
                    //初始化磁盘
                    DSTATUS disk_initialize (
                    	BYTE pdrv				/* Physical drive nmuber to identify the drive */
                    )
                    {
                    	u8 res=0;	    
                    	switch(pdrv)
                    	{
                    		case SD_CARD://SD卡
                    			res=SD_Init();//SD卡初始化 
                      			break;
                    		case EX_FLASH://外部flash
                    			W25QXX_Init();
                    			FLASH_SECTOR_COUNT=2048*12;//W25Q1218,前12M字节给FATFS占用 
                     			break;
                    		default:
                    			res=1; 
                    	}		 
                    	if(res)return  STA_NOINIT;
                    	else return 0; //初始化成功 
                    } 
                    

                    2.3.2 disk_status函数

                    • 函数功能:返回当前磁盘驱动器的状态。
                    • 函数原型:DSTATUS disk_status (BYTE pdrv);
                    • 输入参数:pdrv是要确认的逻辑驱动器号,也就是盘符,取值范围是0~9。
                    • 返回值:有如下几个返回值
                      #define STA_NOINIT		0x01	/* Drive not initialized */
                      #define STA_NODISK		0x02	/* No medium in the drive */
                      #define STA_PROTECT		0x04	/* Write protected */
                      

                        FATFS只使用前两个返回值。

                      //获得磁盘状态
                      DSTATUS disk_status (
                      	BYTE pdrv		/* Physical drive nmuber to identify the drive */
                      )
                      { 
                      	return RES_OK;
                      } 
                      

                      2.3.3 disk_read函数

                      • 函数功能:从磁盘上读取扇区。
                      • 函数原型:DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
                      • 输入参数:pdrv:驱动器逻辑号;

                        buff:指向存储读取数据字节数组的指针;

                        sector:指定起始扇区的逻辑块上的地址;

                        count:指定要读取的扇区数;

                      • 返回值:返回值结构体如下
                        /* Results of Disk Functions */
                        typedef enum {
                        	RES_OK = 0,		/* 0: Successful */
                        	RES_ERROR,		/* 1: R/W Error */
                        	RES_WRPRT,		/* 2: Write Protected */
                        	RES_NOTRDY,		/* 3: Not Ready */
                        	RES_PARERR		/* 4: Invalid Parameter */
                        } DRESULT;
                        
                        //读扇区
                        //pdrv:磁盘编号0~9
                        //*buff:数据接收缓冲首地址
                        //sector:扇区地址
                        //count:需要读取的扇区数
                        DRESULT disk_read (
                        	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
                        	BYTE *buff,		/* Data buffer to store read data */
                        	DWORD sector,	/* Sector address in LBA */
                        	UINT count		/* Number of sectors to read */
                        )
                        {
                        	u8 res=0; 
                            if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误		 	 
                        	switch(pdrv)
                        	{
                        		case SD_CARD://SD卡
                        			res=SD_ReadDisk(buff,sector,count);	 
                        			while(res)//读出错
                        			{
                        				SD_Init();	//重新初始化SD卡
                        				res=SD_ReadDisk(buff,sector,count);	
                        				//printf("sd rd error:%d\r\n",res);
                        			}
                        			break;
                        		case EX_FLASH://外部flash
                        			for(;count>0;count--)
                        			{
                        				W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
                        				sector++;
                        				buff+=FLASH_SECTOR_SIZE;
                        			}
                        			res=0;
                        			break;
                        		default:
                        			res=1; 
                        	}
                           //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
                            if(res==0x00)return RES_OK;	 
                            else return RES_ERROR;	   
                        }
                        

                        2.3.4 disk_write函数

                        • 函数功能:向磁盘写入一个或多个扇区。
                        • 函数原型:DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
                        • 输入参数:pdrv:要操作的逻辑驱动器号;

                          buff:指向要写入的数组的指针;

                          sector:指定起始扇区逻辑块上的地址;

                          count:指定要写入的扇区数,取值范围是1~128;

                        • 返回值:与上面的读函数为同一个结构体。
                          //写扇区
                          //pdrv:磁盘编号0~9
                          //*buff:发送数据首地址
                          //sector:扇区地址
                          //count:需要写入的扇区数
                          #if _USE_WRITE
                          DRESULT disk_write (
                          	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
                          	const BYTE *buff,	/* Data to be written */
                          	DWORD sector,		/* Sector address in LBA */
                          	UINT count			/* Number of sectors to write */
                          )
                          {
                          	u8 res=0;  
                              if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误		 	 
                          	switch(pdrv)
                          	{
                          		case SD_CARD://SD卡
                          			res=SD_WriteDisk((u8*)buff,sector,count);
                          			while(res)//写出错
                          			{
                          				SD_Init();	//重新初始化SD卡
                          				res=SD_WriteDisk((u8*)buff,sector,count);	
                          				//printf("sd wr error:%d\r\n",res);
                          			}
                          			break;
                          		case EX_FLASH://外部flash
                          			for(;count>0;count--)
                          			{										    
                          				W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
                          				sector++;
                          				buff+=FLASH_SECTOR_SIZE;
                          			}
                          			res=0;
                          			break;
                          		default:
                          			res=1; 
                          	}
                              //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
                              if(res == 0x00)return RES_OK;	 
                              else return RES_ERROR;	
                          }
                          #endif
                          

                          2.3.5 disk_ioctl函数

                          • 函数功能:控制设备指定特性和一些除了读写外的杂项功能。
                          • 函数原型:DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
                          • 输入参数:pdrv:要操作的逻辑驱动器号;

                            cmd:命令代码;

                            buff:指向参数缓冲区的指针,取决于命令代码,不使用时指向一个NULL指针;

                          • 返回值:与上面的读/写函数为同一个结构体。
                          • 注意事项:

                              这里贴一下一些命令的宏定义

                            /* Command code for disk_ioctrl fucntion */
                            /* Generic command (Used by FatFs) */
                            #define CTRL_SYNC			0	/* Complete pending write process (needed at _FS_READONLY == 0) */
                            #define GET_SECTOR_COUNT	1	/* Get media size (needed at _USE_MKFS == 1) */
                            #define GET_SECTOR_SIZE		2	/* Get sector size (needed at _MAX_SS != _MIN_SS) */
                            #define GET_BLOCK_SIZE		3	/* Get erase block size (needed at _USE_MKFS == 1) */
                            #define CTRL_TRIM			4	/* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) */
                            /* Generic command (Not used by FatFs) */
                            #define CTRL_POWER			5	/* Get/Set power status */
                            #define CTRL_LOCK			6	/* Lock/Unlock media removal */
                            #define CTRL_EJECT			7	/* Eject media */
                            #define CTRL_FORMAT			8	/* Create physical format on the media */
                            /* MMC/SDC specific ioctl command */
                            #define MMC_GET_TYPE		10	/* Get card type */
                            #define MMC_GET_CSD			11	/* Get CSD */
                            #define MMC_GET_CID			12	/* Get CID */
                            #define MMC_GET_OCR			13	/* Get OCR */
                            #define MMC_GET_SDSTAT		14	/* Get SD status */
                            /* ATA/CF specific ioctl command */
                            #define ATA_GET_REV			20	/* Get F/W revision */
                            #define ATA_GET_MODEL		21	/* Get model name */
                            #define ATA_GET_SN			22	/* Get serial number */
                            
                            //其他表参数的获得
                            //pdrv:磁盘编号0~9
                            //ctrl:控制代码
                            //*buff:发送/接收缓冲区指针
                            #if _USE_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==SD_CARD)//SD卡
                            	{
                            	    switch(cmd)
                            	    {
                            		    case CTRL_SYNC:
                            				res = RES_OK; 
                            		        break;	 
                            		    case GET_SECTOR_SIZE:
                            				*(DWORD*)buff = 512; 
                            		        res = RES_OK;
                            		        break;	 
                            		    case GET_BLOCK_SIZE:
                            				*(WORD*)buff = SDCardInfo.CardBlockSize;
                            		        res = RES_OK;
                            		        break;	 
                            		    case GET_SECTOR_COUNT:
                            		        *(DWORD*)buff = SDCardInfo.CardCapacity/512;
                            		        res = RES_OK;
                            		        break;
                            		    default:
                            		        res = RES_PARERR;
                            		        break;
                            	    }
                            	}else if(pdrv==EX_FLASH)	//外部FLASH  
                            	{
                            	    switch(cmd)
                            	    {
                            		    case CTRL_SYNC:
                            				res = RES_OK; 
                            		        break;	 
                            		    case GET_SECTOR_SIZE:
                            		        *(WORD*)buff = FLASH_SECTOR_SIZE;
                            		        res = RES_OK;
                            		        break;	 
                            		    case GET_BLOCK_SIZE:
                            		        *(WORD*)buff = FLASH_BLOCK_SIZE;
                            		        res = RES_OK;
                            		        break;	 
                            		    case GET_SECTOR_COUNT:
                            		        *(DWORD*)buff = FLASH_SECTOR_COUNT;
                            		        res = RES_OK;
                            		        break;
                            		    default:
                            		        res = RES_PARERR;
                            		        break;
                            	    }
                            	}else res=RES_ERROR;//其他的不支持
                                return res;
                            }
                            #endif
                            

                            2.3.6 get_fattime函数

                            • 函数功能:获取当前时间。
                            • 返回值:返回以双字封装的当前时间,具体格式这里不再详细描述了。
                            • 注意事项:get_fattime函数必须返回一个合法的时间,如果系统不支持实时时钟,可以返回0。

                                通过上述步骤,我们就完成了对于FATFS的移植,FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续 API 的使用。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]