LittleFS文件系统移植和使用记录


前言

在使用单片机设计的系统中经常使用价格低廉的存储方案为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 < 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);
}

四、分享两个应用littlefs文件系统的扩展

1、键值数据存储

使用littlefs做键值对存储,常用场景为参数存储,关键数据存储等,可以搭配上位机使用。

1)代码

lfs_kvdb.h文件

/*** 
 * @Author: 时间世纪 https://blog.youkuaiyun.com/weixin_43908815?type=blog
 * @Date: 2024-06-25 09:25:02
 * @LastEditTime: 2024-06-26 10:44:07
 * @LastEditors: your name
 * @Description: 
 * SPDX-License-Identifier: BSD-3-Clause
 */
#ifndef _LFS_KVDB_H_
#define _LFS_KVDB_H_

#include "lfs_port.h"

typedef struct
{
    char* key;
    void* value;
    uint16_t len : 15;
    uint16_t flash : 1;
}lfs_kvdb_item_t;


typedef struct
{
    char* name;//本键值存储库的名称,在创建结构体时需要手动设置
    uint16_t number;//本键值存储库的键值对数目,在创建结构体时需要手动设置
    lfs_kvdb_item_t* items;//本键值存储库的键值对集合,在创建结构体时需要手动设置,应该指向应该lfs_kvdb_item_t类型的数组
}lfs_kvdb_t;


#define NEW_KV_ITEM(variate) {#variate, &variate, sizeof(variate), 0}
#define NEW_KV_ITEM_RENAME(name, variate) {#name, &variate, sizeof(variate), 0}
#define NEW_KV_ARRAY(variate) {#variate, variate, sizeof(variate), 0}
#define NEW_KV_ARRAY_RENAME(name, variate) {#name, variate, sizeof(variate), 0}
/*** 
 * @description: 初始化KV数据库
 * @param {lfs_t*} lfs 
 * @param {lfs_kvdb_t*} kvdb 
 * @return {*}
 */
int lfs_kvdb_init(lfs_t* lfs, lfs_kvdb_t* kvdb);
/*** 
 * @description: 
 * @param {lfs_t*} lfs
 * @param {lfs_kvdb_t*} kvdb
 * @return {*}
 */
int lfs_kvdb_deinit(lfs_t* lfs, lfs_kvdb_t* kvdb);
/*** 
 * @description: 获取项
 * @param {lfs_t} *lfs 
 * @param {lfs_kvdb_t*} kvdb
 * @param {char*} key 建称
 * @return {*}
 */
lfs_kvdb_item_t* lfs_kvdb_get(lfs_t *lfs, lfs_kvdb_t* kvdb, char* key);
/*** 
 * @description: 设置项,设置时只设置了结构体中的数据,在设置完成需调用"lfs_kvdb_flush"一次性写入进去
 * @param {lfs_t} *lfs
 * @param {lfs_kvdb_t*} kvdb
 * @param {char*} key
 * @param {void*} value
 * @param {uint16_t} len
 * @return {*}
 */
int lfs_kvdb_set(lfs_t *lfs, lfs_kvdb_t* kvdb, char* key, void* value, uint16_t len);
/*** 
 * @description: 将更改项写入存储
 * @param {lfs_t} *lfs
 * @param {lfs_kvdb_t*} kvdb
 * @return {*}
 */
int lfs_kvdb_flush(lfs_t *lfs, lfs_kvdb_t* kvdb);


#endif

lfs_kvdb.c文件

