使用STM32和LittleFS文件系统进行数据记录

文章目录

  • 前言
  • 一、移植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的优化还是和下方一样更改栈的大小。

    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 < 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_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_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"
    
    	lfs_file_seek(&lfs, &file, -2, LFS_SEEK_CUR);//文件指针设置到相对于当前位置-1
    	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 1
    	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);
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32和LittleFS文件系统进行数据记录

    发表评论