1. 程式人生 > 其它 >SD卡應用總結(Fatfs)

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卡帶電操作即可。