RT-Thread-ulog输出到flash-基于FlashDB

本文介绍如何利用ULOG_TSDB将日志数据存储到Flash中,包括后端设备初始化、追加、查询和清除日志数据等功能,并提供MSH命令实现日志查询和清除。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ULOG_TSDB搭建详解

一、依赖

1、打开FAL,挂载好片内/片外Flash,设置分区表

2、打开FlashDB、ulog组件

3、使能硬件RTC

二、实现

1、后端设备初始化

主要实现原理:把tsdb作为ulog输出的后端设备,实现保存日志数据到flash中

仿照console_be.c文件实现tsdb_be.c,参考官方文档ulog日志-日志后端

console_be.c是实现把控制台作为后端设备输出ulog数据

主要需要实现ulog_tsdb_backend_init函数和ulog_tsdb_backend_output函数,再通过INIT_ENV_EXPORT导出命令在系统初始化时执行

tsdb_be.c实现如下👇

#include <rthw.h>
#include <ulog.h>

//需要先在rtconfig.h中定义该宏
#ifdef ULOG_BACKEND_USING_TSDB

#if defined(ULOG_ASYNC_OUTPUT_BY_THREAD) && ULOG_ASYNC_OUTPUT_THREAD_STACK < 384
#error "The thread stack size must more than 384 when using async output by thread (ULOG_ASYNC_OUTPUT_BY_THREAD)"
#endif

extern int ulog_tsdb_init(void );
extern void ulog_tsdb_append(const char *str,size_t len);

static struct ulog_backend tsdb;

void ulog_tsdb_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
        const char *log, size_t len)
{
		ulog_tsdb_append(log,len);		
}

int ulog_tsdb_backend_init(void)
{
    ulog_init();
    tsdb.output = ulog_tsdb_backend_output;
    ulog_tsdb_init();
    ulog_backend_register(&tsdb, "tsdb", RT_TRUE);

    return 0;
}
INIT_ENV_EXPORT(ulog_tsdb_backend_init);

#endif /* ULOG_BACKEND_USING_TSDB */

2、TSDB功能实现

主要实现初始化、追加、查询(全部/日期区间)、清除功能,并把查询、清除功能导出到MSH命令中

①tsdb初始化
//在tsdb_be.c的后端设备初始化时调用
int ulog_tsdb_init(fdb_tsdb_t tsdb);
//该函数主要调用官方API👇
//参数意义:TSDB结构体、TSDB分区名称、挂载分区名称、时间戳回调函数、用户自定义数据
fdb_tsdb_init(&_global_tsdb, "log", "ts_area", get_hw_rtc_time, FDB_MAX_TSL_LEN, NULL);
//注意:移植时需要根据实际情况修改挂载分区名称 "ts_area"
//get_hw_rtc_time : 调用硬件RTC的API获取当前实时时间戳
②追加日志数据
//初始化为后端设备输出函数(在tsdb_be.c中实现)
//str:ulog字符串数据  len:ulog字符串长度
void ulog_tsdb_append(const char *str,size_t len);
//主要调用官方API👇
fdb_tsl_append(&_global_tsdb, fdb_blob_make(&blob, str, len));
//实现后日志将会保存到flash中
③查询日志数据
全部查询

直接调用APIfdb_tsl_iter(&_global_tsdb, query_cb, &_global_tsdb);即可

根据日期区间查询

1、需要使用MSH_CMD_EXPORT把命令到处到MSH中,用户输入想要查询的日期区间

2、对输入的参数进行处理👇

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oA3Gmh1u-1615639547119)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\1615260645666.png)]

argv[]数据转成整型,初始化struct tm结构体,再将其转换为time_t格式

最后调用fdb_tsl_iter_by_time(&_global_tsdb,from_time,to_time,query_cb,&_global_tsdb);API即可查询输入的区间内的日志数据

还需要实现查询回调函数

把查询到的日志数据打印到控制台

