背景介紹
LittleFS是一個應(yīng)用于單片機(jī)內(nèi)部flash和外掛NOR flash的文件系統(tǒng)。由于它相比傳統(tǒng)的FAT文件系統(tǒng)更適合于小型嵌入式系統(tǒng),所以越來越多人把它應(yīng)用于自己的項(xiàng)目中。那么除了NOR/NANDflash類型的存儲設(shè)備外,LittleFS是否可以應(yīng)用于SD卡中呢?其實(shí)也是可以的。本文將使用i.mxRT1050 SDK中的littlefs_shell項(xiàng)目和sdcard_fatfs項(xiàng)目,改造出一個讀寫SD卡的littefs_shell。
操作步驟
本次實(shí)驗(yàn)采用的是MCUXpresso IDE v11.7,SDK使用2.13版本。littleFS文件系統(tǒng)一共只有4個文件,其中l(wèi)fs.h中顯示了當(dāng)前的版本是littleFS 2.5。
1. 首先當(dāng)然是把SD相關(guān)的代碼加入littlefs_shell工程。最簡單的方法莫過于再導(dǎo)入一個sdcard_fatfs項(xiàng)目,隨后將其中的sdmmc目錄全部復(fù)制到我們的工程下面。隨后還要復(fù)制board目錄下的sdmmc_config.c和sdmmc_config.h,drivers目錄下的fsl_usdhc.c和fsl_usdhc.h。
2. 修改程序,包括SD卡檢測和初始化,增加一個從LittleFS到SD驅(qū)動程序的橋梁。在littlefs_shell.c中增加以下代碼。
extern sd_card_t m_sdCard; status_t sdcardWaitCardInsert(void) { BOARD_SD_Config(&m_sdCard, NULL, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY, NULL); /* SD host init function */ if (SD_HostInit(&m_sdCard) != kStatus_Success) { PRINTF(" SD host init fail "); return kStatus_Fail; } /* wait card insert */ if (SD_PollingCardInsert(&m_sdCard, kSD_Inserted) == kStatus_Success) { PRINTF(" Card inserted. "); /* power off card */ SD_SetCardPower(&m_sdCard, false); /* power on the card */ SD_SetCardPower(&m_sdCard, true); // SdMmc_Init(); } else { PRINTF(" Card detect fail. "); return kStatus_Fail; } return kStatus_Success; } status_t sd_disk_initialize() { static bool isCardInitialized = false; /* demostrate the normal flow of card re-initialization. If re-initialization is not neccessary, return RES_OK directly will be fine */ if(isCardInitialized) { SD_Deinit(&m_sdCard); } if (kStatus_Success != SD_Init(&m_sdCard)) { SD_Deinit(&m_sdCard); memset(&m_sdCard, 0U, sizeof(m_sdCard)); return kStatus_Fail; } isCardInitialized = true; return kStatus_Success; }在main()里添加:
if (sdcardWaitCardInsert() != kStatus_Success) { return -1; } status=sd_disk_initialize();
3.新建一個c文件,lfs_sdmmc.c。調(diào)用順序是littlefs->lfs_sdmmc.c->lfs_sdmmc_bridge.c->fsl_sd.c。
lfs_sdmmc.c和lfs_sdmmc_bridge.c作為中間層,可以連接littlefs和sd上層驅(qū)動。其中必須要注意的是地址的映射關(guān)系。littleFS給出的地址是塊地址 + 偏移地址。見下圖。這是一次mount命令所發(fā)出的讀指令。其中的塊地址指的是擦除塊(sector)的地址。而讀寫操作使用的是最小的讀寫塊地址(BLOCK),具體在下文中說明。
因此在lfs_sdmmc.c中先把littleFS給的地址轉(zhuǎn)換成byte地址。再在lfs_sdmmc_bridge.c中把SD卡讀寫地址改為BLOCK地址。由于目前大多數(shù)SD卡都超過了4GB,byte地址需用64位變量。
下圖是littleFS在mount的時候讀BLOCK的情況:
下面是lfs_sdmmc.c中read和erase的函數(shù):
int lfs_sdmmc_read(const struct lfs_config *lfsc, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { struct lfs_sdmmc_ctx *ctx; uint64_t flash_addr; assert(lfsc); flash_addr = block * lfsc->block_size + off; if (lfssd_Read (flash_addr, size, buffer ) != kStatus_Success) return LFS_ERR_IO; return LFS_ERR_OK; } int lfs_sdmmc_erase(const struct lfs_config *lfsc, lfs_block_t block) { status_t status = kStatus_Success; struct lfs_sdmmc_ctx *ctx; uint64_t sdmmc_addr; assert(lfsc); sdmmc_addr = block * lfsc->block_size; for (uint32_t sector_ofs = 0; sector_ofs < lfsc->block_size; sector_ofs +=lfsc->block_size) { status = lfssd_EraseBlocks (sdmmc_addr + sector_ofs, 512); if (status != kStatus_Success) break; } if (status != kStatus_Success) return LFS_ERR_IO; return LFS_ERR_OK; }
這是lfs_sdmmc_bridge.c中read和erase函數(shù)。可以分辨其中的地址映射關(guān)系:
bool lfssd_EraseBlocks (uint64_t address, uint32_t len) { if (address % BLOCK_SIZE > 0) return kStatus_Fail; uint32_t startDataBlockIndex = address / BLOCK_SIZE; if(SD_EraseBlocks (&m_sdCard, startDataBlockIndex, len/BLOCK_SIZE) == kStatus_Success) return kStatus_Success; else return kStatus_Fail; } bool lfssd_Read (uint64_t address, uint32_t dataLen, void* buff) { if (dataLen == 0) return true; if (kStatus_Success != SD_ReadBlocks (&m_sdCard, buff, address/BLOCK_SIZE, SD_CARD_DATA_BLOCK_COUNT)) { return kStatus_Fail; } return kStatus_Success; }4. 最重要的一步是littleFS參數(shù)配置。在peripherals.c中有一個結(jié)構(gòu)體LittlsFS_config,這個結(jié)構(gòu)體中不但包含了SD卡的操作函數(shù),還包括讀寫扇區(qū)和緩存大小。這個結(jié)構(gòu)體的設(shè)置非常關(guān)鍵。如果設(shè)的不好,不但影響性能,更可能會運(yùn)行出錯。在設(shè)置之前,讓我們先來介紹一下SD卡和littleFS的大致原理。
SD卡的存儲單元是BLOCK,讀寫都可以按照BLOCK進(jìn)行。不同的卡每個BLOCK的大小是可以不同的。對于標(biāo)準(zhǔn)SD卡,可以用CMD16設(shè)置塊命令的長度,對于SDHC卡塊命令長度固定為512字節(jié)。SD卡的擦除是按照扇區(qū)或者說SECTOR進(jìn)行的。每個扇區(qū)的大小需要查SD卡的CSD寄存器。
如果CSD寄存器ERASE_BLK_EN= 0時,Sector是最小的擦除單元,它的單位是“塊”。Sector的值等于CSD寄存器中的SECTOR_SIZE的值+1。比如SECTOR_SIZE是127,那么最小擦除單元是512*(127+1)=65536字節(jié)。另外有時候會有疑問,現(xiàn)在的SD卡其實(shí)很多都有磨損功能以降低頻繁擦寫帶來的損耗,延長使用壽命。所以其實(shí)刪除操作或者是讀寫操作并不一定是真正的物理地址。而是經(jīng)過SD控制器映射的。但是對用戶來說,這種映射是透明的。所以不用擔(dān)心這會對正常操作產(chǎn)生影響。
LittleFS是一個輕量級的文件系統(tǒng),相比FAT系統(tǒng),它有掉電恢復(fù)能力和動態(tài)磨損均衡功能。掛載后,littlefs提供了一整套類似POSIX的文件和目錄功能,所以可以象操作一般常見文件系統(tǒng)一樣的進(jìn)行操作。LittleFS一共只有4個文件,使用時基本不需要修改。由于LittleFS要操作的NOR/NAND flash本質(zhì)是一種塊設(shè)備,所以為了使用方便,LittleFS是以塊為單位進(jìn)行讀寫的,對底層NOR/NAND Flash接口驅(qū)動都是以block為單位進(jìn)行的。
下面來看一下LittleFS配置參數(shù)的具體內(nèi)容:
const struct lfs_config LittleFS_config = { .context = (void*)0, .read = lfs_sdmmc_read, .prog = lfs_sdmmc_prog, .erase = lfs_sdmmc_erase, .sync = lfs_sdmmc_sync, .read_size = 512, .prog_size = 512, .block_size = 65536, .block_count = 128, .block_cycles = 100, .cache_size = 512, .lookahead_size = LITTLEFS_LOOKAHEAD_SIZE };
其中,第一項(xiàng)在本項(xiàng)目沒有什么用,在SDK中用來保存文件系統(tǒng)在Flash中存放的偏移量;
第二項(xiàng)(.read)到第五項(xiàng)(.sync)指向各項(xiàng)操作的處理函數(shù);
第六項(xiàng).read_size是讀操作的最小單位。這個值大致等于SD卡的BLOCK大小。在SD卡驅(qū)動程序中,這個大小已經(jīng)固定設(shè)為512。所以為了方便這里也一樣設(shè)為512。
第七項(xiàng).prog_size就是每次寫入的字節(jié)數(shù),這里和.read_size一樣都是512字節(jié)。
第八項(xiàng)是.block_size。這一項(xiàng)可以認(rèn)為就是進(jìn)行擦除操作時SD卡支持的最小擦除塊。這里默認(rèn)值不重要,需要在SD卡初始化后根據(jù)實(shí)際情況在程序中設(shè)置。
第九項(xiàng)(.block_count)是用來表示一共有多少可擦除塊的。和.block_size相乘就可以得到卡的大小。本次實(shí)驗(yàn)中使用的卡就是64k字節(jié)為一個擦除塊,所以這里直接使用65536。如果卡是可換的則需要在SD卡初始化后再根據(jù)參數(shù)確定。
第十項(xiàng)(.block_cycles)是每個block的擦寫循環(huán)次數(shù)。
第十一項(xiàng)(.cache_size)緩存大小。給人的感覺應(yīng)該是越大越好,但實(shí)際上修改這個值后會無法工作。所以還是512。
第十二項(xiàng)(lookahead_size)littlefs中使用一個lookahead buffer來管理和分配塊。lookahead buffer是一個固定大小的bitmap,記錄一片區(qū)域內(nèi)塊分配的信息。lookaheadbuffer只記錄了一片區(qū)域內(nèi)塊分配的信息,當(dāng)需要知道其他區(qū)域塊分配的情況時,就需要進(jìn)行掃描文件系統(tǒng)來查找已分配的塊。如lookahead buffer中已經(jīng)沒有空閑塊、需要推移lookaheadbuffer來查找文件系統(tǒng)中的其他空閑塊。每次lookahead buffer位置推移一個lookahead_size。這里使用原來的值即可。
好了,到此為止基本上都改好了。插上卡試一試。
果然,移植非常成功,format以后,可以寫可以讀可以建目錄。還可以在已有的文件后面添加。
可我們還是在多次測試后發(fā)現(xiàn)一個問題,如果對一個文件進(jìn)行反復(fù)的添加->關(guān)閉->添加->關(guān)閉操作后,這個文件的打開會越來越慢,甚至需要幾秒鐘。這是應(yīng)為添加的內(nèi)容并不是直接寫在文件最后一個BLOCK里,而是會新申請一個BLOCK,不管之前的BLOCK是否寫滿。如圖:
上圖是把每次write命令中用到的所有讀、寫、擦除操作的次數(shù)打印出來。可以看到每次在lfs_file_open中都要比上次寫操作多一次讀。這樣在經(jīng)過幾十上百次循環(huán)后一個文件會涉及很多個BLOCK。這些BLOCK依次讀下來非常耗費(fèi)時間。測試中發(fā)現(xiàn)超過100次寫操作后所用的時間超過秒級。為了加快速度,建議在一個文件添加幾十次后,把內(nèi)容復(fù)制到另一個文件中去。這樣分散的內(nèi)容會整合起來寫入少量的BLOCK。這可以大大加快讀寫的速度。
總結(jié)
LittleFS作為一個輕量級的文件系統(tǒng),具有比FAT小的多的footprint。同時,它又比FAT更加可靠,更適合嵌入式環(huán)境下使用。而SD卡不但容量遠(yuǎn)遠(yuǎn)超過NOR flash,同時又能支持SPI接口,并且可以隨意插拔,具有極大的靈活性。將兩者結(jié)合可以使單片機(jī)系統(tǒng)具有很強(qiáng)的數(shù)據(jù)記錄能力。
審核編輯:劉清
-
控制器
+關(guān)注
關(guān)注
113文章
16787瀏覽量
181888 -
寄存器
+關(guān)注
關(guān)注
31文章
5396瀏覽量
122460 -
SD卡
+關(guān)注
關(guān)注
2文章
573瀏覽量
64687 -
CSD
+關(guān)注
關(guān)注
0文章
56瀏覽量
12840 -
NOR flash
+關(guān)注
關(guān)注
2文章
93瀏覽量
23186
原文標(biāo)題:LittleFS是否可以應(yīng)用于SD卡中呢?不妨這樣試試
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
請問QUAD SPI是否支持SD卡?
SD卡的識別、掛載、讀寫性能是如何檢測的呢?
Rockchip怎樣將SD卡啟動或升級固件到本地存儲呢
如何正確卸載SD卡處理程序呢?
SD-IDE卡是什么卡?
如何實(shí)現(xiàn)單片機(jī)讀寫SD卡的底層驅(qū)動程序

如何使用單片機(jī)讀寫SD卡

評論