前言
在使用单片机设计的系统中经常使用价格低廉的存储方案为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);
读取文件数据文章来源:https://www.toymoban.com/news/detail-607447.html
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);
写文件数据文章来源地址https://www.toymoban.com/news/detail-607447.html
三、演示
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 < 0)
{
lfs_format(&lfs, &lfs_w25qxx_cfg);
llfs_mount(&lfs, &lfs_w25qxx_cfg);
}
//以下操作都为假设操作成功
//创建一个名为test的文件向文件中写入"1234"4个字节数据
lfs_file_open(&lfs, &file, "test", LFS_O_CREAT | LFS_O_RDWR);
lfs_file_write(&lfs, &file, "1234", 4);
lfs_file_close(&lfs, &file);
//这时虽然打开文件时也使用了LFS_O_CREAT标志但是并不会创建一个新的文件也不会报错,在加入LFS_O_EXCL标志后才会报错
//LFS_O_RDONLY 标志表示以只读打开文件
//LFS_O_WRONLY 标志表示以只写打开文件
//LFS_O_RDWR 标志表示以可读可写打开文件,等价于 LFS_O_RDONLY | LFS_O_WRONLY
//LFS_O_CREAT 打开文件时如果文件不存在就创建新文件并打开,如果存在将读写指针定位到文件开头打开文件
//LFS_O_EXCL 打开文件时如果文件不存在就创建新文件并打开,如果存在就报错
//LFS_O_TRUNC 打开一个已有文件并将文件大小设置为0
//LFS_O_APPEND 打开一个已有文件并将文件的读写指针设置到文件最后
lfs_file_open(&lfs, &file, "test", LFS_O_CREAT | LFS_O_RDWR);
lfs_file_write(&lfs, &file, "abc", 3);
lfs_file_sync(&lfs, &file);//这时会见内存中的缓存数据写入到Flash中,这时文件内容为"abc4"
//LFS_SEEK_SET 用绝对位置设置文件的读写指针(用相对用文件开头的位置设置读写指针)
//LFS_SEEK_CUR 用相对于当前的位置位置设置读写指针
//LFS_SEEK_END 用相对用文件末尾的位置设置读写指针
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET);//文件指针返回到文件开头
lfs_file_write(&lfs, &file, "1", 1);
lfs_file_sync(&lfs, &file);//这时文件内容为"1bc4"
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_END);//文件指针设置到文件最后
lfs_file_write(&lfs, &file, "5", 1);
lfs_file_sync(&lfs, &file);//这时文件内容为"1bc45"
//文件指针设置到相对于当前位置-2,1bc45| --> 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 < 0)
break;
}
lfs_dir_close(&lfs, &dir);
lfs_unmount(&lfs);
}
到了这里,关于STM32使用LittleFS文件系统记录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!