0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

恩智浦MCU加油站 来源:恩智浦MCU加油站 2023-12-28 16:41 次阅读

背景介绍

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的情况:

98b7b718-a559-11ee-8b88-92fbcf53809c.png

下面是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。这里使用原来的值即可。

好了,到此为止基本上都改好了。插上卡试一试。

98d939ec-a559-11ee-8b88-92fbcf53809c.png

果然,移植非常成功,format以后,可以写可以读可以建目录。还可以在已有的文件后面添加。

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

98ef557e-a559-11ee-8b88-92fbcf53809c.png

上图是把每次write命令中用到的所有读、写、擦除操作的次数打印出来。可以看到每次在lfs_file_open中都要比上次写操作多一次读。这样在经过几十上百次循环后一个文件会涉及很多个BLOCK。这些BLOCK依次读下来非常耗费时间。测试中发现超过100次写操作后所用的时间超过秒级。为了加快速度,建议在一个文件添加几十次后,把内容复制到另一个文件中去。这样分散的内容会整合起来写入少量的BLOCK。这可以大大加快读写的速度。

总结

LittleFS作为一个轻量级的文件系统,具有比FAT小的多的footprint。同时,它又比FAT更加可靠,更适合嵌入式环境下使用。而SD卡不但容量远远超过NOR flash,同时又能支持SPI接口,并且可以随意插拔,具有极大的灵活性。将两者结合可以使单片机系统具有很强的数据记录能力。







审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 控制器
    +关注

    关注

    112

    文章

    15223

    浏览量

    171192
  • 寄存器
    +关注

    关注

    30

    文章

    5028

    浏览量

    117723
  • SD卡
    +关注

    关注

    2

    文章

    530

    浏览量

    63020
  • CSD
    CSD
    +关注

    关注

    0

    文章

    54

    浏览量

    12540
  • NOR flash
    +关注

    关注

    2

    文章

    83

    浏览量

    22862

原文标题:LittleFS是否可以应用于SD卡中呢?不妨这样试试

文章出处:【微信号:NXP_SMART_HARDWARE,微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    请问QUAD SPI是否支持SD

    如题,单线SPI读取SD肯定没有4线读取的速率快,问题是STM32某些芯片没有SDIO引脚,但是有QUAD SPI,不知道是否能4线驱动SD
    发表于 03-25 06:34

    在AVR 电路设计SD无法识别问题

    在AVR 电路设计,遇到这样一个问题,其中SD电路采用SPI模式接口,我用较短的排线接的时候,可以识别SD
    发表于 01-19 11:43

    FPGA如何与SD结合

    DATx引脚发送/接收。比如下图就是1位SD模式下的连接示意图协议SD有着一套命令/响应的规律。比如,命令“17”可以读取一个扇区(512bytes)的存储
    发表于 08-01 05:00

    请问使用EDMA方式向SD写数据怎么配置?

    已经在初始化SD状态的时候选择了EDMA中断方式,难道需要其他配置嘛?)2、查看驱动SD
    发表于 08-28 08:14

    是否可以通过读取上的MBR或BR查找SD上的文件系统?

    是否可以通过读取上的MBR或BR查找SD上的文件系统?我需要知道它是FAT16、FAT32、NTFS还是exFAT…(我知道它应该在bo
    发表于 09-10 09:32

    SD是什么?SD命令有哪些基本特性

    SD是什么?分为哪几类?SD的物理结构是由哪些部分组成的?SD命令有哪些基本特性
    发表于 12-10 07:06

    怎样采用SPI模式去读写SD

    怎样采用SPI模式去读写SD?如何利用库函数向SD写入数据
    发表于 12-14 07:14

    LittleFS的相关资料下载

    LittleFS - 一个高度完整的嵌入式文件系统 拥有小巧灵活的文件系统对许多物联网设备至关重要。使用文件系统并将其与正确的存储技术(如外部闪存或SD)配对可能很困难。Mbed操作系统使文件系统
    发表于 12-14 08:36

    Rockchip怎样将SD启动或升级固件到本地存储

    分区上,主控从 SD 启动时,SD 升级代码将固件升级到本地主存储。主要用于设备固件损坏,
    发表于 04-07 15:33

    如何将一个C文件丢到SD并运行它

    我想将一个 C 文件丢到 SD ,然后从 SD 运行它。如何去实现
    发表于 12-08 08:42

    如何正确卸载SD处理程序

    我正在努力完成以下任务:无论是否插入 SD 或未开机,我的系统都可以完美地使用 SDCard 运行。因此,如果卡在开机时存在,文件系统就会工作。它还会在系统运行时检测
    发表于 12-15 07:00

    请问一下SD能挂载littleFS文件系统吗?

    请问一下SD能挂载littleFS文件系统吗?谢谢!
    发表于 01-10 16:41

    如何将STM32的SDMMC1接口用于SD

    大家好,我们在设计中使用了 STM 的 STM32H750VB。我们计划将STM32的SDMMC1接口用于SD。我们只是想知道我们可以使用 SD
    发表于 01-12 07:51

    请问RT-Thread怎么检测SD是否已插入

    请问RT-Thread怎么检测SD是否已插入?求解
    发表于 02-16 11:39

    sdio接口除了可以用于SD的读写,还有什么应用吗?

    sdio接口除了可以用于SD的读写,还有什么应用吗?
    发表于 10-24 06:42