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

背景介绍
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 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接口,并且可以随意插拔,具有极大的灵活性。将两者结合可以使单片机系统具有很强的数据记录能力。


蓝牙耳机电路设计(两款模拟电路设计原理图详解)
高新兴智能交通监控一体化应用的功能特点及优势分析
为什么要使用仿真器?
全新一代有源PM2.5监控标签
全双工和半双工RS-485收发器MAX13430E–MAX1
LittleFS是否可以应用于SD卡中呢?
伺服电机工作原理_伺服电机主要参数
诺基亚6香港开卖 竟比国行版贵这么多?
分享常见集成电路封装类型
iPhoneXS出现不在亮屏状态下插线就充不进电问题 疑为软件bug
个人信息保护法或将近期列入立法规划,草案已明确监管职责
智能家居应用落地,全屋智能成趋势
微控制器和光纤的分类及应用设计
SLAM的原理 手持SLAM的优缺点讲解
对于通用汽车来说,寻找财大气粗的合作伙伴符合其既定惯例
人工智能如何落地?Testin云测创造 “AI+测试”新模式
双十二500元内买什么游戏蓝牙耳机好?500内性价比游戏耳机榜单
利用5G网络创新改善VR体验
三星正在计划采用开源RISC-V指令集来打造自己的5G基带芯片
华为手机在部分欧洲国家跃升第一:全球称王快了