1
完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-4-13 12:14 编辑
1)实验平台:正点原子STM32mini开发板 2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第三十四章 FATFS 实验 上一章,我们学习了 SD 卡的使用,不过仅仅是简单的实现读扇区而已,真正要好好应用SD 卡,必须使用文件系统管理,本章,我们将使用 FATFS 来管理 SD 卡,实现 SD 卡文件的读写等基本功能。本章分为如下几个部分: 34.1 FATFS 简介 34.2 硬件设计 34.3 软件设计 34.4 下载验证 34.1 FATFS 简介 FATFS 是一个完全免费开源的 FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言编写,所以具有良好的硬件平***立性,可以移植到 8051、PIC、AVR、SH、Z80、H8、ARM 等系列单片机上而只需做简单的修改。它支持 FATl2、FATl6 和 FAT32,支持 多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。 FATFS 的特点有: ⚫ Windows 兼容的 FAT 文件系统(支持 FAT12/FAT16/FAT32) ⚫ 与平台无关,移植简单 ⚫ 代码量少、效率高 ⚫ 多种配置选项 支持多卷(物理驱动器或分区,最多 10 个卷) 多个 ANSI/OEM 代码页包括 DBCS 支持长文件名、ANSI/OEM 或 Unicode 支持 RTOS 支持多种扇区大小 只读、最小化的 API 和 I/O 缓冲区等 FATFS 的这些特点,加上免费、开源的原则,使得 FATFS 应用非常广泛。FATFS 模块的层次结构如图 34.1.1 所示: 图 34.1.1 FATFS 层次结构图 最顶层是应用层,使用者无需理会 FATFS 的内部结构和复杂的 FAT 协议,只需要调用FATFS 模块提供给用户的一系列应用接口函数,如 f_open,f_read,f_write 和 f_close 等,就可 以像在 PC 上读/写文件那样简单。 中间层 FATFS 模块,实现了 FAT 文件读/写协议。FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。 需要我们编写移植代码的是 FATFS 模块提供的底层接口,它包括存储媒介读/写接口(diskI/O)和供给文件创建修改时间的实时时钟。 FATFS 的源码,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载到,目前最新版本为 R0.10a。本章我们就使用最新版本的 FATFS 作为介绍,下载最新版本的 FATFS 软件包,解压后可以得到两个文件夹:doc 和 src。doc 里面主要是对 FATFS 的介绍,而 src 里面才是我们需要的源码。 其中,与平台无关的是: ffconf.h FATFS 模块配置文件ff.h FATFS 和应用模块公用的包含文件 ff.cFATFS 模块 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)_USE_LABEL。这个用来设置是否支持磁盘盘符(磁盘名字)读取与设置。我们设置为 1,使能,就可以通过相关函数读取或者设置磁盘的名字了。 7)_CODE_PAGE。这个用于设置语言类型,包括很多选项(见 FATFS 官网说明),我们 这里设置为 936,即简体中文(GBK 码,需要 c936.c 文件支持,该文件在 option 文件夹)。 8)_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE 支持),取值范围为 0~3。0,表示不支持长文件名,1~3 是支持长文件名,但是存储地方不一样,我们选择使 用 3,通过 ff_memalloc 函数来动态分配长文件名的存储区域。 9)_VOLUMES。用于设置 FATFS 支持的逻辑设备数目,我们设置为 2,即支持 2 个设备。 10)_MAX_SS。扇区缓冲的最大值,一般设置为 512。 其他配置项,我们这里就不一一介绍了,FATFS 的说明文档里面有很详细的介绍,大家自己阅读即可。下面我们来讲讲 FATFS 的移植,FATFS 的移植主要分为 3 步: ① 数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。 ② 配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。 ③ 函数编写:打开 diskio.c,进行底层驱动编写,一般需要编写 6 个接口函数,如 图 34.1.2 所示: 图 34.1.2 diskio 需要实现的函数 通过以上三步,我们即可完成对 FATFS 的移植。 第一步,我们使用的是 MDK3.80a 编译器,器数据类型和 integer.h 里面定义的一致,所以此步,我们不需要做任何改动。 第二步,关于 ffconf.h 里面的相关配置,我们在前面已经有介绍(之前介绍的 10 个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。 第三步,因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。 这些函数一般有 6 个,在 diskio.c 里面。 首先是 disk_initialize 函数,该函数介绍如图 34.1.3 所示: 图 34.1.3 disk_initialize 函数介绍 第二个函数是 disk_status 函数,该函数介绍如图 34.1.4 所示: 图 34.1.4 disk_status 函数介绍 第三个函数是 disk_read 函数,该函数介绍如图 34.1.5 所示: 图 34.1.5 disk_read 函数介绍 第四个函数是 disk_write 函数,该函数介绍如图 34.1.6 所示: 图 34.1.6 disk_write 函数介绍 第五个函数是 disk_ioctl 函数,该函数介绍如图 34.1.7 所示: 图 34.1.7 disk_ioctl 函数介绍 最后一个函数是 get_fattime 函数,该函数介绍如图 34.1.8 所示: 图 34.1.8 get_fattime 函数介绍 以上六个函数,我们将在软件设计部分一一实现。通过以上 3 个步骤,我们就完成了对FATFS 的移植,就可以在我们的代码里面使用 FATFS 了。 FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍(包括参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount 函数注册一个工作区,才能开始后续 API 的使用,关于 FATFS 的介绍,我们就介绍到这里。大家可以通过 FATFS 自带的介绍文件进一步了解和熟悉 FATFS 的使用。 34.2 硬件设计 本章实验功能简介:开机的时候先初始化 SD 卡,初始化成功之后,注册两个工作区(一个给 SD 卡用,一个给 SPI FLASH 用),然后获取 SD 卡的容量和剩余空间,并显示在 LCD模块上,最后等待 USMART 输入指令进行各项测试。本实验通过 DS0 指示程序运行状态。 本实验用到的硬件资源有: 1) 指示灯 DS0 2) 串口 3) TFTLCD 模块 4) SD 卡 5) SPI FLASH 这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。 34.3 软件设计 本章,我们将 FATFS 部分单独做一个分组,在工程目录下新建一个 FATFS 的文件夹,然后将 FATFS R0.10a 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建一个 exfuns的文件夹,用于存放我们针对 FATFS 做的一些扩展代码。设计完如图 34.3.1 所示: 图 34.3.1 FATFS 文件夹子目录 然后打开我们实验工程可以看到,我们新建了 FATFS 分组,将必要的源文件添加到了FATFS 分组之下。打开 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 //前 4.8M 字节给 fatfs 用,4.8M 字节后~4.8M+100K 给用户用,4.9M 以后,用于存放字库u16 FLASH_SECTOR_COUNT= 9832; //4.8M 字节,默认为 W25Q64 #define FLASH_BLOCK_SIZE 8 //每个 BLOCK 有 8 个扇区 //初始化磁盘 DSTATUS disk_initialize (BYTE pdrv) { u8 res=0; switch(pdrv) { case SD_CARD://SD 卡 res = SD_Initialize();//SD_Initialize() if(res)//sd 卡操作失败的时候如果不执行下面的语句,可能导致 SPI 读写异常 { SD_SPI_SpeedLow(); SD_SPI_ReadWriteByte(0xff);//提供额外的 8 个时钟 SD_SPI_SpeedHigh(); } break; case EX_FLASH://外部 flash W25Q64 SPI_Flash_Init(); if(SPI_FLASH_TYPE==W25Q64)FLASH_SECTOR_COUNT=9832; else FLASH_SECTOR_COUNT=0; break; default: res=1; } if(res)return STA_NOINIT; else return 0; //初始化成功 } //获得磁盘状态 DSTATUS disk_status (BYTE pdrv) { return 0; } //读扇区 //drv:磁盘编号 0~9 //*buff:数据接收缓冲首地址 //sector:扇区地址 //count:需要读取的扇区数 DRESULT disk_read ( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to read (1..128) */ ) { u8 res=0; if (!count)return RES_PARERR;//count 不能等于 0,否则返回参数错误 switch(pdrv) { case SD_CARD://SD 卡 res=SD_ReadDisk(buff,sector,count); if(res)//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 _USE_WRITE DRESULT disk_write ( BYTE pdrv, /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to write (1..128) */ ) { u8 res=0; if (!count)return RES_PARERR;//count 不能等于 0,否则返回参数错误 switch(pdrv) { 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 //其他表参数的获得 //drv:磁盘编号 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: 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(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 //获得时间 //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(size); } //释放内存 void ff_memfree (void* mf) { myfree(mf); } 该部分代码实现了我们 34.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 文件夹加入头文件包含路径。然后,我们打开 main.c,修改 main 函数如下: int main(void) { u32 total,free; u8 t=0; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化按键 LCD_Init(); //初始化 LCD mem_init(); //初始化内存池 POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"Mini STM32"); LCD_ShowString(30,70,200,16,16,"FATFS TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2019/11/18"); LCD_ShowString(30,130,200,16,16,"Use USMART for test"); while(SD_Init())//检测不到 SD 卡 { LCD_ShowString(30,150,200,16,16,"SD Card Error!"); delay_ms(500); LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500); LED0=!LED0;//DS0 闪烁 } exfuns_init(); //为 fatfs 相关变量申请内存 f_mount(fs[0],"0:",1); //挂载 SD 卡 res=f_mount(fs[1],"1:",1); //挂载 FLASH. if(res==0X0D)//FLASH 磁盘,FAT 文件系统错误,重新格式化 FLASH { LCD_ShowString(30,150,200,16,16,"Flash Disk Formatting...");//格式化 FLASH res=f_mkfs("1:",1,4096);//格式化 FLASH,1,盘符;1,不需要引导区,8 个扇区为 1 个簇 if(res==0) { f_setlabel((const TCHAR *)"1:ALIENTEK"); //设置 Flash 磁盘的名字为:ALIENTEK LCD_ShowString(30,150,200,16,16,"Flash Disk Format Finish");//格式化完成 }else LCD_ShowString(30,150,200,16,16,"Flash Disk Format Error ")//格式化失败 delay_ms(1000); } LCD_Fill(30,150,240,150+16,WHITE); //清除显示 while(exf_getfree("0:",&total,&free)) //得到 SD 卡的总容量和剩余容量 { LCD_ShowString(30,150,200,16,16,"SD Card Fatfs Error!"); delay_ms(200); LCD_Fill(30,150,240,150+16,WHITE); //清除显示 delay_ms(200); LED0=!LED0;//DS0 闪烁 } POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,150,200,16,16,"FATFS OK!"); LCD_ShowString(30,170,200,16,16,"SD Total Size: MB"); LCD_ShowString(30,190,200,16,16,"SD Free Size: MB"); LCD_ShowNum(30+8*14,170,total>>10,5,16); //显示 SD 卡总容量 MB LCD_ShowNum(30+8*14,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* path,u8 mt)", (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_closedir,"u8 mf_closedir(void)", (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* path,u8 mode,u16 au)", (void*)mf_unlink,"u8 mf_unlink(u8 *pname)", (void*)mf_rename,"u8 mf_rename(u8 *oldname,u8* newname)", (void*)mf_getlabel,"void mf_getlabel(u8 *path)", (void*)mf_setlabel,"void mf_setlabel(u8 *path)", (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函数的测试。 至此,软件设计部分就结束了。 34.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看到LCD 显示如图 34.4.1 所示的内容(默认 SD 卡已经接上了): 图 34.4.1 程序运行效果图 打开串口调试助手,我们就可以串口调用前面添加的各种 FATFS 测试函数了,比如我们输入 mf_scan_files("0:"),即可扫描 SD 卡根目录的所有文件,如图 34.4.2 所示: 图 34.4.2 扫描 SD 卡根目录所有文件 其他函数的测试,用类似的办法即可实现。注意这里 0 代表 SD 卡,1 代表 SPI FLASH(W25Q64)。另外,提醒大家,mf_unlink 函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。 |
|
相关推荐
|
|
2136 浏览 1 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1970 浏览 3 评论
4566 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
2116 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
2629 浏览 1 评论
小黑屋| 手机版| Archiver| 德赢Vwin官网 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 17:44 , Processed in 0.322125 second(s), Total 32, Slave 25 queries .
Powered by 德赢Vwin官网 网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
德赢Vwin官网 观察
版权所有 © 湖南华秋数字科技有限公司
德赢Vwin官网 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号