STM32的FATFS文件系统移植的debug过程
来源:互联网 发布:俄勒冈大学建筑系 知乎 编辑:程序博客网 时间:2024/06/06 20:40
对于FATFS文件系统的移植,正点原子已经有比较完整的移植视频和相应源码。因此关于FATFS内部诸如diskio.c、ff.c和ffconf.h等文件的功能和修改这里就不再详述。博主在这里主要共享一下自己debug的一些过程(主要是针对FR_NO_FILESYSTEM),希望能让网友们在移植过程中少走一些弯路。
首先,对于移植FATFS,必经的几个步骤我还是再这里概述一下。
1. 移植好官方提供的.c和.h文件。(详情参照正点原子)
2. 编写diskio.c的硬件连接部分代码。
3. 配置好ffconf.h内部的控制用宏变量。
4. 编写好内存管理部分。这一块最开始我也没多大注意,但是十分必要。最简单的表述就是,你要把一个数据块写到SD卡中,你先得在内存中开辟一块空间来存放这些数据才行。
其次,博主描述一下自己遇到的第一个BUG。在f_mount挂载SD卡的时候,通过串口观察该函数的FREASULT型返回值,观察到的始终都是error 13: FR_NO_FILESYSTEM。为了弄清楚这个BUG的来源,我们最好弄清楚SD卡的文件结构以及FATFS文件系统本身的工作原理。
SD卡的文件结构:
在f_mount之前,博主先在PC机上格式化SD卡为FAT32格式。对于SD卡等存储介质,我们需要了解,它一般都有两个地址,一个是物理地址一个是逻辑地址,逻辑地址往往是物理地址基础上加一个偏移量,即逻辑地址零 = 物理地址零 + 偏移。
考虑已格式化为FAT32格式后,在物理地址零处的最初是的512个字节是MBR(Master Boot Record),即主引导记录。其中的前446个字节为引导代码,我们这里不做详解。接下来的64个字节为分区表,其中16个字节为一组总共四组,每一个组都描述了一个分区,最后两个字节为固定的末尾签名,0x55、0xAA。
我们可以看到在0x01BE处开始到0x01CD处的16个字节指示了一个分区。对于这16个字节,我们所需要知道的主要是以该16个字节距起始处偏移0x08个字节处为起始的四个字节表示的是跳转量。即从这个物理地址的0扇区到达逻辑地址的0扇区的偏移量(单位为扇区)。这里我们可以看到这个偏移量为0x0000_00E3。
于是我们到这个偏移量的扇区去看,就可以看到我们逻辑扇区0处的数据。也就是DBR(Dos Boot Record)Dos引导记录。这一块的引导记录与我们FATFS文件系统能否正常工作密切相关。
第一张图是从物理扇区打开看到的效果,第二张图是从逻辑扇区打开看到的效果。具体这一块数据的详细解释将在下面与FATFS内部的文件系统结构体FATFS struct结合在一起解释。
除此之外我们还需要知道的两大块是。对于FAT32,它从DBR开始,间隔若干扇区后会有一个根目录(Directory),在该根目录后间隔若干扇区后会有一个存储数据起始处。知道这点后,我们就可以开启下面的讨论了。
FATFS工作原理
首先我们需要熟悉结构体FATFS,FIL和DIR的具体含义。在我的理解里,FATFS结构体记录的主要是和FATFS文件系统本身相关的一些参数;FIL主要记录的是和特定文件有关的参数;DIR记录的是和目录有关的参数。
下面我们重点讨论一下FATFS的内部变量的含义。
typedef struct { BYTE fs_type; /* FAT sub-type (0:Not mounted) */ BYTE drv; /* Physical drive number */ BYTE csize; /* Sectors per cluster (1,2,4...128)簇的大小,一般一个簇 = 八个扇区;给文件分配簇的时候一次分配三个簇 */ BYTE n_fats; /* Number of FAT copies (1 or 2) */ BYTE wflag; /* win[] flag (b0:dirty) */ BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ WORD id; /* File system mount ID */ WORD n_rootdir; /* Number of root directory entries (FAT12/16) */#if _MAX_SS != _MIN_SS WORD ssize; /* Bytes per sector (512, 1024, 2048 or 4096) */#endif#if _FS_REENTRANT _SYNC_t sobj; /* Identifier of sync object */#endif#if !_FS_READONLY DWORD last_clust; /* Last allocated cluster 最后写入的一个文件的最后一个簇的簇地址,这里标志的是下一个文件写进来的时候应该写在哪个位置上*/ DWORD free_clust; /* Number of free clusters 这里标志的是该文件系统管理下还剩下多少个可用簇*/#endif#if _FS_RPATH DWORD cdir; /* Current directory start cluster (0:root) */#endif DWORD n_fatent; /* Number of FAT entries, = number of clusters + 2 */ DWORD fsize; /* Sectors per FAT 一个FAT分区共有多少个扇区*/ DWORD volbase; /* Volume start sector 这里标志的是逻辑0扇区,即逻辑0扇区相对于物理0扇区偏移的扇区量,对于我的SD卡,这里应该是0xE3*/ DWORD fatbase; /* FAT start sector FAT开始扇区,其实这里我不是特别懂,但是通过Winhex观察可以看到,这一块是用了一些FF作为标志的,对于我的SD卡,这里大小是0x11CF*/ DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) 这里记载的是根目录所在的簇,我的SD卡为0x02*/ DWORD database; /* Data start sector 根目录所在的扇区数,我的SD卡为0x20E3*/ DWORD winsect; /* Current sector appearing in the win[] 这里记载的是我们的窗口win[]看到的是哪个区域的数据*/ BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */} FATFS;
对于f_mount的代码,将SD卡挂载起来,把硬件与软件的连接做好的工作是在find_volume函数内部完成的。对于该函数,首先需要注意的一个片段是:
bsect = 0; fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT boot sector as SFD */ if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */ for (i = 0; i < 4; i++) { /* Get partition offset */ pt = fs->win + MBR_Table + i * SZ_PTE; br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0; } i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */ if (i) i--; do { /* Find an FAT volume */ bsect = br[i]; fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */ } while (!LD2PT(vol) && fmt && ++i < 4); } if (fmt == 3) return FR_DISK_ERR; /* An error occured in the disk I/O layer */ if (fmt) return FR_NO_FILESYSTEM; /* No FAT volume is found */
这一段代码先检测了物理地址的零扇区处。对于部分SD卡,物理地址等于逻辑地址,即找不到MBR区域。暂时我对这个现象不能给出很好的解释。对于另外一部分物理地址和逻辑地址不重合的SD卡,这段代码先通过找物理地址零扇区处记录了逻辑扇区偏移量的那16个字节。换句话说,这段代码的第一个功能是,找到逻辑扇区相对于物理扇区的偏移量,再把该偏移量赋值给bsect。
当找到逻辑地址的零扇区处后,它需要做的事情就是,需要检测这究竟是什么文件系统。因此,在check_fs里面,我们可以看到这样的代码:
BYTE check_fs ( /* 0:Valid FAT-BS, 1:Valid BS but not FAT, 2:Not a BS, 3:Disk error */ FATFS* fs, /* File system object */ DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */){ fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */ if (move_window(fs, sect) != FR_OK) /* Load boot record */ return 3; if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */ return 2; if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */ return 0; if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */ return 0; return 1;}
这段代码首先是把DBR区域的数据取出来,再进行判断这段代码是否合法。判断分为两个部分,一个是检测末尾的标识符0x55、0xAA;另一个是检测文件系统是否为FAT。其中BS_FilSysType=54,BS_FilSysType32=82。而从上面的图我们可以看到,从第0x52字节开始的四个字节正好指示了FAT的存在。
最后根据check_fs的结果,来进行判断。而在调试中,博主恰恰是在这里返回了FR_NO_FILESYSTEM。调试的时候出现了这样的情况,如果我单步调试进入check_fs内部一步一步执行,此时SD卡挂载成功,不会发生错误;但是如果我调试的时候是一步执行过check_fs的话,则会出现没有文件系统的报错。
理论上说,如果单步调试通得过但是直接执行通不过则是时序上出了问题。博主的问题出在,由于SD卡读写速度比较慢而这些代码执行速度比较快。在move_window里面是有一段将SD卡里面的DBR读到内存中的执行步骤。这一个步骤是纯硬件的,因此在执行这个读操作的时候CPU早就去执行下面的语句了。而下面的语句是什么呢?就是那几个判断。于是问题产生了。在SD卡还没来得及把数据全都读进来的时候,我们的观察窗口win[]里的都是一片0x00或是上次观察到的数据。因此在判断语句中,既检测不到0x55AA也检测不到FAT的标志,于是程序便自然用R_NO_FILESYSTEM作为返回值了。解决这个问题只需要在Readblock末尾加一个延迟函数即可。想想也是简单地有点惭愧啊。(:зゝ∠)
关于FATFS结构体内部参数的具体含义我们可以从后面的代码中得知,最重要的几个就是之前博主用中文解释的。大致思想就是,DBR内储存的是有关这个FAT系统的一些具体参数,因为这一块是作为管理区而存在,因此需要记录诸如一个簇多少扇区,总共还剩下多少个可用簇,我的文件目录要放在哪,下一个文件应该从哪里开始写之类的信息。具体计算大家就自己推推看吧,博主不再赘述:
if (LD_WORD(fs->win + BPB_BytsPerSec) != SS(fs)) /* (BPB_BytsPerSec must be equal to the physical sector size) */ return FR_NO_FILESYSTEM; fasize = LD_WORD(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ if (!fasize) fasize = LD_DWORD(fs->win + BPB_FATSz32); fs->fsize = fasize; fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FAT copies */ if (fs->n_fats != 1 && fs->n_fats != 2) /* (Must be 1 or 2) */ return FR_NO_FILESYSTEM; fasize *= fs->n_fats; /* Number of sectors for FAT area */ fs->csize = fs->win[BPB_SecPerClus]; /* Number of sectors per cluster */ if (!fs->csize || (fs->csize & (fs->csize - 1))) /* (Must be power of 2) */ return FR_NO_FILESYSTEM; fs->n_rootdir = LD_WORD(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ if (fs->n_rootdir % (SS(fs) / SZ_DIRE)) /* (Must be sector aligned) */ return FR_NO_FILESYSTEM; tsect = LD_WORD(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ if (!tsect) tsect = LD_DWORD(fs->win + BPB_TotSec32); nrsv = LD_WORD(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ if (!nrsv) return FR_NO_FILESYSTEM; /* (Must not be 0) */ /* Determine the FAT sub type */ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIRE); /* RSV + FAT + DIR */ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ fmt = FS_FAT12; if (nclst >= MIN_FAT16) fmt = FS_FAT16; if (nclst >= MIN_FAT32) fmt = FS_FAT32; /* Boundaries and Limits */ fs->n_fatent = nclst + 2; /* Number of FAT entries */ fs->volbase = bsect; /* Volume start sector */ fs->fatbase = bsect + nrsv; /* FAT start sector */ fs->database = bsect + sysect; /* Data start sector */ if (fmt == FS_FAT32) { if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ fs->dirbase = LD_DWORD(fs->win + BPB_RootClus); /* Root directory start cluster */ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ } else { if (!fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */ fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */ fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); } if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) /* (BPB_FATSz must not be less than the size needed) */ return FR_NO_FILESYSTEM;#if !_FS_READONLY /* Initialize cluster allocation information */ fs->last_clust = fs->free_clust = 0xFFFFFFFF; /* Get fsinfo if available */ fs->fsi_flag = 0x80;#if (_FS_NOFSINFO & 3) != 3 if (fmt == FS_FAT32 /* Enable FSINFO only if FAT32 and BPB_FSInfo == 1 */ && LD_WORD(fs->win + BPB_FSInfo) == 1 && move_window(fs, bsect + 1) == FR_OK) { fs->fsi_flag = 0; if (LD_WORD(fs->win + BS_55AA) == 0xAA55 /* Load FSINFO data if available */ && LD_DWORD(fs->win + FSI_LeadSig) == 0x41615252 && LD_DWORD(fs->win + FSI_StrucSig) == 0x61417272) {#if (_FS_NOFSINFO & 1) == 0 fs->free_clust = LD_DWORD(fs->win + FSI_Free_Count);#endif#if (_FS_NOFSINFO & 2) == 0 fs->last_clust = LD_DWORD(fs->win + FSI_Nxt_Free);#endif } }#endif#endif fs->fs_type = fmt; /* FAT sub-type */ fs->id = ++Fsid; /* File system mount ID */#if _FS_RPATH fs->cdir = 0; /* Set current directory to root */#endif
给大家看几个比较特别的区域长什么样子:
fs->fatbase = 0x11CF 十进制4559
fs->database = 0x20E3 十进制8419 这里是根目录
fs ->last_clust = 0x000F 十进制15 这里是相对于根目录的偏移量
一个簇8个扇区而一个文件有三个簇。8419 + 15*8 = 8539 但因为是最后一个扇区,我们的文件也比较小,所以这里会是一大团无意义的数据。看这一个文件的第一个扇区:8539 - 3*8+1 = 8515。
其实知道了上面的这些知识,博主的另一个BUG就很好解释了。博主在中期写数据的时候始终写在不正确的扇区上。大家可能已经注意到了,对于FATFS代码中使用的基本都是逻辑地址,同时都是以扇区为单位。而博主使用的开发板提供的BSP中的地址是以字节为单位的。换句话说也就是地址转换上出现了错误。
由于博主能力有限,以上可能有表述不清或是出错的地方,欢迎亲们提出问题和出现的错误。
- STM32的FATFS文件系统移植的debug过程
- STM32的FATFS文件系统移植笔记
- STM32的FATFS文件系统移植笔记
- FatFs文件系统的移植
- FatFs文件系统的移植
- FatFs文件系统的移植
- FatFs文件系统的移植
- FATFS文件系统的移植
- FATFS文件系统的移植
- FatFs文件系统的移植
- STM32移植FATFS文件系统
- STM32的FATFS文件系统移植笔记(转…
- 介绍FatFs文件系统移植的文章
- 介绍FatFs文件系统移植的文章
- 转一篇比较详细介绍FatFs文件系统移植的文章 FatFs文件系统的移植
- 文件系统系列文章(1)FatFs文件系统的移植
- 基于STM32的SD卡FATFS文件系统学习笔记
- 对STM32中FATFS文件系统常用API函数的理解
- 高斯消元模板
- 获得activity的rootview
- UVA 1001 Say Cheese(最短路)
- 很奇怪的bootclasspath参数 同文章里说的一样,也没有搞清楚 为什么需要显式地指定一下-bootclasspath
- HDU 5666 Segment
- STM32的FATFS文件系统移植的debug过程
- 越来越好玩的C语言,输出的*
- Deep Learning(深度学习)整理,RNN,CNN,BP
- hadoop平台搭建(2)--jdk的安装及配置
- Server Tomcat v8.0 Server at localhost failed to start. 问题解决方法?
- 设计模式-适配器模式
- dp学习之AvoidRoads解法二(算法优化)
- WEB结构浅析
- Unity3D开发者快速上手Unreal Engine 4指南