/*** 
 * @Author: 时间世纪 https://blog.youkuaiyun.com/weixin_43908815?type=blog
 * @Date: 2024-06-25 13:28:29
 * @LastEditTime: 2024-06-29 14:15:03
 * @LastEditors: your name
 * @Description: 
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * 用法示例
 * 先创建一个lfs_kvdb_item_t的结构体数组用来保存键值对
 * int a = 10;
 * int b = 20;
 * int c[] = {1,2,3};
 * int b[] = {4,5,6};
 * lfs_kvdb_item_t lfs_kvdb_items[] = 
 * {
 *      NEW_KV_ITEM(a),//添加一个键值为a的键值对
 *      NEW_KV_ITEM_RENAME(B, b),//添加一个键值为B,值为变量b的键值对
 *      NEW_KV_ARRAY(c),
 *      NEW_KV_ARRAY_RENAME(D, d),
 * };
 * 然后创建lfs_kvdb_t的结构体
 * lfs_kvdb_t lfs_kvdb = {
 *      .name = "kvdb",
 *      .number = sizeof(lfs_kvdb_items) / sizeof(lfs_kvdb_items[0]),
 *      .items = lfs_kvdb_items
 * };
 * 然后进行初始化
 * lfs_kvdb_init(&lfs, &lfs_kvdb);
 * int value = 55;
 * lfs_kvdb_set(&lfs, &lfs_kvdb, "a", &value, 4);//设置a为55
 * value = 45;
 * lfs_kvdb_set(&lfs, &lfs_kvdb, "B", &value, 4);//设置B为45
 * int array[] = {1, 3, 5};
 * lfs_kvdb_set(&lfs, &lfs_kvdb, "c", array, lfs_kvdb_get(&lfs, &lfs_kvdb, "c")->len);
 * lfs_kvdb_flush(&lfs, &lfs_kvdb);//前面只设置了结构体的值并没有真正更新到存储中去,这里更新到存储中去
 */
#include "lfs_kvdb.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static int cmpfunc(const void *a, const void *b) //  根据名字进行排序
{
    return strcmp(((lfs_kvdb_item_t *)a)->key, ((lfs_kvdb_item_t *)b)->key); // 比较的名字
}

static int lfs_kvdb_nextitem(lfs_t *lfs, lfs_file_t *file, lfs_kvdb_item_t *kv)
{
    int res;
    uint16_t len;
    if (lfs_file_size(lfs, file) - lfs_file_tell(lfs, file) <= 2)
        return -1;
    res = lfs_file_read(lfs, file, &len, 2);//读取键长度
    if (res != 2)
        return -2;
    if (lfs_file_size(lfs, file) - lfs_file_tell(lfs, file) <= len)
        return -1;
    res = lfs_file_read(lfs, file, kv->key, len);//读取键名称
    if (res != len)
        return -2;
    if (lfs_file_size(lfs, file) - lfs_file_tell(lfs, file) <= 2)
        return -1;
    res = lfs_file_read(lfs, file, &len, 2);//读取值长度
    if (res != 2)
        return -2;
    if (lfs_file_size(lfs, file) - lfs_file_tell(lfs, file) < len)
        return -1;
    kv->len = len;
    res = lfs_file_read(lfs, file, kv->value, len);//读取值
    if (res != len)
        return -2;
    return 0;
}

/***
 * @description:
 * @param {lfs_t*} lfs
 * @param {lfs_file_t*} file
 * @param {char*} name
 * @param {lfs_kvdb_t} kvdb
 * @param {uint8_t} num
 * @return {*}
 */
