第二十六章 FATFS介绍及应用

1. FATFS简介

FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89) 编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、 PIC、 AVR、 ARM、 Z80、 RX 等系列单片机上。它支持 FATl2、 FATl6 和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。

最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如 f_open, f_read, f_write 和 f_close 等,就可以像在PC 上读/写文件那样简单。

中间层 FATFS 模块,实现了 FAT 文件读/写协议。 FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。

需要我们编写移植代码的是 FATFS 模块提供的底层接口,它包括存储媒介读/写接口(diskI/O)和供给文件创建修改时间的实时时钟。

2. FATFS移植

2.1 可选配置项

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。 FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。

FF_FS_TINY。这个选项在 R0.07 版本中开始出现,之前的版本都是以独立的 C 文件出现(FATFS 和 TinyFATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用 FATFS,所以把这个选项定义为 0 即可。

FF _FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为 0 即可。

FF _USE_STRFUNC。这个用来设置是否支持字符串类操作,比如 f_putc, f_puts 等,本章我们需要用到,故设置这里为 1。

FF _USE_MKFS。用来定时是否使能格式化,本章需要用到,所以设置这里为 1。

FF _USE_FASTSEEK。这个用来使能快速定位,我们设置为 1,使能快速定位。

FF _USE_LABEL。这个用来设置是否支持磁盘盘符(磁盘名字)读取与设置。我们设置为 1,使能,就可以通过相关函数读取或者设置磁盘的名字了。

FF _CODE_PAGE。这个用于设置语言类型,包括很多选项(见 FATFS 官网说明),我们这里设置为 936,即简体中文(GBK 码, 同一个文件夹下的 ffunicode.c 根据这个宏选择对应的语言设置)。

FF_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE 支持),取值范围为 0~3。 0,表示不支持长文件名, 1~3 是支持长文件名,但是存储地方不一样,我们选择使用 3,通过 ff_memalloc 函数来动态分配长文件名的存储区域。

FF_VOLUMES。用于设置 FATFS 支持的逻辑设备数目,我们设置为 2,即支持 2 个设备。

FF_MAX_SS。扇区缓冲的最大值,一般设置为 512。11) FF_FS_EXFAT。 新版本增加的功能, 使用 exFAT 文件系统,用于支持超过 32Gb 的超大存储。 它们使用的是 exFAT 文件系统,使用它时必须要根据设置 FF_USE_LFN 这个参数的值以决定 exFATs 系统使用的内存来自堆栈还是静态数组。

2.2 移植步骤

数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型

配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。

函数编写:打开 diskio.c,进行底层驱动编写,需要编写 5 个接口函数,如下图所示:

首先是 disk_initialize 函数:

/**

* @brief 初始化磁盘

* @param pdrv : 磁盘编号0~9

* @retval 执行结果(参见FATFS, DSTATUS的定义)

*/

DSTATUS disk_initialize (

BYTE pdrv /* Physical drive number to identify the drive */

)

{

uint8_t res = 0;

switch (pdrv)

{

case SD_CARD: /* SD卡 */

res = sd_init(); /* SD卡初始化 */

break;

case EX_FLASH: /* 外部flash */

spi_flash_init();

break;

default:

res = 1;

break;

}

if (res)

{

return STA_NOINIT;

}

else

{

return 0; /* 初始化成功*/

}

}

第二个函数是 disk_status 函数:

/**

* @brief 获得磁盘状态

* @param pdrv : 磁盘编号0~9

* @retval 执行结果(参见FATFS, DSTATUS的定义)

*/

DSTATUS disk_status (

BYTE pdrv /* Physical drive number to identify the drive */

)

{

return RES_OK;

}

第三个函数是 disk_read 函数:

/**

* @brief 读扇区

* @param pdrv : 磁盘编号0~9

* @param buff : 数据接收缓冲首地址

* @param sector : 扇区地址

* @param count : 需要读取的扇区数

* @retval 执行结果(参见FATFS, DRESULT的定义)

*/

DRESULT disk_read (

BYTE pdrv, /* Physical drive number 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 */

)

{

uint8_t res = 0;

if (!count) return RES_PARERR; /* count不能等于0,否则返回参数错误 */

switch (pdrv)

{

case SD_CARD: /* SD卡 */

res = sd_read_disk(buff, sector, count);

while (res) /* 读出错 */

{

//printf("sd rd error:%d\r\n", res);

sd_init(); /* 重新初始化SD卡 */

res = sd_read_disk(buff, sector, count);

}

break;

case EX_FLASH: /* 外部flash */

for (; count > 0; count--)

{

flash_read(buff, SPI_FLASH_FATFS_BASE + sector * SPI_FLASH_SECTOR_SIZE, SPI_FLASH_SECTOR_SIZE);

sector++;

buff += SPI_FLASH_SECTOR_SIZE;

}

res = 0;

break;

default:

res = 1;

}

/* 处理返回值,将返回值转成ff.c的返回值 */

if (res == 0x00)

{

return RES_OK;

}

else

{

return RES_ERROR;

}

}

第四个函数是 disk_write 函数:

/**

* @brief 写扇区

* @param pdrv : 磁盘编号0~9

* @param buff : 发送数据缓存区首地址

* @param sector : 扇区地址

* @param count : 需要写入的扇区数

* @retval 执行结果(参见FATFS, DRESULT的定义)

*/

DRESULT disk_write (

BYTE pdrv, /* Physical drive number to identify the drive */

const BYTE *buff, /* Data to be written */

DWORD sector, /* Sector address in LBA */

UINT count /* Number of sectors to write */

)