static bool query_cb(fdb_tsl_t tsl, void *arg)
{
    struct fdb_blob blob;
    char str[FDB_MAX_TSL_LEN];
    fdb_tsdb_t db = arg;				
    fdb_blob_read((fdb_db_t) db, fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, str, FDB_MAX_TSL_LEN)));
    FDB_INFO("queried a LOG: %s", str);
    return false;
}
④清空日志数据

直接调用APIfdb_tsl_clean(&_global_tsdb);即可

详细代码实现如下👇

#include <flashdb.h>
#include <string.h>
#include <stdlib.h>

#ifdef FDB_USING_TSDB

#define FDB_LOG_TAG "[tsdb log]"
#define FDB_MAX_TSL_LEN 256

static bool query_cb(fdb_tsl_t tsl, void *arg);
struct fdb_tsdb _global_tsdb = {0};
struct fdb_kvdb _global_kvdb = {0};

static fdb_time_t get_hw_rtc_time(void)
{
    time_t t = 0;
    time(&t);
    return t;
}

int ulog_tsdb_init(fdb_tsdb_t tsdb)
{
	fdb_err_t result;
	result = fdb_tsdb_init(&_global_tsdb, "log", "ts_area", get_hw_rtc_time, FDB_MAX_TSL_LEN, NULL);
	return result;
}

void ulog_tsdb_append(const char *str,size_t len)
{
		struct fdb_blob blob;
		fdb_tsl_append(&_global_tsdb, fdb_blob_make(&blob, str, len));
}

void ulog_add_tsl(int argc, char**argv)
{
	struct fdb_blob blob;
	if(argc >= 2)
	{
		fdb_tsl_append(&_global_tsdb, fdb_blob_make(&blob, argv[1], sizeof(argv[1])));
	}
}
MSH_CMD_EXPORT(ulog_add_tsl , add tsl);

static void ulog_tsdb_clr(void){
    
    fdb_tsl_clean(&_global_tsdb);
}
MSH_CMD_EXPORT(ulog_tsdb_clr , clean tsdb);

void ulog_tsdb_query(void)
{
		fdb_tsl_iter(&_global_tsdb, query_cb, &_global_tsdb);
}
MSH_CMD_EXPORT(ulog_tsdb_query,get all ulog from tsdb);

int ulog_tsdb_query_by_time(int argc, char**argv){
    if(argc >= 10){
        struct tm tm_from = { .tm_year = atoi((const char*)argv[1]) - 1900, 
															.tm_mon  = atoi((const char*)argv[2]) - 1, 		
															.tm_mday = atoi((const char*)argv[3]), 				
															.tm_hour = atoi((const char*)argv[4]), 				
															.tm_min  = atoi((const char*)argv[5]),					
															.tm_sec  = 0};								
        struct tm tm_to   = { .tm_year = atoi((const char*)argv[1]) - 1900, 
															.tm_mon  = atoi((const char*)argv[6]) - 1, 
															.tm_mday = atoi((const char*)argv[7]), 
															.tm_hour = atoi((const char*)argv[8]), 
															.tm_min  = atoi((const char*)argv[9]), 
															.tm_sec  = 0};
        //转换时间格式
        time_t from_time = mktime(&tm_from), to_time = mktime(&tm_to);
        FDB_INFO("from time = %ld,  to time = %ld\n",from_time,to_time);
        fdb_tsl_iter_by_time(&_global_tsdb,from_time,to_time,query_cb,&_global_tsdb);
    }else {
			rt_kprintf("Please input var:'time section [from_ymdh to_ymdh]'\n");
			rt_kprintf("sample : ulog_tsdb_query_by_time 2018 8 8 8 8 9 9 9 9\n");
			rt_kprintf("mean : query from 2018-08-08-8:00 to 2018-09-09-9:00\n");
		}
    return 0;
}
MSH_CMD_EXPORT(ulog_tsdb_query_by_time , tsdb search by time Please input var:time section [from_ymdh to_ymdh]);

