基于 GD32F303ZET6 苹果派开发板
简介
如果硬件系统中有多余的存储介质,例如 Nand Flash,SPI Flash 等,那么我们也可以选择将 SD 卡中的文件复制到这些存储介质,SD 卡仅仅只是用于更新系统。本文以将文件复制到 Nand Flash 为例。
将文件复制到 Nand Flash 时,可以在 Nand Flash 中创建一个文件系统,由 FatFS 负责读写数据;也可以不借助文件系统,直接将文件数据写入到 Nand Flash 中,然后由一张表来记录每个文件在 Nand Flash 中的位置、大小、修改日期等信息。为了适用性更广,本文中用 FatFS 来管理 Nand Flash,即在 Nand Flash 中创建文件系统,然后由 FatFS 负责读写数据。
文件拷贝
简单的文件拷贝驱动如下所示。首先要将 SD 卡中文件的数据加载到内存中,一般是外部 SRAM,然后再调用 f_write 函数将数据写入 Nand Flash 即可。为了能拷贝大文件,CopyFileByName 函数并没有选择一次性加载整个文件到内存中,而是分段读取,依次写入 Nand Flash。
注意:这里并没有在 Nand Flash 中建立文件夹,所有文件默认拷贝到 Nand Flash 的根目录下。(写代码时有点懒,后边不想改了)
static void CopyFileByName(char* file)
{
FIL* nandFile; //NandFlash中的文件
FRESULT result; //文件操作返回变量
unsigned char* dataBuf; //数据缓冲区
unsigned char* newFileName; //新文件名字
unsigned int readNum; //成功读取的字节数
unsigned int writeNum; //成功写入的字节数
unsigned int fileSize; //文件大小
unsigned int byteRemain; //拷贝剩余数据量
unsigned int readAddr; //读取地址
//输出当前文件名
printf("CopyFileByName: Copy file %s\r\n", file);
//申请内存
nandFile = MyMalloc(SRAMIN, sizeof(FIL));
dataBuf = MyMalloc(SRAMEX, COPY_BYTE_NUM);
newFileName = MyMalloc(SRAMIN, _MAX_LFN);
if((NULL == nandFile) || (NULL == dataBuf) || (NULL == newFileName))
{
MyFree(nandFile);
MyFree(dataBuf);
MyFree(newFileName);
printf("CopyFileByName: Fail to malloc\r\n");
while(1){}
}
//生成新的文件名
newFileName[0] = 0;
StringAdd((char*)newFileName, "1:/");
StringAdd((char*)newFileName, GetFileName(file));
//在NandFlash中创建文件
result = f_open(nandFile, (const TCHAR*)newFileName, FA_CREATE_ALWAYS | FA_WRITE);
if(FR_OK != result)
{
printf("CopyFileByName: Fail to create %s in Nand Flash\r\n", GetFileName(file));
while(1){}
}
//获取文件大小
fileSize = ForceGetFileSize(file);
byteRemain = fileSize;
//从文件起始地址开始读取
readAddr = 0;
//循环读取SD卡文件数据并写入Nand Flash中
while(0 != byteRemain)
{
//从SD卡文件中读取数据
if(byteRemain >= COPY_BYTE_NUM)
{
ForceReadByNameWithCopy(readAddr, dataBuf, COPY_BYTE_NUM, file);
readAddr = readAddr + COPY_BYTE_NUM;
byteRemain = byteRemain - COPY_BYTE_NUM;
readNum = COPY_BYTE_NUM;
}
else
{
ForceReadByNameWithCopy(readAddr, dataBuf, byteRemain, file);
readAddr = readAddr + byteRemain;
readNum = byteRemain;
byteRemain = 0;
}
//将数据写入NandFlash
result = f_write(nandFile, dataBuf, readNum, &writeNum);
if((FR_OK != result) || (readNum != writeNum))
{
printf("CopyFileByName: Fail to write date to nand flash, please retry!!!");
f_close(nandFile);
while(1){}
}
}
//关闭保存文件
f_close(nandFile);
//释放内存
MyFree(nandFile);
MyFree(dataBuf);
MyFree(newFileName);
}
拷贝整个目录
我们可以根据需要,一个一个文件的拷贝到 Nand Flash,也可以直接一次性拷贝整个文件夹。有了单个文件的拷贝函数,那么拷贝整个文件夹就简单多了,具体如下所示。首先要获取文件夹下文件数量,然后依次调用 CopyFileByName 函数拷贝即可。
注意:CopyFolderByPath 函数并未对子文件夹做处理,所有并不能拷贝子文件夹下的文件。
static void CopyFolderByPath(char* path)
{
unsigned int fileNum; //目录下文件数量
unsigned int fileCnt; //文件计数
char* name; //文件名,不含路径
char* nameWithPath; //文件名,含路径
//文件名缓冲区内存申请
name = MyMalloc(SRAMIN, _MAX_LFN);
nameWithPath = MyMalloc(SRAMIN, _MAX_LFN);
if((NULL == name) || (NULL == nameWithPath))
{
MyFree(name);
MyFree(nameWithPath);
printf("CopyFolderByPath: fail to malloc\r\n");
while(1){}
}
//获取该目录下文件数量
fileNum = GetFileNumInPath(path);
//文件计数清零
fileCnt = 0;
//循环拷贝所有文件
while(fileCnt < fileNum)
{
//获取指定序号下的文件名
GetFileNameWithNode(path, fileCnt, name);
//序号加一
fileCnt++;
//将路径添加到文件名中
nameWithPath[0] = 0;
StringAdd(nameWithPath, path);
StringAdd(nameWithPath, "/");
StringAdd(nameWithPath, name);
//跳过旧文件
if(0 == IsNewFile(nameWithPath))
{
continue;
}
//拷贝文件
CopyFileByName(nameWithPath);
}
//释放内存
MyFree(name);
MyFree(nameWithPath);
}
文件的遍历可参考如下代码,知道了如何遍历文件夹,也就知道了如何统计文件夹下文件数量,以及获取指定序号的文件名了。
注意:使能了长文件名后,如果文件的名字过短,那么该文件的文件名有可能只存储在 fileInfo.fname,而不是 fileInfo.lfname 中。
void PrintAllFileNameInPath(char* path)
{
FRESULT result; //文件操作返回变量
DIR direct; //路径
FILINFO fileInfo; //文件信息
int fileCnt; //文件计数
//文件计数清零
fileCnt = 0;
//长文件名内存申请
#if (1 == _USE_LFN)
fileInfo.lfname = MyMalloc(SRAMIN, _MAX_LFN);
fileInfo.lfsize = _MAX_LFN;
if(NULL == fileInfo.lfname)
{
printf("PrintAllFileNameInPath: fail to malloc\r\n");
while(1){}
}
#endif
//打开特定路径
while(1)
{
result = f_opendir(&direct, path);
if(result != FR_OK)
{
printf("PrintAllFileNameInPath: fail to open path %s\r\n", path);
printf("PrintAllFileNameInPath: retry...\r\n");
DelayNms(500);
ForceMountByName(path);
}
else
{
break;
}
}
printf("PrintAllFileNameInPath: Start print all file name in %s\r\n", path);
//查询目录下所有文件
while(1)
{
result = f_readdir(&direct, &fileInfo);
if(result != FR_OK)
{
break;
}
//长文件名
#if _USE_LFN
if(0 == (AM_DIR & fileInfo.fattrib))
{
if(0 != fileInfo.lfname[0])
{
fileCnt++;
printf("%s\r\n", fileInfo.lfname);
}
else if(0 != fileInfo.fname[0])
{
fileCnt++;
printf("%s\r\n", fileInfo.fname);
}
else
{
break;
}
}
//短文件名
#else
if(0 == (AM_DIR & fileInfo.fattrib))
{
if(0 == fileInfo.fname[0])
{
break;
}
else
{
fileCnt++;
printf("%s\r\n", fileInfo.fname);
}
}
#endif
}
//关闭目录
result = f_closedir(&direct);
if(result != FR_OK)
{
printf("PrintAllFileNameInPath: Fail to close path (%d)\r\n", result);
}
//长文件名内存释放
#if (1 == _USE_LFN)
MyFree(fileInfo.lfname);
#endif
printf("PrintAllFileNameInPath: file num %d\r\n", fileCnt);
}
文件信息表
为了避免文件重复拷贝,只拷贝那些更新过的文件,我们可以在建立一张文件信息表,里边储存着系统版本号、文件的修改日期等信息,存储在 Nand Flash 中。拷贝文件时,首先检索文件信息表,如果该文件有被更新过,那么就复制该文件;如果该文件没被更新,即 Nand Flash 中的文件就是最新版本,那么就可以选择不复制该文件。如此一来,插入 SD 卡时,就可以避免每次开机都复制文件。
文件信息表的文件头信息以及单个文件的信息如下所示,具体实现在此不过多描述,读者可以参考本系列提供的源码。
//文件头信息
typedef __packed struct
{
char flag[4]; //文件标志,固定为 "INFO"
char version[256]; //版本信息
unsigned int fileNum; //已经记录的文件的数量
unsigned int fileSize; //整个文件的大小,字节
unsigned int dataSize; //数据域的大小,字节
unsigned int offSet; //文件开头到第一个文件信息的偏移量
}StructFileInfoHead;
//单个文件的信息
typedef struct
{
char name[256]; //SD 卡中的文件名,含路径
unsigned int sdDate; //SD 卡中的修改日期
}StructFileInfo;
总结
对于简单的应用,文件数量不多,读者完全可以只拷贝指定文件,而不用拷贝整个文件夹。同样的 Nand Flash 中也没必要必须使用文件系统,直接读写速度会更快一些,但不方便管理。
实验结果
实验结果如下所示。
-将文件复制到-NandFlash-实验结果-20230304-1024x768.jpg)
源码
本章节中的源码请参考《单片机 GUI 设计(零)- 大纲》