{

uint8_t res = 0;

if (!count) return RES_PARERR; /* count不能等于0,否则返回参数错误 */

switch (pdrv)

{

case SD_CARD: /* SD卡 */

res = sd_write_disk((uint8_t *)buff, sector, count);

while (res) /* 写出错 */

{

//printf("sd wr error:%d\r\n", res);

sd_init(); /* 重新初始化SD卡 */

res = sd_write_disk((uint8_t *)buff, sector, count);

}

break;

case EX_FLASH: /* 外部flash */

for (; count > 0; count--)

{

flash_write((uint8_t *)buff, SPI_FLASH_FATFS_BASE + sector * SPI_FLASH_SECTOR_SIZE, SPI_FLASH_SECTOR_SIZE);

sector++;

buff += SPI_FLASH_SECTOR_SIZE;

}

res = 0;

break;

default:

res = 1;

}

/* 处理返回值,将返回值转成ff.c的返回值 */

if (res == 0x00)

{

return RES_OK;

}

else

{

return RES_ERROR;

}

}

第五个函数是 disk_ioctl 函数:

/**

* @brief 获取其他控制参数

* @param pdrv : 磁盘编号0~9

* @param ctrl : 控制代码

* @param buff : 发送/接收缓冲区指针

* @retval 执行结果(参见FATFS, DRESULT的定义)

*/

DRESULT disk_ioctl (

BYTE pdrv, /* Physical drive number (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 = sdcard_info_handle.LogBlockSize;

res = RES_OK;

break;

case GET_SECTOR_COUNT:

*(DWORD *)buff = sdcard_info_handle.LogBlockNbr;

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 = SPI_FLASH_SECTOR_SIZE;

res = RES_OK;

break;

case GET_BLOCK_SIZE:

*(WORD *)buff = SPI_FLASH_BLOCK_SIZE;

res = RES_OK;

break;

case GET_SECTOR_COUNT:

*(DWORD *)buff = SPI_FLASH_SECTOR_COUNT;

res = RES_OK;

break;

default:

res = RES_PARERR;

break;

}

}

else

{

res = RES_ERROR; /* 其他的不支持 */

}

return res;

}

以上五个函数,我们将在软件设计部分一一实现。通过以上 3 个步骤,我们就完成了对FATFS 的移植,就可以在我们的代码里面使用 FATFS 了。

3. FATFS应用示例

3.1 fattester.h

#ifndef __FATTESTER_H

#define __FATTESTER_H

#include "sys.h"

#include "ff.h"

/* 定义是否支持文件系统测试功能

* 1, 支持(开启);

* 0, 不支持(关闭);

*/

#define USE_FATTESTER 1

/* 如果支持文件系统测试 ,则使能以下代码 */

#if USE_FATTESTER == 1

/* FATFS 测试用结构体 */

typedef struct

{

FIL *file; /* 文件结构体指针1 */

FILINFO fileinfo; /* 文件信息 */

DIR dir; /* 目录 */

uint8_t *fatbuf; /* 读写缓存 */

uint8_t initflag; /* 初始化标志 */

} _m_fattester;

extern _m_fattester fattester; /* FATFS测试结构体 */

uint8_t mf_init(void); /* 初始化文件系统测试 */

void mf_free(void); /* 释放文件系统测试时申请的内存 */

uint8_t mf_mount(uint8_t* path, uint8_t mt);

uint8_t mf_open(uint8_t*path, uint8_t mode);

uint8_t mf_close(void);

uint8_t mf_read(uint16_t len);

uint8_t mf_write(uint8_t*dat, uint16_t len);

uint8_t mf_opendir(uint8_t* path);

uint8_t mf_closedir(void);

uint8_t mf_readdir(void);

uint8_t mf_scan_files(uint8_t * path);

uint32_t mf_showfree(uint8_t *drv);

uint8_t mf_lseek(uint32_t offset);

uint32_t mf_tell(void);

uint32_t mf_size(void);

uint8_t mf_mkdir(uint8_t*pname);

uint8_t mf_fmkfs(uint8_t* path, uint8_t opt, uint16_t au);

uint8_t mf_unlink(uint8_t *pname);

uint8_t mf_rename(uint8_t *oldname, uint8_t* newname);

void mf_getlabel(uint8_t *path);

void mf_setlabel(uint8_t *path);

void mf_gets(uint16_t size);

uint8_t mf_putc(uint8_t c);

uint8_t mf_puts(uint8_t*c);

#endif

#endif

3.2 fattester.c

#include "string.h"

#include "malloc.h"

#include "usart.h"

#include "exfuns.h"

#include "fattester.h"

#include "sdio.h"

/* 如果支持文件系统测试 ,则使能以下代码 */

#if USE_FATTESTER == 1

/* FATFS测试结构体

* 主要包含了文件指针/文件信息/目录/读写缓存等信息, 方便fattester.c

* 里面的测试函数使用. 当不需要使用文件系统测试功能时

*/

_m_fattester fattester;

/**

* @brief 初始化文件系统测试(申请内存)

* @note 该函数必须在执行任何文件系统测试之前被调用一次.

* 该函数只需要被成功调用一次即可,无需重复调用!!

* @param 无

* @retval 执行结果: 0, 成功; 1, 失败;

*/

uint8_t mf_init(void)

{

fattester.file = (FIL *)mymalloc(SRAMIN, sizeof(FIL)); /* 为file申请内存 */

fattester.fatbuf = (uint8_t *)mymalloc(SRAMIN, 512); /* 为fattester.fatbuf申请内存 */

if (fattester.file && fattester.fatbuf)

{

return 0; /* 申请成功 */

}

else

{

mf_free(); /* 释放内存 */

return 1; /* 申请失败 */

}

}

/**

* @brief 释放文件系统测试申请的内存

* @note 调用完该函数以后, 文件系统测试功能将失效.

* @param 无

* @retval 无

*/

