LittleFS:一个完整的嵌入式文件系统介绍、移植使用教程

LittleFS - 一个高度完整的嵌入式文件系统、移植使用教程


关注以下公众号,回复关键字stm32-lfs获取下载链接!


1、介绍

源码下载地址:https://github.com/littlefs-project/littlefs/tags

截止2021年9月15日10:21:17,最新版本为2.4.1,当前移植使用教程基于2.4.1版本为例。

LittleFS 由ARM官方发布,ARM mbedOS的官方推荐文件系统,具有轻量级,掉电安全的特性。主要用在微控制器和flash上,特点如下:

  • 掉电恢复,在写入时即使复位或者掉电也可以恢复到上一个正确的状态。
  • 擦写均衡,有效延长flash的使用寿命。例如W25QXX系列的spi接口的flash,擦写次数大概在10万次,如果是操作flash比较频繁那么这10万次很快就会到达上限从而导致芯片废掉。
  • 有限的RAM/ROM,相对于FATFS节省ROM和RAM空间

缺点:不兼容windows。


2、移植使用

LittleFS的移植是比较简单的,但是移植接口官方说的不是很明白,并且提供了动态内存和静态内存两种使用方式,所以还是有一些需要注意的地方,本教程基于STM32+W25QXX系列的SPI接口FLASH芯片进行移植教程。

解压LittleFS源码后,我们只需要将这四个文件加入我们的工程即可:

我还新建了一个lfs_port.c文件,将这个文件作为移植接口:

2.1 动态内存使用方式

使用动态内存比较简单,默认是使用的malloc函数进行的内存申请,通过修改lfs_util.h文件中lfs_malloc函数接口调整内存申请函数:

官方示例代码:

#include "lfs.h"

// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;

// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
    // block device operations
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // block device configuration
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// entry point
int main(void) {
    // mount the filesystem
    int err = lfs_mount(&lfs, &cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) {
        lfs_format(&lfs, &cfg);
        lfs_mount(&lfs, &cfg);
    }

    // read current count
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

    // release any resources we were using
    lfs_unmount(&lfs);

    // print the boot count
    printf("boot_count: %d\n", boot_count);
}

接口函数主要就是cfg这个结构中的read、prog、erase、sync这四个函数指针,将这四个函数指针指向我们实际的FLASH操作函数即可,示例如下:

2.2 静态内存使用方式

先说一下几个宏:

  • LFS_NO_MALLOC:决定使用动态内存还是静态内存
  • LFS_NO_ASSERT:决定是否使用断言
  • LFS_YES_TRACE:决定是否使用LFS函数调用跟踪,调试时候可以打开
  • LFS_NO_DEBUG:决定是否使用调试信息输出
  • LFS_NO_ERROR:决定是否使用错误信息输出
  • LFS_NO_WARN:决定是否使用警告信息输出

这几个宏需要在头文件lfs_util.h头部定义,例如:

LFS当使用静态内存时,LFS的源码还是使用了lfs_malloc函数,具体在:

lfs_file_rawopen->lfs_file_rawopencfg中,有这么一段代码:


由于在lfs_file_rawopen函数中他是使用的一个固定的静态lfs_file_config结构传入lfs_file_rawopencfg函数中的:

并且这个结构中的buffer成员是NULL,所以必定会执行这一句进行内存申请:

如果不修改lfs_malloc函数,那么就会申请内存失败。

我们修改一下lfs_malloc函数,直接返回一个静态数组就可以:

这里需要注意一点,通过我的使用和查看源码发现,也就是有我上面提到的用到了lfs_malloc函数申请内存的地方,这个地方的内存申请是用于cache_buffer,所以lfs_malloc返回的数组大小必须大于等于cacha_size的大小才可以。

然后与动态内存不同的是,还需要在cfg结构中指定几个缓存的地址,如下:

const struct lfs_config cfg =
{
	// block device operations
	.read  = lfs_deskio_read,
	.prog  = lfs_deskio_prog,
	.erase = lfs_deskio_erase,
	.sync  = lfs_deskio_sync,

	// block device configuration
	.read_size = 16,
	.prog_size = 16,
	.block_size = 4096,
	.block_count = 128,
	.cache_size = 16,
	.lookahead_size = 16,
	.block_cycles = 500,

	//
	// 使用静态内存必须设置这几个缓存
	//
	.read_buffer = read_buffer,
	.prog_buffer = prog_buffer,
	.lookahead_buffer = lookahead_buffer,
};