int lfs_kvdb_init(lfs_t *lfs, lfs_kvdb_t *kvdb)
{
    char temp1[128];
    char temp2[128];
    uint8_t syncFlag = 0;
    lfs_file_t kvdbFile;
    int res;
    qsort(kvdb->items, kvdb->number, sizeof(lfs_kvdb_item_t), cmpfunc); // 数组排序,可以用二分查找进查找优化
    sprintf(temp1, "./kvdb/%s", kvdb->name);
    if ((res = lfs_file_open(lfs, &kvdbFile, temp1, LFS_O_RDWR | LFS_O_CREAT)) == LFS_ERR_NOENT)
    {
        lfs_mkdir(lfs, "./kvdb");
        lfs_file_open(lfs, &kvdbFile, temp1, LFS_O_RDWR | LFS_O_CREAT);
    }
    if (lfs_file_size(lfs, &kvdbFile) > 0)
    {//当前已经存在数据里
        uint16_t index = 0;
        lfs_file_rewind(lfs, &kvdbFile);
        while (1)
        {
            lfs_kvdb_item_t tempkv;
            tempkv.key = temp1;
            tempkv.value = temp2;
            res = lfs_kvdb_nextitem(lfs, &kvdbFile, &tempkv);//读取下一项配置
            if (res == 0)
            {
                res = strcmp(tempkv.key, kvdb->items[index].key);
                if (res == 0 && tempkv.len == kvdb->items[index].len)
                    memcpy(kvdb->items[index].value, tempkv.value, tempkv.len);//键和值长度没有变化,拷贝值
                else if ((res == 0 && tempkv.len != kvdb->items[index].len) || (index + 1 == kvdb->number))
                {//值的长度变了,不同步到结构体中来,并且把结构体的更新到存储中去, 或者如果当前已经到最后一个了不一样直接更新
                    syncFlag = 1;
                }
                else
                {
                    //键不一样了,应该是有删减,如果这个键在结构体中没有找到那么应该是删减了正常继续索引下一个数据
                    //如果在结构体中找到了这个键,那么是结构体中增加了数据,就要更新存储的数据了
                    for (uint16_t i = index + 1; i < kvdb->number; i++)
                    {
                        res = strcmp(tempkv.key, kvdb->items[i].key);
                        if (res == 0)
                        {
                            if (kvdb->items[i].len == tempkv.len)
                            {
                                memcpy(kvdb->items[index].value, tempkv.value, tempkv.len);
                            }
                            index = i;
                            syncFlag = 1;
                            break;
                        }
                    }
                }
            }
            else if (res == -1)
            {
                syncFlag = 1;
                break;
            }
            else if (res == -2)
            {
                break;
            }
            index++;
            if (index == kvdb->number)
                break;
        }
    }
    else
        syncFlag = 1;
    lfs_file_close(lfs, &kvdbFile);
    if (syncFlag)
    {
        sprintf(temp1, "./kvdb/%s_temp", kvdb->name);
        sprintf(temp2, "./kvdb/%s", kvdb->name);
        res = lfs_file_open(lfs, &kvdbFile, temp1, LFS_O_RDWR | LFS_O_CREAT);
        for (uint16_t i = 0; i < kvdb->number; i++)
        {
            uint16_t len = strlen(kvdb->items[i].key) + 1;
            lfs_file_write(lfs, &kvdbFile, &len, 2);
            lfs_file_write(lfs, &kvdbFile, kvdb->items[i].key, len);
            len = kvdb->items[i].len;
            lfs_file_write(lfs, &kvdbFile, &len, 2);
            lfs_file_write(lfs, &kvdbFile, kvdb->items[i].value, len);
        }
        lfs_file_close(lfs, &kvdbFile);
        lfs_remove(lfs, temp2);
        lfs_rename(lfs, temp1, temp2);
    }
    return 0;
}

int lfs_kvdb_deinit(lfs_t* lfs, lfs_kvdb_t* kvdb)
{
    char temp1[128], temp2[128];
    lfs_file_t kvdbFile;
    int res;
    sprintf(temp1, "./kvdb/%s_temp", kvdb->name);
    res = lfs_file_open(lfs, &kvdbFile, temp1, LFS_O_RDWR | LFS_O_CREAT);
    if(res != LFS_ERR_OK)
        return res;
    for (uint16_t i = 0; i < kvdb->number; i++)
    {
        uint16_t len = strlen(kvdb->items[i].key) + 1;
        lfs_file_write(lfs, &kvdbFile, &len, 2);
        lfs_file_write(lfs, &kvdbFile, kvdb->items[i].key, len);
        len = kvdb->items[i].len;
        lfs_file_write(lfs, &kvdbFile, &len, 2);
        lfs_file_write(lfs, &kvdbFile, kvdb->items[i].value, len);
        kvdb->items[i].flash = 0;
    }
    lfs_file_close(lfs, &kvdbFile);
    sprintf(temp2, "./kvdb/%s", kvdb->name);
    lfs_remove(lfs, temp2);
    lfs_rename(lfs, temp1, temp2);
    return 0;
}

lfs_kvdb_item_t *lfs_kvdb_get(lfs_t *lfs, lfs_kvdb_t *kvdb, char *key)
{
    lfs_kvdb_item_t item = {key, 0, 0};
    // 二分法查找
    lfs_kvdb_item_t *search_p = bsearch(&item, kvdb->items, kvdb->number, sizeof(lfs_kvdb_item_t), cmpfunc);
    return search_p;
}

