转载自:SD卡应用总结(FatFs)
对于SD卡的应用,想必大家都尝试多。不过,很多网友恐怕只停留在实验的基础上吧。对于SD卡在文件系统下或者不带文件系统下,对SD卡的操作都是很简单的。是的,只是简单的文件读写确实不难。但是,如果每秒钟不停的写数据,而且是不停的工作,恐怕SD卡的应用就没有这么简单了吧,有时总会出现一些莫名其妙的问题。
不知道大家是否遇到过这些问题?本人开发了几个关于SD卡的项目,例如,定时拍照、定时录音等。对于这样的项目,基本上要求每一秒都在不停的写数据,而且一般一天工作好几个小时,甚至会不停的工作。在这些项目中,本人遇到太多的问题,下面把遇到的问题及解决方法与大家分享,希望有同样经验的网友一起分享一下您的经验。
--------------------------------------------------------------------------------------------------------
问题1:根目录下文件毁坏。
现象:在FatFs下可以读写文件,可在PC上无法打开目录,提示文件毁坏。
分析:通过WinHex软件打开磁盘,发现目录完全正常,但是FAT表已经毁坏,引起的原因可能是带电插拔。
解决:既然是FAT表与目录对不上,而且FAT毁坏,就是用PC修复也只会删除这些文件,对于我们的单片机来说,也没有好的解决方法,那就格式吧。
下面的代码用于判断FAT表是否和文件目录对应的上,使用的方法是:扫描FAT表,看看应用了多少簇,在通过读取FSInfo扇区的信息,看这两者是否一致。一致时为正确,不一致一般有问题。
1 /*-----------------------------------------------------------------------*/ 2 /* File system check */ 3 /*-----------------------------------------------------------------------*/ 4 5 FRESULT f_fsCheck( 6 const TCHAR *path, /* Pointer to the logical drive number (root dir) */ 7 DWORD *nclst, /* Pointer to the variable to return number of free clusters */ 8 FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ 9 ) 10 { 11 FRESULT res; 12 FATFS *fs; 13 DWORD n, clst, sect, stat; 14 UINT i; 15 BYTE fat, *p; 16 17 /* Get drive number */ 18 res = chk_mounted(&path, fatfs, 0); 19 fs = *fatfs; 20 if (res == FR_OK) 21 { 22 /* Get number of free clusters */ 23 fat = fs->fs_type; 24 n = 0; 25 if (fat == FS_FAT12) 26 { 27 clst = 2; 28 do { 29 stat = get_fat(fs, clst); 30 if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } 31 if (stat == 1) { res = FR_INT_ERR; break; } 32 if (stat == 0) n++; 33 } while (++clst < fs->n_fatent); 34 } 35 else 36 { 37 clst = fs->n_fatent; 38 sect = fs->fatbase; 39 i = 0; p = 0; 40 BYTE cnt = 0; 41 do{ 42 if(!i) 43 { 44 res = move_window(fs, sect++); 45 if (res != FR_OK) break; 46 p = fs->win; 47 i = SS(fs); 48 } 49 if (fat == FS_FAT16) 50 { 51 if (LD_WORD(p) == 0) n++; 52 p += 2; i -= 2; 53 } 54 else 55 { 56 if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) 57 { 58 if (++cnt > 10) // 连续10个空簇,退出 59 { 60 break; 61 } 62 } 63 else 64 { 65 n++; 66 cnt = 0; 67 } 68 p += 4; i -= 4; 69 } 70 } while (--clst); 71 } 72 73 if (fs->last_clust > (n+10)) 74 { 75 res = FR_INT_ERR; 76 } 77 } 78 LEAVE_FF(fs, res); 79 }
FatFs中并没有这个函数,是本添加的。后面我们可以调用这个函数实现FAT检查功能。
1 /************************************************************************************** 2 * FunctionName : FatFileSystemCheck() 3 * Description : FsInfo校验 4 * EntryParameter : None 5 * ReturnValue : 返回操作结果 6 **************************************************************************************/ 7 u8 FatFileSystemCheck(void) 8 { 9 FATFS *pFs; 10 FATFS fs; 11 FRESULT res; 12 DWORD fre_clust; 13 14 f_mount(0, &fs); 15 res = f_fsCheck("", &fre_clust, &pFs); 16 f_mount(0, 0); 17 18 return res; 19 }
后面我们可以通过此函数的返回值,看是否要格式。
1 if (FatFileSystemCheck() != FR_OK) // 文件系统毁坏,格式 2 { 3 App_Format(); 4 }
问题2:根目录正常,里边的文件夹毁坏。
现象:在FatFs下可以读写文件,可在PC上可以打开根目录,却无法里面的文件夹,提示文件毁坏。
分析:通过WinHex软件打开磁盘,发现目录完全正常,但是FAT表与目录数据对应不上,引起的原因可能是带电插拔。
解决:既然是FAT表与目录对不上,就是用PC修复也只会删除这些文件,对于我们的单片机来说,也没有好的解决方法,那就删除这个文件吧。
下面的代码用于判断用于判断是否可以在这个文件夹下新建文件,能新建就是正常的,否则异常,删除这个文件夹。
1 /**************************************************************************** 2 * FunctionName : FatCreateDir() 3 * Description : 创建一个新目录 4 * EntryParameter : folder - 文件夹的名称 5 * ReturnValue : 成功返回真,否则返回假 6 ****************************************************************************/ 7 u8 FatCreateDir(u8 *dir) 8 { 9 FATFS fs; 10 FRESULT res; 11 DIR dirs; 12 f_mount(0, &fs); 13 14 res = f_opendir(&dirs, (const TCHAR *)dir); // 打开目录 15 if (res == FR_NO_PATH) // 没有则创建 16 { 17 res = f_mkdir((const TCHAR *)dir); // 创建目录 18 if (res != FR_OK) 19 { 20 FatDeleteFile(dir); 21 res = (FRESULT)FatCreateDir(dir); 22 } 23 } 24 25 f_mount(0, 0); 26 return res; 27 }
问题3:文件大小为0字节,并且无法删除。
现象:文件已经存在,但在PC下无法删除,删除后会自动生成。
分析:既然文件已经创建,但没有内容,说明,文件打开后,写数据失败。
解决:既然文件已经新建,但没有写内容,我们可以在写内容失败后删除此文件,否则后面就删不掉了,只能格式了。
下面的代码用于判断文件是否读写正确,文件内容为0字节,而且写失败就删除。
1 /************************************************************************************** 2 * FunctionName : FatWriteFile() 3 * Description : 写一个文件 4 * EntryParameter : fname - 文件名,包含路径,pBuf - 缓冲,len - 长度 5 * ReturnValue : 成功返回真,否则返回假 6 **************************************************************************************/ 7 u8 FatWriteFile(u8 *fname, u8 *pBuf, u16 len) 8 { 9 FATFS fs; 10 FIL fno; 11 UINT bw; 12 FRESULT res; 13 14 f_mount(0, &fs); 15 res = f_open(&fno, (const TCHAR *)fname, FA_OPEN_ALWAYS|FA_WRITE); 16 17 if (res == FR_OK) 18 { 19 res = f_lseek(&fno, fno.fsize); // 获取偏移指针 20 if (res == FR_OK) 21 { 22 res = f_write(&fno, pBuf, len, &bw); // 数据写入 23 if ((res != FR_OK) && (fno.fsize == 0)) 24 { 25 res = f_unlink((const TCHAR *)fname); 26 } 27 } 28 } 29 30 f_close(&fno); 31 f_mount(0, 0); 32 return res; 33 }
问题4:SD卡电源无法关断。
现象:通过I/O端口控制SD卡电源,关断后SD卡电源端还有2.9V左右的电压。
分析:不管用mos管还是电源芯片,通过I/O端口控制都应该截断电源,但事实上SD卡电源叫还是有电,原因是这些电压是通过SPI的4个端口串进去了,特别是片选管脚。
解决:既然是通过这几个管脚窜进去的,那么在关掉电源之前让这几个管脚都没有电压输入就可以了。
问题5:临界代码。
现象:在操作文件系统时有时还没有读写完成,就断电或插拔SD卡。
分析:如果没有写完数据就直接断电或插拔会导致文件或文件系统毁坏。
解决:在对文件进行写操作时进来减小临界代码的尺寸。
我们可以尽量减少操作文件的时间,如果时间不能减少,我们可以减少临界代码的尺寸,可以在代码中添加f_sync()函数。例如下面的写WAV文件中,由于需要分别写入头和文件内容,我们可以再写入一段数据后添加一个同步还是。
1 /**************************************************************************** 2 * FunctionName : FatWriteWave() 3 * Description : 写WAV文件 4 * EntryParameter : fname - 路径,pHd - 文件头,pDat - 数据,datLen - 数据长度 5 * ReturnValue : 成功返回0,否则返回1 6 ****************************************************************************/ 7 u8 FatWriteWave(u8 *fname, u8 *pHd, u8 hdLen, u8 *pDat, u16 datLen) 8 { 9 FATFS fs; 10 FIL fno; 11 UINT bw; 12 FRESULT res; 13 14 f_mount(0, &fs); 15 res = f_open(&fno, (const TCHAR *)fname, FA_OPEN_ALWAYS|FA_WRITE); 16 if (res == FR_OK) 17 { 18 res = (fno.fsize > 0) ? f_lseek(&fno, fno.fsize) : f_lseek(&fno, hdLen); 19 if (res == FR_OK) 20 { 21 res = f_write(&fno, pDat, datLen, &bw); // 数据写入 22 if ((res != FR_OK) && (fno.fsize == 0)) 23 { 24 res = f_unlink((const TCHAR *)fname); 25 } 26 else 27 { 28 f_sync(&fno); 29 if (res == FR_OK) 30 { 31 res = f_lseek(&fno, 0); 32 if (res == FR_OK) 33 { 34 res = f_write(&fno, pHd, hdLen, &bw); // 数据写入成功后再写文件头 35 if ((res != FR_OK) && (fno.fsize == 0)) 36 { 37 res = f_unlink((const TCHAR *)fname); 38 } 39 } 40 } 41 } 42 } 43 } 44 45 f_close(&fno); 46 f_mount(0, 0); 47 return res; 48 }
问题6:FAT表与FSInfo信息不匹配。
现象:为了尽快操作文件,而不用通过FAT遍历就可以知道SD卡的存储状态,在FSInfo中存储了未使用簇数和空闲簇号,但某种原因导致FAT表中是实际使用情况与FSInfo中信息不匹配。
分析:FSInfo中的信息可以快速定位到SD卡中的空闲区域,如果这里的信息不正确,我们只能通过FAT表获取这些信息。如果SD卡很大,特别是应用了很大空间,从FAT表中获取这些信息非常缓慢。
解决:如果某处读写操作非常缓慢时,可能是FAT表与FSInfo中的信息不匹配,我们需要进行一次匹配以矫正FSInfo中的信息。
下面的代码可以通过扫描FAT区获取真正的空闲号和空余空间,同时矫正这些信息。
1 /*-----------------------------------------------------------------------*/ 2 /* Get Number of Free Clusters and proof */ 3 /*-----------------------------------------------------------------------*/ 4 5 FRESULT f_getfreeproof( 6 const TCHAR *path, /* Pointer to the logical drive number (root dir) */ 7 DWORD *nclst, /* Pointer to the variable to return number of free clusters */ 8 FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ 9 ) 10 { 11 FRESULT res; 12 FATFS *fs; 13 DWORD n, clst, sect, stat; 14 UINT i; 15 BYTE fat, *p; 16 17 /* Get drive number */ 18 res = chk_mounted(&path, fatfs, 0); 19 fs = *fatfs; 20 if (res == FR_OK) 21 { 22 /* Get number of free clusters */ 23 fat = fs->fs_type; 24 n = 0; 25 if (fat == FS_FAT12) 26 { 27 clst = 2; 28 do{ 29 stat = get_fat(fs, clst); 30 if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } 31 if (stat == 1) { res = FR_INT_ERR; break; } 32 if (stat == 0) n++; 33 } while (++clst < fs->n_fatent); 34 } 35 else 36 { 37 clst = fs->n_fatent; 38 sect = fs->fatbase; 39 i = 0; p = 0; 40 do { 41 if (!i) 42 { 43 res = move_window(fs, sect++); 44 if (res != FR_OK) break; 45 p = fs->win; 46 i = SS(fs); 47 } 48 if (fat == FS_FAT16) 49 { 50 if (LD_WORD(p) == 0) n++; 51 p += 2; i -= 2; 52 } 53 else 54 { 55 if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) n++; 56 p += 4; i -= 4; 57 } 58 } while (--clst); 59 } 60 61 if (fs->free_clust != n) 62 { 63 fs->free_clust = n; 64 fs->last_clust = 0x02; 65 if (fat == FS_FAT32) fs->fsi_flag = 1; 66 *nclst = n; 67 sync(fs); 68 } 69 } 70 LEAVE_FF(fs, res); 71 }
问题7:文件毁坏。
现象:在FatFs下写入文件时,有时由于头没有写对,有时由于尾没有写读,导致文件文件打开。
分析:通过WinHex软件打开磁盘,发现文件内容不正确,有点缺头,有的缺尾。
解决:既然是文件头或未不正确,我们可以对其头或尾进行判断,不正确的可以删除掉。
下面的代码是以JPGE文件为例,如果JPGE文件的头和尾不正确时,图片显示不对,对于头不对时,显示无法打开,如果是尾不正确可以打开,但部分内容无法显示,只能显示部分图像。我们可以通过判断,把不正确的图片删除,保留也没有意义。此发可以应用到其他文件上,至于该判断头还是尾,根据文件更改。
1 /************************************************************************************** 2 * FunctionName : FatJpgFileJud() 3 * Description : 文件判断 4 * EntryParameter : None 5 * ReturnValue : 格式错误返回1,否则返回0 6 **************************************************************************************/ 7 u8 FatJpgFileJud(u8 *fname) 8 { 9 FATFS fs; 10 FRESULT res; 11 FIL file; 12 UINT br; 13 u8 reVal = 1; 14 u8 buf[2] = {0}; 15 16 f_mount(0, &fs); 17 res = f_open(&file, (const TCHAR *)fname, FA_READ); // 打开文件 18 19 if (res == FR_OK) 20 { 21 f_lseek(&file, 0); // 打开指定位置 22 res = f_read(&file, buf, 2, &br); // 读取数据 23 if ((res == FR_OK) && (buf[0] == 0xFF) && (buf[1] == 0xD8)) // 头判断 24 { 25 f_lseek(&file, file.fsize-2); // 获取偏移指针 26 res = f_read(&file, buf, 2, &br); // 读取数据 27 if ((res == FR_OK) && (buf[0] == 0xFF) && (buf[1] == 0xD9)) // 尾判断 28 { 29 reVal = FR_OK; 30 } 31 } 32 } 33 34 f_close(&file); 35 return (reVal); 36 }
问题8:SD卡数据写入失败。
现象:在FatFs下写入文件时,有时会一次写入不了数据,有时会连续几次写入不了数据。
分析:写入不了数据,是一些存储异常或者SD卡异常导致,例如接触不良、内存或堆栈问题等。
解决:写不了数据并不意味做SD卡有问题,我们可以让设备重启,再写入数据。
如果连续几次写不了数据就格式化SD卡,势必导致SD卡中文件内容的丢失,为了把损失将到最低,我们可以让设备重启,如果仍然无法写入数据,再格式化SD卡。
1 /**************************************************************************** 2 * FunctionName : AppSDCardAbnormal() 3 * Description : SD卡异常处理 4 * EntryParameter : None 5 * ReturnValue : None 6 ****************************************************************************/ 7 void AppSDCardAbnormal(void) 8 { 9 if ((AppPar.WrdErr+CMRPar.SECnt > APP_WRD_ER) && (AppPar.SdcSta == APP_SDC_NRM)) 10 { 11 if (SDGetCardStatus() != SD_CARD_NO) // 读取SD卡状态 12 { 13 FLSRestart(); // 重启 14 } 15 else 16 { 17 AppPar.WrdErr = 0; 18 CMRPar.SECnt = 0; 19 } 20 } 21 }
问题9:SD卡热插拔。
现象:在很都时候,我们都需要对SD卡进行热插拔操作,而我们知道,很多文件毁坏都是这样操作导致的。
分析:在读写SD卡时,突出断电由于文件并没有操作完成,会导致文件毁坏。
解决:在对SD卡进行插拔操作时,断掉SD卡的供电。
要读SD卡进行断电操作,可以有很多方法,例如,可以把SD卡锁在设备中,扒卡之前必须开锁,通过锁我们知道要对SD卡进行插拔了,所以,不能再对SD卡操作了,切断SD卡供电。在没有插入卡之前不能对SD卡供电。
当然我们还可以通过按键之类的东西实现,以保证不对SD卡带电操作即可。
SD卡应用总结(Fatfs)