原创 深入理解文件系统(六)

2010-4-2 20:06 4505 10 18 分类: MCU/ 嵌入式

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

 


六、支持长文件名(一)。


 


1、代码页的功能


1)在原来的程序中,我把 #define _CODE_PAGE   936,代码页定义为936.


这时候可以在磁盘上新建中文名称的文件夹和文件,(通过fwrite命令实现,只要数目小于4个就行,也可以用fread命令读出来),也可以显示中文文件名。(通过命令flist来实现)。


2)我把 #define _CODE_PAGE  1,代码页定义为1后,调用flist能正常显示中文名。但是调用freadfwrite命令不能读取和创建中文名文件了。


进入代码分析,显示时关键在函数 get_fileinfo(),它原封不动的将系统标准名 转换为一般字符串,对字符串不做任何处理,送到串口以后自然可以正常显示。


但是读取文件和写入文件时,首先调用f_open()函数,进入follow_path()函数,然后调用create_name(),当它遇到大于 0x80 的字符时,产生如下效果:


             if (c >= 0x80) {                         /* Extended char */


#ifdef _EXCVT


                    c = cvt[c - 0x80];              /* Convert extend char (SBCS) */


#else


                    b |= 3;                         /* Eliminate NT flag if ext char is exist */


#if !_DF1S       /* ASCII only cfg */


                    return FR_INVALID_NAME;


#endif


#endif


             }


返回FR_INVALID_NAME,所以它不识别中文GB2312OEM)代码。


如果定义了define _CODE_PAGE==936,则同时就会定义DF1S,也同时定义了IsDBCS1(c)IsDBCS2(d)。因此可以将中文OEM代码转换为标准文件名,存储于文件系统的目录项里面。


 


2、如果定义长文件名_USE_LFN,而不定义UNICODE码。有什么效果呢?


根据要求,先要添加ff_convert() and ff_wtoupper()两个函数。


结果发现,光两个转换表就要150K左右,我这个128k的空间是远远不够了。只能看代码而不能测试了。不定义UNICODE码的含义是用户提供的路径名不是以UNICODE形式出现的。


以下,就只能纸上谈兵了。


 


3、如果0:/doc 目录下有一个文件名 (这是一个very long的文件.它里面是空的.txt文档)。


如果要显示这个目录下所有的文件信息,我会在命令界面上输入:


Flist 0:/doc


执行这个命令,程序首先调用


F_opendirDIR djconst void *path函数,对输入的路径进行分析。这个函数首先调用函数 chk_mounted()检查磁盘上是否有文件系统,如果已经取得了信息,则很快返回。如果没有,则初始化磁盘,并读取信息,填充文件系统信息结构体FATFS。它去掉了路径前面的 0


F_opendir接下来调用follow_path(dj, path)这个函数根据路径填充目录信息结构体。这个函数首先设置dj->sclust,如果前面有/,会去掉这个符号。接下来一直读取目录名直到下一个/。这个工作调用create_name(dj, &path)来完成。


 


 


 


进入create_name(dj, &path),首先做的工作是根据路径填充dj->lfn指向的内存单元,由于我的这个目录是短文件名:doc,所以填入缓冲区的 d0‘,’o0‘,’c0,由于目录到此结束,所以NS_LAST属性被设置,表明已经到路径末尾。同时lfn[di] = 0di此时指向c0后面那个内存单元。注意,存储进长目录区是以unicode的形式,而path用的是ANSi码,汉字两个字节,英文字母一个字节


create_name(dj, &path)继续执行mem_set(dj->fn, ' ', 11),先将dj结构体的标准短文件名填充空格if (si) cf |= NS_LOSS | NS_LFN通过这句话的判断,长文件名前面没有空格和.,没有超出8.3格式。当然后面还要继续判断。执行while (di && lfn[di - 1] != '.') di—这句后,di=0,表示没有找到扩展名(doc没有扩展名)dj->fn[i++] = (BYTE)w,通过这个方式doc存进了短文件名存储区dj->fn[NS] = cf,短文件名存储区12个字节,前11个是标准短文件名,最后一个是路径属性字节:是否.目录、大小写标志,超出8.3格式,是否长文件名、是否路径结束标志等等。


所以create_name(dj, &path)的工作主要是根据路径填充dj->lfndj->fn,前者每个字符16Byte,以‘\0’结尾,后者12个字节,以路径的整体属性结尾


 


 


 