int lfs_kvdb_set(lfs_t *lfs, lfs_kvdb_t *kvdb, char *key, void *value, uint16_t len)
{
    lfs_kvdb_item_t *p = lfs_kvdb_get(lfs, kvdb, key); 
    if (p == 0)
        return 1;
    if (p->len != len) 
        return 2;
    if (memcmp(value, p->value, p->len) == 0)
        return 0;
    p->flash = 1;
    memcpy(p->value, value, p->len);
    return 0;
}

int lfs_kvdb_flush(lfs_t *lfs, lfs_kvdb_t *kvdb)
{
    char temp1[128];
    char temp2[128];
    lfs_file_t kvdbFile;
    int res, count = 0;
    uint32_t offset = 0;
    sprintf(temp1, "./kvdb/%s", kvdb->name);
    res = lfs_file_open(lfs, &kvdbFile, temp1, LFS_O_RDWR | LFS_O_CREAT);
    if (res != LFS_ERR_OK)
    {
        lfs_file_close(lfs, &kvdbFile);
        return -1;
    }
    for (uint16_t i = 0; i < kvdb->number; i++)
    {
        if (kvdb->items[i].flash)
        {
            kvdb->items[i].flash = 0;
            count++;
            lfs_file_seek(lfs, &kvdbFile, offset + 4 + strlen(kvdb->items[i].key) + 1, LFS_SEEK_SET);
            lfs_file_write(lfs, &kvdbFile, kvdb->items[i].value, kvdb->items[i].len);
        }
        offset += 4;
        offset += strlen(kvdb->items[i].key) + 1;
        offset += kvdb->items[i].len;
    }
    lfs_file_close(lfs, &kvdbFile);
    return count;
}

2、littlefs日志存储

主要用来记录系统中发生的事件及时间等信息。
lfs_tsdb.h

/*** 
 * @Author: 时间世纪 https://blog.youkuaiyun.com/weixin_43908815?type=blog
 * @Date: 2024-06-25 09:41:25
 * @LastEditTime: 2024-06-26 09:49:15
 * @LastEditors: your name
 * @Description: 
 * SPDX-License-Identifier: BSD-3-Clause
 */
#ifndef _LFS_TSDB_H_
#define _LFS_TSDB_H_

#include "lfs_port.h"
#include "semphr.h"

typedef struct
{
    char *name;                     // 库名称,需手动设置
    uint16_t max_itme_size;         // 每条记录条目最大大小,需手动设置,这个设置一旦初始化后不允许改变,否则格式会错乱
    uint32_t max_item_num;          // 单记录文件记录条数最大数目,需手动设置
    uint32_t max_file_num;          // 最大文件数,存储库自动管理,在达到这个数目时会删除最早的一个文件,需手动设置
    uint32_t (*get_current_time)(void); // 获取系统时间的回调,返回应该unix时间戳,需手动设置
    uint32_t last_time;             // 最后一次保存的时间,unix时间戳,无需手动设置,在调用初始化函数中自动设置
    uint32_t file_count;            //当前文件数目,无需手动设置,在调用初始化函数中自动设置
    uint32_t min_file_count;        //最小的文件索引,无需手动设置,在调用初始化函数中自动设置
    uint32_t open_file_size;        //当前打开的文件已达到的文件大小,无需手动设置,在调用初始化函数中自动设置
    SemaphoreHandle_t sem;          //在写这个库时使用的FreeRTOS,所以这里直接用FreeRTOS的互斥
    lfs_file_t file;                //当前正在操作的文件
} lfs_tsdb_t;


/***
 * @description: 时序数据库初始化
 * @param {lfs_t*} lfs
 * @param {lfs_tsdb_t*} tsdb 数据库句柄
 * @return {*}
 */
int lfs_tsdb_init(lfs_t *lfs, lfs_tsdb_t *tsdb);
/***
 * @description: 写入日志
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {char} *buf
 * @param {uint16_t} len
 * @return {*}
 */
