贡献我的FAT32文件系统代码以及相关资料,里面还有我的代码库,大家可以拾掇一下

2020-01-26 12:53发布

经过一个多星期的努力,把以前写的文件系统的功能拓展了一下,也只是完成了核心的任意读写的功能

现在把代码献给大家,希望大家喜欢

介绍介绍功能:
fread和fwrite是核心的功能(个人这个认为的)另外还有fseek,可以读写任意目录下的文件(创建和删除文件的功能我还没写)的任意字节

其实网上很多代码都有这些功能,那么这个代码有哪些不同?(感觉我好像在推销产品 = = )(让菜鸟自夸一下,勿喷哈~)

1. 每个文件内部花了16个字节去维护了一个簇链,如果有一个好几M的文件在物理分布上是连续的,那么,如果你是一直顺序读这个文件,会很流畅,不需要

去读FAT表,也就是速度会快一点,(而且fread本来是基于缓存的,但是我留出了一种更高效的用法,详细内容见代码里面的注释)

2. 个人认为内存开销还是比较小的,可以很容易移植到另的平台上(废话文件系统都应该这样。。。)

3. 我认为最大的优点就是,能支持同时多文件操作,等下会演示,当然,在51单片机上这样做效率会偏低,不过总会有应用场合的,比如说,你在处理一个文件的同时,在要利用字库文件等各种辅助的系统文件。刚才说到效率偏低,因为是利用了以时间换空间的原理,我也留出了修改的接口,在比较大的RAM上想提高效率,或者你只想处理一个文件(这种情况下没什么必要改,因为处理方式本身对效率影响不大,我认为从算法分析的角度上讲,影响速度的主要是硬件驱动的问题),都可以对代码进行一些修改

也有最明显的缺点
    因为我是菜鸟级的,所以文件系统模块的代码特别长,超过2K行了。。。我先在这里说明了,所以看到代码以后就不要喷我了。。。

下面演示我测试的内容:
MUC用的是STC12C5A60S2,存储器是SD卡
因为还没有增加创建文件的功能,所以我先在电脑上新建了两个文件,hello.txt,new.txt
然后在程序里同时打开文件,写入数据,最后同时读取写入的内容:
然后这个是串口读到的信息:

DATE :Feb 02 2012 TIME :13:32:43

SD init success!

fat_fs init success!

I'm going to open file 1.

file1 open succeed.

I'm going to open file 2.

file2 open succeed.

fwrite write 13 bytes to file1
fwrite write 19 bytes to file1file2 is closed.

file open succeed.

fread read 13 bytes to file1

fread read 19 bytes to file2

now the contents of buffer :hello~ourdev~my name is Pony279~

最后把卡放到PC上看看:



为了证明这个是多文件操作,我就贴上测试的代码
void main()
{
        u16 xdata temp;

        Debug_Init();

        //初始化
        //初始化动态内存
        init_mempool(mem_pool, MEM_POOL_SIZE);
        SPI_Init();
        while(SD_Init())
        {
                DB_Delay_ms(1000);
        };

        DB_SendString("SD init success! ");
       
        if(FileSysInit())
        {
                DB_SendString(" fat_fs init failed! ");
                while(1);
        }
        else
        {
                DB_SendString(" fat_fs init success! ");
        }

        DB_SendString(" I'm going to open file 1. ");
        p_file1 = fopen(name1, FILE_READ | FILE_WRITE);
        if(p_file1 == NULL)
        {
                DB_SendString(" open file error! ");
                while(1);        //错误,不要进行下去了
        }
        else
        {
                DB_SendString(" file1 open succeed. ");
        }

        DB_SendString(" I'm going to open file 2. ");
        p_file2 = fopen(name2, FILE_READ | FILE_WRITE);
        if(p_file2 == NULL)
        {
                DB_SendString(" open file error! ");
                while(1);        //错误,不要进行下去了
        }
        else
        {
                DB_SendString(" file2 open succeed. ");
        }

        temp = fwrite(p_file1, str1, StringLength(str1));
        DB_SendString(" fwrite write ");
        DB_SendDec(temp);
        DB_SendString(" bytes to file1");

        temp = fwrite(p_file2, str2, StringLength(str2));
        DB_SendString(" fwrite write ");
        DB_SendDec(temp);
        DB_SendString(" bytes to file1");

        if(fclose(p_file2) == 0)
        {
                DB_SendString("file2 is closed. ");
                p_file2 = NULL;
        }
        else
        {
                DB_SendString("fclose error! ");
                while(1);
        }

        p_file2 = fopen(name2, FILE_READ | FILE_WRITE);
        if(p_file2 == NULL)
        {
                DB_SendString(" open file error! ");
                while(1);        //错误,不要进行下去了
        }
        else
        {
                DB_SendString(" file open succeed. ");
        }

        fseek(p_file1, 0, SEEK_SET);        //重新定位到文件开始
        temp = fread(p_file1, buffer, StringLength(str1) + 5);
        DB_SendString(" fread read ");
        DB_SendDec(temp);
        DB_SendString(" bytes to file1 ");

        temp = fread(p_file2, buffer + temp, StringLength(str2) + 5);
        DB_SendString(" fread read ");
        DB_SendDec(temp);
        DB_SendString(" bytes to file2 ");

        DB_SendString(" now the contents of buffer :");
        DB_SendString(buffer);

        //不再使用文件后一定要关闭文件特别是对于写入过的文件
        fclose(p_file1);
        fclose(p_file2);

        while(1);
}



