前言
在使用单片机设计的系统中经常使用价格低廉的存储方案为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)代码
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: 初始化KV存储
* @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)
{
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;
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)用法示例
uint8_t Key1 = 0;
uint8_t Key2 = 1;
uint8_t Key3 = {3, 4};
uint8_t Key4 = {5, 6};
static lfs_kvdb_item_t lfs_kv_items[] = {
NEW_KV_ITEM(Key1),//这4个宏都是在lfs_kvdb.h中定义
NEW_KV_ITEM_RENAME(K2,Key2),
NEW_KV_ARRAY(Key3),
NEW_KV_ARRAY_RENAME(K4, Key4)
};
lfs_kvdb_t lfs_kvdb =
{
.name = "config",
.number = sizeof(lfs_kv_items) / sizeof(lfs_kvdb_item_t),
.items = lfs_kv_items,
};
lfs_kvdb_init(&lfs, &lfs_kvdb);
lfs_kvdb_set(&lfs, &lfs_kvdb, "Key1", &Key2, 1);
uint8_t val = 10;
lfs_kvdb_set(&lfs, &lfs_kvdb, "K2", &val, 1);
lfs_kvdb_flush(&lfs, &lfs_kvdb);
2、littlefs日志存储
主要用来记录系统中发生的事件及时间等信息。
/***
* @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, fileCount;
uint32_t maxFileLen;
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);
}
}
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;
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, temp, len);
if (cb(time, temp, len))
{
if(pfile == &file)
lfs_file_close(lfs, pfile);
goto finally;
}
}
if(pfile == &file)
lfs_file_close(lfs, pfile);
}
}
finally:
{
lfs_file_seek(lfs, &tsdb->file, 0, LFS_SEEK_END);
xSemaphoreGive(tsdb->sem);
return readCount;
}
}
int lfs_tsdb_create_iterator(lfs_t* lfs, lfs_tsdb_t* tsdb, lfs_tsdb_iterator_t* iterator, uint32_t startTime, uint32_t endTime)
{}