打印

【连载】STM32开发指南--第四十五章 FATFS实验

[复制链接]
6016|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
正点原子|  楼主 | 2013-4-5 22:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子 于 2013-4-5 22:56 编辑

   第四十五章  FATFS实验
上一章,我们学习了SD卡的使用,不过仅仅是简单的实现读扇区而已,真正要好好应用SD卡,必须使用文件系统管理,本章,我们将使用FATFS来管理SD卡,实现SD卡文件的读写等基本功能。本章分为如下几个部分:
45.1 FATFS简介
45.2 硬件设计
45.3 软件设计
45.4 下载验证

45.1 FATFS简介     
FATFS是一个完全免费开源的FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准C 语言编写,所以具有良好的硬件平**立性,可以移植到8051、PIC、AVR、SH、Z80、H8、ARM 等系列单片机上而只需做简单的修改。它支持FATl2、FATl6 和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8 位单片机和16 位单片机做了优化。
FATFS的特点有:
l  Windows兼容的FAT文件系统(支持FAT12/FAT16/FAT32)
l  与平台无关,移植简单
l  代码量少、效率高
l  多种配置选项
²  支持多卷(物理驱动器或分区,最多10个卷)
²  多个ANSI/OEM代码页包括DBCS
²  支持长文件名、ANSI/OEM或Unicode
²  支持RTOS
²  支持多种扇区大小
²  只读、最小化的API和I/O缓冲区等
FATFS的这些特点,加上免费、开源的原则,使得FATFS应用非常广泛。FATFS模块的层次结构如图45.1.1所示:


45.1.1 FATFS层次结构图
最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT 协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write 和f_close等,就可以像在PC 上读/写文件那样简单。
中间层FATFS模块,实现了FAT 文件读/写协议。FATFS模块提供的是ff.c和ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
需要我们编写移植代码的是FATFS模块提供的底层接口,它包括存储媒介读/写接口(disk I/O)和供给文件创建修改时间的实时时钟。
FATFS的源码,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载到,目前最新版本为R0.09a。本章我们就使用最新版本的的FATFS作为介绍,下载最新版本的FATFS软件包,解压后可以得到两个文件夹:doc和src。doc里面主要是对FATFS的介绍,而src里面才是我们需要的源码。
其中,与平台无关的是:
ffconf.h                 FATFS模块配置文件
ff.h                       FATFS和应用模块公用的包含文件
ff.c                       FATFS模块
diskio.h                 FATFS和disk I/O模块公用的包含文件
interger.h               数据类型定义
option                   可选的外部功能(比如支持中文等)
与平台相关的代码(需要用户提供)是:
diskio.c                 FATFS和disk I/O模块接口层文件
FATFS模块在移植的时候,我们一般只需要修改2个文件,即ffconf.h和diskio.c。FATFS模块的所有配置项都是存放在ffconf.h里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
1)_FS_TINY。这个选项在R0.07版本中开始出现,之前的版本都是以独立的C文件出现(FATFS和Tiny FATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用FATFS,所以把这个选项定义为0即可。
2)_FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为0即可。
3)_USE_STRFUNC。这个用来设置是否支持字符串类操作,比如f_putc,f_puts等,本章我们需要用到,故设置这里为1。
4)_USE_MKFS。这个用来定时是否使能格式化,本章需要用到,所以设置这里为1。
5)_USE_FASTSEEK。这个用来使能快速定位,我们设置为1,使能快速定位。
6)_CODE_PAGE。这个用于设置语言类型,包括很多选项(见FATFS官网说明),我们这里设置为936,即简体中文(GBK码,需要c936.c文件支持,该文件在option文件夹)。
7)_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE支持),取值范围为0~3。0,表示不支持长文件名,1~3是支持长文件名,但是存储地方不一样,我们选择使用3,通过ff_memalloc函数来动态分配长文件名的存储区域。
8)_VOLUMES。用于设置FATFS支持的逻辑设备数目,我们设置为2,即支持2个设备。
9)_MAX_SS。扇区缓冲的最大值,一般设置为512。
其他配置项,我们这里就不一一介绍了,FATFS的说明文档里面有很详细的介绍,大家自己阅读即可。下面我们来讲讲FATFS的移植,FATFS的移植主要分为3步:
①     数据类型:在integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数
据类型,并根据编译器定义好数据类型。
②     配置:通过ffconf.h配置FATFS的相关功能,以满足你的需要。
③     函数编写:打开diskio.c,进行底层驱动编写,一般需要编写6 个接口函数,如
图45.1.2 所示:


45.1.2 diskio 需要实现的函数
       通过以上三步,我们即可完成对FATFS的移植。
       第一步,我们使用的是MDK3.80a编译器,器数据类型和integer.h里面定义的一致,所以此步,我们不需要做任何改动。
       第二步,关于ffconf.h里面的相关配置,我们在前面已经有介绍(之前介绍的9个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为FATFS模块完全与磁盘I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘I/O 模块并不是FATFS的一部分,并且必须由用户提供。这些函数一般有6个,在diskio.c里面。
首先是disk_initialize函数,该函数介绍如图45.1.3所示:

45.1.3 disk_initialize函数介绍
       第二个函数是disk_status函数,该函数介绍如图45.1.4所示:

图45.1.4 disk_status函数介绍
       第三个函数是disk_read函数,该函数介绍如图45.1.5所示:


45.1.5 disk_read函数介绍
       第四个函数是disk_write函数,该函数介绍如图45.1.6所示:


45.1.6 disk_write函数介绍
       第五个函数是disk_ioctl函数,该函数介绍如图45.1.7所示:


45.1.7 disk_ioctl函数介绍
       最后一个函数是get_fattime函数,该函数介绍如图45.1.8所示:


45.1.8 get_fattime函数介绍
       以上六个函数,我们将在软件设计部分一一实现。通过以上3个步骤,我们就完成了对FATFS的移植,就可以在我们的代码里面使用FATFS了。
       FATFS提供了很多API函数,这些函数FATFS的自带介绍文件里面都有详细的介绍(包括参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续API的使用,关于FATFS的介绍,我们就介绍到这里。大家可以通过FATFS自带的介绍文件进一步了解和熟悉FATFS的使用。
45.2 硬件设计  
本章实验功能简介:开机的时候先初始化SD卡,初始化成功之后,注册两个工作区(一个给SD卡用,一个给SPI FLASH用),然后获取SD卡的容量和剩余空间,并显示在LCD模块上,最后等待USMART输入指令进行各项测试。本实验通过DS0指示程序运行状态。
本实验用到的硬件资源有:
1)  指示灯DS0  
2)  串口
3)  TFTLCD模块
4)  SD卡
5)  SPI FLASH
      这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。
45.3 软件设计
本章,我们将FATFS部分单独做一个分组,在工程目录下新建一个FATFS的文件夹,然后将FATFS R0.09a程序包解压到该文件夹下。同时,我们在FATFS文件夹里面新建一个exfuns的文件夹,用于存放我们针对FATFS做的一些扩展代码。设计完如图45.3.1所示:


45.3.1 FATFS文件夹子目录
然后,打开上一章的工程,新建一个FATFS分组,然后将图45.3.1的src文件夹里面的ff.h、diskio.h以及option文件夹下的cc936.c等3个文件加入到FATFS组下,并将src文件夹加入头文件包含路径。
    打开diskio.c,修改代码如下:
#include "mmc_sd.h"
#include "diskio.h"
#include "flash.h"
#include "malloc.h"                              
#define SD_CARD 0  //SD,卷标为0
#define EX_FLASH 1   //外部flash,卷标为1
#define FLASH_SECTOR_SIZE 512                  
//对于W25Q64
//6M字节给fatfs,6M字节后~6M+500K给用户用,6M+500K以后,用于存放字库,
//字库占用1.5M.  
u16      FLASH_SECTOR_COUNT=2048*6;   //6M字节,默认为W25Q64
#define FLASH_BLOCK_SIZE  8                //每个BLOCK8个扇区
//初始化磁盘
DSTATUS disk_initialize (
       BYTE drv                           /* Physical drive nmuber (0..) */
)
{     
       u8 res=0;     
       switch(drv)
       {
              case SD_CARD://SD
                     res = SD_Initialize();//SD_Initialize()
                    if(res)//STM32 SPIbug,sd卡操作失败的时候如果不执行下面的语句,
{    //可能导致SPI读写异常
                            SD_SPI_SpeedLow();
                            SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
                            SD_SPI_SpeedHigh();
                     }
                    break;
              case EX_FLASH://外部flash
                     SPI_Flash_Init();
if(SPI_FLASH_TYPE==W25Q64)FLASH_SECTOR_COUNT=2048*6;
                     else FLASH_SECTOR_COUNT=2048*2;                                       //其他
                    break;
              default:
                     res=1;
       }            
       if(res)return  STA_NOINIT;
       else return 0; //初始化成功
}   
//获得磁盘状态
DSTATUS disk_status (
       BYTE drv             /* Physical drive nmuber (0..) */
)
{               
    return 0;
}

实验40 FATFS实验.rar (962.63 KB)

《STM32开发指南》第四十五章 FATFS实验.rar (1.24 MB)



沙发
正点原子|  楼主 | 2013-4-5 22:55 | 只看该作者
本帖最后由 正点原子 于 2013-4-5 22:58 编辑