int lfs_tsdb_write(lfs_t *lfs, lfs_tsdb_t *tsdb, char *buf, uint16_t len);
/*** 
 * @description: 
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {uint32_t} startTime
 * @param {uint32_t} endTime
 * @return {*}
 */
int lfs_tsdb_count(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t startTime, uint32_t endTime);
/*** 
 * @description: 
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {uint32_t} startTime
 * @param {uint32_t} endTime
 * @param {uint8_t (*cb)(uint32_t, char *, uint16_t)} 读取数据时的回调,每一条数据调用一次
 *          参数1:时间戳
 *          参数2:日志数据
 *          参数3:日志数据长度
 *          返回值:0:继续下一条,1:停止读取
 * @return {*}
 */
int lfs_tsdb_read(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t startTime, uint32_t endTime, uint8_t (*cb)(uint32_t, char *, uint16_t));

#endif

lfs_tsdb.c

/*** 
 * @Author: 时间世纪 https://blog.youkuaiyun.com/weixin_43908815?type=blog
 * @Date: 2024-06-25 09:41:14
 * @LastEditTime: 2024-06-26 14:20:15
 * @LastEditors: your name
 * @Description: 
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * 用法示例
 * 需要先创建一个lfs_tsdb_t结构体,并将需要手动设置的项设置一个值
 * lfs_tsdb_t lfs_tsdb = 
 * {
 *      .name = "tsdb",
 *      .max_itme_size = 128,
 *      .max_item_num = 30,
 *      .max_file_num = 10,
 *      .get_current_time = get_time
 * };
 * 
 * lfs_tsdb_init(&lfs, &lfs_tsdb);
 * lfs_tsdb_write(&lfs, &lfs_tsdb, "test", 4);
 */




#include "lfs_tsdb.h"
#include "semphr.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/***
 * @description: 时序数据库初始化
 * @param {lfs_t*} lfs
 * @param {lfs_tsdb_t*} tsdb 数据库句柄
 * @return {*}
 */
int lfs_tsdb_init(lfs_t *lfs, lfs_tsdb_t *tsdb)
{
    int res;
    char temp1[128];
    char temp2[128];
    lfs_dir_t tsdbDir;
    struct lfs_info info;
    tsdb->file_count = 1;
    tsdb->min_file_count = 1;
    tsdb->open_file_size = 0;
    tsdb->sem = xSemaphoreCreateMutex();
    sprintf(temp1, "./tsdb/%s", tsdb->name);
    if ((res = lfs_dir_open(lfs, &tsdbDir, temp1)) == LFS_ERR_NOENT)
    {
        lfs_mkdir(lfs, "./tsdb");
        lfs_mkdir(lfs, temp1);
        sprintf(temp2, "./tsdb/%s/1", tsdb->name);
        lfs_file_open(lfs, &tsdb->file, temp2, LFS_O_RDWR | LFS_O_CREAT);
        return 0;
    }
    if (res == LFS_ERR_OK)
    {
        uint32_t maxFileCount = 0, minFileCount = 0, fileCount = 0;
        uint32_t maxFileLen = 0;
        while (1)
        {//找到最晚创建的文件
            res = lfs_dir_read(lfs, &tsdbDir, &info);
            if (res == 0)
                break;
            if (info.type == LFS_TYPE_REG)
            {
                fileCount = atoi(info.name);
                if (fileCount > maxFileCount)
                {
                    maxFileCount = fileCount;
                    maxFileLen = info.size;
                }
                if (minFileCount == 0 || fileCount < minFileCount)
                    minFileCount = fileCount;
            }
        }
        if (maxFileCount - minFileCount > tsdb->max_file_num)
        {//需要删除多余的文件
            for (; minFileCount < maxFileCount - tsdb->max_file_num; minFileCount++)
            {
                sprintf(temp1, "./tsdb/%s/%d", tsdb->name, minFileCount);
                lfs_remove(lfs, temp1);
            }
        }
        tsdb->min_file_count = minFileCount;
        tsdb->file_count = maxFileCount;
        //确认最后一次写入时的文件的路径
        if(maxFileLen == 0 && maxFileCount > 1)
            sprintf(temp1, "./tsdb/%s/%d", tsdb->name, maxFileCount - 1);
        else if(maxFileLen != 0)
            sprintf(temp1, "./tsdb/%s/%d", tsdb->name, maxFileCount);
        else
            temp1[0] = 0;
        if(temp1[0] != 0)
        {//读出最后的写入时间
            lfs_file_open(lfs, &tsdb->file, temp1, LFS_O_RDWR | LFS_O_CREAT);
            lfs_file_seek(lfs, &tsdb->file, -(lfs_file_size(lfs, &tsdb->file) - tsdb->max_itme_size - 2 - 4), LFS_SEEK_END);
            lfs_file_read(lfs, &tsdb->file, &tsdb->last_time, 4);
            lfs_file_close(lfs, &tsdb->file);
        }
        //打开或创建接下来操作的文件
        sprintf(temp1, "./tsdb/%s/%d", tsdb->name, maxFileCount);
        lfs_file_open(lfs, &tsdb->file, temp1, LFS_O_RDWR | LFS_O_CREAT);
        tsdb->open_file_size = lfs_file_size(lfs, &tsdb->file);
        lfs_file_seek(lfs, &tsdb->file, 0, LFS_SEEK_END);
    }
    lfs_dir_close(lfs, &tsdbDir);
    return 0;
}

