(1)实验平台:
普中STM32F103 朱雀、玄武开发板
https://item.taobao.com/item.htm?id=620302685024(2)资料下载 :普中科技-各型号产品资料下载链接
前面我们学习了 SD 卡、 SPI FLASH 的使用, 不过仅仅是简单的读写扇区操作, 要真正应用它们, 必须学习使用文件系统。 什么是文件系统呢, 操作系统用于明确磁盘或分区上的文件的方法和数据结构, 即在磁盘上组织文件的方法。 我们电脑的 windows 系统使用的就是 FAT 文件系统, 大家都知道, 一般我们新买的 SD 卡, 如果没有使用过, 插入电脑的时候都要先格式化, 是因为 SD 卡里面没有建立 FAT 文件系统, 所以格式化之后, 建立了 FAT 文件系统, 电脑才能识别 SD 卡上面的内存和文件。 本章我们就使用 FATFS 文件系统来管理 SD 卡, 本章要实现的功能是: 系统开启时, 先检测 SD 卡是否存在, 如果存在则使用 FATFS文件系统挂载 SD、 SPI-FLASH, 如果是第一次挂载则对其相应格式化操作, 并将对应设备磁盘重命名。 然后通过 FATFS 获取 SD 卡的容量等信息, 同时 DS0 指示灯闪烁, 提示系统正常运行。 学习本章可以参考"\8--STM32 相关资料\STM32深入学习资料\FAT 及 FATFS 资料" 内文档。 本章分为如下几部分内容:
[48.1 FATFS 文件系统介绍](#48.1 FATFS 文件系统介绍)
[48.2 FATFS 文件系统移植](#48.2 FATFS 文件系统移植)
[48.2.1 FATFS 源码文件介绍](#48.2.1 FATFS 源码文件介绍)
[48.2.2 FATFS 文件系统移植步骤](#48.2.2 FATFS 文件系统移植步骤)
[48.3 硬件设计](#48.3 硬件设计)
[48.4 软件设计](#48.4 软件设计)
[48.5 实验现象](#48.5 实验现象)
48.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 的内部结构和复杂的 FAT 协议, 只要会调用 FATFS 模块提供给用户的一系列应用接口函数(如 f_open, f_read, f_write 和 f_close 等) 就可以像在 PC 上读/ 写文件那样简单。 中间层是 FATFS 模块, 实现了 FAT 文件读/ 写协议。 FATFS 模块提供的是 ff.c 和 ff.h。 使用者一般不用修改, 使用时将头文件直接包含进去即可。 最底层是 FATFS 模块的底层接口, 包括括存储媒介读/ 写接口(diskI/O)和供给文件创建修改时间的实时时钟, 这些在移植时都需要我们编写对应的代码, 下面来看下如何将 FATFS 文件系统移植到我们开发板上。
48.2 FATFS 文件系统移植
48.2.1 FATFS 源码文件介绍
要移植 FATFS 文件系统, 首先需要下载 FATFS 文件系统的源码文件, 可以到http://elm-chan.org/fsw/ff/00index_e.html 网站上下载, 这里我们已经帮大家下载好了 FATFS 最新版本 R0.12 源码, 放在光盘"\8--STM32 相关资料\STM32深入学习资料\FAT 及 FATFS 资料\FatFs R0.12 源码" 中, 本章就用此源码进行移植。 将下载的源码压缩包解压后得到两个文件夹: doc 和 src, 如下图所示:

doc 文件夹内是一些使用帮助文档, src 文件夹内才是 FATFS 文件系统的源码。
我们先介绍下 doc 文件夹, 将其打开如下:

其中 en 和 ja 这两个文件夹里面是编译好的 html 文档, 讲的是 FATFS里面各个函数的使用方法, 这些函数都是封装得非常好的函数, 利用这些函数我们就可以操作 SD 卡或外部 FLASH 芯片。 这两个文件夹的唯一区别就是 en 文件夹下的文档是英文的, ja 文件夹下的是日文的。 img 文件夹包含 en 和 ja 文件夹下文件需要用到的图片, 还有四个名为 app.c 文件, 内容都是 FatFs 具体应用例程。 00index_e.html 和 00index_j.html 是一些关于 FATFS 的简介, 至于另外两个文件可以不看。
我们再来看下 src 文件夹, 将其打开如下:

option 文件夹下是一些可选的外部 c 文件, 包含了多语言支持需要用到的文件和转换函数。 将其打开如下:

通常移植时我们会使用 cc936.c 文件, 用来支持简体中文, 包含了简体中文的 GBK 和 Unicode 相互转换功能函数。
diskio.c文件是 FatFs 移植最关键的文件, 它为文件系统提供了最底层的接口访问函数, 这些函数在移植时都需要我们来编写。 diskio.h 定义了 FatFs用到的宏, 以及 diskio.c 文件内与底层硬件接口相关的函数声明。
00history.txt介绍了 FatFs 的版本更新情况。
00readme.txt 说明了当前目录下 diskio.c 、 diskio.h、 ff.c、 ff.h、integer.h 的功能。
integer.h文件中包含了一些数值类型定义。
ff.c是 FatFs 核心文件, 文件管理的实现方法。 该文件独立于底层介质操作文件的函数, 通常用户无需修改, 利用这些函数实现文件的读写。 ff.h 是它的头文件。
ffconf.h 这个头文件包含了对 FatFs 功能配置的宏定义, 通过修改这些宏定义就可以裁剪 FatFs 的功能, 已达到自己想要的标准。 下面我们看下此文件内的几个重要选项配置。
1) _FS_TINY
这个设置是配置是使用标准模式, 还是微小模式。 我们使用标准模式, 这里设置为: 0。
2) _FS_READONLY
是否使用只读模式, 我们选择可读可写。 设置为: 0。
3) _FS_MINIMIZE
这个设置是选择是否裁剪掉一些函数, 具体裁剪那些, 大家可以查看下面的设置说明, 我们这里使用的全功能模式。 设置为: 0。
4) _USE_STRFUNC
这个用来设置是否支持字符串类操作, 我们选择使用。 设置为: 1。
5) _USE_MKFS
使用使用格式化, 我们这里选择使用。 设置为: 1。
6) _USE_FASTSEEK
使用使能快速定位, 我们选择使用。 设置为: 1。
7) _USE_LABEL
是否启用磁盘卷标功能。 使用也可以, 不使用也可以。 我们这里使用。 设置为: 1。
8) _USE_FORWARD
这个是在 TINY 模式下的设置, 我们这里使用标准模式, 不理它。
9) _CODE_PAGE
这个用于设置语言类型。 我们使用的中文。 设置为: 936, 需要将 cc936.c文件添加到工程中。
10) _USE_LFN
该选项用于设置是否支持长文件名(还需要_CODE_PAGE 支持) , 取值范围为 0~3。 0, 表示不支持长文件名, 1~3 是支持长文件名, 但是存储地方不一样。我们选择使用 3, 通过 ff_memalloc 函数来动态分配长文件名的存储区域。
11) _MAX_LFN
设置最大文件名长度, 我们直接设置最大。 设置为: 255。
12) _LFN_UNICODE
是否使用 FATFS 的字符编码。 我们不使用。 设置为: 0
13) _FS_RPATH
这里也是一些函数裁剪, 我们这里设置为: 0。
14) _VOLUMES
这里设置的是支持的卷轴数量, 这里我们挂载 SD 卡, 有时候可以挂载下FLASH 和外部 USB, 所以这里我们设置为 3 个。
15) _MAX_SS
这里是设置扇区缓冲的最大值, 我们设置为: 512。
16) _MULTI_PARTITION
是否使用多重分区分配。 我们这里设置为: 0。
17) _FS_EXFAT
用于定义是否支持 exFAT 文件系统, 我们设置为 1, 以支持 exFAT 文件系统。
其他配置项, 我们这里就不一一介绍了, FATFS 的说明文档里面有很详细的介绍, 大家自己阅读即可。 下面我们来介绍下 FATFS 的移植。
48.2.2 FATFS 文件系统移植步骤
要将 FATFS 文件系统移植到 STM32F1 上, 我们看下需要经过哪些步骤。
(1) 首先我们复制上一章 SDIO-SD 卡实验程序, 将实验文件夹命名为"FATFS文件系统实验" 。 在此工程目录下新建一个 Fatfs 文件夹, 用于存放 FATFS 文件系统源码。 如下图:

把前面我们下载好的FATFS源码中的src文件夹内容全部复制到这个文件夹内。 并在此文件夹下再新建一个 fatfs_app 文件夹, 在其内新建 fatfs_app.c和 fatfs_app.h, 用于存放我们编写的扩展应用程序, 如下图:


(2) 使用 KEIL 将此工程打开, 新建一个"Fatfs" 工程组, 并将 diskio.c、ff.c、 cc936.c、 fatfs_app.c 添加到此工程组中, 并添加对应的头文件路径。如下图:

(3) 添加完成以后, 如果编译工程会有报错, 这个时候不要紧, 我们还需要修改 diskio.c 文件和 ffconf.h 文件。 根据前面的介绍我们知道 ffconf.h是用来裁剪 FATFS, 只需按照前面 ffconf.h 文件介绍配置即可。 我们重点看下diskio.c 文件, diskio.c 是最底层的接口函数。 一般需要编写 6 个接口函数,如图: (注意在 diskio.c 文件内要调用 SD、 EN25Q128 驱动的头文件, 参考本章实验代码)

①disk_initialize()函数

移植代码如下:
cpp
//初始化磁盘
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
EN25QXX_Init();
FLASH_SECTOR_COUNT=2048*12;//W25Q1218,前12M字节给FATFS占用
break;
default:
res=1;
}
if(res)return STA_NOINIT;
else return 0; //初始化成功
}
②disk_status()函数