void mf_free(void)

{

myfree(SRAMIN, fattester.file);

myfree(SRAMIN, fattester.fatbuf);

}

/**

* @brief 为磁盘注册工作区

* @param path : 磁盘路径,比如"0:"、"1:"

* @param mt : 0,不立即注册(稍后注册); 1,立即注册

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_mount(uint8_t *path, uint8_t mt)

{

return f_mount(fs[1], (const TCHAR *)path, mt);

}

/**

* @brief 打开文件

* @param path : 路径 + 文件名

* @param mode : 打开模式

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_open(uint8_t *path, uint8_t mode)

{

uint8_t res;

res = f_open(fattester.file, (const TCHAR *)path, mode); /* 打开文件 */

return res;

}

/**

* @brief 关闭文件

* @param 无

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_close(void)

{

f_close(fattester.file);

return 0;

}

/**

* @brief 读出数据

* @param len : 读出的长度

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_read(uint16_t len)

{

uint16_t i, t;

uint8_t res = 0;

uint16_t tlen = 0;

uint32_t br = 0;

printf("\r\nRead fattester.file data is:\r\n");

for (i = 0; i < len / 512; i++)

{

res = f_read(fattester.file, fattester.fatbuf, 512, &br);

if (res)

{

printf("Read Error:%d\r\n", res);

break;

}

else

{

tlen += br;

for (t = 0; t < br; t++)printf("%c", fattester.fatbuf[t]);

}

}

if (len % 512)

{

res = f_read(fattester.file, fattester.fatbuf, len % 512, &br);

if (res) /* 读数据出错了 */

{

printf("\r\nRead Error:%d\r\n", res);

}

else

{

tlen += br;

for (t = 0; t < br; t++)printf("%c", fattester.fatbuf[t]);

}

}

if (tlen)printf("\r\nReaded data len:%d\r\n", tlen); /* 读到的数据长度 */

printf("Read data over\r\n");

return res;

}

/**

* @brief 写入数据

* @param pdata : 数据缓存区

* @param len : 写入长度

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_write(uint8_t *pdata, uint16_t len)

{

uint8_t res;

uint32_t bw = 0;

printf("\r\nBegin Write fattester.file...\r\n");

printf("Write data len:%d\r\n", len);

res = f_write(fattester.file, pdata, len, &bw);

if (res)

{

printf("Write Error:%d\r\n", res);

}

else

{

printf("Writed data len:%d\r\n", bw);

}

printf("Write data over.\r\n");

return res;

}

/**

* @brief 打开目录

* @param path : 路径

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_opendir(uint8_t *path)

{

return f_opendir(&fattester.dir, (const TCHAR *)path);

}

/**

* @brief 关闭目录

* @param 无

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_closedir(void)

{

return f_closedir(&fattester.dir);

}

/**

* @brief 打读取文件夹

* @param 无

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_readdir(void)

{

uint8_t res;

res = f_readdir(&fattester.dir, &fattester.fileinfo); /* 读取一个文件的信息 */

if (res != FR_OK)return res; /* 出错了 */

printf("\r\n fattester.dir info:\r\n");

printf("fattester.dir.dptr:%d\r\n", fattester.dir.dptr);

printf("fattester.dir.obj.id:%d\r\n", fattester.dir.obj.id);

printf("fattester.dir.obj.sclust:%d\r\n", fattester.dir.obj.sclust);

printf("fattester.dir.obj.objsize:%lld\r\n", fattester.dir.obj.objsize);

printf("fattester.dir.obj.c_ofs:%d\r\n", fattester.dir.obj.c_ofs);

printf("fattester.dir.clust:%d\r\n", fattester.dir.clust);

printf("fattester.dir.sect:%d\r\n", fattester.dir.sect);

printf("fattester.dir.blk_ofs:%d\r\n", fattester.dir.blk_ofs);

printf("\r\n");

printf("fattester.file Name is:%s\r\n", fattester.fileinfo.fname);

printf("fattester.file Size is:%lld\r\n", fattester.fileinfo.fsize);

printf("fattester.file data is:%d\r\n", fattester.fileinfo.fdate);

printf("fattester.file time is:%d\r\n", fattester.fileinfo.ftime);

printf("fattester.file Attr is:%d\r\n", fattester.fileinfo.fattrib);

printf("\r\n");

return 0;

}

/**

* @brief 遍历文件

* @param path : 路径

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_scan_files(uint8_t *path)

{

FRESULT res;

res = f_opendir(&fattester.dir, (const TCHAR *)path); /* 打开一个目录 */

if (res == FR_OK)

{

printf("\r\n");

while (1)

{

res = f_readdir(&fattester.dir, &fattester.fileinfo); /* 读取目录下的一个文件 */

if (res != FR_OK || fattester.fileinfo.fname[0] == 0)

{

break; /* 错误了/到末尾了,退出 */

}

// if (fattester.fileinfo.fname[0] == '.') continue; /* 忽略上级目录 */

printf("%s/", path); /* 打印路径 */

printf("%s\r\n", fattester.fileinfo.fname); /* 打印文件名 */

}

}

return res;

}

/**

* @brief 显示剩余容量

* @param path : 路径(盘符)

* @retval 剩余容量(字节)

*/

uint32_t mf_showfree(uint8_t *path)

