背景介绍
LittleFS是一个应用于单片机内部flash和外挂NOR flash的文件系统。由于它相比传统的FAT文件系统更适合于小型嵌入式系统,所以越来越多人把它应用于自己的项目中。那么除了NOR/NANDflash类型的存储设备外,LittleFS是否可以应用于SD卡中呢?其实也是可以的。本文将使用i.mxRT1050 SDK中的littlefs_shell项目和sdcard_fatfs项目,改造出一个读写SD卡的littefs_shell。
操作步骤
本次实验采用的是MCUXpresso IDE v11.7,SDK使用2.13版本。littleFS文件系统一共只有4个文件,其中lfs.h中显示了当前的版本是littleFS 2.5。
1. 首先当然是把SD相关的代码加入littlefs_shell工程。最简单的方法莫过于再导入一个sdcard_fatfs项目,随后将其中的sdmmc目录全部复制到我们的工程下面。随后还要复制board目录下的sdmmc_config.c和sdmmc_config.h,drivers目录下的fsl_usdhc.c和fsl_usdhc.h。
2. 修改程序,包括SD卡检测和初始化,增加一个从LittleFS到SD驱动程序的桥梁。在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。调用顺序是littlefs->lfs_sdmmc.c->lfs_sdmmc_bridge.c->fsl_sd.c。
lfs_sdmmc.c和lfs_sdmmc_bridge.c作为中间层,可以连接littlefs和sd上层驱动。其中必须要注意的是地址的映射关系。littleFS给出的地址是块地址 + 偏移地址。见下图。这是一次mount命令所发出的读指令。其中的块地址指的是擦除块(sector)的地址。而读写操作使用的是最小的读写块地址(BLOCK),具体在下文中说明。
因此在lfs_sdmmc.c中先把littleFS给的地址转换成byte地址。再在lfs_sdmmc_bridge.c中把SD卡读写地址改为BLOCK地址。由于目前大多数SD卡都超过了4GB,byte地址需用64位变量。
下图是littleFS在mount的时候读BLOCK的情况:

