SD卡應用總結(Fatfs)
技術標籤:FatFs模組系統應用指南SD卡應用總結(FatFs)
http://bbs.eeworld.com.cn/forum.php?mod=viewthread&tid=377465&page=1&authorid=66265
問題1:根目錄下檔案毀壞。
SD卡應用總結(FatFs)[複製連結]對於SD卡的應用,想必大家都嘗試多。不過,很多網友恐怕只停留在實驗的基礎上吧。對於SD卡在檔案系統下或者不帶檔案系統下,對SD卡的操作都是很簡單的。是的,只是簡單的檔案讀寫確實不難。但是,如果每秒鐘不停的寫資料,而且是不停的工作,恐怕SD卡的應用就沒有這麼簡單了吧,有時總會出現一些莫名其妙的問題。不知道大家是否遇到過這些問題?本人開發了幾個關於SD卡的專案,例如,定時拍照、定時錄音等。對於這樣的專案,基本上要求每一秒都在不停的寫資料,而且一般一天工作好幾個小時,甚至會不停的工作。在這些專案中,本人遇到太多的問題,下面把遇到的問題及解決方法與大家分享,希望有同樣經驗的網友一起分享一下您的經驗。 此帖出自NXP MCU論壇 |
現象:在FatFs下可以讀寫檔案,可在PC上無法開啟目錄,提示檔案毀壞。
分析:通過WinHex軟體開啟磁碟,發現目錄完全正常,但是FAT表已經毀壞,引起的原因可能是帶電插拔。
解決:既然是FAT表與目錄對不上,而且FAT毀壞,就是用PC修復也只會刪除這些檔案,對於我們的微控制器來說,也沒有好的解決方法,那就格式吧。
下面的程式碼用於判斷FAT表是否和檔案目錄對應的上,使用的方法是:掃描FAT表,看看應用了多少簇,在通過讀取FSInfo扇區的資訊,看這兩者是否一致。一致時為正確,不一致一般有問題。
/*-----------------------------------------------------------------------*/ /* File system check */ /*-----------------------------------------------------------------------*/ FRESULT f_fsCheck( const TCHAR *path, /* Pointer to the logical drive number (root dir) */ DWORD *nclst, /* Pointer to the variable to return number of free clusters */ FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */ ) { FRESULT res; FATFS *fs; DWORD n, clst, sect, stat; UINT i; BYTE fat, *p; /* Get drive number */ res = chk_mounted(&path, fatfs, 0); fs = *fatfs; if (res == FR_OK) { /* Get number of free clusters */ fat = fs->fs_type; n = 0; if (fat == FS_FAT12) { clst = 2; do { stat = get_fat(fs, clst); if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } if (stat == 1) { res = FR_INT_ERR; break; } if (stat == 0) n++; } while (++clst < fs->n_fatent); } else { clst = fs->n_fatent; sect = fs->fatbase; i = 0; p = 0; BYTE cnt = 0; do { if (!i) { res = move_window(fs, sect++); if (res != FR_OK) break; p = fs->win; i = SS(fs); } if (fat == FS_FAT16) { if (LD_WORD(p) == 0) n++; p += 2; i -= 2; } else { if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) { if (++cnt > 10) // 連續10個空簇,退出 { break; } } else { n++; cnt = 0; } p += 4; i -= 4; } } while (--clst); } if (fs->last_clust > (n+10)) { res = FR_INT_ERR; } } LEAVE_FF(fs, res); }
FatFs中並沒有這個函式,是本新增的。後面我們可以呼叫這個函式實現FAT檢查功能。
/**************************************************************************************
* FunctionName : FatFileSystemCheck()
* Description : FsInfo校驗
* EntryParameter : None
* ReturnValue : 返回操作結果
**************************************************************************************/
u8 FatFileSystemCheck(void)
{
FATFS *pFs;
FATFS fs;
FRESULT res;
DWORD fre_clust;
f_mount(0, &fs);
res = f_fsCheck("", &fre_clust, &pFs);
f_mount(0, 0);
return res;
}
後面我們可以通過此函式的返回值,看是否要格式。
if (FatFileSystemCheck() != FR_OK) // 檔案系統毀壞,格式
{
App_Format();
}
問題2:根目錄正常,裡邊的資料夾毀壞。
現象:在FatFs下可以讀寫檔案,可在PC上可以開啟根目錄,卻無法裡面的資料夾,提示檔案毀壞。
分析:通過WinHex軟體開啟磁碟,發現目錄完全正常,但是FAT表與目錄資料對應不上,引起的原因可能是帶電插拔。
解決:既然是FAT表與目錄對不上,就是用PC修復也只會刪除這些檔案,對於我們的微控制器來說,也沒有好的解決方法,那就刪除這個檔案吧。
下面的程式碼用於判斷用於判斷是否可以在這個資料夾下新建檔案,能新建就是正常的,否則異常,刪除這個資料夾。
/**************************************************************************************
* FunctionName : FatCreateDir()
* Description : 建立一個新目錄
* EntryParameter : folder - 資料夾的名稱
* ReturnValue : 成功返回真,否則返回假
**************************************************************************************/
u8 FatCreateDir(u8 *dir)
{
FATFS fs;
FRESULT res;
DIR dirs;
f_mount(0, &fs);
res = f_opendir(&dirs, (const TCHAR *)dir); // 開啟目錄
if (res == FR_NO_PATH) // 沒有則建立
{
res = f_mkdir((const TCHAR *)dir); // 建立目錄
if (res != FR_OK)
{
FatDeleteFile(dir);
res = (FRESULT)FatCreateDir(dir);
}
}
f_mount(0, 0);
return res;
}
問題3:檔案大小為0位元組,並且無法刪除。
現象:檔案已經存在,但在PC下無法刪除,刪除後會自動生成。
分析:既然檔案已經建立,但沒有內容,說明,檔案開啟後,寫資料失敗。
解決:既然檔案已經新建,但沒有寫內容,我們可以在寫內容失敗後刪除此檔案,否則後面就刪不掉了,只能格式了。
下面的程式碼用於判斷檔案是否讀寫正確,檔案內容為0位元組,而且寫失敗就刪除。
/**************************************************************************************
* FunctionName : FatWriteFile()
* Description : 寫一個檔案
* EntryParameter : fname - 檔名,包含路徑,pBuf - 緩衝,len - 長度
* ReturnValue : 成功返回真,否則返回假
**************************************************************************************/
u8 FatWriteFile(u8 *fname, u8 *pBuf, u16 len)
{
FATFS fs;
FIL fno;
UINT bw;
FRESULT res;
f_mount(0, &fs);
res = f_open(&fno, (const TCHAR *)fname, FA_OPEN_ALWAYS|FA_WRITE);
if (res == FR_OK)
{
res = f_lseek(&fno, fno.fsize); // 獲取偏移指標
if (res == FR_OK)
{
res = f_write(&fno, pBuf, len, &bw); // 資料寫入
if ((res != FR_OK) && (fno.fsize == 0))
{
res = f_unlink((const TCHAR *)fname);
}
}
}
f_close(&fno);
f_mount(0, 0);
return res;
}
問題4:SD卡電源無法關斷。
現象:通過I/O埠控制SD卡電源,關斷後SD卡電源端還有2.9V左右的電壓。
分析:不管用mos管還是電源晶片,通過I/O埠控制都應該截斷電源,但事實上SD卡電源叫還是有電,原因是這些電壓是通過SPI的4個埠串進去了,特別是片選管腳。
解決:既然是通過這幾個管腳竄進去的,那麼在關掉電源之前讓這幾個管腳都沒有電壓輸入就可以了。
問題5:臨界程式碼。
現象:在操作檔案系統時有時還沒有讀寫完成,就斷電或插拔SD卡。
分析:如果沒有寫完資料就直接斷電或插拔會導致檔案或檔案系統毀壞。
解決:在對檔案進行寫操作時進來減小臨界程式碼的尺寸。
我們可以儘量減少操作檔案的時間,如果時間不能減少,我們可以減少臨界程式碼的尺寸,可以在程式碼中新增f_sync()函式。例如下面的寫WAV檔案中,由於需要分別寫入頭和檔案內容,我們可以再寫入一段資料後新增一個同步還是。
/**************************************************************************************
* FunctionName : FatWriteWave()
* Description : 寫WAV檔案
* EntryParameter : fname - 路徑,pHd - 檔案頭,pDat - 資料,datLen - 資料長度
* ReturnValue : 成功返回0,否則返回1
**************************************************************************************/
u8 FatWriteWave(u8 *fname, u8 *pHd, u8 hdLen, u8 *pDat, u16 datLen)
{
FATFS fs;
FIL fno;
UINT bw;
FRESULT res;
f_mount(0, &fs);
res = f_open(&fno, (const TCHAR *)fname, FA_OPEN_ALWAYS|FA_WRITE);
if (res == FR_OK)
{
res = (fno.fsize > 0) ? f_lseek(&fno, fno.fsize) : f_lseek(&fno, hdLen);
if (res == FR_OK)
{
res = f_write(&fno, pDat, datLen, &bw); // 資料寫入
if ((res != FR_OK) && (fno.fsize == 0))
{
res = f_unlink((const TCHAR *)fname);
}
else
{
f_sync(&fno);
if (res == FR_OK)
{
res = f_lseek(&fno, 0);
if (res == FR_OK)
{
res = f_write(&fno, pHd, hdLen, &bw); // 資料寫入成功後再寫檔案頭
if ((res != FR_OK) && (fno.fsize == 0))
{
res = f_unlink((const TCHAR *)fname);
}
}
}
}
}
}
f_close(&fno);
f_mount(0, 0);
return res;
}
問題6:FAT表與FSInfo資訊不匹配。
現象:為了儘快操作檔案,而不用通過FAT遍歷就可以知道SD卡的儲存狀態,在FSInfo中儲存了未使用簇數和空閒簇號,但某種原因導致FAT表中是實際使用情況與FSInfo中資訊不匹配。
分析:FSInfo中的資訊可以快速定位到SD卡中的空閒區域,如果這裡的資訊不正確,我們只能通過FAT表獲取這些資訊。如果SD卡很大,特別是應用了很大空間,從FAT表中獲取這些資訊非常緩慢。
解決:如果某處讀寫操作非常緩慢時,可能是FAT表與FSInfo中的資訊不匹配,我們需要進行一次匹配以矯正FSInfo中的資訊。
下面的程式碼可以通過掃描FAT區獲取真正的空閒號和空餘空間,同時矯正這些資訊。
/*-----------------------------------------------------------------------*/
/* Get Number of Free Clusters and proof */
/*-----------------------------------------------------------------------*/
FRESULT f_getfreeproof(
const TCHAR *path, /* Pointer to the logical drive number (root dir) */
DWORD *nclst, /* Pointer to the variable to return number of free clusters */
FATFS **fatfs /* Pointer to pointer to corresponding file system object to return */
)
{
FRESULT res;
FATFS *fs;
DWORD n, clst, sect, stat;
UINT i;
BYTE fat, *p;
/* Get drive number */
res = chk_mounted(&path, fatfs, 0);
fs = *fatfs;
if (res == FR_OK) {
/* Get number of free clusters */
fat = fs->fs_type;
n = 0;
if (fat == FS_FAT12) {
clst = 2;
do {
stat = get_fat(fs, clst);
if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
if (stat == 1) { res = FR_INT_ERR; break; }
if (stat == 0) n++;
} while (++clst < fs->n_fatent);
} else {
clst = fs->n_fatent;
sect = fs->fatbase;
i = 0; p = 0;
do {
if (!i) {
res = move_window(fs, sect++);
if (res != FR_OK) break;
p = fs->win;
i = SS(fs);
}
if (fat == FS_FAT16) {
if (LD_WORD(p) == 0) n++;
p += 2; i -= 2;
} else {
if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) n++;
p += 4; i -= 4;
}
} while (--clst);
}
if (fs->free_clust != n)
{
fs->free_clust = n;
fs->last_clust = 0x02;
if (fat == FS_FAT32) fs->fsi_flag = 1;
*nclst = n;
sync(fs);
}
}
LEAVE_FF(fs, res);
}
問題7:檔案毀壞。
現象:在FatFs下寫入檔案時,有時由於頭沒有寫對,有時由於尾沒有寫讀,導致檔案檔案開啟。
分析:通過WinHex軟體開啟磁碟,發現檔案內容不正確,有點缺頭,有的缺尾。
解決:既然是檔案頭或未不正確,我們可以對其頭或尾進行判斷,不正確的可以刪除掉。
下面的程式碼是以JPGE檔案為例,如果JPGE檔案的頭和尾不正確時,圖片顯示不對,對於頭不對時,顯示無法開啟,如果是尾不正確可以開啟,但部分內容無法顯示,只能顯示部分影象。我們可以通過判斷,把不正確的圖片刪除,保留也沒有意義。此發可以應用到其他檔案上,至於該判斷頭還是尾,根據檔案更改。
/**************************************************************************************
* FunctionName : FatJpgFileJud()
* Description : 檔案判斷
* EntryParameter : None
* ReturnValue : 格式錯誤返回1,否則返回0
**************************************************************************************/
u8 FatJpgFileJud(u8 *fname)
{
FATFS fs;
FRESULT res;
FIL file;
UINT br;
u8 reVal = 1;
u8 buf[2] = {0};
f_mount(0, &fs);
res = f_open(&file, (const TCHAR *)fname, FA_READ); // 開啟檔案
if (res == FR_OK)
{
f_lseek(&file, 0); // 開啟指定位置
res = f_read(&file, buf, 2, &br); // 讀取資料
if ((res == FR_OK) && (buf[0] == 0xFF) && (buf[1] == 0xD8)) // 頭判斷
{
f_lseek(&file, file.fsize-2); // 獲取偏移指標
res = f_read(&file, buf, 2, &br); // 讀取資料
if ((res == FR_OK) && (buf[0] == 0xFF) && (buf[1] == 0xD9)) // 尾判斷
{
reVal = FR_OK;
}
}
}
f_close(&file);
return (reVal);
}
問題8:SD卡資料寫入失敗。
現象:在FatFs下寫入檔案時,有時會一次寫入不了資料,有時會連續幾次寫入不了資料。
分析:寫入不了資料,是一些儲存異常或者SD卡異常導致,例如接觸不良、記憶體或堆疊問題等。
解決:寫不了資料並不意味做SD卡有問題,我們可以讓裝置重啟,再寫入資料。
如果連續幾次寫不了資料就格式化SD卡,勢必導致SD卡中檔案內容的丟失,為了把損失將到最低,我們可以讓裝置重啟,如果仍然無法寫入資料,再格式化SD卡。
/**************************************************************************************
* FunctionName : AppSDCardAbnormal()
* Description : SD卡異常處理
* EntryParameter : None
* ReturnValue : None
**************************************************************************************/
void AppSDCardAbnormal(void)
{
if ((AppPar.WrdErr+CMRPar.SECnt > APP_WRD_ER) && (AppPar.SdcSta == APP_SDC_NRM))
{
if (SDGetCardStatus() != SD_CARD_NO) // 讀取SD卡狀態
{
FLSRestart(); // 重啟
}
else
{
AppPar.WrdErr = 0;
CMRPar.SECnt = 0;
}
}
}
問題9:SD卡熱插拔。
現象:在很都時候,我們都需要對SD卡進行熱插拔操作,而我們知道,很多檔案毀壞都是這樣操作導致的。
分析:在讀寫SD卡時,突出斷電由於檔案並沒有操作完成,會導致檔案毀壞。
解決:在對SD卡進行插拔操作時,斷掉SD卡的供電。
要讀SD卡進行斷電操作,可以有很多方法,例如,可以把SD卡鎖在裝置中,扒卡之前必須開鎖,通過鎖我們知道要對SD卡進行插拔了,所以,不能再對SD卡操作了,切斷SD卡供電。在沒有插入卡之前不能對SD卡供電。
當然我們還可以通過按鍵之類的東西實現,以保證不對SD卡帶電操作即可。