{

FATFS *fs1;

uint8_t res;

uint32_t fre_clust = 0, fre_sect = 0, tot_sect = 0;

/* 得到磁盘信息及空闲簇数量 */

res = f_getfree((const TCHAR *)path, (DWORD *)&fre_clust, &fs1);

if (res == 0)

{

tot_sect = (fs1->n_fatent - 2) * fs1->csize;/* 得到总扇区数 */

fre_sect = fre_clust * fs1->csize; /* 得到空闲扇区数 */

#if FF_MAX_SS!=512

tot_sect *= fs1->ssize / 512;

fre_sect *= fs1->ssize / 512;

#endif

if (tot_sect < 20480) /* 总容量小于10M */

{

/* Print free space in unit of KB (assuming 512 bytes/sector) */

printf("\r\n磁盘总容量:%d KB\r\n"

"可用空间:%d KB\r\n",

tot_sect >> 1, fre_sect >> 1);

}

else

{

/* Print free space in unit of KB (assuming 512 bytes/sector) */

printf("\r\n磁盘总容量:%d MB\r\n"

"可用空间:%d MB\r\n",

tot_sect >> 11, fre_sect >> 11);

}

}

return fre_sect;

}

/**

* @brief 文件读写指针偏移

* @param offset : 相对首地址的偏移量

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_lseek(uint32_t offset)

{

return f_lseek(fattester.file, offset);

}

/**

* @brief 读取文件当前读写指针的位置

* @param 无

* @retval 当前位置

*/

uint32_t mf_tell(void)

{

return f_tell(fattester.file);

}

/**

* @brief 读取文件大小

* @param 无

* @retval 文件大小

*/

uint32_t mf_size(void)

{

return f_size(fattester.file);

}

/**

* @brief 创建目录

* @param path : 目录路径 + 名字

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_mkdir(uint8_t *path)

{

return f_mkdir((const TCHAR *)path);

}

/**

* @brief 格式化

* @param path : 磁盘路径,比如"0:"、"1:"

* @param opt : 模式,FM_FAT,FM_FAT32,FM_EXFAT,FM_ANY等...

* @param au : 簇大小

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_fmkfs(uint8_t *path, uint8_t opt, uint16_t au)

{

MKFS_PARM temp = {FM_ANY, 0, 0, 0, 0};

temp.fmt = opt; /* 文件系统格式,1:FM_FAT;2,FM_FAT32;4,FM_EXFAT; */

temp.au_size = au; /* 簇大小定义,0则使用默认簇大小 */

return f_mkfs((const TCHAR *)path, &temp, 0, FF_MAX_SS); /* 格式化,默认参数,workbuf,最少_MAX_SS大小 */

}

/**

* @brief 删除文件/目录

* @param path : 文件/目录路径+名字

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_unlink(uint8_t *path)

{

return f_unlink((const TCHAR *)path);

}

/**

* @brief 修改文件/目录名字(如果目录不同,还可以移动文件哦!)

* @param oldname : 之前的名字

* @param newname : 新名字

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_rename(uint8_t *oldname, uint8_t *newname)

{

return f_rename((const TCHAR *)oldname, (const TCHAR *)newname);

}

/**

* @brief 获取盘符(磁盘名字)

* @param path : 磁盘路径,比如"0:"、"1:"

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

void mf_getlabel(uint8_t *path)

{

uint8_t buf[20];

uint32_t sn = 0;

uint8_t res;

res = f_getlabel ((const TCHAR *)path, (TCHAR *)buf, (DWORD *)&sn);

if (res == FR_OK)

{

printf("\r\n磁盘%s 的盘符为:%s\r\n", path, buf);

printf("磁盘%s 的序列号:%X\r\n\r\n", path, sn);

}

else

{

printf("\r\n获取失败,错误码:%X\r\n", res);

}

}

/**

* @brief 设置盘符(磁盘名字),最长11个字符!!,支持数字和大写字母组合以及汉字等

* @param path : 磁盘号+名字,比如"0:ALIENTEK"、"1:OPENEDV"

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

void mf_setlabel(uint8_t *path)

{

uint8_t res;

res = f_setlabel ((const TCHAR *)path);

if (res == FR_OK)

{

printf("\r\n磁盘盘符设置成功:%s\r\n", path);

}

else printf("\r\n磁盘盘符设置失败,错误码:%X\r\n", res);

}

/**

* @brief 从文件里面读取一段字符串

* @param size : 要读取的长度

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

void mf_gets(uint16_t size)

{

TCHAR *rbuf;

rbuf = f_gets((TCHAR *)fattester.fatbuf, size, fattester.file);

if (*rbuf == 0)return ; /* 没有数据读到 */

else

{

printf("\r\nThe String Readed Is:%s\r\n", rbuf);

}

}

/**

* @brief 写一个字符到文件(需要 FF_USE_STRFUNC >= 1)

* @param c : 要写入的字符

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_putc(uint8_t c)

{

return f_putc((TCHAR)c, fattester.file);

}

/**

* @brief 写字符串到文件(需要 FF_USE_STRFUNC >= 1)

* @param str : 要写入的字符串

* @retval 执行结果(参见FATFS, FRESULT的定义)

*/

uint8_t mf_puts(uint8_t *str)

{

return f_puts((TCHAR *)str, fattester.file);

}

#endif

3.3 exfuns.h

#ifndef __EXFUNS_H

#define __EXFUNS_H

#include "sys.h"

#include "ff.h"

extern FATFS *fs[FF_VOLUMES];

extern FIL *file;

extern FIL *ftemp;

extern UINT br, bw;

extern FILINFO fileinfo;

extern DIR dir;

extern uint8_t *fatbuf; /* SD卡数据缓存区 */

/* exfuns_file_type返回的类型定义

* 根据表FILE_TYPE_TBL获得.在exfuns.c里面定义

*/

#define T_BIN 0X00 /* bin文件 */

#define T_LRC 0X10 /* lrc文件 */

#define T_NES 0X20 /* nes文件 */

#define T_SMS 0X21 /* sms文件 */

#define T_TEXT 0X30 /* .txt文件 */