下面是lfs_sdmmc.c中read和erase的函数:
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函数。可以分辨其中的地址映射关系:
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参数配置。在peripherals.c中有一个结构体LittlsFS_config,这个结构体中不但包含了SD卡的操作函数,还包括读写扇区和缓存大小。这个结构体的设置非常关键。如果设的不好,不但影响性能,更可能会运行出错。在设置之前,让我们先来介绍一下SD卡和littleFS的大致原理。
SD卡的存储单元是BLOCK,读写都可以按照BLOCK进行。不同的卡每个BLOCK的大小是可以不同的。对于标准SD卡,可以用CMD16设置块命令的长度,对于SDHC卡块命令长度固定为512字节。SD卡的擦除是按照扇区或者说SECTOR进行的。每个扇区的大小需要查SD卡的CSD寄存器。
如果CSD寄存器ERASE_BLK_EN= 0时,Sector是最小的擦除单元,它的单位是“块”。Sector的值等于CSD寄存器中的SECTOR_SIZE的值+1。比如SECTOR_SIZE是127,那么最小擦除单元是512*(127+1)=65536字节。另外有时候会有疑问,现在的SD卡其实很多都有磨损功能以降低频繁擦写带来的损耗,延长使用寿命。所以其实删除操作或者是读写操作并不一定是真正的物理地址。而是经过SD控制器映射的。但是对用户来说,这种映射是透明的。所以不用担心这会对正常操作产生影响。
LittleFS是一个轻量级的文件系统,相比FAT系统,它有掉电恢复能力和动态磨损均衡功能。挂载后,littlefs提供了一整套类似POSIX的文件和目录功能,所以可以象操作一般常见文件系统一样的进行操作。LittleFS一共只有4个文件,使用时基本不需要修改。由于LittleFS要操作的NOR/NAND flash本质是一种块设备,所以为了使用方便,LittleFS是以块为单位进行读写的,对底层NOR/NAND Flash接口驱动都是以block为单位进行的。
下面来看一下LittleFS配置参数的具体内容:
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
};
其中,第一项在本项目没有什么用,在SDK中用来保存文件系统在Flash中存放的偏移量;
第二项(.read)到第五项(.sync)指向各项操作的处理函数;
第六项.read_size是读操作的最小单位。这个值大致等于SD卡的BLOCK大小。在SD卡驱动程序中,这个大小已经固定设为512。所以为了方便这里也一样设为512。
第七项.prog_size就是每次写入的字节数,这里和.read_size一样都是512字节。
第八项是.block_size。这一项可以认为就是进行擦除操作时SD卡支持的最小擦除块。这里默认值不重要,需要在SD卡初始化后根据实际情况在程序中设置。
第九项(.block_count)是用来表示一共有多少可擦除块的。和.block_size相乘就可以得到卡的大小。本次实验中使用的卡就是64k字节为一个擦除块,所以这里直接使用65536。如果卡是可换的则需要在SD卡初始化后再根据参数确定。
第十项(.block_cycles)是每个block的擦写循环次数。
第十一项(.cache_size)缓存大小。给人的感觉应该是越大越好,但实际上修改这个值后会无法工作。所以还是512。
第十二项(lookahead_size)littlefs中使用一个lookahead buffer来管理和分配块。lookahead buffer是一个固定大小的bitmap,记录一片区域内块分配的信息。lookaheadbuffer只记录了一片区域内块分配的信息,当需要知道其他区域块分配的情况时,就需要进行扫描文件系统来查找已分配的块。如lookahead buffer中已经没有空闲块、需要推移lookaheadbuffer来查找文件系统中的其他空闲块。每次lookahead buffer位置推移一个lookahead_size。这里使用原来的值即可。
好了,到此为止基本上都改好了。插上卡试一试。

果然,移植非常成功,format以后,可以写可以读可以建目录。还可以在已有的文件后面添加。
可我们还是在多次测试后发现一个问题,如果对一个文件进行反复的添加->关闭->添加->关闭操作后,这个文件的打开会越来越慢,甚至需要几秒钟。这是应为添加的内容并不是直接写在文件最后一个BLOCK里,而是会新申请一个BLOCK,不管之前的BLOCK是否写满。如图:

上图是把每次write命令中用到的所有读、写、擦除操作的次数打印出来。可以看到每次在lfs_file_open中都要比上次写操作多一次读。这样在经过几十上百次循环后一个文件会涉及很多个BLOCK。这些BLOCK依次读下来非常耗费时间。测试中发现超过100次写操作后所用的时间超过秒级。为了加快速度,建议在一个文件添加几十次后,把内容复制到另一个文件中去。这样分散的内容会整合起来写入少量的BLOCK。这可以大大加快读写的速度。
总结
LittleFS作为一个轻量级的文件系统,具有比FAT小的多的footprint。同时,它又比FAT更加可靠,更适合嵌入式环境下使用。而SD卡不但容量远远超过NOR flash,同时又能支持SPI接口,并且可以随意插拔,具有极大的灵活性。将两者结合可以使单片机系统具有很强的数据记录能力。
审核编辑:刘清
-
控制器
+关注
关注
114文章
17638浏览量
190170 -
寄存器
+关注
关注
31文章
5588浏览量
129035 -
SD卡
+关注
关注
2文章
583浏览量
67723 -
CSD
+关注
关注
0文章
81浏览量
13377 -
NOR flash
+关注
关注
2文章
98浏览量
23836
原文标题:LittleFS是否可以应用于SD卡中呢?不妨这样试试
文章出处:【微信号:NXP_SMART_HARDWARE,微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
请问QUAD SPI是否支持SD卡?
如何正确卸载SD卡处理程序呢?
如何使用单片机读写SD卡

LittleFS是否可以应用于SD卡中呢?
评论