移植代码如下:
cpp
//获得磁盘状态
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return RES_OK;
}
这里我们没有使用这个函数, 所以我们什么都不写, 直接让他返回即可。
③disk_read()函数

移植代码如下:
cpp
//读扇区
//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--)
{
EN25QXX_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;
}
因为 EN25Q128 扇区是 4K 字节大小, 我们为了方便设计, 强制将其扇区定义为 512 字节(在 diskio.c 文件开头的宏定义) , 这样带来的好处就是设计使用相对简单, 坏处就是擦除次数大增, 所以不要随便往外部 FLASH 里面写数据,最好别写, 如果频繁写的话, 很容易将 EN25Q128 写坏。 并且我们使用 EN25Q128的前 12MB 给 Fatfs 使用, 后面剩余的预留给字库存储。 该宏定义可在 diskio.c 文件开头查看到。 如下:
cpp
#define FLASH_SECTOR_SIZE 512
//对于W25Q128
//前12M字节给fatfs用,12M字节后,用于存放字库,字库占用3.09M. 剩余部分,给客户自己用
u16 FLASH_SECTOR_COUNT=2048*12; //W25Q1218,前12M字节给FATFS占用
#define FLASH_BLOCK_SIZE 8 //每个BLOCK有8个扇区
修改的内容非常少, 大家可以对比 FATFS 源码中的 diskio.c 文件。
④disk_write()函数

移植代码如下:
cpp
//写扇区
//pdrv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
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--)
{
EN25QXX_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;
}
⑤disk_ioctl()函数

移植代码如下:
cpp
//其他表参数的获得
//pdrv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
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;
}
⑥get_fattime()函数