read_buffer 、prog_buffer 、lookahead_buffer 这三个缓存当我们使用动态内存时它是自动申请的,现在我们使用的是静态内存,需要手动指定缓存地址。

接口文件lfs_port.c完整代码如下:

#include "lfs.h"
#include "w25qxx.h"

/**
 * lfs与底层flash读数据接口
 * @param  c
 * @param  block  块编号
 * @param  off    块内偏移地址
 * @param  buffer 用于存储读取到的数据
 * @param  size   要读取的字节数
 * @return
 */
static int lfs_deskio_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
	W25QXX_Read((uint8_t *)buffer, c->block_size * block + off, size);
	return LFS_ERR_OK;
}

/**
 * lfs与底层flash写数据接口
 * @param  c
 * @param  block  块编号
 * @param  off    块内偏移地址
 * @param  buffer 待写入的数据
 * @param  size   待写入数据的大小
 * @return
 */
static int lfs_deskio_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
	W25QXX_Write_NoCheck((uint8_t *)buffer, c->block_size * block + off, size);
	return LFS_ERR_OK;
}

/**
 * lfs与底层flash擦除接口
 * @param  c
 * @param  block 块编号
 * @return
 */
static int lfs_deskio_erase(const struct lfs_config *c, lfs_block_t block)
{
	W25QXX_Erase_Sector(block);
	return LFS_ERR_OK;
}

static int lfs_deskio_sync(const struct lfs_config *c)
{
	return LFS_ERR_OK;
}


///
/// 静态内存使用方式必须设定这四个缓存
///
__align(4) static uint8_t read_buffer[16];
__align(4) static uint8_t prog_buffer[16];
__align(4) static uint8_t lookahead_buffer[16];

// lfs句柄
lfs_t lfs_w25qxx;

lfs_file_t lfs_file_w25qxx;

const struct lfs_config cfg =
{
	// block device operations
	.read  = lfs_deskio_read,
	.prog  = lfs_deskio_prog,
	.erase = lfs_deskio_erase,
	.sync  = lfs_deskio_sync,

	// block device configuration
	.read_size = 16,
	.prog_size = 16,
	.block_size = 4096,
	.block_count = 128,
	.cache_size = 16,
	.lookahead_size = 16,
	.block_cycles = 500,

	//
	// 使用静态内存必须设置这几个缓存
	//
	.read_buffer = read_buffer,
	.prog_buffer = prog_buffer,
	.lookahead_buffer = lookahead_buffer,
};

// entry point
void lfs_test(void)
{
	// mount the filesystem
	int err = lfs_mount(&lfs_w25qxx, &cfg);

	// reformat if we can't mount the filesystem
	// this should only happen on the first boot
	if (err)
	{
		lfs_format(&lfs_w25qxx, &cfg);
		lfs_mount(&lfs_w25qxx, &cfg);
	}

	// read current count
	uint32_t boot_count = 0;
	lfs_file_open(&lfs_w25qxx, &lfs_file_w25qxx, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
	lfs_file_read(&lfs_w25qxx, &lfs_file_w25qxx, &boot_count, sizeof(boot_count));

	// update boot count
	boot_count += 1;
	lfs_file_rewind(&lfs_w25qxx, &lfs_file_w25qxx);
	lfs_file_write(&lfs_w25qxx, &lfs_file_w25qxx, &boot_count, sizeof(boot_count));

	// remember the storage is not updated until the file is closed successfully
	lfs_file_close(&lfs_w25qxx, &lfs_file_w25qxx);

	// release any resources we were using
	lfs_unmount(&lfs_w25qxx);

	// print the boot count
	printf("boot_count: %d\n", boot_count);
}


3、测试

测试代码如下:

将程序下载进STM32,不断按复位,查看打印:

boot_count这个计数掉电后不会从0开始,说明移植并且读写成功!


ends…

版权声明:本文为CSDN博主「雍正不秃头」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq153471503/article/details/120303225

生成海报
点赞 0

雍正不秃头

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

基于STM32的指纹密码锁

设计简介: 本设计是基于单片机的指纹密码锁,主要实现以下功能: 矩阵按键输入密码,并通过按键显示*号可通过按键或手机开门密码可通过按键进行开门可通过蓝牙模块连接手机进行开门可通过指纹进

定时器触发STM32 ADC的采样转换示例

开发板:STM32F446 Nucleo开发板IDE:  keil MDK初始化配置工具:stm32cubeMx例程内容:通过定时器触发ADC规则通道及注入通道的模数转换工作。下面基于STM32CubeMx进行些必要