起因
去年做了个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:
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:
基本知识到此结束. 如果想进一步研究FAT文件系统, 可参考本文末尾给的的链接.
FatFs检测文件系统的手段.
既然FatFs提示无文件系统, 那FatFs是通过何种手段来检测是否存在文件系统的呢?
在FatFs中有一个check_fs()函数(第1981行 – 第1998行)用来检测FAT文件系统的有效性, 代码如下:
此函数先读取了指定的扇区, 然后执行相应的检查.
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行开始:
解释一下这段代码.
如果没有定义_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所在的物理扇区), 再次进行文件系统有效性检测.
逻辑很完美, 但问题恰恰出在这里.
注意这几行代码:
前面说过, 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.