打印
[znFAT的移植与应用]

znFAT学习笔记02(文件相关操作)_2014_11_15

[复制链接]
2301|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
petp|  楼主 | 2014-11-15 21:07 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
*************************
例子2: 打开根目录下的短名文件

*************************

这个例子执行后,串口会显示出文件的信息:
================================
文件名为:test.txt
文件大小(字节):22
文件创建时间:
2013年8月13日22时35分51秒 File_StartClust:8
File_CurClust:8
File_CurSec:7795
File_CurPos:0
文件当前偏移量(字节):0
================================

其实,使用znFAT 有一个模式,

struct znFAT_Init_Args idata Init_Args; //初始化参数集合
struct FileInfo idata fileinfo; //文件信息集合
znFAT_Device_Init(); //存储设备初始化

znFAT_Select_Device(0,&Init_Args); //选择设备
res=znFAT_Init(); //文件系统初始化
if(!res) //文件系统初始化成功
{
。。。。。。。
}
else //文件系统初始化失败
{
UART_Send_StrNum("文件系统初始化失败,错误码:",res);
}
//==================================================================
res=znFAT_Open_File(&fileinfo,"/test.txt",0,1); //打开文件
if(!res) //如果打开文件成功
{
。。。。。
}
else //如果打开文件不成功
{
UART_Send_StrNum("打开文件失败, 错误码:",res);

}

这里需要理解一个结构体
struct FileInfo idata fileinfo; //文件信息集合


和笔记01中的结构体不同的是,这个结构是为了 逻辑功能而人为设定的;笔记01中的结构 很多都是直接对应 物理结构。
FileInfo 既含有普通的属性(文件大小和日期等),也有 间接物理结构方面的属性(蓝色字体,例如当前簇等),为什么说间接,是因为 文件的定位是需要推导出来。


