FatFs读写SD卡出现FR_NO_FILESYSTEM解决方法.

DevLabs Post in MSP430,Tags: ,
3

起因

去年做了个GPS路径记录器, 将路径息记录于TF卡上, 上了FatFs系统. 刚开始那会虽然偶尔罢工, 但好歹能工作. 后来没时间也没心情了, 就搁在一边, 没再管. 前几天又找出来, 想着弄稳定了, 过年回家的时候玩一下, 结果发现居然不工作了. 想当初调试的时候折腾的死去活来, 走了无数弯路, 现在说不工作就不工作, 心里各种不服, 加了一堆调试信息输出后, 发现SD卡初始化是正常的, 但在读写的时候返回FR_NO_FILESYSTEM. 于是找出仿真器, 跟踪了一下, 另外祭出神器WinHex, 终于找出问题所在.

环境:
MPU: MSP430F5418.
SD Card: SanDisk 1G T-Flash.
Interface: 硬件SPI.
FatFs: 0.09

问题现像: 在电脑上读写正常的SD卡, 使用FatFs读写不正常, 返回的错误类型为FR_NO_FILESYSTEM.
问题原因: 如果你移植时SD卡低层读写函数正确的话, 就不是你的问题. 具体下面会分析.
解决方法: 1. 使用FatFs自带的f_mkfs()函数格式化SD卡, 但注意底层函数的正确(disk_ioctl()), 要不会出现莫名其妙的问题, 比如将你1G的卡格式化为30M.
                  2. 使用FatFs V0.10.

基本知识.

1.从MBR说起.
MBR(Master Boot Record, 主引导记录)位于SD卡的第0扇区(物理), 共512个字节. 其中前446个字节为引导代码, 接下来64个字节为分区表, 再接下来两个字节为签名, 固定为 0×55, 0xAA.

下图是我的SD卡的MBR:

mbr

64个字节的分区表分为4组, 每16字节为一组, 每一组描述一个分区. 这16个字节数据的意义在此不做详述, 只须了解偏移为0H, 4H, 8H处的数据即可.

偏移 0x00H 起的一个字节表示分区状态, 0×00表示非活动状态, 0×80表示活动状态. 其它数值无意义.
偏移 0x04H 起的一个字节表示文件系统类型, 常见的有: 01, FAT32; 06, FAT16; 07, NTFS. 0为法值.
偏移 0x08H 起的4个字节数据表示相对扇区数, 即从磁盘开始到该分区开始的偏移量, 以扇区计算. 此数值指向的物理扇区也就是逻辑扇区0. 比如此数值为63, 则第63个物理扇区为逻辑扇区0.

2.DBR(Dos Boot Record, Dos引导记录).
DBR位于逻辑扇区0, 属于FAT文件系统的一部分, 记录着文件系统的相关信息.
类似MBR, DBR也由几部分组成, 如下:
    偏移        字段长度        名称
    0×00        3字节           跳转指令
    0×03        8字节           厂商标志和OS版本号
    0x0B        53字节         BPB(BIOS Parameter Bblock)
    0×40        26字节          扩展BPB
    0x5A        420字节       引导程序代码
    0x1FE       2字节           签名, 0×55, 0xAA
我不打算研究文件系统, 所以这些数据不用去管它, 我们唯一需要了解的是, 当SD卡被格式化为FAT文件系统后, 会在DBR的BPB或者扩展BPB中出现FAT字样.

下图是我的SD卡的DBR:

DBR2

基本知识到此结束. 如果想进一步研究FAT文件系统, 可参考本文末尾给的的链接.

FatFs检测文件系统的手段.

既然FatFs提示无文件系统, 那FatFs是通过何种手段来检测是否存在文件系统的呢?
在FatFs中有一个check_fs()函数(第1981行 – 第1998行)用来检测FAT文件系统的有效性, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static BYTE check_fs (    /* 0:FAT-VBR, 1:Valid BR but not FAT, 2:Not a BR, 3:Disk error */
            FATFS *fs,    /* File system object */
            DWORD sect    /* Sector# (lba) to check if it is an FAT boot record or not */
)
{
    if (disk_read(fs->drv, fs->win, sect, 1) != RES_OK)    /* Load boot record */
        return 3;
    /* Check record signature (always placed at offset 510 even if the sector size is >512) */
    if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55)
        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)
        return 0;
 
    return 1;
}

 

此函数先读取了指定的扇区, 然后执行相应的检查.
BS_FilSysType和BS_FilSysType2均为常数宏定义, 值分别为54和82, 代表了”FAT”字样出现在DBR中的偏移量.
将0×544146拆分为三个8位数据, 转为ASCII码, 即为FAT.
可见FatFs就是通过检测DBR的BPB和扩展BPB中是否存在FAT字样来判断目标文件系统是否为FAT文件系统的.

另外注意函数的返回值:
0为有效的FAT分区;
1为BR(Boot Record)有效但并不是FAT分区.
2为BR无效.
3为读取数据出现错误.
 