#define T_C 0X31 /* .c文件 */

#define T_H 0X32 /* .h文件 */

#define T_WAV 0X40 /* WAV文件 */

#define T_MP3 0X41 /* MP3文件 */

#define T_OGG 0X42 /* OGG文件 */

#define T_FLAC 0X43 /* FLAC文件 */

#define T_AAC 0X44 /* AAC文件 */

#define T_WMA 0X45 /* WMA文件 */

#define T_MID 0X46 /* MID文件 */

#define T_BMP 0X50 /* bmp文件 */

#define T_JPG 0X51 /* jpg文件 */

#define T_JPEG 0X52 /* jpeg文件 */

#define T_GIF 0X53 /* gif文件 */

#define T_AVI 0X60 /* avi文件 */

uint8_t exfuns_init(void); /* 申请内存 */

uint8_t exfuns_file_type(char *fname); /* 识别文件类型 */

uint8_t exfuns_get_free(uint8_t *pdrv, uint32_t *total, uint32_t *free); /* 得到磁盘总容量和剩余容量 */

uint32_t exfuns_get_folder_size(uint8_t *fdname); /* 得到文件夹大小 */

uint8_t *exfuns_get_src_dname(uint8_t *dpfn);

uint8_t exfuns_file_copy(uint8_t(*fcpymsg)(uint8_t *pname, uint8_t pct, uint8_t mode), uint8_t *psrc, uint8_t *pdst, uint32_t totsize, uint32_t cpdsize, uint8_t fwmode); /* 文件复制 */

uint8_t exfuns_folder_copy(uint8_t(*fcpymsg)(uint8_t *pname, uint8_t pct, uint8_t mode), uint8_t *psrc, uint8_t *pdst, uint32_t *totsize, uint32_t *cpdsize, uint8_t fwmode); /* 文件夹复制 */

#endif

3.4 exfuns.c

#include "string.h"

#include "malloc.h"

#include "usart.h"

#include "exfuns.h"

#include "fattester.h"

#define FILE_MAX_TYPE_NUM 7 /* 最多FILE_MAX_TYPE_NUM个大类 */

#define FILE_MAX_SUBT_NUM 7 /* 最多FILE_MAX_SUBT_NUM个小类 */

/* 文件类型列表 */

char *const FILE_TYPE_TBL[FILE_MAX_TYPE_NUM][FILE_MAX_SUBT_NUM] =

{

{"BIN"}, /* BIN文件 */

{"LRC"}, /* LRC文件 */

{"NES", "SMS"}, /* NES/SMS文件 */

{"TXT", "C", "H"}, /* 文本文件 */

{"WAV", "MP3", "OGG", "FLAC", "AAC", "WMA", "MID"}, /* 支持的音乐文件 */

{"BMP", "JPG", "JPEG", "GIF"}, /* 图片文件 */

{"AVI"}, /* 视频文件 */

};

/******************************************************************************************/

/* 公共文件区, 使用malloc的时候 */

/* 逻辑磁盘工作区(在调用任何FATFS相关函数之前,必须先给fs申请内存) */

FATFS *fs[FF_VOLUMES];

/******************************************************************************************/

/**

* @brief 为exfuns申请内存

* @param 无

* @retval 0, 成功; 1, 失败.

*/

uint8_t exfuns_init(void)

{

uint8_t i;

uint8_t res = 0;

for (i = 0; i < FF_VOLUMES; i++)

{

fs[i] = (FATFS *)mymalloc(SRAMIN, sizeof(FATFS)); /* 为磁盘i工作区申请内存 */

if (!fs[i])break;

}

#if USE_FATTESTER == 1 /* 如果使能了文件系统测试 */

res = mf_init(); /* 初始化文件系统测试(申请内存) */

#endif

if (i == FF_VOLUMES && res == 0)

{

return 0; /* 申请有一个失败,即失败. */

}

else

{

return 1;

}

}

/**

* @brief 将小写字母转为大写字母,如果是数字,则保持不变.

* @param c : 要转换的字母

* @retval 转换后的字母,大写

*/

uint8_t exfuns_char_upper(uint8_t c)

{

if (c < 'A')return c; /* 数字,保持不变. */

if (c >= 'a')

{

return c - 0x20; /* 变为大写. */

}

else

{

return c; /* 大写,保持不变 */

}

}

/**

* @brief 报告文件的类型

* @param fname : 文件名

* @retval 文件类型

* @arg 0XFF , 表示无法识别的文件类型编号.

* @arg 其他 , 高四位表示所属大类, 低四位表示所属小类.

*/

uint8_t exfuns_file_type(char *fname)

{

uint8_t tbuf[5];

char *attr = 0; /* 后缀名 */

uint8_t 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;

}

}

if (attr == 0)return 0XFF;

strcpy((char *)tbuf, (const char *)attr); /* copy */

for (i = 0; i < 4; i++)tbuf[i] = exfuns_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; /* 没找到 */

}

/**

* @brief 获取磁盘剩余容量

* @param pdrv : 磁盘编号("0:"~"9:")

* @param total: 总容量 (KB)

* @param free : 剩余容量 (KB)

* @retval 0, 正常; 其他, 错误代码

*/

uint8_t exfuns_get_free(uint8_t *pdrv, uint32_t *total, uint32_t *free)