/***
 * @description: 写入日志
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {char} *buf
 * @param {uint16_t} len
 * @return {*}
 */
int lfs_tsdb_write(lfs_t *lfs, lfs_tsdb_t *tsdb, char *buf, uint16_t len)
{
    char path[128];
    uint32_t temp = tsdb->get_current_time();
    if (temp < tsdb->last_time)
        return -1;
    uint16_t wlen = len > tsdb->max_itme_size ? tsdb->max_itme_size : len;
    if (xSemaphoreTake(tsdb->sem, 5000) != pdTRUE)
    {
        return -1;
    }
    lfs_file_write(lfs, &tsdb->file, &temp, 4);
    lfs_file_write(lfs, &tsdb->file, &wlen, 2);
    lfs_file_write(lfs, &tsdb->file, buf, wlen);
    if (wlen < tsdb->max_itme_size)
    {
        for (uint32_t i = 0; i < tsdb->max_itme_size - wlen; i++)
        {
            lfs_file_write(lfs, &tsdb->file, &(uint8_t){0}, 1);//补0还是0xff呢,FLASH擦除状态下好像是0xFF,如果是0xFF那么FLASH好像可以延寿
        }
    }
    tsdb->last_time = temp;
    tsdb->open_file_size += (6 + tsdb->max_itme_size);
    if (lfs_file_size(lfs, &tsdb->file) >= (tsdb->max_itme_size + 6) * tsdb->max_item_num)
    {//文件已经到达了设置的指定大小
        lfs_file_close(lfs, &tsdb->file);
        tsdb->file_count++;
        if (tsdb->file_count - tsdb->min_file_count > tsdb->max_file_num)
        {
            sprintf(path, "./tsdb/%s/%d", tsdb->name, tsdb->min_file_count);
            lfs_remove(lfs, path);
            tsdb->min_file_count++;
        }
        sprintf(path, "./tsdb/%s/%d", tsdb->name, tsdb->file_count);
        lfs_file_open(lfs, &tsdb->file, path, LFS_O_RDWR | LFS_O_CREAT);
        lfs_file_sync(lfs, &tsdb->file);
        tsdb->open_file_size = 0;
    }
    xSemaphoreGive(tsdb->sem);
    return 0;
}

int lfs_tsdb_flush(lfs_t* lfs, lfs_tsdb_t* tsdb)
{
    if (xSemaphoreTake(tsdb->sem, 5000) != pdTRUE)
    {
        return -1;
    }
    lfs_file_sync(lfs, &tsdb->file);
    xSemaphoreGive(tsdb->sem);
    return 0;
}

/***
 * @description: 二分查找前边界
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {uint32_t} time
 * @param {uint32_t*} fileCount
 * @param {uint32_t*} fileIndex
 * @return {*}
 */
