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

雍正不秃头

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

暂无评论

发表评论

相关推荐

74HC138译码器的原理和使用

前言 译码器就是将每个输入的二进制代码译成对应的输出高低电平信号,和编码器互为逆过程。 百度百科 74HC138是一款高速CMOS器件,74HC138引脚兼容低功耗肖特基TTL(LSTTL&#xf

ESP32-GY_30光照强度传感器

GY_30光照强度传感器介绍 GY-30光强传感器特点及使用介绍 一、连接引脚 GY_30光照强度传感器使用I2C传输数据 。 5根引脚,名称与功能如下; vcc 为外接供电电源输入端 GND 地线 SCL I2C通信模式时钟