从8月8号开始,连续一个月利用每天下班时间和周末的时间终于初步完成了一个电子工程师的电路板名片,就像U盘一样,不过这个FLASH只有64KB的大小,用的单片机是C8051F320,是一个USB型的单片机。这次获取的教训是一开始想好要做什么事,无论如何要坚持下来,也许困难有很大,但是和困难磨磨突然会发现原来眼前的困难也是有解决的办法的,突然的豁然开朗。
整体结构如下:
我打算把自己所做的按照我自己调试的过程写下来,会连续发几篇博客,欢迎关注我哦。FAT16文件系统是在最后才搞出来,趁热打铁,把它作为第一篇先记下来。
电子工程师电路板名片之FAT16文件系统
FAT16文件系统的介绍在这篇博客介绍比较详细:http://blog.csdn.net/menghnhhuan/article/details/4270168
FAT16文件系统在U盘、MMC卡、SD卡、以及一些小型系统上用的比较多,我在MX25L512这个64KB的FLASH安装的文件系统如下:
注:第一个8*512中包含DBR区共一个512 Bytes,其余7个512 Bytes内容为0,但这8个512 Bytes都是保留扇区
一、DBR区(DOS BOOT RECORD):即操作系统引导区,本身只占512Bytes,但是从0地址开始有8个保留扇区(包括DBR区),所以FAT1的起始地址为8×512Bytes
注:BPB区记录本分区的起始扇区,结束扇区,文件存储格式,磁盘介质描述符,根目录大小,FAT个数,分配单元大小等。
下面是我程序中具体去设置DBR的512个字节
code unsigned char DBR[] =
{
0xeb,0x3c,0x90, //跳转指令,跳转到0x3c+2的引导程序代码处
0x4d,0x53,0x44, 0x4f, 0x53, 0x35, 0x2e, 0x30, //厂商标志和OS版本号 //BPB(BIOS parameter block)
0x00,0x02, //扇区字节数512(合法的有512,1024,4096),C8051F单片机是大端模式
0x01, //每簇扇区数(合法的有1,2,4,8,16,32,64,128)
0x08,0x00, //保留扇区数,这样可以知道FAT1区起始地址,为8×512
0x02, //FAT数目
0x00,0x02, //根目录数,典型为512个目录,每个文件或文件夹名长度为32 bytes
0x80,0x00, //小扇区数,(对于大于65536个扇区的分区,本字段为0,采用大扇区表示,)
0xf8, //媒体描述符,0xf8表示硬盘,0xf0表示3.5寸软盘
0x08,0x00, //每个FAT占用的扇区数
0x3F,0x00, //每道扇区数
0xFF,0x00, //磁头数
0x00,0x00,0x00,0x00, //隐藏扇区数
0x00,0x00,0x00,0x00, //大扇区数,如果小扇区字段为0,本字段就包含了FAT16中的总扇区数
//如果小扇区字段不为0,那么本字段为0 //BPB扩展字段
0x80, //物理驱动器,硬盘被标志为0x80,软盘驱动器被标志为0x00
0x00, //保留字段
0x29, //扩展引导标签,要有能被windows 2000所识别的0x28或0x29
0xA8, 0x8B, 0x36, 0x52, //卷序号,在格式化磁盘时所产生的一个随机号,助于区分磁盘
0x4E, 0x4F, 0x20, 0x4E, 0x41, 0x4D, 0x45, 0x20, 0x20, 0x20, 0x20, //卷标
0x46, 0x41, 0x54, 0x31, 0x36, 0x20, 0x20, 0x20, //文件系统类型FAT16 //操作系统引导代码
0xf1, 0x7d,
0xfa, 0x33, 0xc9, 0x8e, 0xd1, 0xbc, 0xfc, 0x7b, 0x16, 0x07, 0xbd, 0x78, 0x00, 0xc5, 0x76, 0x00,
0x1e, 0x56, 0x16, 0x55, 0xbf, 0x22, 0x05, 0x89, 0x7e, 0x00, 0x89, 0x4e, 0x02, 0xb1, 0x0b, 0xfc,
0xf3, 0xa4, 0x06, 0x1f, 0xbd, 0x00, 0x7c, 0xc6, 0x45, 0xfe, 0x0f, 0x8b, 0x46, 0x18, 0x88, 0x45,
0xf9, 0xfb, 0x38, 0x66, 0x24, 0x7c, 0x04, 0xcd, 0x13, 0x72, 0x3c, 0x8a, 0x46, 0x10, 0x98, 0xf7,
0x66, 0x16, 0x03, 0x46, 0x1c, 0x13, 0x56, 0x1e, 0x03, 0x46, 0x0e, 0x13, 0xd1, 0x50, 0x52, 0x89,
0x46, 0xfc, 0x89, 0x56, 0xfe, 0xb8, 0x20, 0x00, 0x8b, 0x76, 0x11, 0xf7, 0xe6, 0x8b, 0x5e, 0x0b,
0x03, 0xc3, 0x48, 0xf7, 0xf3, 0x01, 0x46, 0xfc, 0x11, 0x4e, 0xfe, 0x5a, 0x58, 0xbb, 0x00, 0x07,
0x8b, 0xfb, 0xb1, 0x01, 0xe8, 0x94, 0x00, 0x72, 0x47, 0x38, 0x2d, 0x74, 0x19, 0xb1, 0x0b, 0x56,
0x8b, 0x76, 0x3e, 0xf3, 0xa6, 0x5e, 0x74, 0x4a, 0x4e, 0x74, 0x0b, 0x03, 0xf9, 0x83, 0xc7, 0x15,
0x3b, 0xfb, 0x72, 0xe5, 0xeb, 0xd7, 0x2b, 0xc9, 0xb8, 0xd8, 0x7d, 0x87, 0x46, 0x3e, 0x3c, 0xd8,
0x75, 0x99, 0xbe, 0x80, 0x7d, 0xac, 0x98, 0x03, 0xf0, 0xac, 0x84, 0xc0, 0x74, 0x17, 0x3c, 0xff,
0x74, 0x09, 0xb4, 0x0e, 0xbb, 0x07, 0x00, 0xcd, 0x10, 0xeb, 0xee, 0xbe, 0x83, 0x7d, 0xeb, 0xe5, 0xbe, 0x81, 0x7d, 0xeb, 0xe0, 0x33, 0xc0, 0xcd, 0x16, 0x5e, 0x1f, 0x8f, 0x04, 0x8f, 0x44, 0x02,
0xcd, 0x19, 0xbe, 0x82, 0x7d, 0x8b, 0x7d, 0x0f, 0x83, 0xff, 0x02, 0x72, 0xc8, 0x8b, 0xc7, 0x48,
0x48, 0x8a, 0x4e, 0x0d, 0xf7, 0xe1, 0x03, 0x46, 0xfc, 0x13, 0x56, 0xfe, 0xbb, 0x00, 0x07, 0x53,
0xb1, 0x04, 0xe8, 0x16, 0x00, 0x5b, 0x72, 0xc8, 0x81, 0x3f, 0x4d, 0x5a, 0x75, 0xa7, 0x81, 0xbf,
0x00, 0x02, 0x42, 0x4a, 0x75, 0x9f, 0xea, 0x00, 0x02, 0x70, 0x00, 0x50, 0x52, 0x51, 0x91, 0x92,
0x33, 0xd2, 0xf7, 0x76, 0x18, 0x91, 0xf7, 0x76, 0x18, 0x42, 0x87, 0xca, 0xf7, 0x76, 0x1a, 0x8a,
0xf2, 0x8a, 0x56, 0x24, 0x8a, 0xe8, 0xd0, 0xcc, 0xd0, 0xcc, 0x0a, 0xcc, 0xb8, 0x01, 0x02, 0xcd,
0x13, 0x59, 0x5a, 0x58, 0x72, 0x09, 0x40, 0x75, 0x01, 0x42, 0x03, 0x5e, 0x0b, 0xe2, 0xcc, 0xc3,
0x03, 0x18, 0x01, 0x27, 0x0d, 0x0a, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x73, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x20, 0x64, 0x69, 0x73, 0x6b, 0xff, 0x0d, 0x0a, 0x44, 0x69, 0x73, 0x6b,
0x20, 0x49, 0x2f, 0x4f, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0xff, 0x0d, 0x0a, 0x52, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2c, 0x20, 0x61,
0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6e,
0x79, 0x20, 0x6b, 0x65, 0x79, 0x0d, 0x0a, 0x00, 0x49, 0x4f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x53, 0x59, 0x53, 0x4d, 0x53, 0x44, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x53, 0x59, 0x53, 0x80, 0x01,
0x00, 0x57, 0x49, 0x4e, 0x42, 0x4f, 0x4f, 0x54, 0x20, 0x53, 0x59, 0x53, 0x00, 0x00, //扇区结束标志
0x55, 0xaa
};
二、FAT16文件系统
FAT(File Allocation Table)文件分配表,是微软在FAT文件系统中用于磁盘数据文件索引和定位引进的一种链式结构,有两个,另一个是备份。FAT文件系统之所以有12、16、32不同的版本之分,其根本在于FAT表用来记录任意一簇链接的二进制位数。以FAT16为例,每一簇在FAT表中占据两个字节(二进制16位)。所以,FAT16最大可以表示的簇号为0xFFFF,以32K为一簇的大小,FAT16可以管理的最大磁盘空间为:32KB×65536=2048B,所以FAT16最大支持2GB的分区。
因此FAT表以2个字节为一个记录项,通常第1、2个记录项用作介质描述(0xF8、0xFF、0xFF、0xFF),从第3个记录项开始记录除根目录外的其他文件夹的簇链情况。具体FAT16表如何用很多个簇号链接表示见http://bbs.ednchina.com/BLOG_ARTICLE_226477.HTM,一个文件结尾的簇号为0xFFFF。
三、文件目录
跟目录的位置紧随FAT2表之后,系统以32个字节为单位进行目录文件所占簇的分配,文件目录必须占据32个扇区
0x1A~0x1B为文件开始簇号,例如簇号为02 00,因为是使用小端模式,转化后簇号为00 02,根据这个可以找到文件第一个簇号在FAT1中的位置,0x1000H+02H*02H=1004H,因此可以计算文件第一簇数据存放地址为56×512(用户数据起始地址)+(02H-02H)×512(1簇占1个扇区),而在FAT1表1004H的地址上的数据为03 00,转换后为00 03,那么可以计算第二簇的数据存放地址为56×512(用户数据起始地址)+(03H-02H)×512(1簇占1个扇区),一直到FAT1表的某两个字节为FF FF时代表文件结束。
四、处理PC端发来的读写文件系统的固件程序
U盘固件提供的功能:当主机要求读取某个扇区,定位到该扇区,从扇区里面读出内容,并输出给主机,他不需知道U盘是什么文件,只需把数据读出来,主机通过读取的扇区的内容来判断是什么文件系统。
FLASH知识:
64kB的flash本身的sector为4kB,Page为256Byte
Page为写入数据的最小单位,sector为数据擦除的最小单位。
而现在作为文件系统,我已经修改为:LBA(Logical BlockAddress)128个,每个Block的大小为512Byte,128*512B=65536B=64kB。
处理好LBA和Pagenum的关系
LBA指起始逻辑块地址,即flash中的第几个Block(512Byte)
Pagenum为有多少个逻辑块地址,最大为上面计算的128个Block。
因为现在文件系统Block的大小为512Bytes,而flash的page大小为256,所以每次写512字节要分两次来写。
SPI_Write_Page(buff,0,2*(LBA+Pagenum)-2);
SPI_Write_Page(buff,0,2*(LBA+Pagenum)-1);
case Read_10://回发MBR主引导扇区数据
//sector:擦除数据的最小单位
//page读写数据的最小单位
// LBA:logical block address
// PageNum:number of LBA
// 16 sectors 1 sector=16 page 1 page=256 byte
if(PageNum>)
{
for(i=;i<=PageNum;i++)
{
PageNumL=*(LBA+i-);
SPI_READ_Page(, PageNumL);
for(t=;t<;t++)
{
FIFO_Write(buff + t*MaxSize, FIFO1, MaxSize);
SFR_Write(EINCSRL,0x01);
while(SFR_Read(EINCSRL) & 0x01);
}
PageNumH=*(LBA+i-)+;
SPI_READ_Page(, PageNumH);
for(t=;t<;t++)
{
FIFO_Write(buff + t*MaxSize, FIFO1, MaxSize);
SFR_Write(EINCSRL,0x01);
while(SFR_Read(EINCSRL) & 0x01);
}
}
}
csw=;
sendDebug(Read_10);
sendDebug(LBA);
sendDebug(PageNum);
break;
case Write_10://host向slave发数据并写在U 盘里面
if(PageNum>)
{
SPI_SECTOR_ERASE(LBA/);
for(i=;i<=PageNum;i++)
{
for(j=;j<;j++)
{
for(t=;t<;t++)
buff[*j+t]=SFR_Read(FIFO1);
SFR_Write(EOUTCRSL,0x00);
}
PageNumL=*(LBA+i-);
SPI_WRITE_Page(buff, , PageNumL);
for(j=;j<;j++)
{
for(t=;t<;t++)
buff[*j+t]=SFR_Read(FIFO1);
SFR_Write(EOUTCRSL,0x00);
}
PageNumH=*(LBA+i-)+;
SPI_WRITE_Page(buff, , PageNumH);
}
}
flags.epin1=;
csw=;
sendDebug(Write_10);
sendDebug(LBA);
sendDebug(PageNum);
break;