static bool query_cb(fdb_tsl_t tsl, void *arg)
{
    struct fdb_blob blob;
    char str[FDB_MAX_TSL_LEN];
    fdb_tsdb_t db = arg;				
    fdb_blob_read((fdb_db_t) db, fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, str, FDB_MAX_TSL_LEN)));
    FDB_INFO("queried a LOG: %s", str);
    return false;
}

三、测试

初始化成功

在这里插入图片描述

查询所有日志

日志在main函数中每20s增加一条

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GKCT8ouj-1615639547129)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\1615261410234.png)]

根据时间查询日志

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q407sWxa-1615639547134)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\1615261464705.png)]

查询2021-3-9-11:35 到 2021-3-9-11:40产生的日志

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v0png28D-1615639547137)(C:\Users\user\AppData\Roaming\Typora\typora-user-images\1615261591633.png)]

<think>嗯,用户现在的问题是关于如何优化STM32使用FAL和FLASHDB管理Flash的读取速度。当前的情况是读取60条数据需要4秒,204条数据要60秒,这明显太慢了。首先,我需要理解FAL和FLASHDB的基本工作原理,以及它们如何与STM32的Flash交互。 首先,FAL(Flash抽象层)应该是负责底层Flash的操作,比如读写擦除,而FLASHDB则是基于FAL的嵌入式数据库,提供数据存储管理。用户提到使用的是版本1.1.2,可能需要查看该版本的文档,了解其特性和可能的限制。 接下来,用户遇到的读取速度问题,可能涉及到多个方面。比如硬件层面的QSPI配置是否合理,驱动效率如何;软件层面是否有不必要的操作,比如频繁的擦写,或者数据结构的效率问题。也可能存在Flash本身的特性,比如块大小、页编程时间等影响速度。 首先应该考虑硬件配置是否正确。例如,QSPI的时钟频率是否最大化,是否启用了DMA传输以减少CPU负担。如果使用的是W25Q64这样的外部Flash,通过SFUD库驱动,那么需要确认SFUD的配置是否优化,比如是否开启了快速读取模式,或者四线模式(QSPI)而不是标准的SPI。 然后是FAL的配置。检查分区是否合理,如果频繁读取的数据集中在某个分区,可能需要调整分区大小以减少寻址时间。同时,FAL的读写接口是否使用了最有效的方式,比如块读取而非单字节读取。 FLASHDB方面,需要确认其日志结构存储的特性。每次读取是否需要遍历整个日志,导致时间线性增长。比如,当数据量增加时,查询时间可能成倍上升。这时候可能需要优化数据存储结构,比如使用索引或者定期进行碎片整理,减少无效数据的扫描。 另外,缓存机制也是一个重点。是否可以在内存中缓存热点数据,减少对Flash的直接访问。FLASHDB本身是否有缓存机制,或者需要用户自己实现。例如,将频繁访问的数据在启动时加载到RAM中,后续读取直接从RAM获取。 代码层面,检查读取操作的实现是否有冗余步骤。比如,是否在每次读取时都进行了校验或解密,这些操作可能耗时较长。如果可能,可以简化校验流程,或者使用硬件加速的CRC模块。 还有一个可能是Flash的擦写次数过多导致寿命下降,但用户的问题主要是读取速度,所以可能更多是读取方式的问题。不过,长时间使用后,Flash的读取性能是否会有影响,这点需要确认。 另外,测试当前读取速度是否符合Flash的理论速度。比如,W25Q64的最大读取速度在QSPI模式下可能达到几十MHz,计算理论传输时间,与实际时间对比,找出瓶颈所在。如果理论时间和实际时间差距大,说明软件或驱动有问题。 最后,考虑升级FLASHDB或FAL到最新版本,可能已经有性能优化的更新。或者查看社区是否有类似的案例和解决方案。 总结下来,可能的优化方向包括硬件配置优化(QSPI频率、DMA)、驱动和中间件配置(SFUD、FAL分区)、FLASHDB的使用方式(数据索引、碎片整理)、缓存机制以及代码层面的读取逻辑优化。需要逐一排查这些方面,找到导致速度慢的主要原因,再针对性地进行优化。</think>### 优化 STM32 使用 FAL 和 FlashDBFlash 读取性能方案 #### 一、硬件与驱动层优化 1. **QSPI 时钟频率优化** 确保 QSPI 时钟频率设置为芯片支持的最高值(例如 W25Q64 可达 104MHz),在 `sfud_cfg.h` 中配置 `SFUD_FLASH_DEVICE_TABLE` 时需明确支持高速模式。验证时钟树配置是否合理[^1]。 2. **启用 DMA 传输模式** 在 QSPI 初始化代码中启用 DMA 传输,减少 CPU 占用并提高连续读取效率: ```c hqspi.Init.FifoThreshold = 4; hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE; hqspi.Init.MemorySize = QSPI_MEMSIZE_64MB; // 对齐 Flash 容量 ``` #### 二、FAL 层配置优化 1. **合理划分 Flash 分区** 通过 `fal_cfg.h` 调整分区策略,将频繁访问的数据集中在连续物理块中,减少寻址耗时。例如: ```c static const struct fal_partition _partitions[] = { {FAL_PART_MAGIC_WORD, "tsdb", "W25Q64", 0, 256*1024, 0}, // 分配更大块减少擦写次数 }; ``` 2. **批量读取优化** 使用 FAL 提供的块读取接口 `fal_read` 代替逐字节读取,通过增大单次读取长度(如 256/512 字节)提升效率: ```c int fal_read(long offset, uint8_t *buf, size_t size); ``` #### 三、FlashDB 应用层优化 1. **TSDB 时序数据库配置** 在 `tsdb_cfg.h` 中调整日志块大小与缓存配置: ```c #define TSDB_LOG_BLOCK_SIZE 4096 // 对齐 Flash 擦除块大小 #define TSDB_CACHE_SIZE 1024 // 增大缓存减少 Flash 访问频次 ``` 2. **数据索引与查询优化** 避免全表扫描,通过时间戳或标签建立索引: ```c tsdb_iter_iterate(iter, TSDB_TIME_FROM(2023,1,1), TSDB_TIME_TO(2023,12,31)); ``` 3. **定期执行碎片整理** 在空闲时段调用 `tsdb_clean` 合并冗余数据,减少无效数据扫描量[^2]。 #### 四、代码级加速技巧 1. **启用 Flash 内存映射模式** 若支持 XIP(eXecute In Place),直接通过地址访问 Flash 数据: ```c uint8_t *flash_addr = (uint8_t*)(0x90000000 + offset); memcpy(buf, flash_addr, size); ``` 2. **关闭调试日志输出** 在正式版本中移除 `FAL_DEBUG` 和 `TSDB_DEBUG` 的日志输出,减少串口延迟。 #### 五、性能验证方法 1. **基准测试对比** 使用裸机 Flash 读取函数(无中间件)测试理论速度: $$T_{\text{理论}} = \frac{\text{数据量}}{\text{时钟频率} \times \text{数据线宽度}}$$ 例如 1MB 数据在 104MHz QSPI 下理论耗时约: $$T = \frac{1 \times 10^6}{104 \times 10^6 \times 4} \approx 0.24 \text{ms}$$ 2. **性能分析工具** 通过 STM32 的 DWT 周期计数器精确测量函数执行时间: ```c DWT->CYCCNT = 0; // 启动计数器 fal_read(...); uint32_t cycles = DWT->CYCCNT; // 计算实际耗时 ``` #### 六、扩展优化方向 1. **升级 FlashDB 版本** 最新版本可能包含性能改进(如 v1.2.0 引入的预取优化)。 2. **混合存储策略** 将高频数据存储在片内 Flash(访问速度更快),低频数据存外部 Flash
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值