{

FATFS *fs1;

uint8_t res;

uint32_t fre_clust = 0, fre_sect = 0, tot_sect = 0;

/* 得到磁盘信息及空闲簇数量 */

res = (uint32_t)f_getfree((const TCHAR *)pdrv, (DWORD *)&fre_clust, &fs1);

if (res == 0)

{

tot_sect = (fs1->n_fatent - 2) * fs1->csize; /* 得到总扇区数 */

fre_sect = fre_clust * fs1->csize; /* 得到空闲扇区数 */

#if FF_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;

}

/**

* @brief 文件复制

* @note 将psrc文件,copy到pdst.

* 注意: 文件大小不要超过4GB.

*

* @param fcpymsg : 函数指针, 用于实现拷贝时的信息显示

* pname:文件/文件夹名

* pct:百分比

* mode:

* bit0 : 更新文件名

* bit1 : 更新百分比pct

* bit2 : 更新文件夹

* 其他 : 保留

* 返回值: 0, 正常; 1, 强制退出;

*

* @param psrc : 源文件

* @param pdst : 目标文件

* @param totsize : 总大小(当totsize为0的时候,表示仅仅为单个文件拷贝)

* @param cpdsize : 已复制了的大小.

* @param fwmode : 文件写入模式

* @arg 0: 不覆盖原有的文件

* @arg 1: 覆盖原有的文件

*

* @retval 执行结果

* @arg 0 , 正常

* @arg 0XFF, 强制退出

* @arg 其他, 错误代码

*/

uint8_t exfuns_file_copy(uint8_t(*fcpymsg)(uint8_t *pname, uint8_t pct, uint8_t mode), uint8_t *psrc, uint8_t *pdst,

uint32_t totsize, uint32_t cpdsize, uint8_t fwmode)

{

uint8_t res;

uint16_t br = 0;

uint16_t bw = 0;

FIL *fsrc = 0;

FIL *fdst = 0;

uint8_t *fbuf = 0;

uint8_t curpct = 0;

unsigned long long lcpdsize = cpdsize;

fsrc = (FIL *)mymalloc(SRAMIN, sizeof(FIL)); /* 申请内存 */

fdst = (FIL *)mymalloc(SRAMIN, sizeof(FIL));

fbuf = (uint8_t *)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;

}

/**

* @brief 得到路径下的文件夹

* @note 即把路径全部去掉, 只留下文件夹名字.

* @param pname : 详细路径

* @retval 0 , 路径就是个卷标号.

* 其他, 文件夹名字首地址

*/

uint8_t *exfuns_get_src_dname(uint8_t *pname)

{

uint16_t temp = 0;

while (*pname != 0)

{

pname++;

temp++;

}

if (temp < 4)return 0;

while ((*pname != 0x5c) && (*pname != 0x2f))pname--; /* 追述到倒数第一个"\"或者"/"处 */

return ++pname;

}

/**

* @brief 得到文件夹大小

* @note 注意: 文件夹大小不要超过4GB.

* @param pname : 详细路径

* @retval 0 , 文件夹大小为0, 或者读取过程中发生了错误.

* 其他, 文件夹大小

*/

uint32_t exfuns_get_folder_size(uint8_t *fdname)

{

#define MAX_PATHNAME_DEPTH 512 + 1 /* 最大目标文件路径+文件名深度 */

uint8_t res = 0;

DIR *fddir = 0; /* 目录 */

FILINFO *finfo = 0; /* 文件信息 */

uint8_t *pathname = 0; /* 目标文件夹路径+文件名 */

uint16_t pathlen = 0; /* 目标路径长度 */

uint32_t 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 += exfuns_get_folder_size(pathname); /* 得到子目录大小,递归调用 */

pathname[pathlen] = 0; /* 加入结束符 */

}

else

{

fdsize += finfo->fsize; /* 非目录,直接加上文件的大小 */

}

}

}

myfree(SRAMIN, pathname);

}

}

myfree(SRAMIN, fddir);

myfree(SRAMIN, finfo);

if (res)

{

return 0;

}

else

{

return fdsize;

}

}

/**

* @brief 文件夹复制

* @note 将psrc文件夹, 拷贝到pdst文件夹.

* 注意: 文件大小不要超过4GB.

*

* @param fcpymsg : 函数指针, 用于实现拷贝时的信息显示

* pname:文件/文件夹名

* pct:百分比

* mode:

* bit0 : 更新文件名

* bit1 : 更新百分比pct

* bit2 : 更新文件夹

* 其他 : 保留

* 返回值: 0, 正常; 1, 强制退出;

*

* @param psrc : 源文件夹

* @param pdst : 目标文件夹

* @note 必须形如"X:"/"X:XX"/"X:XX/XX"之类的. 且要确认上一级文件夹存在

*

* @param totsize : 总大小(当totsize为0的时候,表示仅仅为单个文件拷贝)

* @param cpdsize : 已复制了的大小.

* @param fwmode : 文件写入模式

* @arg 0: 不覆盖原有的文件

* @arg 1: 覆盖原有的文件

*

* @retval 执行结果

* @arg 0 , 正常

* @arg 0XFF, 强制退出

* @arg 其他, 错误代码

*/

uint8_t exfuns_folder_copy(uint8_t(*fcpymsg)(uint8_t *pname, uint8_t pct, uint8_t mode), uint8_t *psrc, uint8_t *pdst,

uint32_t *totsize, uint32_t *cpdsize, uint8_t fwmode)