static int search_front(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t time, uint32_t *fileCount, uint32_t *fileIndex)
{
    int result = -1;
    char path[128];
    uint8_t isOpen = 0;
    lfs_file_t file, *pfile;
    uint32_t temp, tempFileCount = 0;
    int maxTableIndex, minTableIndex, tableIndex;
    minTableIndex = 0;
    maxTableIndex = (tsdb->file_count - tsdb->min_file_count) * tsdb->max_item_num + (tsdb->open_file_size / (6 + tsdb->max_itme_size));
    while (minTableIndex <= maxTableIndex)
    {
        tableIndex = minTableIndex + (maxTableIndex - minTableIndex) / 2;
        *fileCount = tsdb->min_file_count + tableIndex / tsdb->max_item_num;
        *fileIndex = tableIndex % tsdb->max_item_num;
        if(isOpen == 0 || tempFileCount != *fileCount)
        {
            tempFileCount = *fileCount;
            if(isOpen && pfile == &file)
                lfs_file_close(lfs, &file);
            isOpen = 1;
            if(tempFileCount == tsdb->file_count)
            {
                pfile = &tsdb->file;
            }
            else
            {
                sprintf(path, "./tsdb/%s/%d", tsdb->name, tempFileCount);
                lfs_file_open(lfs, &file, path, LFS_O_RDWR);
                pfile = &file;
            }
        }
        lfs_file_seek(lfs, pfile, *fileIndex * (6 + tsdb->max_itme_size), LFS_SEEK_SET);
        lfs_file_read(lfs, pfile, &temp, 4);
        if (temp >= time)
        {
            result = tableIndex;
            maxTableIndex = tableIndex - 1; // 继续向左查找,直到找到最左边的元素
        }
        else
        {
            minTableIndex = tableIndex + 1;
        }
    }
    if(isOpen && pfile == &file)
    {
        lfs_file_close(lfs, pfile);
    }
    return result;
}

/***
 * @description: 二分查找后边界
 * @param {lfs_t} *lfs
 * @param {lfs_tsdb_t} *tsdb
 * @param {uint32_t} time
 * @param {uint32_t*} fileCount
 * @param {uint32_t*} fileIndex
 * @return {*}
 */
static int search_back(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t time, uint32_t *fileCount, uint32_t *fileIndex)
{
    int result = -1;
    char path[128];
    uint8_t isOpen = 0;
    lfs_file_t file, *pfile;
    uint32_t temp, tempFileCount = 0;
    int maxTableIndex, minTableIndex, tableIndex;
    minTableIndex = 0;
maxTableIndex = (tsdb->file_count - tsdb->min_file_count) * tsdb->max_item_num + (tsdb->open_file_size / (6 + tsdb->max_itme_size));
    while (minTableIndex <= maxTableIndex)
    {
        tableIndex = minTableIndex + (maxTableIndex - minTableIndex) / 2;
        *fileCount = tsdb->min_file_count + tableIndex / tsdb->max_item_num;
        *fileIndex = tableIndex % tsdb->max_item_num;
        if(isOpen == 0 || tempFileCount != *fileCount)
        {
            tempFileCount = *fileCount;
            if(isOpen && pfile == &file)
                lfs_file_close(lfs, &file);
            isOpen = 1;
            if(tempFileCount == tsdb->file_count)
            {
                pfile = &tsdb->file;
            }
            else
            {
                sprintf(path, "./tsdb/%s/%d", tsdb->name, tempFileCount);
                lfs_file_open(lfs, &file, path, LFS_O_RDWR);
                pfile = &file;
            }
        }
        lfs_file_seek(lfs, pfile, *fileIndex * (6 + tsdb->max_itme_size), LFS_SEEK_SET);
        lfs_file_read(lfs, pfile, &temp, 4);
        if (temp <= time)
        {
            result = tableIndex;
            minTableIndex = tableIndex + 1; // 继续向后方查找
        }
        else
        {
            maxTableIndex = tableIndex - 1;
        }
    }
    if(isOpen && pfile == &file)
    {
        lfs_file_close(lfs, pfile);
    }
    // 如果没找到小于等于x的,则返回找到的最后一个元素的索引(如果不存在则返回-1)
    if (result == -1 && maxTableIndex >= 0)
    {
        return maxTableIndex;
    }
    return result;
}