最后上传工程代码:
(呃。。。这个工程是专门用来开发各种驱动的,所以有我的现在用的代码库在里面,很多东西,所以编译的时候要参考这个帖子的四楼:
http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=5090488&bbs_page_no=1&search_mode=3&search_text=Pony279&bbs_id=9999

声明:文件系统部分的代码欢迎用于各种用途,但使用时请注明出处。其它模块的代码,只供学习交流,禁止用于商业(因为有些代码不是我写的。。。)
点击此处下载 ourdev_715583BLXRME.zip(文件大小:240K) (原文件名:Test-20120202.zip)
代码里面都有注释希望大家喜欢

补充内容 (2012-3-25 15:14):
文件写功能的应用,请看这里
http://www.ourdev.cn/thread-5451505-1-1.html
需要注意的时,那个帖子的代码和这里的代码有些不同,和原来的不兼容了。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
44条回答
Pony279
1楼-- · 2020-01-27 21:40
简单介绍下代码的结构好了


先讲从外部看,文件系统模块的功能

文件系统本身已经算是抽象层的了,但是也需要硬件(存储器,我这里是SD卡)的支持,所以需要提供两个驱动函数,一个写扇区一个读扇区,根据你自己的硬件的不同,可以通过宏来配置,在我的头文件里是这样定义的
#define        ReadSector(sector_address, buffer) SD_ReadSingleBlock(sector_address, buffer)
#define WriteSector(sector_address, buffer) SD_WriteSingleBlock(sector_address, buffer)
在文件系统内部,只使用这两个宏,这样就实现了硬件无关性

然后介绍介绍给外部提供有哪些函数,这些函数在定义那里都有详细说明功能,我这里只写名字,相信学过C的都看得懂:
File* fopen(const u8* name_x,u8 mode);
u16 fread(File* p_file,u8* buffer,u16 size);
u16 fwrite(File* p_file,u8* buffer,u16 size);
u8 fseek(File* p_file, signed long int offset, u8 fromwhere);
u8 fclose(File* p_file);

u8 ChangeDir(u8* path, u8 len, File* p_dir);  //改变当前目录的,第三个参数保留作拓展用的,一般为空就可以了

这三个函数是扫描当前目录下有哪些文件,然后得到这些文件的文件名的,参数也是保留作拓展用的
u8 MoveToFirstFile(File* p_dir);
u8 MoveToNextFile(File* p_dir);
u8 GetShortFileNameX(File* p_dir, u8* buf);

-------------------------------------------------------------------------------------------------------------------------------------

然后简单讲下文件系统的实现

在FAT文件系统中,文件的存储是一种链表结构,这种结构记录在FAT表中,在初始化的时候通过读第0扇区可以得到一些基本的信息(如果第0扇区是MBR,那么还要去找DBR的位置)

然后遵循这种结构,边看资料边写代码,读写的功能都不难实现(想是这样想的,其实写起代码来来是比较麻烦的),

还有一点就是我在调试的时候通过串口信息发现,很多时候一个文件的存放在物理上是连续的,所以我就为每个被打开的文件加上16个字节去记录文件的分布情况,如果文件是完全连续的,那么读文件的时候就会非常流畅
(注:其实这里说的被打开只是从逻辑上看好像是这样,因为得到一个文件的信息和操作一个文件需要开辟一些内存空间嘛,在不使用这个文件的时候又要回收这些空间嘛,回收的过程就是关闭文件啦)


然后就是多文件操作的实现,我一开始以为不可能的,但是后来理了下思路后发现,是可以的。
我只有一个512 BYTE的缓冲区,但是在每次读写文件前,都可以记录下当前在使用缓冲区的文件,并记录一些文件是否有被写入的信息,然后到另一个文件要使用缓冲区时,就根据之前记录的信息(比如说上一个文件如果有写入内容,那么就需要先把去更新缓冲区的内容写到相应的扇区)

这样,从逻辑上看,每个被打开的文件都独占有一个512字节的缓冲区,文件内部只需要记录缓冲区对应的扇区地址,和文件是否被写入新数据的信息就可以了

相信大家很容易看出,我这样处理会带来效率问题,但是,多文件操作我也只能这样实现了,如果只是操作一个文件,那么中间的几句判断对总体效率的影响是很小的。
而且我也留出了让用户去修改的接口,可以改变我的多文件实现机制(就是文件创建的时候怎样分配缓存,文件操作前怎样处理缓存),但是一般不需要去改。

--------------------------------------------------------------------------------------------------------------------

再说说文件系统依赖的一些其它的模块


debug.h 调试的时候辅助用的,如果需要这个模块,那么还需要串口模块,如果不需要,在包含之前通过一个宏定义#define NODEBUG就可以解决,相应的代码就全部变成空语句了

utilities.h 一些比较简单又常用的函数,比如说StringCopy之类的,因为不想依赖没有源码的库,所以一些的函数就自己实现了

memory.c  这块代码是从KEIL的安装目录下的malloc.c free.c 和 init_mempool.c 三个文件整合起来的,代码没有改动,使用也很
          简单,只需要定义一个比较大的数组,传给init_mempool去初始化,这样你就有动态内存了。每打开一个文件,都会在动态
          内存里申请一些空间,如果你只想操作一个文件,那个比较大的数数组(叫内存池)的大小定义到60就可以了

-------------------------------------------------------------------------------------------------------------------

然后介绍整个代码库的一些东西


先说硬件无关的:
type.h  一些类型定义,没什么好说的

macro_functions.h
         这里面有一些提高代码阅读性的宏,比如说BIT(x)  SetBit() 通过使用这些宏,可以在写代码的时候和那些烦人的与或非说88了

utilities 一些简单的常用的函数,也没什么好说的

debug.h  这个不算和硬件无关,但是推荐那些经常为调试程序焦头烂额的菜鸟们看看,因为这种调试方式很好,调试文件系统的时候很多地方都是靠这种方式去定位错误的。对于没有仿真器的我来说,这种调试方式就是法宝哇~(其实也就是简单的串口信息,只是过是消除了你写太多零散的调试语句后不知道哪些该删哪些不该删的顾忌。。。一个宏定义_解决)


另外就是一些和硬件有关的了,有些和传统51单片机兼容,有些不兼容,因为我用的片子是STC12C5A60S2,定时器中断部分应该是兼容的,GPIO应该也是。

核心思想就是,我老是记不住那些寄存器,所以我就把那些烦人的东西全部用宏来封装了,比如说,我要重新设置定时器0的初值,就去头文件找到Timer0_AssignCounter,复制过来,写个参数就行了,(其实如果代码编辑器强大的话完全可以直接写关键字,从提示里就可以找到),不损失效率,又提高了阅读性,何乐而不为呢?
也有损失效率的,比如说GPIO_Config和相关宏GPIO_IO,GPIO_I等等...,是配置IO口的模式是输入,输出还是高阻之类的,但是这些配置一般只出现在初始化阶段,基本上是可以忽略那点效率的
madswan
2楼-- · 2020-01-27 22:13
mark
llysc
3楼-- · 2020-01-28 02:56
 精彩回答 2  元偷偷看……
LXM_0922
4楼-- · 2020-01-28 07:26
好贴子,顶
hefq
5楼-- · 2020-01-28 09:02
标记
dingliming
6楼-- · 2020-01-28 14:39
不错

一周热门 更多>