既然SD卡经过正确的格式化, 那么DBR一定不会出错, 所以问题不会在这里.

接下来找到调用check_fs()的地方, 从chk_mounted()函数(第2007行)的2066行开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Search FAT partition on the drive. Supports only generic partitionings, FDISK and SFD. */
fmt = check_fs(fs, bsect = 0);      /* Load sector 0 and check if it is an FAT-VBR (in SFD) */
if (LD2PT(vol) && !fmt) fmt = 1;    /* Force non-SFD if the volume is forced partition */
if (fmt == 1) {                     /* Not an FAT-VBR, the physical drive can be partitioned */
    /* Check the partition listed in the partition table */
    pi = LD2PT(vol);
    if (pi) pi--;
    tbl = &fs->win[MBR_Table + pi * SZ_PTE];    /* Partition table */
    if (tbl[4]) {                               /* Is the partition existing? */
        bsect = LD_DWORD(&tbl[8]);              /* Partition offset in LBA */
        fmt = check_fs(fs, bsect);              /* Check the partition */
    }
}
if (fmt == 3) return FR_DISK_ERR;
if (fmt) return FR_NO_FILESYSTEM;               /* No FAT volume is found */

解释一下这段代码.
如果没有定义_MULLTI_PARTITION的话, 则LD2PT(vol)值始终为0(参照LDPT的宏定义).
这里我们只有一个分区, 所以此值为0.
fmt = check_fs(fs, bsect = 0)对SD卡的0扇区(物理)执行文件系统有效性检查. 如果SD卡无MBR, 即第0扇区就为DBR,且文件系统正确的话, 那么第二个if将不会再执行.
如果SD卡有MBR, 那么函数必定返回1, 那么下面的函数将会从MBR的分区表中查找到逻辑扇区0(即第一个分区DBR所在的物理扇区), 再次进行文件系统有效性检测.
逻辑很完美, 但问题恰恰出在这里.

注意这几行代码:

1
2
3
4
5
6
7
pi = LDPT(vol);
if (pi) pi--;
tbl = &fs->win[MBR_Table + pi * SZ_PTE];        // 得到一个分区表项 SE_PTE = 16
if (tbl[4]) {                                   // 如果存在文件系统
    bsect = LD_DWORD(&tbl[8]);                  // 得到分区的DBR所在位置(物理扇区)
    fmt = check_fs(fs, bsect);                  // 执行检测
}

 

前面说过, LDPT(vol) 的值始终为0, tbl[4](即分区表第4个字节)指的是文件系统类型, 不可为0.
如果SD卡存在MBR, 则从MBR的第一个分区表项中得到第一个分区的相关信息, 如果此分区存在文件系统的话(检查第4个字节得到), 通过分区表项找到分区的DBR位置, 再次进行文件系统有效性检查.

以上就是这段代码的所有的逻辑.
看起来还是没有问题.

是的, 代码是没有问题, 但是SD卡会耍流氓, 不, 应该是格式化SD卡的软件会耍流氓.
一般我们不会给SD卡分区, 所以SD卡只有一个分区. 如果SD卡有MBR(一般都会有), 那意味着将会有4个分区表项. 重点就在这里. 按道理说SD卡分区相关数据应该在MBR的第一个分区表项里面, 但是, 这些数据存在于第二个分区表项或者第三个或者第四个都没有问题, PC都是可以正确认出来的, FatFs不行. 如果分区数据正好在第4个分区表项里, PC识别是没有问题的, FatFs傻乎乎的只会去检查第一个分区表项, 然后就杯具了, 只好报告FR_NO_FILESYSTEM.

如果你仔细看图一的话你就会发现, 我的这张SD卡的分区相关数据正好就在MBR的第4个分区表项里!

更糟糕的时, 就算你使用Windows(我使用的是Win7 32bit sp1)将SD卡再格式化一次, 分区表是不会改变的. 所以只好使用FatFs自带的f_mkfs()再格式化一次了, 或者, 你可以使用WinHex手工更改一下(我就是这么干的), 再或者, 自己动手更改FatFs源代码.

关于我的SD卡为什么会出现这种情况:
记得前几个月给电脑更新了一下BIOS, 正好使用这张SD卡做引导, 所以应该是更改BIOS的程序制作引导磁盘的时候”闯的祸”.

后记:
打算向ELM ChaN反映这个问题, 于是到FatFs的官网转了一圈, 发现FatFs已经更新到V0.10了, 下载下来一看, 此问题已经得到解决, 所以解决此问题又多了一个选择: 使用FatFs V0.10.

声明: 原创文章, 转载请注明出处. 若有问题, 请留言指出, 不胜感激.

相关链接:

FAT文件系统原理

维基百科–主引导记录

« Prev: :Next »

3 Responses to “FatFs读写SD卡出现FR_NO_FILESYSTEM解决方法.”

Leave a Reply