执行路径返回follow_path(dj, path),接下来调用dir_find(dj),这个函数的作用是在当前目录(对应dj->sclust下,搜索dj->lfndj->fn对应的目录项,找到以后dj->indexdj->dir指向该目录项的数据。追踪那个进入该函数。


函数dir_find(dj)里,首先dir_seek(dj, 0),将索引定位于0,目录第一项。ord = sum = 0xFF长目录项索引和校验和设为-1c = dir[DIR_Name]取出目录项的第一个字符进行判断,a = dir[DIR_Attr] & AM_MASK取出属性进行判断。建立一个循环,不断移动目录缓冲(每次下移32个字节),通过dir_next(dj, FALSE)来实现。当找到”doc        目录项时,if (!(dj->fn[NS] & NS_LOSS) && !mem_cmp(dir, dj->fn, 11)) break跳出循环,此时的有效信息就是dj->sclust加上dj->indexdj->dir,它指向根目录里的“doc”目录项。


 


执行路径返回follow_path(dj, path),由于last = *(dj->fn+NS) & NS_LAST,标志被置位,很快跳出路径分解的循环,返回到F_opendirDIR djconst void *path)。通过下面这个计算:


             dj->sclust = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO)真正得到了分配给doc目录的起始簇的地址。接下来就可以读取 0:/doc 目录下所有的目录项,并显示它包含文件、目录的属性信息了。


 


 


 


回到主程序执行路径,接下来调用:


f_readdir()这个函数功能是根据DIR结构体提供的信息,填充文件属性结构体,包括文件名字符串、文件大小、文件修改日期等。进入该函数进行追踪。这里就快要找到我的长文件名目录项《这是一个very long的文件.它里面是空的.txt文档》了,它在目录项里是怎样的存在形式呢?


共有29个字(包括空格和.,共占据3个长目录项和一个短目录项:其格式应该如下。



43


t文档


0000H


FFFFH


0FH


00H


Num  


FFFFH


长文件名 


 


FFFFH FFFFH FFFFH FFFFH


0000


FFFFH


FFFFH


02


的文件.


0FH


00H


Num



面是空的.


0000


tx


01


这是一个v


0FH


00H


Num


e


ry lo


0000


ng





~


1


T


X


T


20H


NT


XX


创建时间


短文件名


创建日期


访问日期


起始簇号高位


修改时间


修改日期


起始簇号低位


文件的大小


 


f_readdir()这个函数调用dir_read()函数,读取各个目录项的信息,然后调用get_fileinfo()函数得到文件的各项信息。下面进入dir_read()进行跟踪,看它是怎样对待长目录项的。


进入dir_read()函数,首先ord, sum = 0xFF,读取属性a = dir[DIR_Attr] & AM_MASKif (a == AM_LFN),也就是当遇到长目录索引“43”的时候sum = dir[LDIR_Chksum];c &= 0xBF; ord = c; dj->lfn_idx = dj->index;取得校验和,取得索引(这里是3,使长目录索引指向“43”目录项的索引号。


如果pick_lfn(dj->lfn, dir)返回TRUE则索引自动ord=ord-1变为2。进入pick_lfn(dj->lfn, dir),它的作用是将当前目录项里的长文件名部分写入dj->lfn指向的名字缓冲区。先从偏移26开始存入“t文档”三个unicode字符,然后写入0,返回TRUE。也就是ord=2


继续执行,dir_next(dj, FALSE)dj->index指向下一项,a = dir[DIR_Attr] & AM_MASK,重新取得属性。此时c = dir[DIR_Name]=02,满足(c == ord && sum == dir[LDIR_Chksum] && pick_lfn(dj->lfn, dir))这个表达式,所以索引又被设置为ord=1


同上,再次读取ord=1的那一项填充长文件名缓冲区至此unicode名称已经完全存入dj->lfn指向的缓冲区,并且ord=0,表明长目录项已经结束


通过dir_next()再次循环,接下来dj->index指向对应的短目录项,只要sum_sfn(dir)计算出来的校验和与前面长目录项里取得的校验和相等,dir_read()函数的任务就算完成了。它获得的信息包括:dj->lfnidx(长目录项开始索引),dj->lfn(完整的unicode长文件名),dj->indexdj->dir指向短目录项。执行路径回到f_readdir()。


f_readdir()函数接下来调用get_fileinfo(dj, fno)获取目录项的详细信息。追踪该函数的执行。这个函数共获取六个信息:大小、属性、修改时间、日期、短文件名、长文件名字符串通过读取dj->lfn指向的缓冲区,并将unicode转换为OEM代码)。有了这个文件属性结构体的数据,用户就可以在串口终端显示目录下所有目录项的信息了。


 


 


 


 


 

文章评论8条评论)

登录后参与讨论

用户219698 2010-4-25 12:57

非常感谢您的指点,我也找到一篇文章查看sd中汉字文件名的存储的情况,的确已经是bgk码了,我再好好检查是哪里的问题.

nthq2004 2010-4-25 09:29

另外create_name()是ff.c内部定义的静态函数,用户在外部是不能调用它的。 它的功能实现都在 f_open() 和 f_opendir() 中体现出来了,我们没有必要去调用它。

nthq2004 2010-4-25 09:25

