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。

物理扇区0处

物理扇区0处

我们可以看到在0x01BE处开始到0x01CD处的16个字节指示了一个分区。对于这16个字节,我们所需要知道的主要是以该16个字节距起始处偏移0x08个字节处为起始的四个字节表示的是跳转量。即从这个物理地址的0扇区到达逻辑地址的0扇区的偏移量(单位为扇区)。这里我们可以看到这个偏移量为0x0000_00E3。
于是我们到这个偏移量的扇区去看,就可以看到我们逻辑扇区0处的数据。也就是DBR(Dos Boot Record)Dos引导记录。这一块的引导记录与我们FATFS文件系统能否正常工作密切相关。

逻辑扇区0处

逻辑扇区0处

第一张图是从物理扇区打开看到的效果,第二张图是从逻辑扇区打开看到的效果。具体这一块数据的详细解释将在下面与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
fatbase
fs->database = 0x20E3 十进制8419 这里是根目录
database
fs ->last_clust = 0x000F 十进制15 这里是相对于根目录的偏移量
一个簇8个扇区而一个文件有三个簇。8419 + 15*8 = 8539 但因为是最后一个扇区,我们的文件也比较小,所以这里会是一大团无意义的数据。看这一个文件的第一个扇区:8539 - 3*8+1 = 8515。

last_clust

其实知道了上面的这些知识,博主的另一个BUG就很好解释了。博主在中期写数据的时候始终写在不正确的扇区上。大家可能已经注意到了,对于FATFS代码中使用的基本都是逻辑地址,同时都是以扇区为单位。而博主使用的开发板提供的BSP中的地址是以字节为单位的。换句话说也就是地址转换上出现了错误。

由于博主能力有限,以上可能有表述不清或是出错的地方,欢迎亲们提出问题和出现的错误。

1 0
原创粉丝点击