{

#define MAX_PATHNAME_DEPTH 512 + 1 /* 最大目标文件路径+文件名深度 */

uint8_t res = 0;

DIR *srcdir = 0; /* 源目录 */

DIR *dstdir = 0; /* 源目录 */

FILINFO *finfo = 0; /* 文件信息 */

uint8_t *fn = 0; /* 长文件名 */

uint8_t *dstpathname = 0; /* 目标文件夹路径+文件名 */

uint8_t *srcpathname = 0; /* 源文件夹路径+文件名 */

uint16_t dstpathlen = 0; /* 目标路径长度 */

uint16_t 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 = exfuns_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 = (uint8_t *)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 = exfuns_folder_copy(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 = exfuns_file_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;

}

3.5 主函数测试

#include "bsp_init.h"

#include "exfuns.h"

#include "sdio.h"

int main(void)

{

uint32_t total, free;

uint8_t i = 0;

uint8_t res =0;

bsp_init();

LCD_ShowString(30,70,200,16,16,"FATFS-Test");

while(1);

while(sd_init())

{

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);

}

exfuns_init(); // 为fatfs相关变量申请内存

f_mount(fs[0],"0:",1); // 挂载SD卡

res = f_mount(fs[1],"1:",1); // 挂载flash

if(res = 0x0D)

{

LCD_ShowString(30,150,200,16,16,"Flash Disk Formatting...");

res = f_mkfs("1:",0,0,FF_MAX_SS);

if(res == 0)

{

f_setlabel((const TCHAR*)"1:FLASH");

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);

}

while(exfuns_get_free("0",&total,&free)) // 获取SD卡剩余空间

{

LCD_ShowString(30,150,200,16,16,"SD Card Fatfs Error!");

delay_ms(200);

LED_TOGGLE(LED0_GPIO_Pin);

}

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");

while(1)

{

i++;

delay_ms(200);

LED_TOGGLE(LED1_GPIO_Pin);

}

}

4. API总结

4.1 FATFS 模块结构

4.1.1 核心对象

FATFS fs; // 文件系统对象

FIL file; // 文件对象

DIR dir; // 目录对象

FILINFO fileinfo; // 文件信息结构体

4.1.2 文件信息结构

typedef struct {

DWORD fsize; // 文件大小

WORD fdate; // 修改日期

WORD ftime; // 修改时间

BYTE fattrib; // 文件属性

TCHAR fname[13]; // 短文件名

#if _USE_LFN

TCHAR* lfname; // 长文件名指针

int lfsize; // 长文件名缓冲区大小

#endif

} FILINFO;

4.2 文件系统管理 API

4.2.1 挂载/卸载文件系统

// 挂载文件系统

FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt);

// 卸载文件系统

FRESULT f_unmount(const TCHAR* path);

使用示例:

FATFS fs; // 文件系统对象

// 挂载SD卡 (路径为 "0:")

FRESULT res = f_mount(&fs, "0:", 1);

if (res != FR_OK) {

printf("Mount error: %d\n", res);

}

// 使用后卸载

f_unmount("0:");

4.2.2 格式化存储设备

FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len);

参数说明:

path: 逻辑驱动器号 (如 "0:")

opt: 格式化选项 (FM_FAT, FM_FAT32, FM_EXFAT)

au: 分配单元大小 (0表示自动)

work: 工作缓冲区

len: 缓冲区大小

示例:

uint8_t work_buffer[4096]; // 格式化所需工作缓冲区

// 格式化SD卡为FAT32

res = f_mkfs("0:", FM_FAT32, 0, work_buffer, sizeof(work_buffer));

if (res == FR_OK) {

printf("Format successful!\n");

}

4.2.3 获取可用空间

FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs);

使用示例:

FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs);

4.3 文件操作 API

4.3.1 打开/创建文件

FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode);

打开模式:

模式

描述

FA_READ

读访问

FA_WRITE

写访问

FA_OPEN_EXISTING

打开现有文件

FA_CREATE_NEW

创建新文件 (存在则失败)

FA_CREATE_ALWAYS

创建或覆盖

FA_OPEN_ALWAYS

打开或创建

FA_OPEN_APPEND

打开并在末尾追加

示例:

FIL file;

// 以读模式打开文件

res = f_open(&file, "0:/data.txt", FA_READ);

if (res != FR_OK) {

printf("Open error: %d\n", res);

}

// 创建新文件并写入

res = f_open(&file, "0:/newfile.txt", FA_CREATE_ALWAYS | FA_WRITE);

4.3.2 关闭文件

FRESULT f_close(FIL* fp);

示例:

f_close(&file); // 操作完成后必须关闭文件

4.3.3 读写文件

// 读取文件

FRESULT f_read(FIL* fp, void* buff, UINT btr, UINT* br);

// 写入文件

FRESULT f_write(FIL* fp, const void* buff, UINT btw, UINT* bw);

示例:

char buffer[128];

UINT bytes_read;

// 读取文件内容

res = f_read(&file, buffer, sizeof(buffer), &bytes_read);

if (res == FR_OK) {

printf("Read %d bytes: %.*s\n", bytes_read, bytes_read, buffer);

}

// 写入数据

const char* text = "Hello FATFS!";

UINT bytes_written;

res = f_write(&file, text, strlen(text), &bytes_written);

4.3.4 文件定位

// 移动读写指针

FRESULT f_lseek(FIL* fp, FSIZE_t ofs);

// 截断文件

FRESULT f_truncate(FIL* fp);

// 刷新缓存

FRESULT f_sync(FIL* fp);

示例:

// 跳转到文件末尾追加数据

f_lseek(&file, f_size(&file));

// 截断文件到当前大小

f_truncate(&file);

// 确保数据写入物理设备

f_sync(&file);

4.3.5 文件管理

// 删除文件

FRESULT f_unlink(const TCHAR* path);

// 重命名/移动文件

FRESULT f_rename(const TCHAR* old_name, const TCHAR* new_name);

// 获取文件状态

FRESULT f_stat(const TCHAR* path, FILINFO* fno);

示例:

// 删除文件

f_unlink("0:/oldfile.txt");

// 重命名文件

f_rename("0:/temp.txt", "0:/data.txt");

// 检查文件是否存在

FILINFO info;

if (f_stat("0:/config.ini", &info) == FR_OK) {

printf("File size: %lu bytes\n", info.fsize);

}

