一、通过格式化命令-看磁盘文件系统的建立过程
1、添加format命令,单步调试
所有的底层驱动函数都已经准备好。添加格式化命令format后,编译下载。
Format命令的执行主要是调用f_mkfs()函数,下面进行单步调试。
以下主要列出函数的主要执行步骤:
res=f_mkfs( 0, 1, 4096 ); //1表示不需要引导扇区。4096是8个扇区。
进入f_mkfs()函数,这里只列出主要执行步骤:
if (disk_ioctl(drv, GET_SECTOR_COUNT, &n_part) != RES_OK || n_part < MIN_SECTOR)
return FR_MKFS_ABORTED;这个函数调用后,n_part=0x000F,3400 = 996 352,这是SD的总块数。allocsize /= SS(fs); 等于8/*Number of sectors per cluster */
n_clst = n_part / allocsize; //等于0x1E680 = 124 544 簇。
if (n_clst >= 0xFFF5) fmt = FS_FAT32; 所以文件系统确定为FAT32类型。
n_fat = ((n_clst * 4) + 8 + SS(fs) - 1) / SS(fs); 等于0x3CE = 974,表示FAT要占据974个扇区。
n_rsv = 33 - partition; 保留扇区32个。
n_dir = 0;
b_fat = b_part + n_rsv; /* FATs start sector 32扇区*/
b_dir = b_fat + n_fat * N_FATS; /* Directory start sector 0x3EE =1006,由于FAT表个数设为1个,所以目录区=FAT起始+FAT占用扇区数*/
b_data = b_dir + n_dir; /* Data start sector */
以上三项确定FAT区域、根目录区、数据区的起始扇区。
disk_ioctl(drv, GET_BLOCK_SIZE, &n) != RES_OK,这个函数调用没有正确返回可擦出扇区的总数。接下来程序会出错,因此退出,修改disk_ioctl()函数后,再次分析。把这个函数返回值直接改为32。并且把FAT表的个数定义为2.
N_FATS改为2后,根目录区、数据区的起始扇区的起始扇区变为0x7BC=1980扇区。继续往下执行。
n = (b_data + n - 1) & ~(n - 1);
n_fat += (n - b_data) / N_FATS;这两句话对fat所占扇区数进行了修正,保证擦除时,以32个扇区为一个单位。
n_clst = (n_part - n_rsv - n_fat * N_FATS - n_dir) / allocsize; =0x1E588。
tbl = fs->win; /* Clear buffer */
mem_set(tbl, 0, SS(fs)); 清零文件系统缓冲区。
mem_set(tbl, 0, SS(fs));
ST_DWORD(tbl+BS_jmpBoot, 0x90FEEB); /* Boot code (jmp $, nop) */
ST_WORD(tbl+BPB_BytsPerSec, SS(fs)); /* Sector size */
tbl[BPB_SecPerClus] = (BYTE)allocsize; /* Sectors per cluster */
ST_WORD(tbl+BPB_RsvdSecCnt, n_rsv); /* Reserved sectors */
格式化命令format参数
上面的工作主要是填充引导扇区缓冲区,也就是常说的DBR扇区缓冲,等所有的参数写好,就可以写回磁盘。
ST_WORD(tbl+BS_55AA, 0xAA55); /* Signature */
if (disk_write(drv, tbl, b_part+0, 1) != RES_OK)
return FR_DISK_ERR; //这就是在写有效引导标志sec[510]=0x55, sec[511]=0xAA。
if (fmt == FS_FAT32)
disk_write(drv, tbl, b_part+6, 1); //FAT32在第六扇区有个备份引导扇区。
for (m = 0; m < N_FATS; m++) {
mem_set(tbl, 0, SS(fs)); /* 1st sector of the FAT */
if (fmt != FS_FAT32) {
n = (fmt == FS_FAT12) ? 0x00FFFF00 : 0xFFFFFF00;
n |= partition;
ST_DWORD(tbl, n); /* Reserve cluster #0-1 (FAT12/16) */
} else {
ST_DWORD(tbl+0, 0xFFFFFFF8); /* Reserve cluster #0-1 (FAT32) */
ST_DWORD(tbl+4, 0xFFFFFFFF);
ST_DWORD(tbl+8, 0x0FFFFFFF);/* Reserve cluster #2 for root dir */ } //簇0和簇1保留,簇2分配给根目录区。
if (disk_write(drv, tbl, b_fat++, 1) != RES_OK)
return FR_DISK_ERR;
mem_set(tbl, 0, SS(fs)); /* Following FAT entries are filled by zero */ //接下来所有的扇区都清0,表示该簇未被占用。
for (n = 1; n < n_fat; n++) {
if (disk_write(drv, tbl, b_fat++, 1) != RES_OK)
return FR_DISK_ERR;
} //第一次是写从0x21扇区开始,总共0x3CF个扇区(第一个扇区已经写了)。第二次开始时是备份引导扇区,0x20+0x3D0=0x3F0。同理,第一扇区与0x21扇区相同,后面都清零。
Format命令要执行较长的时间,主要就是FAT表接近2000个扇区要清零。
m = (BYTE)((fmt == FS_FAT32) ? allocsize : n_dir); m=8,每簇8扇区。
do {
if (disk_write(drv, tbl, b_fat++, 1) != RES_OK)
return FR_DISK_ERR;
} while (--m); //以上部分是将根目录区的8个扇区清零,表示目录项未被占用。
if (fmt == FS_FAT32) {
ST_WORD(tbl+BS_55AA, 0xAA55);
ST_DWORD(tbl+FSI_LeadSig, 0x41615252);
ST_DWORD(tbl+FSI_StrucSig, 0x61417272);
ST_DWORD(tbl+FSI_Free_Count, n_clst - 1); //根目录区已经用掉了一簇。
ST_DWORD(tbl+FSI_Nxt_Free, 0xFFFFFFFF);
disk_write(drv, tbl, b_part+1, 1); //分别写入1号和7号扇区。
disk_write(drv, tbl, b_part+7, 1);
} //这是写FAT32文件系统的FSI扇区,包括空闲簇总数和上次分配的簇号。
FAT32文件系统的格式化到此完成。
2、再以Fdisk的方式格式化一次,这时候格式系统要写MBR扇区,FAT引导扇区不在0号扇区了。同时MBR要建立分区表,以到DBR引导扇区。其过程与上述差不多,只是多执行了以下代码,详细过程就不叙述了。
if (!partition) {
DWORD n_disk = b_part + n_part;
mem_set(fs->win, 0, SS(fs));
tbl = fs->win+MBR_Table; //分区表从0x1BE开始。
ST_DWORD(tbl, 0x00010180); /*Partition start in CHS */ Table[0x1BE] =0x80,表明该分区是活动扇区。00表示开始柱面,01、01表示开始扇区、开始磁头。
if (n_disk < 63UL * 255 * 1024) { /* Partition end in CHS */
n_disk = n_disk / 63 / 255;
tbl[7] = (BYTE)n_disk; //表示结束的柱面。
tbl[6] = (BYTE)((n_disk >> 2) | 63); //结束的扇区。
} else {
ST_WORD(&tbl[6], 0xFFFF); //
}
tbl[5] = 254; //结束的磁头。
if (fmt != FS_FAT32) /* System ID */
tbl[4] = (n_part < 0x10000) ? 0x04 : 0x06;
else
tbl[4] = 0x0c; // 表示该分区类型为win95 FAT32
ST_DWORD(tbl+8, 63); /*起始扇区0x3F in LBA */
ST_DWORD(tbl+12, n_part); /*分区的大小,总扇区数减去MBR及其占据的一个柱面。*/
ST_WORD(tbl+64, 0xAA55); /* Signature */
if (disk_write(drv, fs->win, 0, 1) != RES_OK)
return FR_DISK_ERR;
partition = 0xF8; //MBR标志。
} else {
partition = 0xF0;
}
3、观察在有MBR区域的情况下,如何检查文件系统
fmt = check_fs(fs, bsect = 0); /*检查0扇区的时候,没有发现FAT文件系统扇区,但是有0x55 0xAA标志,说明这是有效磁盘,但是返回1. */
if (fmt == 1) { /* 表明可能存在分区*/
/* Check a partition listed in top of the partition table */
tbl = &fs->win[MBR_Table + LD2PT(vol) * 16]; /* Partition table */
if (tbl[4]) { 实际这里应该是0x0c,表示FAT32系统。
bsect = LD_DWORD(&tbl[8]); /* 这个是文件系统引导扇区的号码。*/
fmt = check_fs(fs, bsect); /* 再到这个扇区检查是否存在FAT文件系统标志。*/ } }
执行过后,仍然能够建立完整的文件系统信息结构体,只是里面的FAT分配起始扇区、数据区起始扇区地址相对没有MBR的时候改变了,其它都差不多。
二、将SD卡格式化成具有两个分区的磁盘。
1、目的
(1)深入理解MBR、DPT等概念。
(2)修改ff.c中的f_mkfs函数,得到一个新函数,f_format(u8 partition,u16 allocsize),前一个参数是指磁盘等分的个数,接受1、2、3、4四个参数,默认为1,最大为4。后一个参数是指每簇占用的字节数。
(3)添加命令fdisk,调用上述函数。执行完成后,用读卡器在PC上读取该SD卡,应该显示两个可移动磁盘。
2、f_format()函数的编写
首先新建一个文件fext.c,该文件就实现一个函数f_format.c,首先将f_mkfs()函数复制过来,在此基础上修改。
编译后,首先解决警告和错误:包含头文件ff.h和diskio.h。引用了ff.c中的静态函数mem_set()和mem_clr(),复制过来。定义Null为0。将FATFS * FatFs[_Drives]做外部声明。
同时发现,不同c源文件中#define 同样的宏相互之间是不影响的。说明预处理的时候是一个一个文件处理的,不检查相互之间的关联。但同一个文件中,一个宏不能两次定义。
#ifndef NULL
#define NULL 0
#endif //采取这种方式,主要是防止其他包含的头文件也对NULL进行了定义。
函数中主要修改的地方就是:
n_part = n_part /drv; //进行drv等分。每个磁盘的扇区总数就是这么多。
在DPT对应增加的分区部分,填好分区表16个字节。特别重要的是四个地方:0字节写为00或0x80,第四字节写入0xc0表示FAT32系统。(第一次调试不到新磁盘,就是由于这个字节默认为0)。第8-11写入分区引导扇区的线性扇区地址。第12-15写入该磁盘分区的大小。
for ( i="0"; i<drv; i++){ //每个分区都要做一次,DBR的写入,FAT分配表初始化,根目录初始化。b_part+= i*n_part; //调整该分区引导扇区和FAT表起始地址。
b_fat=b_part + n_rsv;
3、其它修改的地方
定义_DRIVE为2,定义_MULTI_PARTITION为1,表示支持多分区。同时初始化磁盘物理驱动与分区号转换结构体(每个逻辑磁盘对应一个结构体)。
主程序中也要定义两个文件系统结构体,每个对应一个磁盘。然后分别调用f_mount()函数。
4、调用执行
void UartCmdFdisk(u8 argc,void **argv)
{
FRESULT res;
res=f_format( 2, 2048 ); /2表示格式化成两个磁盘。一簇是4个扇区、2048字节。
执行完这个函数后,磁盘被分成两个分区。
实际结果是,命令界面上可以查询到两个分区。但是利用读卡器放到PC上,却只看到250M的第一分区,不知什么原因。
三、添加命令fchdrive、fmove。
1、目的
(1)fchdrive:在支持相对目录的情况下改变当前逻辑磁盘。
(2)fmove:两个参数,将文件移动到另一个位置,可以改名,也可以保留原名。
2、fchdrive 命令的实现
(1)添加命令支持
这个不再赘述。
(2)实现代码
void UartCmdFChDrive(u8 argc,void **argv){
BYTE Drive;
FRESULT res="FR"_INV ALID_DRIVE;
if ( *( (BYTE*)argv[1]+1 )==':' ){ 检查参数,是不是1:类似格式。
Drive =*( (BYTE*)argv[1]) - '0'; 取出磁盘号
res=f_chdrive( Drive ); }
if ( res!=FR_OK) { Uart_PutString( "Invalid path!\r\n");return;}
Uart_PutString( "Current disk is changed!\r\n");
}
*( (BYTE*)argv[1])这个意思是先将argv[1]转换为BYTE指针,再取出里面的数据(BYTE型,一个字节。)
以下是实现的截图:
3、fmove命令的实现
(1)这个命令带两个参数:第一个是文件的原有路径,第二个是新路径,同时可以更改文件名。(2)实现的原理
以创建新文件的方式打开新路径的文件。这样会创建一个目录项,填充一个文件信息结构体。然后打开原有路径的文件,也会得到一个文件信息结构体。将旧结构体的文件大小、起始簇号复制到新的结构体,然后更新新文件的信息。然后删除原有路径的文件。移动操作完成。
(3)代码编写
需要两次打开文件,所以需要两个文件信息结构体。
res = f_open ( &FileOld, (const char *)argv[1], FA_WRITE );
res = f_open ( &FileNew, (const char *)argv[2], FA_CREATE_NEW );
FileNew.fsize = FileOld.fsize; // 改变新文件的大小等于原文件
<_clust = _clust; //新文件的簇等于原文件的簇。
FileNew.flag |= FA__WRITTEN; //文件属性已改变,需要更新
res=f_close ( &FileNew ); //关闭时进行更新。
res=f_unlink ( (const char *)argv[1] ); //删除原文件
编译,下载测试,功能正常。以下是实现的截图。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论