//读扇区
//drv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
DRESULT disk_read (
       BYTE drv,             /* Physical drive nmuber (0..) */
       BYTE *buff,         /* Data buffer to store read data */
       DWORD sector,     /* Sector address (LBA) */
       BYTE count          /* Number of sectors to read (1..255) */
)
{
       u8 res=0;
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误                    
       switch(drv)
       {
              case SD_CARD://SD
                     res=SD_ReadDisk(buff,sector,count);   
                    if(res)     //STM32 SPIbug,sd卡操作失败的时候如果不执行下面的语句,
{            //可能导致SPI读写异常
                            SD_SPI_SpeedLow();
                            SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
                            SD_SPI_SpeedHigh();
                     }
                     break;
              case EX_FLASH://外部flash
                     for(;count>0;count--)
                     {
                     SPI_Flash_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;         
}  
//写扇区
//drv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数      
#if _READONLY == 0
DRESULT disk_write (
       BYTE drv,                    /* Physical drive nmuber (0..) */
       const BYTE *buff,         /* Data to be written */
       DWORD sector,            /* Sector address (LBA) */
       BYTE count                 /* Number of sectors to write (1..255) */
)
{
       u8 res=0;  
    if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误                    
       switch(drv)
       {
              case SD_CARD://SD
                     res=SD_WriteDisk((u8*)buff,sector,count);
                     break;
              case EX_FLASH://外部flash
                     for(;count>0;count--)
                     {
SPI_Flash_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 /* _READONLY */
//其他表参数的获得
//drv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
DRESULT disk_ioctl (
       BYTE drv,             /* Physical drive nmuber (0..) */
       BYTE ctrl,            /* Control code */
       void *buff             /* Buffer to send/receive control data */
)
{     
       DRESULT res;                                                                  
       if(drv==SD_CARD)//SD
       {
           switch(ctrl)
           {
                  case CTRL_SYNC:
                            SD_CS=0;
                      if(SD_WaitReady()==0)res = RES_OK;
                      else res = RES_ERROR;         
                            SD_CS=1;
                      break;     
                  case GET_SECTOR_SIZE:
                      *(WORD*)buff = 512; res = RES_OK;
                      break;     
                  case GET_BLOCK_SIZE:
                      *(WORD*)buff = 8; res = RES_OK;
                      break;     
                  case GET_SECTOR_COUNT:
                      *(DWORD*)buff = SD_GetSectorCount();res = RES_OK;
                      break;
                  default:
                      res = RES_PARERR;
                      break;
           }
       }else if(drv==EX_FLASH)   //外部FLASH  
       {
           switch(ctrl)
           {
                  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;
}   
//获得时间
//User defined function to give a current time to fatfs module      */
//31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */                                                                                                                                                                                                                                          
//15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */                                                                                                                                                                                                                                                
DWORD get_fattime (void)
{                          
       return 0;
}                  
//动态分配内存
void *ff_memalloc (UINT size)                  
{
       return (void*)mymalloc(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)         
{
       myfree(SRAMIN,mf);
}

该函数实现了我们45.1节提到的6个函数,同时因为在ffconf.h里面设置对长文件名的支持为方法3,所以必须实现ff_memalloc和ff_memfree这两个函数。本章,我们用FATFS管理了2个磁盘:SD卡和SPI FLASH。SD卡比较好说,但是SPI FLASH,因为其扇区是4K字节大小,我们为了方便设计,强制将其扇区定义为512字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次数大增,所以不要随便往SPI FLASH里面写数据,非必要最好别写,如果频繁写的话,很容易将SPI FLASH写坏。
保存diskio.c,然后打开ffconf.h,修改相关配置,并保存,此部分就不贴代码了,请大家参考光盘源码。
前面提到,我们在FATFS文件夹下还新建了一个exfuns的文件夹,该文件夹用于保存一些FATFS一些针对FATFS的扩展代码,本章,我们编写了4个文件,分别是:exfuns.c、exfuns.h、fattester.c和fattester.h。其中exfuns.c主要定义了一些全局变量,方便FATFS的使用,同时实现了磁盘容量获取等函数。而fattester.c文件则主要是为了测试FATFS用,因为FATFS的很多函数无法直接通过USMART调用,所以我们在fattester.c里面对这些函数进行了一次再封装,使得可以通过USMART调用。这几个文件的代码,我们就不贴出来了,请大家参考光盘源码,我们将exfuns.c和fattester.c加入FATFS组下,同时将exfuns文件夹加入头文件包含路径。
然后,我们打开test.c,修改main函数如下:
int main(void)
{            
      u32 total,free;
       u8 t=0;                    
      Stm32_Clock_Init(9);    //系统时钟设置
       delay_init(72);                     //延时初始化
       uart_init(72,9600);      //串口1初始化      
      exfuns_init();                //fatfs相关变量申请内存                     
       LCD_Init();                  //初始化液晶
       LED_Init();            //LED初始化
       usmart_dev.init(72);      
      mem_init(SRAMIN);     //初始化内部内存池      
      POINT_COLOR=RED;//设置字体为红色      
       LCD_ShowString(60,50,200,16,16,"WarShip STM32");   
       LCD_ShowString(60,70,200,16,16,"FATFS TEST");  
       LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
       LCD_ShowString(60,110,200,16,16,"Use USMART for test");   
       LCD_ShowString(60,130,200,16,16,"2012/9/18");         
       while(SD_Initialize())                                //检测SD
       {
              LCD_ShowString(60,150,200,16,16,"SD Card Error!");
              delay_ms(200);
              LCD_Fill(60,150,240,150+16,WHITE);//清除显示                    
              delay_ms(200);
              LED0=!LED0;//DS0闪烁
       }                                                           
      exfuns_init();                                            //fatfs相关变量申请内存                     
      f_mount(0,fs[0]);                                    //挂载SD
      f_mount(1,fs[1]);                                  //挂载FLASH.        
       while(exf_getfree("0",&total,&free))   //得到SD卡的总容量和剩余容量
       {
              LCD_ShowString(60,150,200,16,16,"Fatfs Error!");
              delay_ms(200);
              LCD_Fill(60,150,240,150+16,WHITE);//清除显示                    
              delay_ms(200);
              LED0=!LED0;//DS0闪烁
       }                                                                                         
      POINT_COLOR=BLUE;//设置字体为蓝色   
       LCD_ShowString(60,150,200,16,16,"FATFS OK!");  
       LCD_ShowString(60,170,200,16,16,"SD Total Size:     MB");      
       LCD_ShowString(60,190,200,16,16,"SD  Free Size:     MB");      
      LCD_ShowNum(172,170,total>>10,5,16);          //显示SD卡总容量 MB
      LCD_ShowNum(172,190,free>>10,5,16);          //显示SD卡剩余容量 MB                     
       while(1)
       {
              t++;
              delay_ms(200);                                   
              LED0=!LED0;
       }
}
在main函数里面,我们为SD卡和FLASH都注册了工作区(挂载),在初始化SD卡并显示其容量信息后,进入死循环,等待USMART测试。
最后,我们在usmart_config.c里面的usmart_nametab数组添加如下内容:
(void*)mf_mount,"u8 mf_mount(u8 drv)",
(void*)mf_open,"u8 mf_open(u8*path,u8 mode)",
(void*)mf_close,"u8 mf_close(void)",
(void*)mf_read,"u8 mf_read(u16 len)",
(void*)mf_write,"u8 mf_write(u8*dat,u16 len)",
(void*)mf_opendir,"u8 mf_opendir(u8* path)",
(void*)mf_readdir,"u8 mf_readdir(void)",
(void*)mf_scan_files,"u8 mf_scan_files(u8 * path)",
(void*)mf_showfree,"u32 mf_showfree(u8 *drv)",
(void*)mf_lseek,"u8 mf_lseek(u32 offset)",
(void*)mf_tell,"u32 mf_tell(void)",
(void*)mf_size,"u32 mf_size(void)",
(void*)mf_mkdir,"u8 mf_mkdir(u8*pname)",
(void*)mf_fmkfs,"u8 mf_fmkfs(u8 drv,u8 mode,u16 au)",
(void*)mf_unlink,"u8 mf_unlink(u8 *pname)",
(void*)mf_rename,"u8 mf_rename(u8 *oldname,u8* newname)",
(void*)mf_gets,"void mf_gets(u16 size)",
(void*)mf_putc,"u8 mf_putc(u8 c)",
(void*)mf_puts,"u8 mf_puts(u8*c)",
这些函数均是在fattester.c里面实现,通过调用这些函数,即可实现对FATFS对应API函数的测试。 至此,软件设计部分就结束了。
45.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,可以看到LCD显示如图45.4.1所示的内容(默认SD卡已经接上了):


45.4.1 程序运行效果图
打开串口调试助手,我们就可以串口调用前面添加的各种FATFS测试函数了,比如我们输入mf_scan_files("0:"),即可扫描SD卡根目录的所有文件,如图45.4.2所示:


45.4.2 扫描SD卡根目录所有文件
        其他函数的测试,用类似的办法即可实现。注意这里0代表SD卡,1代表SPI FLASH。另外,提醒大家,mf_unlink函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。

使用特权

评论回复
板凳
longfenghugui| | 2014-8-21 15:25 | 只看该作者

使用特权

评论回复
地板
FAQ| | 2015-5-9 13:54 | 只看该作者

使用特权

评论回复
5
zhaoxiaolong1| | 2019-4-25 14:46 | 只看该作者
特别好

使用特权

评论回复
6
characteristic| | 2019-4-26 14:13 | 只看该作者
感谢分享!这是干货啊

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