4.4 目录操作 API

4.4.1 打开/关闭目录

// 打开目录

FRESULT f_opendir(DIR* dp, const TCHAR* path);

// 关闭目录

FRESULT f_closedir(DIR* dp);

4.4.2 读取目录内容

FRESULT f_readdir(DIR* dp, FILINFO* fno);

示例:

DIR dir;

FILINFO fileinfo;

// 打开根目录

res = f_opendir(&dir, "0:/");

if (res == FR_OK) {

// 遍历目录内容

while (f_readdir(&dir, &fileinfo) == FR_OK && fileinfo.fname[0]) {

printf("%s %8lu\n", fileinfo.fname, fileinfo.fsize);

}

f_closedir(&dir);

}

4.4.3 创建/删除目录

// 创建目录

FRESULT f_mkdir(const TCHAR* path);

// 删除目录

FRESULT f_unlink(const TCHAR* path); // 同样用于删除目录

示例:

// 创建新目录

f_mkdir("0:/logs");

// 删除空目录

f_unlink("0:/temp_dir");

4.5 高级功能

4.5.1 长文件名支持 (LFN)

在 ffconf.h 中启用:

#define _USE_LFN 2 // 0:禁用, 1:静态缓冲区, 2:栈缓冲区

#define _MAX_LFN 255 // 最大长文件名长度

使用示例:

FILINFO info;

TCHAR lfn_buffer[_MAX_LFN + 1];

info.lfname = lfn_buffer;

info.lfsize = sizeof(lfn_buffer);

f_readdir(&dir, &info);

printf("Long name: %s\n", lfn_buffer);

4.5.2 多卷管理

// 设置逻辑驱动器号

FRESULT f_setlabel(const TCHAR* label);

// 获取卷标

FRESULT f_getlabel(const TCHAR* path, TCHAR* label, DWORD* vsn);

示例:

// 设置卷标

f_setlabel("0:MY_SD_CARD");

// 获取卷标

TCHAR label[12];

DWORD vsn;

f_getlabel("0:", label, &vsn);

4.5.3 文件系统时间戳

在 ffconf.h 中启用:

#define _FS_NORTC 0 // 不使用RTC

#define _NORTC_MON 1 // 默认月份

#define _NORTC_MDAY 1 // 默认日

#define _NORTC_YEAR 2023 // 默认年

设置时间函数:

DWORD get_fattime(void) {

// 从RTC获取时间 (格式: YYYY-MM-DD HH:MM:SS)

RTC_DateTypeDef date;

RTC_TimeTypeDef time;

HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);

HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);

// 转换为FATFS时间格式

return ((date.Year + 20) << 25) |

(date.Month << 21) |

(date.Date << 16) |

(time.Hours << 11) |

(time.Minutes << 5) |

(time.Seconds >> 1);

}

4.6 错误处理

4.6.1 常见错误代码

错误代码

描述

FR_OK

成功

FR_DISK_ERR

底层磁盘错误

FR_INT_ERR

FATFS内部错误

FR_NOT_READY

存储设备未就绪

FR_NO_FILE

文件不存在

FR_NO_PATH

路径不存在

FR_INVALID_NAME

无效文件名

FR_DENIED

访问被拒绝

FR_EXIST

文件已存在

FR_INVALID_OBJECT

无效文件对象

FR_WRITE_PROTECTED

写保护

FR_INVALID_DRIVE

无效驱动器号

FR_NOT_ENABLED

文件系统未挂载

FR_NO_FILESYSTEM

无有效文件系统

FR_TIMEOUT

操作超时

FR_LOCKED

文件被锁定

FR_NOT_ENOUGH_CORE

内存不足

4.6.2 错误处理函数

// 获取错误信息字符串

const char* f_errmsg(FRESULT res) {

switch(res) {

case FR_OK: return "Succeeded";

case FR_DISK_ERR: return "Disk error";

case FR_INT_ERR: return "Internal error";

// ... 其他错误

default: return "Unknown error";

}

}

// 使用示例

if (f_open(&file, "0:/data.txt", FA_READ) != FR_OK) {

printf("Error: %s\n", f_errmsg(res));

}

4.7 性能优化技巧

4.7.1 增大文件缓冲区

// 在ffconf.h中调整

#define _MAX_SS 512 // 最大扇区大小

#define _MAX_SS 4096 // 某些SD卡支持4K扇区

// 使用更大的文件缓冲区

FIL file;

uint8_t file_buffer[2048]; // 2KB缓冲区

f_open(&file, "0:/largefile.bin", FA_READ);

f_setbuf(&file, file_buffer); // 设置自定义缓冲区

4.7.2 启用快速搜索

// 在ffconf.h中启用

#define _USE_FIND 1

// 使用示例

DIR dir;

FILINFO info;

char pattern[] = "*.TXT"; // 搜索所有TXT文件

f_findfirst(&dir, &info, "0:/", pattern);

while (f_findnext(&dir, &info) == FR_OK && info.fname[0]) {

printf("Found: %s\n", info.fname);

}

f_closedir(&dir);

4.7.3 使用直接数据传输 (避免缓存)

// 直接读写扇区

FRESULT f_read_skip_buf(FIL* fp, void* buff, UINT btr, UINT* br);

FRESULT f_write_skip_buf(FIL* fp, const void* buff, UINT btw, UINT* bw);

4.7.4 禁用不需要的功能

在 ffconf.h 中:

#define _FS_READONLY 0 // 0:读写, 1:只读

#define _FS_MINIMIZE 0 // 优化级别 (0-3)

#define _USE_STRFUNC 0 // 禁用字符串函数

#define _USE_MKFS 1 // 启用格式化功能

#define _USE_CHMOD 1 // 启用属性控制