这里用到了文件操作中,最重要的一个函数
打开文件:
UINT8 znFAT_Open_File(struct FileInfo *pfi,INT8 *filepath,UINT32 n,UINT8 is_file)
参数说明
pfi 结构指针变量,用来装载文件的参数信息,比如文件的大小、文件的名称等等,以备后面使用,struct
FileInfoStruct结构体在znFAT.h中有定义。
Filepath 文件的路径,支持任意层目录,比如“\\dir1\\dir2\\dir3\\....\\test.txt”, 路径可用“\\”
或“/”表示,不能用“//”和“\”,否则打开文件操作将会失败。
n 在文件名中有通配符*或?的情况下,实际与之匹配的文件有多个,n就是打开的文件的项数,比如符
合通配条件的文件有6个,如果n=3,那么此函数就会打开这6个文件中按顺序排号为3的那个文件
(n编号从0开始)。
is_file 区别要打开的是文件还是目录,1—要打开的是文件,0—要打开的是目录。
注:在打开目录的时候,路径最后是目录名,且不可以\\或/结束。比如要打开/dir1/dir2 ,则不能写成
/dir1/dir2/,这样znFAT会认为要打开的是/dir1/dir2/下的名字为空的目录,而非dir2。(打开目录功能
常用于检测某个目录是否存在,或配合通配功能。

res=znFAT_Open_File(&fileinfo,"/test.txt",0,1); //打开文件
if(!res) //如果打开文件成功
{
UART_Send_Str("打开文件成功\n");
UART_Send_Str("================================\n");
UART_Send_Str("文件名为:");
UART_Send_Str(fileinfo.File_Name);UART_Send_Enter();
UART_Send_StrNum("文件大小(字节):",fileinfo.File_Size);
UART_Send_Str("文件创建时间:\n");
UART_Send_Num(fileinfo.File_CDate.year); UART_Send_Str("年");
UART_Send_Num(fileinfo.File_CDate.month);UART_Send_Str("月");
UART_Send_Num(fileinfo.File_CDate.day); UART_Send_Str("日");
UART_Send_Num(fileinfo.File_CTime.hour); UART_Send_Str("时");
UART_Send_Num(fileinfo.File_CTime.min); UART_Send_Str("分");
UART_Send_Num(fileinfo.File_CTime.sec); UART_Send_Str("秒\r\n");
UART_Send_StrNum("File_StartClust:",fileinfo.File_StartClust);
UART_Send_StrNum("File_CurClust:",fileinfo.File_CurClust);
UART_Send_StrNum("File_CurSec:",fileinfo.File_CurSec);
UART_Send_StrNum("File_CurPos:",fileinfo.File_CurPos);
UART_Send_StrNum("文件当前偏移量(字节):",fileinfo.File_CurOffset);
UART_Send_Str("================================\n");
}

znFAT_Open_File 这个函数还是蛮复杂的,再继续例程之前,需要专门理解一下。

相关帖子

沙发
petp|  楼主 | 2014-11-16 18:52 | 只看该作者
***************************
复杂的open file 函数—— 第一个拦路虎

***************************

其实在笔记01中,没有很大难度,原因是,都是直观的,容易理解的,虽然也绕迷宫,但是路线还是比较清晰的。
不过,到具体实施中,就有很多细节问题要注意,老虎藏在细节中。

首先,是路径的规定:Filepath 文件的路径,支持任意层目录,比如“\\dir1\\dir2\\dir3\\....\\test.txt”, 路径可用“\\”或“/”表示,不能用“//”和“\”,否则打开文件操作将会失败。


另外,‘\0’表示ASCII码值为0的字符。
在字符串中'\0'用作字符串(路径名)的结束标志。

举例如下,如果 就是根目录,怎么识别, \\test.txt
//如果是“\\”,则直接取首目录簇,即第2簇
if(('\\'==dirpath[0] || '/'==dirpath[0]) &&'\0'==dirpath[1])

学过走迷宫后,现在学 俄罗斯套娃。不错,这也是 open file 这个函数 比较复杂的地方。

先简化,
UINT8 znFAT_Open_File(struct FileInfo *pfi,INT8*filepath,UINT32 n,UINT8 is_file)
{
。。。。
result=znFAT_Enter_Dir(filepath,&Cur_Cluster,&fn_pos);
//获取路径最后一级目录的开始簇
。。。。
}


******************************

open file 的第一个任务是 获得路径最后一级目录的开始簇



这个函数的核心是调用
UINT8 Get_Dir_Start_Cluster(INT8*dir_name,UINT32 *pCluster) //获取目录下名为dir_name的子目录的开始簇

而这个函数的结构如下,



之所以有循环体套循环体,再套循环体,是因为要 遍历簇,然后遍历簇中的扇区,然后遍历扇区中的文件目录项,目的是找到文件目录项,然后对比文件目录项中名字,如果一致,则返回该文件目录项的开始簇。再复习一下文件目录项



当然,Get_Dir_Start_Cluster 也调用了一个函数,Get_Next_Cluster(Cur_Clust); //获取下一簇


获取下一簇这个函数比较简单,而且利用 / 和 % 两个运算符号就解决了问题。


这样 就得到了 最后一级目录的开始项。


************************************

再回到 open file 函数,
UINT8 znFAT_Open_File(struct FileInfo *pfi,INT8*filepath,UINT32 n,UINT8 is_file)
{
。。。。
result=znFAT_Enter_Dir(filepath,&Cur_Cluster,&fn_pos);
//获取路径最后一级目录的开始簇
do
{
sec_temp=SOC(Cur_Cluster); //当前簇首扇区
for(iSec=0;iSec<(pInit_Args->SectorsPerClust);iSec++)
  {
  znFAT_Device_Read_Sector(sec_temp+(UINT32)iSec,znFAT_Buffer);
   pitems=(structFDIesInSEC *)znFAT_Buffer;
  for(iFDI=0;iFDI<NFDI_PER_SEC;iFDI++) //访问扇区中各文件目录项
   {
    。。。。。
  }
}
}while(!IS_END_CLU(Cur_Cluster)); //如果不是最后一个簇,则继续循环
     
}

得到最后一级目录开始簇后,然后又是一个循环体套循环体,再套。和 刚才那个结构一样,不过这次 查找的就是文件了。再复习一下,文件目录项,同时记目录 和文件,它们的格式是一样的。

这样就完成了打开一个文件,个人感觉还是比较复杂的。

使用特权

评论回复
板凳
petp|  楼主 | 2014-11-16 21:06 | 只看该作者
本帖最后由 petp 于 2014-11-16 21:22 编辑
petp 发表于 2014-11-16 18:52
***************************
复杂的open file 函数—— 第一个拦路虎

**************************
把 Open file 再简单些

********************************

上面的 open file 函数之所以显得很复杂,是因为 要 查到最后一级目录,相对于曲折了很多。

如果直接打开根目录中的文件,函数就会简化很多。
//自创函数,非znFAT中函数,只是为了展示open file 函数,而简化,类似伪代码
UINT8 znFAT_Open_File_of_Root(structFileInfo *pfi)
{
UINT8 flag=0;
UINT32 sec_temp=0,Cur_Cluster=2,fn_pos=1;
UINT32 iSec=0,iFDI=0;
INT8 *filename;
struct FDIesInSEC *pitems; //指向文件目录项扇区数据的指针
struct FDI *pitem; //指向文件目录项数据的指针
//================================================
do
{
sec_temp=SOC(Cur_Cluster); //当前簇首扇区
for(iSec=0;iSec<(pInit_Args->SectorsPerClust);iSec++)
  {
  znFAT_Device_Read_Sector(sec_temp+(UINT32)iSec,znFAT_Buffer);
  pitems=(struct FDIesInSEC *)znFAT_Buffer;
  for(iFDI=0;iFDI<NFDI_PER_SEC;iFDI++) //访问扇区中各文件目录项
   {
   pitem=&(pitems->FDIes[iFDI]); //指向一个文件目录项数据
     
    To_File_Name((INT8*)(pitem->Name),temp_filename);
//FDI中的文件名字段转为8.3文件名
              
     if(!SFN_Match(filename,temp_filename)) //短文件名通配
           {
            Analyse_FDI(pfi,pitem); //解析匹配的文件目录项
             pfi->FDI_Sec=sec_temp+iSec; //文件目录项所在的扇区
             pfi->nFDI=(UINT8)iFDI; //文件目录项在扇区中的索引
             return ERR_SUCC;
             flag=1;
           }// if(!SFN_Match(filename,temp_filename))
         
    }// for
  }//for
Cur_Cluster=Get_Next_Cluster(Cur_Cluster); //获取下一簇
}while(!IS_END_CLU(Cur_Cluster)); //如果不是最后一个簇,则继续循环
return ERR_NO_FILE;

}

这个函数的核心是 Analyse_FDI(pfi,pitem); //解析匹配的文件目录项
这样才能将文件相关参数填入文件信息集合,如目录创建时间等


**************************************

看起来非常简单的 文件目录项解析函数
/************************************************************************
功能:解析一个文件目录项,将解析得到的参数装入到文件信息集合中
形参:pfi:指向文件信息集合的指针pFDI:指向文件目录项的指针
返回:0
详解:此函数被打开文件的函数调用,对符合匹配条件的文件目录项进行解析,并将参数装入到文件信
      息集合中,以便对文件进行进一步的操作时使用。
*************************************************************************/
UINT8 Analyse_FDI(struct FileInfo*pfi,struct FDI *pFDI)
{
UINT32 temp=0,i=0;
just_file=pfi;
pfi->File_Attr=pFDI->Attributes; //文件属性
pfi->File_StartClust=Bytes2Value(pFDI->LowClust,2)+Bytes2Value(pFDI->HighClust,2)*65536;
pfi->File_Size=Bytes2Value(pFDI->FileSize,4);
  
//解析文件创建时间与日期
temp=Bytes2Value(pFDI->CTime,2);
pfi->File_CTime.sec=(UINT8)((temp&TIME_SEC_MARK)*2);temp>>=TIME_SEC_NBITS;  //创建时间的2秒位
pfi->File_CTime.min=(UINT8)(temp&TIME_MIN_MARK);   temp>>=TIME_MIN_NBITS; //创建时间的分位
pfi->File_CTime.hour=(UINT8)(temp&TIME_HOUR_MARK);//创建时间的时位
pfi->File_CTime.sec+=(UINT8)((UINT16)(pFDI->CTime10ms)/100);//在秒上加上10毫秒位
temp=Bytes2Value(pFDI->CDate,2);
pfi->File_CDate.day=(UINT8)(temp&DATE_DAY_MARK);     temp>>=DATE_DAY_NBITS;   //创建日期的日位
pfi->File_CDate.month=(UINT8)(temp&DATE_MONTH_MARK);temp>>=DATE_MONTH_NBITS; //创建日期的月位
pfi->File_CDate.year=(UINT16)((temp&DATE_YEAR_MARK)+DATE_YEAR_BASE);//创建日期的年位(加上年份基数)
pfi->File_CurClust=pfi->File_StartClust;
pfi->File_CurSec=(pfi->File_CurClust)?SOC(pfi->File_CurClust):0;
pfi->File_CurPos=0;
pfi->File_CurOffset=0;
pfi->File_IsEOF=(UINT8)((pfi->File_StartClust)?BOOL_FALSE:BOOL_TRUE);
return 0;
}

*************************************
再回到主函数,
。。。
UART_Send_Num(fileinfo.File_CDate.year); UART_Send_Str("年");
UART_Send_Num(fileinfo.File_CDate.month);UART_Send_Str("月");
。。。。

主函数中的结构体中数据就来自解析函数的贡献。

使用特权

评论回复
地板
petp|  楼主 | 2014-11-17 20:41 | 只看该作者
petp 发表于 2014-11-16 21:06
**************************
把 Open file 再简单些

******************
文件目录项的再度理解

**********************

前期,文件目录项值得多多复习和深入,因为名字匹配和开始簇都来自于此。



访问FDI ,其实有一个固定格式,那就是 从 簇->扇区->文件目录项,



回顾一下,Get_Dir_Start_Cluster,获取目录下名为dir_name的子目录的开始簇
因为上面已经介绍了模式,直接看 主要处理函数部分,



这样就完成了 对文件目录项的访问。
不过,还有些 辅助函数,



文件名匹配,需要 理解算法,暂时超过目前需求,直接使用即可。
来看看著名的 8 3 命名,
功能:将一个文件目录项中的前11个字节(用于记录8.3短文件名)转为文件名
形参:name_in_fdi:指向文件目录项中用于记录文件名的字节序列的指针 pfilename:用于记录转换后的
       文件名的数组的指针
UINT8 To_File_Name(INT8 *name_in_fdi,INT8 *pfileName)





使用特权

评论回复
5
EDL陆| | 2016-4-7 08:41 | 只看该作者
对我相当有帮助

使用特权

评论回复
6
l254560546| | 2016-7-15 09:17 | 只看该作者
顶一下。不错的帖子

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

7

帖子

0

粉丝