文章目录
- 前言
- 一、移植LittleFS
- 二、API
- 三、演示
前言
在使用单片机设计的系统中经常使用价格低廉的存储方案为SPI FLASH(W25Qxx),在单片机中使用最多的文件系统为FatFS,但对于W25Qxx存储芯片来说FatFS并不是一个好的方案,原因如下:
(图片来源网络,侵删)1、FatFS不支持擦写均衡,LittleFS支持,Flash扇区有擦写寿命,如果一直擦写一个扇区会很快将一个扇区擦写坏。
2、FatFS不支持掉电保存功能,LittleFS支持,如果在写入数据时掉电虽然不会保存本次写入的数据但也不会丢失上次写入之前的数据。
(图片来源网络,侵删)3、LittleFS支持坏块检测功能,在写入后会进行回读判断写入数据是否正确
4、LittleFS占用的RAM,ROM资源少
LittleFS缺点是无法和Windows通用
一、移植LittleFS
LittleFS下载地址
首先下载LittleFS,将lfs.c,lfs.h,lfs_util.c,lfs_util.h复制到自己的工程中,有几点需要注意:
1.在lfs_util.h中有两个函数lfs_malloc和lfs_free,虽然可以用宏定义定义LFS_NO_MALLOC不使用动态内存,但是在文件系统中打开文件时仍然调用了这个两个函数,在使用不使用动态内存时需要自己定义一个cache_size大小数组在lfs_malloc中返回这个数组,在不使用动态内存的情况下只能打开一个文件进行读写。
extern uint8_t file_buf[cache_size]; #define LFS_NO_MALLOC //不使用动态内存 static inline void *lfs_malloc(size_t size) { #ifndef LFS_NO_MALLOC return malloc(size); #else return file_buf;//返回数组 #endif } // Deallocate memory, only used if buffers are not provided to littlefs static inline void lfs_free(void *p) { #ifndef LFS_NO_MALLOC free(p); #else (void)p; #endif }
2.在keil中-O0优化时lfs文件系统使用的栈最大深度大于1040个字节(具体大小没有细测),-O2优化时大于800个字节,STM32的启动文件中分配的栈大小为1024个字节,所以这里需要注意,是选择-O2的优化还是和下方一样更改栈的大小,当然对于不带RTOS的程序影响不大,因为栈分配时默认是在单片机的RAM的最高地址分配向下生长的,而程序中用的变量是从RAM的低地址开始分配的,使用时只要不是将RAM全部用完了那么影响就不是很大,但对于带RTOS的程序来说就必须要注意给任务分配的栈大小了。
Stack_Size EQU 0x00000400
更改为
Stack_Size EQU 0x00001000
然后需要实现一个lfs.c文件中的lfs_config结构体,结构体内容如下:
context 用户自己定义的变量,LittleFS不会使用 read 一个函数指针,指向用执行Flash读操作的函数 prog 一个函数指针,指向用执行Flash写操作的函数 erase 一个函数指针,指向用执行Flash擦除扇区操作的函数 sync 一个函数指针,同步状态 lock 一个函数指针,在使用RTOS时上锁 unlock 一个函数指针,在使用RTOS时解锁 read_size 读取的最小单位 prog_size 写入的最小单位 block_size 块大小 block_count 块个数 block_cycles 擦写均衡的系数 cache_size 读写缓存区大小 lookahead_buffer 用于搜索文件的缓存区大小 我这里使用的为W25Q128,扇区大小为4K,共4096个扇区。
static uint8_t read_buf[4096]; static uint8_t write_buf[4096]; static uint8_t lookahead_buf[4096]; static struct lfs_config lfs_w25qxx_cfg = { .read = lfs_read, .prog = lfs_prog, .erase = lfs_erase, .sync = lfs_sync, .read_size = 1,//最小读取单位为1字节 .prog_size = 1,//最小编程单位为1字节 .block_size = 4096,//块大小为4096 .block_count = 4096,//块个数为4096个 .block_cycles = 500,// .cache_size = 4096,//读写缓存为4096字节 .lookahead_size = 4096, .read_buffer = read_buf, .prog_buffer = write_buf, .lookahead_buffer = lookahead_buf, };
二、API
int lfs_format(lfs_t *lfs, const struct lfs_config *config);
格式存储设备,一般在调用lfs_mount挂载失败后掉用。
int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
挂载文件系统
int lfs_remove(lfs_t *lfs, const char *path);
删除文件或文件夹
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
重命名
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
获取文件或目录信息
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, uint8_t type, void *buffer, lfs_size_t size);
获取文件或目录一个自定义的属性
int lfs_setattr(lfs_t *lfs, const char *path,
uint8_t type, const void *buffer, lfs_size_t size);
给文件或目录设置一个自定义的属性
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
移除自定义属性
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags);
打开文件
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags,
const struct lfs_file_config *config);
自己提供一个文件配置打开文件
int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
关闭文件
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
同步内存和片外存储,将缓冲数据写入到片外存储
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size);
读取文件数据
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);
写文件数据
三、演示
lfs_t lfs; lfs_file_t file; lfs_dir_t dir; struct lfs_info info; int main(void) { int err; err = lfs_mount(&lfs, &lfs_w25qxx_cfg);//第一步要挂载文件系统 if(err 1bc|45 lfs_file_seek(&lfs, &file, -2, LFS_SEEK_CUR); lfs_file_write(&lfs, &file, "d", 1); lfs_file_sync(&lfs, &file);//这时文件内容为"1bcd5" lfs_file_close(&lfs, &file); //对test文件设置一个时间和一个日期的自定义属性,在删除文件时也会删除 #define FILE_TIME_TYPE 1 #define FILE_DATE_TYPE 2 lfs_setattr(&lfs, "test", FILE_TIME_TYPE, "12:00:00", 8); lfs_setattr(&lfs, "test", FILE_DATE_TYPE, "2023-1-1", 8); //在根目录下创建了一个名为abc的目录 //在abc目录下创建了一个名为test的文件,当前有两个test文件一个在根目录一个在abc目录中 lfs_mkdir(&lfs, "abc"); lfs_dir_open(&lfs, &dir, "abc"); lfs_file_open(&lfs, &file, "test"); lfs_file_close(&lfs, &file); lfs_dir_close(&lfs, &dir); //遍历根目录下的内容,会递归遍历根目录下的目录里的内容 //同时每个目录都会遍历到一个"."和一个".."的文件夹表示当前文件夹和返回上一个文件夹的路径 lfs_dir_open(&lfs, &dir, "."); while(1) { err = lfs_dir_read(&lfs, &dir, &info); if(err