1)实验平台:正点原子水星 STM32F4/F7 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
http://weixin.qq.com/r/hEhUTLbEdesKrfIv9x2W (二维码自动识别)
第四十五章 FATFS 实验
上两章,我们学习了 SD 卡和 NAND FLASH 的使用,不过仅仅是简单的实现读写扇区而
已,真正要好好应用它们,必须使用文件系统管理,本章,我们将使用 FATFS 来管理 SD 卡(同
时也管理 NAND FLASH 和 SPI FLASH,不过仅以 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 和 exFAT
(R0.12 及以后版本),支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并
特别对 8 位单片机和 16 位单片机做了优化。
FATFS 的特点有:
⚫ Windows 兼容的 FAT 文件系统(支持 FAT12/FAT16/FAT32/exFAT)
⚫ 与平台无关,移植简单
⚫ 代码量少、效率高
⚫ 多种配置选项
支持多卷(物理驱动器或分区,最多 10 个卷)
多个 ANSI/OEM 代码页包括 DBCS
支持长文件名、ANSI/OEM 或 Unicode
支持 RTOS
支持多种扇区大小
只读、最小化的 API 和 I/O 缓冲区等
FATFS 的这些特点,加上免费、开源的原则,使得 FATFS 应用非常广泛。FATFS 模块的层
次结构如图 45.1.1 所示:
![1c056f32f305a477ed5a70e2e6ce7481.png](https://i-blog.csdnimg.cn/blog_migrate/7c1b4788306e7edd3f6d5503a2e5e8a4.png)
图 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.12a。本章我们就使用最新版本的 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)_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 支持的逻辑设备数目,我们设置为 3,即支持 3 个设备。
10)_MAX_SS。扇区缓冲的最大值,一般设置为 512。
11)_FS_EXFAT。用于定义是否支持 exFAT 文件系统,我们设置为 1,以支持 exFAT 文件系统。
其他配置项,我们这里就不一一介绍了,FATFS 的说明文档里面有很详细的介绍,大家自
己阅读即可。下面我们来讲讲 FATFS 的移植,FATFS 的移植主要分为 3 步:
① 数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数
据类型,并根据编译器定义好数据类型。
② 配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。
③ 函数编写:打开 diskio.c,进行底层驱动编写,一般需要编写 6 个接口函数,如
图 45.1.2 所示:
图 45.1.2 diskio 需要实现的函数
通过以下三步,我们即可完成对 FATFS 的移植。
第一步,我们使用的是 MDK5.26 编译器,器数据类型和 integer.h 里面定义的一致,所以
此步,我们不需要做任何改动。
第二步,关于 ffconf.h 里面的相关配置,我们在前面已经有介绍(之前介绍的 11 个配置),
我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁
盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。
这些函数一般有 6 个,在 diskio.c 里面。
首先是 disk_initialize 函数,该函数介绍如图 45.1.3 所示:
![5f21fb19bf3921df8f34e6ebf5c77204.png](https://i-blog.csdnimg.cn/blog_migrate/5615aeec9b4804950f977755241802d7.png)
图 45.1.3 disk_initialize 函数介绍
第二个函数是 disk_status 函数,该函数介绍如图 45.1.4 所示
![034355420e4a6e3306a16037a78eb037.png](https://i-blog.csdnimg.cn/blog_migrate/beeeea1e378992304cecd1e79a01c8e4.png)
![5e5371c0a9f1ca35efed065403c806be.png](https://i-blog.csdnimg.cn/blog_migrate/07e9f63d0a0853006e3e8e164eb36760.png)
图 45.1.4 disk_status 函数介绍
第三个函数是 disk_read 函数,该函数介绍如图 45.1.5 所示:
![cf2b573f9a064db3fb66d35b979e1d3b.png](https://i-blog.csdnimg.cn/blog_migrate/7683a70b06448f3d86e2b0a05b8de165.jpeg)
图 45.1.5 disk_read 函数介绍
第四个函数是 disk_write 函数,该函数介绍如图 45.1.6 所示:
![145d87de4f5d33e373ce376333d7f292.png](https://i-blog.csdnimg.cn/blog_migrate/118951ea1ad0637b114c56ffe778779c.jpeg)
图 45.1.6 disk_write 函数介绍
第五个函数是 disk_ioctl 函数,该函数介绍如图 45.1.7 所示:
![6bfb2ffea51d68c99fcab4c301c482e7.png](https://i-blog.csdnimg.cn/blog_migrate/ea0acdaddc93160fd2cc112003a2145d.png)
![2ca86b11cabceb87d6923836b9bd4139.png](https://i-blog.csdnimg.cn/blog_migrate/5a50281b7f26978519c47f9668f6a4c9.jpeg)
图 45.1.7 disk_ioctl 函数介绍
最后一个函数是 get_fattime 函数,该函数介绍如图 47.1.8 所示:
![3650eca55489326c67743e7bdb80459b.png](https://i-blog.csdnimg.cn/blog_migrate/3147c6428e714b74adaf3d93980abf53.png)
图 45.1.8 get_fattime 函数介绍
以上六个函数,我们将在软件设计部分一一实现。通过以上 3 个步骤,我们就完成了对
FATFS 的移植,就可以在我们的代码里面使用 FATFS 了。
FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍(包括
参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount
函数注册一个工作区,才能开始后续 API 的使用,关于 FATFS 的介绍,我们就介绍到这里。大
家可以通过 FATFS 自带的介绍文件进一步了解和熟悉 FATFS 的使用。
45.2 硬件设计
本章实验功能简介:开机的时候先初始化 SD 卡,初始化成功之后,注册三个工作区(一
个给 SD 卡用、一个给 SPI FLASH 用、一个给 NAND FLASH 用),然后获取 SD 卡的容量和
剩余空间,并显示在 LCD 模块上,最后等待 USMART 输入指令进行各项测试。本实验通过DS0 指示程序运行状态。
本实验用到的硬件资源有:
1) 指示灯 DS0
2) 串口
3) LCD 模块
4) SD 卡
5) SPI FLASH
6) NAND FLASH
这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。
45.3 软件设计
打开本章实验目录可以看到,我们在工程目录下新建了一个 FATFS 的文件夹,然后将
FATFS R0.12b 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建了一个 exfuns
的文件夹,用于存放我们针对 FATFS 做的一些扩展代码。设计完如图 45.3.1 所示:
![a9397758f266e36004f87a12185e0719.png](https://i-blog.csdnimg.cn/blog_migrate/6f727cd2a6fb5aeb5d474f9d83de5945.png)
图 45.3.1 FATFS 文件夹子目录
然后打开我们实验工程可以看到,我们新建了 FATFS 分组,将必要的源文件添加到了
FATFS 分组之下。打开 diskio.c,代码如下:
#define SD_CARD 0 //SD 卡,卷标为 0
#define EX_FLASH 1 //外部 spi flash,卷标为 1
#define EX_NAND 2 //外部 nand flash,卷标为 2
//对于 W25Q256
//前 25M 字节给 fatfs 用,25M 字节后存放字库,字库占用 6.01M.剩余部分,给客户自己用
#define FLASH_SECTOR_SIZE 512
#define FLASH_SECTOR_COUNT 1024*25*2 //W25Q1218,前 25M 字节给FATFS 占用
#define FLASH_BLOCK_SIZE
8 //每个 BLOCK 有 8 个扇区
//NAND FALSH 全部归 FATFS 管理
u32 NANDFLASH_SECTOR_COUNT;
u8 NANDFLASH_BLOCK_SIZE;
//初始化磁盘
DSTATUS disk_initialize (
BYTE pdrv
/* Physical drive nmuber (0..) */
)
{
u8 res=0;
switch(pdrv)
{
case SD_CARD: //SD 卡
res=SD_Init(); //SD 卡初始化
break;
case EX_FLASH: //外部 flash
W25QXX_Init(); //W25QXX 初始化
break;
case EX_NAND:
//外部 NAND
res=FTL_Init();
//NAND 初始化
break;
default:
res=1;
}
if(res)return STA_NOINIT;
else return 0; //初始化成功
}
//获得磁盘状态
DSTATUS disk_status (
BYTE pdrv
/* Physical drive nmuber (0..) */
)
{
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);
while(res)//读出错
{
SD_Init();
//重新初始化 SD 卡
res=SD_ReadDisk(buff,sector,count);
//printf("sd rd error:%drn",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;
case EX_NAND: //外部 NAND FLASH
res=FTL_ReadSectors(buff,sector,512,count);
//读取数据
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);
while(res)//写出错
{
SD_Init();
//重新初始化 SD 卡
res=SD_WriteDisk((u8*)buff,sector,count);
//printf("sd wr error:%drn",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;
case EX_NAND: //外部 NAND FLASH
res=FTL_WriteSectors((u8*)buff,sector,512,count);//写入数据
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:
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 if(pdrv==EX_NAND) //外部 NAND FLASH
{
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = 512; //NAND FLASH 扇区强制为 512 字节大小
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = nand_dev.page_mainsize/512;
//block 大小,定义成一个 page 的大小
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff =
nand_dev.valid_blocknum*nand_dev.block_pagenum*
nand_dev.page_mainsize/512;//NAND FLASH 的总扇区大小
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(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)
{
myfree(SRAMIN,mf);
}
该函数实现了我们 47.1 节提到的 6 个函数,同时因为在 ffconf.h 里面设置对长文件名的
支持为方法 3,所以必须实现 ff_memalloc 和 ff_memfree 这两个函数。本章,我们用 FATFS 管
理了 3 个磁盘:SD 卡、SPI FLASH 和 NAND FLASH。SD 卡和 NAND FLASH,其扇区大小一般固定
为 512 字节,而对 SPI FLASH,因为其物理扇区(擦除单位)是 4K 字节大小,我们为了方便设
计,强制将其扇区定义为 512 字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次
数大增,所以不要随便往 SPI FLASH 里面写数据,非必要最好别写,如果频繁写的话,很容易
将 SPI FLASH 写坏。
NAND FLASH 与文件系统的读写接口,就是采用上一章介绍的 FTL 层函数(FTL_ReadSectors
和 FTL_WriteSectors 函数)来实现的,有了 FTL 层,我们就可以像访问 SD 卡一样,访问 NAND
FLASH,而无需担心坏块和磨损均衡问题。
另外,diskio.c 里面的函数,直接决定了磁盘编号(盘符/卷标)所对应的具体设备,比
如,以上代码中,我们设置 SD_CARD 为 0,EX_FLASH 位为 1,EX_NAND 为 2,对应到 disk_read
/disk_write函数里面,我们就通过switch来判断,到底要操作SD卡、SPI FLASH或NAND FLASH,
然后,分别执行对应设备的相关操作。以此实现磁盘编号和磁盘的关联。
保存 diskio.c,然后打开 ffconf.h,修改相关配置,并保存,此部分就不贴代码了,请大
家参考本例程源码。另外,cc936.c 主要提供 UNICODE 到 GBK 以及 GBK 到 UNICODE 的码表转换,
里面就是两个大数组,并提供一个 ff_convert 的转换函数,供 UNICODE 和 GBK 码互换,这个在
中文长文件名支持的时候,必须用到!!
前面提到,我们在 FATFS 文件夹下还新建了一个 exfuns 的文件夹,该文件夹用于保存一些
FATFS 一些针对 FATFS 的扩展代码,本章,我们编写了 4 个文件,分别是:exfuns.c、exfuns.h、
fattester.c 和 fattester.h。其中 exfuns.c 主要定义了一些全局变量,方便 FATFS 的使用,
同时实现了磁盘容量获取等函数。而 fattester.c 文件则主要是为了测试 FATFS 用,因为 FATFS
的很多函数无法直接通过 USMART 调用,所以我们在 fattester.c 里面对这些函数进行了一次再
封装,使得可以通过 USMART 调用。这几个文件的代码,我们就不贴出来了,请大家参考本例程
源码。最后,我们打开 main.c, main 函数如下:
int main(void)
{
u32 total,free;
u8 t=0;
u8 res=0;
Cache_Enable();
//打开 L1-Cache
MPU_Memory_Protection(); //保护相关存储区域
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200);
//串口初始化
usmart_dev.init(108);
//初始化 USMART
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init();
//初始化 LCD
W25QXX_Init();
//初始化 W25Q256
my_mem_init(SRAMIN);
//初始化内部内存池
my_mem_init(SRAMEX);
//初始化外部内存池
my_mem_init(SRAMDTCM);
//初始化 CCM 内存池
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Apollo STM32F4/F7");
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,"2016/7/15");
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_Toggle;//DS0 闪烁
}
FTL_Init();
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);
}
res=f_mount(fs[2],"2:",1);
//挂载 NAND FLASH.
if(res==0X0D)//NAND FLASH 磁盘,FAT 文件系统错误,重新格式化 NAND FLASH
{
LCD_ShowString(30,150,200,16,16,"NAND Disk Formatting...");//格式化 NAND
res=f_mkfs("2:",1,4096);//格式化 FLASH,2,盘符;1,不需要引导区,8 个扇区为 1 个簇
if(res==0)
{
f_setlabel((const TCHAR *)"2:NANDDISK");
//设置 Flash 磁盘的名字为:NANDDISK
LCD_ShowString(30,150,200,16,16,"NAND Disk Format Finish");//格式化完成
}else LCD_ShowString(30,150,200,16,16,"NAND 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_Toggle;//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_Toggle;
}
}
在 main 函数里面,我们为 SD、SPI FLASH 和 NAND FLASH 都注册了工作区(挂载),在初始
化 SD 卡并显示其容量信息后,进入死循环,等待 USMART 测试。
最后,我们在 usmart_config.c 里面的 usmart_nametab 数组添加如下内容:
(void*)W25QXX_Erase_Chip,"void W25QXX_Erase_Chip(void)",
(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)",
(void*)NAND_EraseBlock,"u8 NAND_EraseBlock(u32 BlockNum)",
(void*)NAND_EraseChip,"void NAND_EraseChip(void)",
这些函数均是在 fattester.c 里面实现,通过调用这些函数,即可实现对 FATFS 对应 API
函数的测试。 至此,软件设计部分就结束了。
45.4 下载验证
在代码编译成功之后,我们通过下载代码到 ALIENTEK 水星 STM32 开发板上,可以看到
LCD 显示如图 45.4.1 所示的内容(假定 SD 卡已经插上了):
![298511b57c676114fbca9f042e0dac39.png](https://i-blog.csdnimg.cn/blog_migrate/84d3691a171dafb58f6effe00eab3d0c.png)
图 45.4.1 程序运行效果图
打开串口调试助手,我们就可以串口调用前面添加的各种 FATFS 测试函数了,比如我们输
入 mf_scan_files("0:"),即可扫描 SD 卡根目录的所有文件,如图 45.4.2 所示:
![fe69fcbc034b679ea6dbde9bae06b404.png](https://i-blog.csdnimg.cn/blog_migrate/0bb7d02c9e37c5c00d89ac95ec01bfeb.jpeg)
图 45.4.2 扫描 SD 卡根目录所有文件
其他函数的测试,用类似的办法即可实现。注意这里 0 代表 SD 卡,1 代表 SPI FLASH,
2 代表 NAND FLASH。另外,提醒大家,mf_unlink 函数,在删除文件夹的时候,必须保证文
件夹是空的,才可以正常删除,否则不能删除。