对。显示符合“8.3”格式的短中文名文件不需要将OEM码 转换为 UNICODE码。 只要配置中定义 #define CODE_PAGE 936就行了。 显示中文需要中文字库 以及从汉字OEM码 得到相应汉字在字库中的位置就行了。

用户219698 2010-4-24 23:03

是的,我暂时先显示10个数据,您的意思是打开不了中文名文件与oem的转换无关吗?

nthq2004 2010-4-22 22:37

char record[]={"0:/book/"}; strcpy(&fn[8],filename); 前面这个record数组要写清楚长度,不指明长度,编译器只会分配与你初始化字符串相等的长度。后面这个字符串复制操作可能会覆盖其它数据。 fn = *fno.lfname ? fno.lfname : fno.fname; 前面 *fno.lfname 这里不要用加*, 加了*实际会取出指针所指内容,字符串缓冲区未初始化时可能是0。 实际上这里最好不要用 FILINO结构,直接声明一个字符数组,足够长,把文件名复制进去就行了。 FILINFO结构是读取目录项信息时所用到的结构体。 f_read(&fap,buff,10,&br); p=buff; ShowString16(0,0,p,Black,White); 要使buff[10] = '\0',才能显示10个字节的字符串吧。

用户219698 2010-4-22 22:01

是这样的,我现在把字库,uincode码,oem码都拷进了储存芯片里,支持中文长名字的设置都设置好了,读sd卡里的长名字,中英文都没问题,能正常显示文件名,showString16是调用字库显示的,能自动区分是英文还是中文显示,所以显示文件名这个功能都正常,现在我想依据文件名字来打开读取文件内容,比如ReadSDTXT(“asd.txt”),就能显示里面的内容,而我这个程序现在只能用在英文名文件,中文名,比如ReadSDTXT(“我.txt”)就打不开了,我想应该是要把oem码转换成unicode才行,不知create_name()是不是可以用在这里呢?

nthq2004 2010-4-22 12:22

我把我的理解归纳成以下几点,看对你有没有帮助。 1、如果文件名是中文名,则配置时需要定义: #define CODE_PAGE = 936; 这样程序才能支持中文OEM码,实际上就是GB内码。 2、如果是短文件名,符合8.3格式的,则不需要进行 OEM和UNICODE的转化,不需要支持LFN(长文件名)。 比如"我的文档.txt",打开时跟普通的短英文文件 名处理方式是一样的。但如果中文超过4个字节,则 程序就需要进行长文件名处理,你必须定义: #define LFN 1 否则打开文件函数就会出错。 3、我看了一下你呢程序,在不确定错误发生地点的 情况下,我建议你调用文件操作函数,比如 f_open()的时候,检查它的返回值FRESULT。 4、如果文件里有中文,你在读取文件时读到的一般 是ANSI码,它把英文单字节、中文双字节是混排的 你必须先区分。如果要显示汉字,必须有相应的字库。 我不知道你这个“showString16"函数是怎么处理 汉字内码的?

用户219698 2010-4-21 21:52

你好,大侠,看到你的文章,我受益很多,我现在遇到的一个问题,没办法度取中文文件名的内容,大概是缺少了把oem码转换成unicode,之前我自己写了一个用ff_convert(),但没成功,那其实是不是用create_name()就能转换呢?又是怎么用的?以下是我读txt文件的程序请大侠指教...您的技术笔记真的写得很不错. void ReadSDTXT(char *name) { FILINFO fno; char record[]={"0:/book/"}; char *fn; f_mount(0, &fs); #if _USE_LFN //长名字使能 fn = *fno.lfname ? fno.lfname : fno.fname; #else fn = fno.fname; #endif strcpy(fn,record); //复制根目录 strcpy(&fn[8],filename); //复制名字 f_open(&fap,fn,FA_OPEN_EXISTING|FA_READ);//打开文件 f_read(&fap,buff,10,&br); //读取数据 p=buff; //赋数据首地址 ShowString16(0,0,p,Black,White); //显示数据 f_close(&fsrc); }
相关推荐阅读
nthq2004 2010-05-08 20:04
USB自定义设备驱动02
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />  本来还想编写应用程序测试一下自定...
nthq2004 2010-05-07 21:35
USB自定义设备驱动01
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />  一、USB设备驱动入门1、学习目...
nthq2004 2010-05-04 21:01
智林开发板上实现自定义的USB HID设备
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />  一、自定义HID设备的相关概念1...
nthq2004 2010-05-01 21:58
U盘例程在智林开发板上的移植
 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 一、移植前的准备工作1、有哪些操...
nthq2004 2010-04-30 19:19
U盘实现流程跟踪分析02
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />   二、追踪USB大容量设备的实现...
nthq2004 2010-04-27 21:51
U盘实现流程跟踪分析01
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />   一、追踪USB大容量设备的实现...
我要评论
8
10
关闭 站长推荐上一条 /2 下一条