一、驱动层:SFUD(Serial Flash Universal Driver) 是一款开源的串行 SPI Flash 通用驱动库
二、中间层:FAL(FLASH ABSTRACTION LAYER))FLASH 抽象层
三、应用层:FlashDB(FlashDB 是一款超轻量级的嵌入式数据库)
后记1:FlashDB嵌入式数据库之TSDB数据存储解析
FlashDB之TSDB解析
写在前面,好久才来写这边文章,读源码还是比较累的,先记录下来后期会议的时候方便一点。
一、初始化过程记录
先看一下帧头结构体
struct sector_hdr_data {
uint8_t status[FDB_STORE_STATUS_TABLE_SIZE]; /**< sector store status @see fdb_sector_store_status_t */
uint32_t magic; /**< magic word(`T`, `S`, `L`, `0`) */
fdb_time_t start_time; /**< the first start node's timestamp */
struct {
fdb_time_t time; /**< the last end node's timestamp */
uint32_t index; /**< the last end node's index */
uint8_t status[TSL_STATUS_TABLE_SIZE]; /**< end node status, @see fdb_tsl_status_t */
} end_info[2];
uint32_t reserved;
};
根据计算sizeof(struct sector_hdr_data)= 40bytes
根据注释可以看出来,帧头中存储了扇区的状态、关键字、开始时间、结束时间、和预留了一个32位的数这样就可以实现对单个扇区的快速检索,
只需要读取头部的40个字节,就可以快速判断扇区是否已存满,扇区中的数据的开始时间和结束时间,如果需要检索数据本扇区中的数据是否符合条件,当然前提是时间戳是连续的,这个是默认条件。
二、Flash中的存储格式
源代码调用的层次很多,我们直接讲结果,有兴趣的自己看源码,都是开源的。
可以看到帧头后面紧跟着的是数据头信息,每条数据头信息由16个字节组成
- 数据头信息 = 数据状态 + 数据时间戳 + 数据长度 + 数据内容起始地址
- 这样的好处是数据头信息是固定长度和固定位置的,可以方便我们快速根据时间戳检索,数据的内容是可以不定长的。
- log内容用指针指向特定的地址,长度就不需要固定的
- 每个扇区单独成块,就是一个最小的数据库单元
扇区头 | log1_hdr | log2_hdr | 剩余空间 | 剩余空间 | log2_data | log1_data |
---|
可以看到log1的数据头指向的数据内容在扇区的最后面,log2指向的内容再次后面,二边向中间挤压
三、实际使用率计算
每条记录固定占用空间为16字节
如果你的数据内容是16字节
实际使用率 = 16/(16+16) = 50%
一个扇区可以储存数量 = (4096 - 40) / 16 + 16 = 126(条)
1M FLASH有扇区数量 = 1M / 4096 = 256 (个)
1M FLASH可以存储数据 = 126*256 = 32256(条)
如果你的flash是8M的自己算一下。
四、查询流程
原生的api接口只有一个按时间查询的命令
/**
* The TSDB iterator for each TSL by timestamp.
*
* @param db database object
* @param from starting timestap
* @param to ending timestap
* @param cb callback
* @param arg callback argument
*/
void fdb_tsl_iter_by_time(fdb_tsdb_t db, fdb_time_t from, fdb_time_t to, fdb_tsl_cb cb, void *cb_arg)
{
struct tsdb_sec_info sector;
uint32_t sec_addr, oldest_addr = db->oldest_addr, traversed_len = 0;
struct fdb_tsl tsl;
bool found_start_tsl = false;
if (!db_init_ok(db)) {
FDB_INFO("Error: TSL (%s) isn't initialize OK.\n", db_name(db));
}
// FDB_INFO("from %s", ctime((const time_t * )&from));
// FDB_INFO("to %s", ctime((const time_t * )&to));
if (cb == NULL) {
return;
}
sec_addr = oldest_addr;
/* search all sectors */
do {
if (read_sector_info(db, sec_addr, §or, false) != FDB_NO_ERR) {
continue;
}
/* sector has TSL */
if ((sector.status == FDB_SECTOR_STORE_USING || sector.status == FDB_SECTOR_STORE_FULL)) {
if (sector.status == FDB_SECTOR_STORE_USING) {
/* copy the current using sector status */
sector = db->cur_sec;
}
if ((!found_start_tsl && ((from >= sector.start_time && from <= sector.end_time)
|| (sec_addr == oldest_addr && from <= sector.start_time))) || (found_start_tsl)) {
uint32_t start = sector.addr + SECTOR_HDR_DATA_SIZE, end = sector.end_idx;
found_start_tsl = true;
/* search start TSL address, using binary search algorithm */
while (start <= end) {
tsl.addr.index = start + ((end - start) / 2 + 1) / LOG_IDX_DATA_SIZE * LOG_IDX_DATA_SIZE;
read_tsl(db, &tsl);
if (tsl.time < from) {
start = tsl.addr.index + LOG_IDX_DATA_SIZE;
} else {
end = tsl.addr.index - LOG_IDX_DATA_SIZE;
}
}
tsl.addr.index = start;
/* search all TSL */
do {
read_tsl(db, &tsl);
if (tsl.time >= from && tsl.time <= to)
{
/* iterator is interrupted when callback return true */
if (cb(&tsl, cb_arg)) {
return;
}
} else {
return;
}
} while ((tsl.addr.index = get_next_tsl_addr(§or, &tsl)) != FAILED_ADDR);
}
} else if (sector.status == FDB_SECTOR_STORE_EMPTY) {
return;
}
traversed_len += db_sec_size(db);
} while ((sec_addr = get_next_sector_addr(db, §or, traversed_len)) != FAILED_ADDR);
}
就是从起始时间查询到结束时间,提供了一个回调接口,在这个接口中把数据显示在LCD屏上。
五、补充:性能测试
单纯的速度测试我没有做,原文给出了大概的速度,而且也没有实际的使用价值。
原文给的速度
msh />tsl bench
Append 13421 TSL in 5 seconds, average: 2684.20 tsl/S, 0.37 ms/per
Query total spent 1475 (ms) for 13422 TSL, min 0, max 1, average: 0.11 ms/per
我的环境是 STM32F4+TSDB+FATFS+U盘(USB_Host)
导出了10W条速度用时1分35秒(实际工程中测试)
导出至USB ≈ 1052per/s,差距还是挺大的。