这个函数没有直接写在 diskio.c 里面, 需要自己编写, 本章未使用到文件时间, 所以函数直接返回 0 即可, 如果需要显示文件时间需另外编程。 移植代码如下:
cpp
//获得时间
//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;
}
(4)在 ffconf.h 配置文件中, 我们将_USE_LFN 配置为 3, 即使用 ff_memalloc函数来动态分配长文件名的存储区域。 因此我们还需要编写 ff_memalloc 和ff_memfree 函数, 实际上也就是调用我们内存管理实验中的 mymalloc 和 myfree 函数, 代码如下:
cpp
//动态分配内存
void *ff_memalloc (UINT size)
{
return (void*)mymalloc(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)
{
myfree(SRAMIN,mf);
}
到这里, 我们就将 FATFS 移植到 STM32F1 上了, 至于移植过程中定义的一些宏, 这里不介绍, 大家可以打开实验工程查看。 现在就可以使用 FATFS 文件系统来管理 SD 卡、 外部 FLASH。
FATFS 提供了很多 API 函数供给我们使用, 主要函数如下:
f_mount - Register/Unregister a work area
f_open - Open/Create a file
f_close - Close a file
f_read - Read file
f_write - Write file
f_lseek - Move read/write pointer, Expand file size
f_truncate - Truncate file size
f_sync - Flush cached data
f_opendir - Open a directory
f_readdir - Read a directory item
f_getfree - Get free clusters
f_stat - Get file status
f_mkdir - Create a directory
f_unlink - Remove a file or directory
f_chmod - Change attribute
f_utime - Change timestamp
f_rename - Rename/Move a file or directory
f_chdir - Change current directory
f_chdrive - Change current drive
f_getcwd - Retrieve the current directory
f_getlabel - Get volume label
f_setlabel - Set volume label
f_forward - Forward file data to the stream directly
f_mkfs - Create a file system on the drive
f_fdisk - Divide a physical drive
f_gets - Read a string
f_putc - Write a character
f_puts - Write a string
f_printf - Write a formatted string
f_tell - Get the current read/write pointer
f_eof - Test for end-of-file on a file
f_size - Get size of a file
f_error - Test for an error on a file
这里我们就不一一介绍这些函数是怎么使用的, 大家可以查看 FATFS 源码中 doc 文件夹下的使用说明, 在使用说明中也有一些简单的例程, 可以帮助大家很好的理解这些函数。 这里需要注意的是, 在使用 FATFS 的时候, 必须先通过 f_mount 函数注册一个工作区, 才能开始后续 API 的使用。
关于 FATFS 的介绍, 我们就到这里。 大家可以通过 FATFS 自带的介绍文件进一步了解和熟悉 FATFS 的使用。
48.3 硬件设计
本实验使用到硬件资源如下:
(1) DS0 指示灯
(2) 串口 1
(3) TFTLCD 模块
(4) EN25Q128
(5) TF 卡
这些模块电路在前面章节都介绍过, 这里就不多说, 至于 FATFS, 它属于软件组织, 无需硬件电路。
48.4 软件设计
本章所要实现的功能是: 系统开启时, 先检测 SD 卡是否存在, 如果存在则使用 FATFS 文件系统挂载 SD、 SPI-FLASH, 如果是第一次挂载则对其相应格式化操作, 并将对应设备磁盘重命名。 然后通过 FATFS 获取 SD 卡的容量等信息, 同时 DS0 指示灯闪烁, 提示系统正常运行。
前面我们已经介绍了 FATFS 的移植, 所以直接可以看工程代码。 下面我们打开"\4--实验程序\1--基础实验\40-FATFS 文件系统实验" 工程。
cpp
//为exfuns申请内存
//返回值:0,成功
//1,失败
u8 FATFS_Init(void)
{
u8 i;
for(i=0;i<_VOLUMES;i++)
{
fs[i]=(FATFS*)mymalloc(SRAMIN,sizeof(FATFS)); //为磁盘i工作区申请内存
if(!fs[i])break;
}
file=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //为file申请内存
ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL)); //为ftemp申请内存
fatbuf=(u8*)mymalloc(SRAMIN,512); //为fatbuf申请内存
if(i==_VOLUMES&&file&&ftemp&&fatbuf)return 0; //申请有一个失败,即失败.
else return 1;
}
//将小写字母转为大写字母,如果是数字,则保持不变.
u8 char_upper(u8 c)
{
if(c<'A')return c;//数字,保持不变.
if(c>='a')return c-0x20;//变为大写.
else return c;//大写,保持不变
}
//报告文件的类型
//fname:文件名
//返回值:0XFF,表示无法识别的文件类型编号.
// 其他,高四位表示所属大类,低四位表示所属小类.
u8 f_typetell(u8 *fname)
{
u8 tbuf[5];
u8 *attr='\0';//后缀名
u8 i=0,j;
while(i<250)
{
i++;
if(*fname=='\0')break;//偏移到了最后了.
fname++;
}
if(i==250)return 0XFF;//错误的字符串.
for(i=0;i<5;i++)//得到后缀名
{
fname--;
if(*fname=='.')
{
fname++;
attr=fname;
break;
}
}
strcpy((char *)tbuf,(const char*)attr);//copy
for(i=0;i<4;i++)tbuf[i]=char_upper(tbuf[i]);//全部变为大写
for(i=0;i<FILE_MAX_TYPE_NUM;i++) //大类对比
{
for(j=0;j<FILE_MAX_SUBT_NUM;j++)//子类对比
{
if(*FILE_TYPE_TBL[i][j]==0)break;//此组已经没有可对比的成员了.
if(strcmp((const char *)FILE_TYPE_TBL[i][j],(const char *)tbuf)==0)//找到了
{
return (i<<4)|j;
}
}
}
return 0XFF;//没找到
}
//得到磁盘剩余容量
//drv:磁盘编号("0:"/"1:")
//total:总容量 (单位KB)
//free:剩余容量 (单位KB)
//返回值:0,正常.其他,错误代码
u8 fatfs_getfree(u8 *drv,u32 *total,u32 *free)
{
FATFS *fs1;
u8 res;
u32 fre_clust=0, fre_sect=0, tot_sect=0;
//得到磁盘信息及空闲簇数量
res =(u32)f_getfree((const TCHAR*)drv, (DWORD*)&fre_clust, &fs1);
if(res==0)
{
tot_sect=(fs1->n_fatent-2)*fs1->csize; //得到总扇区数
fre_sect=fre_clust*fs1->csize; //得到空闲扇区数
#if _MAX_SS!=512 //扇区大小不是512字节,则转换为512字节
tot_sect*=fs1->ssize/512;
fre_sect*=fs1->ssize/512;
#endif
*total=tot_sect>>1; //单位为KB
*free=fre_sect>>1; //单位为KB
}
return res;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//文件复制
//注意文件大小不要超过4GB.
//将psrc文件,copy到pdst.
//fcpymsg,函数指针,用于实现拷贝时的信息显示
// pname:文件/文件夹名
// pct:百分比
// mode:
// [0]:更新文件名
// [1]:更新百分比pct
// [2]:更新文件夹
// [3~7]:保留
//psrc,pdst:源文件和目标文件
//totsize:总大小(当totsize为0的时候,表示仅仅为单个文件拷贝)
//cpdsize:已复制了的大小.
//fwmode:文件写入模式
//0:不覆盖原有的文件
//1:覆盖原有的文件
//返回值:0,正常
// 其他,错误,0XFF,强制退出
u8 fatfs_copy(u8(*fcpymsg)(u8*pname,u8 pct,u8 mode),u8 *psrc,u8 *pdst,u32 totsize,u32 cpdsize,u8 fwmode)
{
u8 res;
u16 br=0;
u16 bw=0;
FIL *fsrc=0;
FIL *fdst=0;
u8 *fbuf=0;
u8 curpct=0;
unsigned long long lcpdsize=cpdsize;
fsrc=(FIL*)mymalloc(SRAMIN,sizeof(FIL));//申请内存
fdst=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
fbuf=(u8*)mymalloc(SRAMIN,8192);
if(fsrc==NULL||fdst==NULL||fbuf==NULL)res=100;//前面的值留给fatfs
else
{
if(fwmode==0)fwmode=FA_CREATE_NEW;//不覆盖
else fwmode=FA_CREATE_ALWAYS; //覆盖存在的文件
res=f_open(fsrc,(const TCHAR*)psrc,FA_READ|FA_OPEN_EXISTING); //打开只读文件
if(res==0)res=f_open(fdst,(const TCHAR*)pdst,FA_WRITE|fwmode); //第一个打开成功,才开始打开第二个
if(res==0)//两个都打开成功了
{
if(totsize==0)//仅仅是单个文件复制
{
totsize=fsrc->obj.objsize;
lcpdsize=0;
curpct=0;
}else curpct=(lcpdsize*100)/totsize; //得到新百分比
fcpymsg(psrc,curpct,0X02); //更新百分比
while(res==0)//开始复制
{
res=f_read(fsrc,fbuf,8192,(UINT*)&br); //源头读出512字节
if(res||br==0)break;
res=f_write(fdst,fbuf,(UINT)br,(UINT*)&bw); //写入目的文件
lcpdsize+=bw;
if(curpct!=(lcpdsize*100)/totsize)//是否需要更新百分比
{
curpct=(lcpdsize*100)/totsize;
if(fcpymsg(psrc,curpct,0X02))//更新百分比
{
res=0XFF;//强制退出
break;
}
}
if(res||bw<br)break;
}
f_close(fsrc);
f_close(fdst);
}
}
myfree(SRAMIN,fsrc);//释放内存
myfree(SRAMIN,fdst);
myfree(SRAMIN,fbuf);
return res;
}
//得到路径下的文件夹
//返回值:0,路径就是个卷标号.
// 其他,文件夹名字首地址
u8* fatfs_get_src_dname(u8* dpfn)
{
u16 temp=0;
while(*dpfn!=0)
{
dpfn++;
temp++;
}
if(temp<4)return 0;
while((*dpfn!=0x5c)&&(*dpfn!=0x2f))dpfn--; //追述到倒数第一个"\"或者"/"处
return ++dpfn;
}
//得到文件夹大小
//注意文件夹大小不要超过4GB.
//返回值:0,文件夹大小为0,或者读取过程中发生了错误.
// 其他,文件夹大小.
u32 fatfs_fdsize(u8 *fdname)
{
#define MAX_PATHNAME_DEPTH 512+1 //最大目标文件路径+文件名深度
u8 res=0;
DIR *fddir=0; //目录
FILINFO *finfo=0; //文件信息
u8 * pathname=0; //目标文件夹路径+文件名
u16 pathlen=0; //目标路径长度
u32 fdsize=0;
fddir=(DIR*)mymalloc(SRAMIN,sizeof(DIR));//申请内存
finfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO));
if(fddir==NULL||finfo==NULL)res=100;
if(res==0)
{
pathname=mymalloc(SRAMIN,MAX_PATHNAME_DEPTH);
if(pathname==NULL)res=101;
if(res==0)
{
pathname[0]=0;
strcat((char*)pathname,(const char*)fdname); //复制路径
res=f_opendir(fddir,(const TCHAR*)fdname); //打开源目录
if(res==0)//打开目录成功
{
while(res==0)//开始复制文件夹里面的东东
{
res=f_readdir(fddir,finfo); //读取目录下的一个文件
if(res!=FR_OK||finfo->fname[0]==0)break; //错误了/到末尾了,退出
if(finfo->fname[0]=='.')continue; //忽略上级目录
if(finfo->fattrib&0X10)//是子目录(文件属性,0X20,归档文件;0X10,子目录;)
{
pathlen=strlen((const char*)pathname); //得到当前路径的长度
strcat((char*)pathname,(const char*)"/"); //加斜杠
strcat((char*)pathname,(const char*)finfo->fname); //源路径加上子目录名字
//printf("\r\nsub folder:%s\r\n",pathname); //打印子目录名
fdsize+=fatfs_fdsize(pathname); //得到子目录大小,递归调用
pathname[pathlen]=0; //加入结束符
}else fdsize+=finfo->fsize; //非目录,直接加上文件的大小
}
}
myfree(SRAMIN,pathname);
}
}
myfree(SRAMIN,fddir);
myfree(SRAMIN,finfo);
if(res)return 0;
else return fdsize;
}
//文件夹复制
//注意文件夹大小不要超过4GB.
//将psrc文件夹,copy到pdst文件夹.
//pdst:必须形如"X:"/"X:XX"/"X:XX/XX"之类的.而且要实现确认上一级文件夹存在
//fcpymsg,函数指针,用于实现拷贝时的信息显示
// pname:文件/文件夹名
// pct:百分比
// mode:
// [0]:更新文件名
// [1]:更新百分比pct
// [2]:更新文件夹
// [3~7]:保留
//psrc,pdst:源文件夹和目标文件夹
//totsize:总大小(当totsize为0的时候,表示仅仅为单个文件拷贝)
//cpdsize:已复制了的大小.
//fwmode:文件写入模式
//0:不覆盖原有的文件
//1:覆盖原有的文件
//返回值:0,成功
// 其他,错误代码;0XFF,强制退出
u8 fatfs_fdcopy(u8(*fcpymsg)(u8*pname,u8 pct,u8 mode),u8 *psrc,u8 *pdst,u32 *totsize,u32 *cpdsize,u8 fwmode)
{
#define MAX_PATHNAME_DEPTH 512+1 //最大目标文件路径+文件名深度
u8 res=0;
DIR *srcdir=0; //源目录
DIR *dstdir=0; //源目录
FILINFO *finfo=0; //文件信息
u8 *fn=0; //长文件名
u8 * dstpathname=0; //目标文件夹路径+文件名
u8 * srcpathname=0; //源文件夹路径+文件名
u16 dstpathlen=0; //目标路径长度
u16 srcpathlen=0; //源路径长度
srcdir=(DIR*)mymalloc(SRAMIN,sizeof(DIR));//申请内存
dstdir=(DIR*)mymalloc(SRAMIN,sizeof(DIR));
finfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO));
if(srcdir==NULL||dstdir==NULL||finfo==NULL)res=100;
if(res==0)
{
dstpathname=mymalloc(SRAMIN,MAX_PATHNAME_DEPTH);
srcpathname=mymalloc(SRAMIN,MAX_PATHNAME_DEPTH);
if(dstpathname==NULL||srcpathname==NULL)res=101;
if(res==0)
{
dstpathname[0]=0;
srcpathname[0]=0;
strcat((char*)srcpathname,(const char*)psrc); //复制原始源文件路径
strcat((char*)dstpathname,(const char*)pdst); //复制原始目标文件路径
res=f_opendir(srcdir,(const TCHAR*)psrc); //打开源目录
if(res==0)//打开目录成功
{
strcat((char*)dstpathname,(const char*)"/");//加入斜杠
fn=fatfs_get_src_dname(psrc);
if(fn==0)//卷标拷贝
{
dstpathlen=strlen((const char*)dstpathname);
dstpathname[dstpathlen]=psrc[0]; //记录卷标
dstpathname[dstpathlen+1]=0; //结束符
}else strcat((char*)dstpathname,(const char*)fn);//加文件名
fcpymsg(fn,0,0X04);//更新文件夹名
res=f_mkdir((const TCHAR*)dstpathname);//如果文件夹已经存在,就不创建.如果不存在就创建新的文件夹.
if(res==FR_EXIST)res=0;
while(res==0)//开始复制文件夹里面的东东
{
res=f_readdir(srcdir,finfo); //读取目录下的一个文件
if(res!=FR_OK||finfo->fname[0]==0)break; //错误了/到末尾了,退出
if(finfo->fname[0]=='.')continue; //忽略上级目录
fn=(u8*)finfo->fname; //得到文件名
dstpathlen=strlen((const char*)dstpathname); //得到当前目标路径的长度
srcpathlen=strlen((const char*)srcpathname); //得到源路径长度
strcat((char*)srcpathname,(const char*)"/");//源路径加斜杠
if(finfo->fattrib&0X10)//是子目录(文件属性,0X20,归档文件;0X10,子目录;)
{
strcat((char*)srcpathname,(const char*)fn); //源路径加上子目录名字
res=fatfs_fdcopy(fcpymsg,srcpathname,dstpathname,totsize,cpdsize,fwmode); //拷贝文件夹
}else //非目录
{
strcat((char*)dstpathname,(const char*)"/");//目标路径加斜杠
strcat((char*)dstpathname,(const char*)fn); //目标路径加文件名
strcat((char*)srcpathname,(const char*)fn); //源路径加文件名
fcpymsg(fn,0,0X01);//更新文件名
res=fatfs_copy(fcpymsg,srcpathname,dstpathname,*totsize,*cpdsize,fwmode);//复制文件
*cpdsize+=finfo->fsize;//增加一个文件大小
}
srcpathname[srcpathlen]=0;//加入结束符
dstpathname[dstpathlen]=0;//加入结束符
}
}
myfree(SRAMIN,dstpathname);
myfree(SRAMIN,srcpathname);
}
}
myfree(SRAMIN,srcdir);
myfree(SRAMIN,dstdir);
myfree(SRAMIN,finfo);
return res;
}
//得到path路径下,目标文件的总个数
//path:路径
//type:类型:图片/文本/MP3等 TYPE_PICTURE、TYPE_MUSIC、TYPE_TEXT
//返回值:总有效文件数
u16 fatfs_get_filetype_tnum(u8 *path,u8 type)
{
u8 res;
u16 rval=0;
DIR tdir; //临时目录
FILINFO *tfileinfo; //临时文件信息
tfileinfo=(FILINFO*)mymalloc(SRAMIN,sizeof(FILINFO));//申请内存
res=f_opendir(&tdir,(const TCHAR*)path); //打开目录
if(res==FR_OK&&tfileinfo)
{
while(1)//查询总的有效文件数
{
res=f_readdir(&tdir,tfileinfo); //读取目录下的一个文件
if(res!=FR_OK||tfileinfo->fname[0]==0)break;//错误了/到末尾了,退出
res=f_typetell((u8*)tfileinfo->fname);
if((res&0XF0)==TYPE_BIN)//取高四位,看看是不是BIN文件
{
rval++;//有效文件数增加1
}
else if((res&0XF0)==TYPE_LRC)//取高四位,看看是不是LRC文件
{
rval++;//有效文件数增加1
}
else if((res&0XF0)==TYPE_GAME)//取高四位,看看是不是GAME文件
{
rval++;//有效文件数增加1
}
else if((res&0XF0)==TYPE_TEXT)//取高四位,看看是不是TEXT文件
{
rval++;//有效文件数增加1
}
else if((res&0XF0)==TYPE_MUSIC)//取高四位,看看是不是MUSIC文件
{
rval++;//有效文件数增加1
}
else if((res&0XF0)==TYPE_PICTURE)//取高四位,看看是不是PICTURE文件
{
rval++;//有效文件数增加1
}
}
}
myfree(SRAMIN,tfileinfo);//释放内存
return rval;
}
主函数实现的功能很简单, 首先调用之前编写好的硬件初始化函数, 包括SysTick 系统时钟, LED 初始化等。 然后调用 SD_Init 函数, 检测 SD 卡是否存在,如果 SD 卡初始化成功, 调用 f_mount 函数挂载 SD 卡、 挂载外部 FLASH, 如果是第一次挂载即会对外部 FLASH 进行格式化, 并重命名磁盘名。 然后获取 SD 卡总容量及剩余容量, 将 SD 卡容量通过 TFTLCD 和串口显示。 最后进入 while 循环,控制 DS0 指示灯间隔 200ms 闪烁, 提示系统正常运行。
48.5 实验现象
将工程程序编译后下载到开发板内, 插入 TF 卡, 可以看到 TFTLCD 上显示SD 卡的容量信息, 同时 DS0 指示灯不断闪烁, 表示程序正常运行。 如果未插 TF卡将显示"SD Card Error!" 提示信息, TFTLCD 模块界面如下图所示:

如果需要通过串口助手显示, 请按照前面实验设置串口助手方式操作。
课后作业
(1) 使用 FATFS 文件系统对 SD 卡进行读写文件操作。 (温馨提示: 可以先在 SD 卡内写入一个文件, 然后读取该文件内数据, 通过串口或者 TFTLCD 显示)
(2) 将 FATFS 文件系统内常用 API 函数都熟悉一下。