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
暂无评论