int lfs_tsdb_count(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t startTime, uint32_t endTime)
{
    uint32_t startIndex, endIndex;
    uint32_t startCount, endCount;
    int startResult, endResult;
    if (xSemaphoreTake(tsdb->sem, 5000) != pdTRUE)
    {
        return -1;
    }
    startResult = search_front(lfs, tsdb, startTime, &startCount, &startIndex);
    endResult = search_back(lfs, tsdb, endTime, &endCount, &endIndex);
    {
        lfs_file_seek(lfs, &tsdb->file, 0, LFS_SEEK_END);
        xSemaphoreGive(tsdb->sem);
    }
    if (startResult != -1 && endResult != -1 && startResult <= endResult)
    {
        return endResult - startResult;
    }
    else
    {
        return 0;
    }
}

int lfs_tsdb_read(lfs_t *lfs, lfs_tsdb_t *tsdb, uint32_t startTime, uint32_t endTime, uint8_t (*cb)(uint32_t, char *, uint16_t))
{
    uint32_t startIndex, endIndex;
    uint32_t startCount, endCount;
    int startResult, endResult;
    uint32_t readCount = 0;
    if (xSemaphoreTake(tsdb->sem, 5000) != pdTRUE)
    {
        return -1;
    }
    startResult = search_front(lfs, tsdb, startTime, &startCount, &startIndex);
    endResult = search_back(lfs, tsdb, endTime, &endCount, &endIndex);//考虑之后改变为只读取前边界,然后从前边界开始读取直到时间戳到达结束时间戳为止,这样可以节省扫描后边界时打开文件的时间
    if (startResult != -1 && endResult != -1 && startResult <= endResult)
    {
        lfs_file_t file, *pfile;
        char temp[128];
        uint32_t time;
        char* log = lfs_malloc(tsdb->max_itme_size);
        uint16_t len;
        uint32_t i, j;
        for (i = startCount; i <= endCount; i++)
        {
            if(i == tsdb->file_count)
            {
                pfile = &tsdb->file;
            }
            else 
            {
                sprintf(temp, "./tsdb/%s/%d", tsdb->name, i);
                lfs_file_open(lfs, &file, temp, LFS_O_RDWR);
                pfile = &file;
            }
            for (j = (i == startCount ? startIndex : 0); j < (i == endCount ? endIndex : tsdb->max_item_num); j++)
            {
                readCount++;
                lfs_file_seek(lfs, pfile, j * (6 + tsdb->max_itme_size), LFS_SEEK_SET);
                lfs_file_read(lfs, pfile, &time, 4);
                lfs_file_read(lfs, pfile, &len, 2);
                lfs_file_read(lfs, pfile, log, len);
                if (cb(time, log, len))
                {
                    if(pfile == &file)
                        lfs_file_close(lfs, pfile);
                    lfs_free(log);
                    goto finally;
                }
            }
            if(pfile == &file)
                lfs_file_close(lfs, pfile);
        }
        lfs_free(log);
    }

finally:
    {
        lfs_file_seek(lfs, &tsdb->file, 0, LFS_SEEK_END);
        xSemaphoreGive(tsdb->sem);
        return readCount;
    }
}

typedef struct
{
    lfs_t* lfs;
    lfs_tsdb_t* tsdb;
    lfs_file_t* pfile;
    lfs_file_t* tfile;
}lfs_tsdb_iterator_t;


int lfs_tsdb_create_iterator(lfs_t* lfs, lfs_tsdb_t* tsdb, lfs_tsdb_iterator_t* iterator, uint32_t startTime, uint32_t endTime)
{
    uint32_t startIndex, endIndex;
    uint32_t startCount, endCount;
    int startResult, endResult;

    startResult = search_front(lfs, tsdb, startTime, &startCount, &startIndex);

    if(startResult != -1)
    {
        iterator->lfs = lfs;
        iterator->tsdb = tsdb;
        iterator->tfile = lfs_malloc(sizeof(lfs_file_t));
    }
    
    

    return 0;
}

int lfs_tsdb_iterator(lfs_t* lfs, lfs_tsdb_t* tsdb)
{

}

评论 67
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值