2、日志系统配置:LVGL日志等级设置、日志输出目标配置、日志格式自定义
调试嵌入式GUI,说白了就是跟看不见摸不着的东西打交道。
你画了一个按钮,它没显示出来。你改了颜色,它还是老样子。这时候怎么办?
我个人的习惯是——先看日志。
LVGL的日志系统,其实是被很多人忽略的调试利器。今天我们就把它彻底讲透。
2.1 日志等级设置:从“闭嘴”到“话痨”
LVGL的日志分五个等级,从低到高分别是:
| 宏定义 | 等级 | 说明 |
|---|---|---|
LV_LOG_LEVEL_TRACE |
0 | 最详细,连函数进出都打印 |
LV_LOG_LEVEL_INFO |
1 | 常规信息,比如初始化完成 |
LV_LOG_LEVEL_WARN |
2 | 警告,不影响运行但值得注意 |
LV_LOG_LEVEL_ERROR |
3 | 错误,功能可能异常 |
LV_LOG_LEVEL_USER |
4 | 用户自定义,一般不用 |
LV_LOG_LEVEL_NONE |
5 | 关闭所有日志 |
怎么设置?很简单,在 lv_conf.h 里改一个宏:
/* 在 lv_conf.h 中设置 */
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
我建议你调试阶段开到 LV_LOG_LEVEL_INFO 甚至 LV_LOG_LEVEL_TRACE。等产品稳定了,再改成 LV_LOG_LEVEL_WARN 或 LV_LOG_LEVEL_ERROR。
lv_task_handler() 两次。日志里清清楚楚地打印了重复调用的堆栈。你说,这要是靠肉眼盯代码,得盯到什么时候?
2.2 日志输出目标配置:把话说给谁听?
日志等级设好了,但输出到哪儿呢?
LVGL默认是往 printf 里吐。但嵌入式环境千奇百怪——有的板子没有串口,有的只能用SEGGER RTT,有的需要写到文件系统里。
这时候就要靠 LV_LOG_PRINTF 这个宏了。它的原型是:
/* 默认实现,直接调用 printf */
#define LV_LOG_PRINTF(fmt, ...) printf(fmt, ##__VA_ARGS__)
你可以把它重定向到任何地方。比如重定向到SEGGER RTT:
/* 在 lv_conf.h 或你的头文件里 */
#define LV_LOG_PRINTF(fmt, ...) SEGGER_RTT_printf(0, fmt, ##__VA_ARGS__)
或者你想加个时间戳?没问题:
/* 自定义日志输出,带上时间戳 */
#define LV_LOG_PRINTF(fmt, ...) do { \
printf("[%lu] ", (unsigned long)get_system_tick_ms()); \
printf(fmt, ##__VA_ARGS__); \
} while(0)
lv_label_set_text() 里,结果日志一多,直接死循环了。日志系统本身不能依赖 LVGL 的图形资源。
2.3 日志格式自定义:让信息一眼看懂
默认的日志格式长这样:
[INFO] lv_disp.c: 123 - Display driver registered
格式是:[等级] 文件名: 行号 - 消息
说实话,这个格式够用了。但如果你觉得不够,LVGL 提供了 LV_LOG_USE_FILE_LINE 和 LV_LOG_USE_FUNC 两个宏来控制细节:
/* 在 lv_conf.h 中 */
#define LV_LOG_USE_FILE_LINE 1 /* 显示文件名和行号,默认开启 */
#define LV_LOG_USE_FUNC 1 /* 显示函数名,默认关闭 */
打开函数名后,日志会变成:
[WARN] lv_obj.c: 456 (lv_obj_set_pos) - Position out of screen bounds
我个人习惯把函数名打开。为什么呢?因为有时候同一个文件里好几个函数都在调同一个API,光看文件名和行号,你还得去翻代码。有了函数名,一眼就知道是哪个函数出了问题。
LV_LOG_USE_FILE_LINE 关了,想着省点Flash。结果后来出bug了,日志里全是 [ERROR] - Buffer overflow detected,根本不知道是哪个文件哪一行。那叫一个抓狂。从那以后,我再也不敢关这个选项了。
2.4 实战:一套完整的日志配置方案
说了这么多,给出一套我常用的配置吧:
/* lv_conf.h 中的日志配置 */
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN /* 发布版用 WARN,调试版用 INFO */
#define LV_LOG_USE_FILE_LINE 1 /* 必须开,不然查bug累死 */
#define LV_LOG_USE_FUNC 1 /* 建议开,省得翻代码 */
#define LV_LOG_PRINTF my_custom_log /* 重定向到自定义函数 */
/* 自定义日志函数 */
void my_custom_log(const char *buf)
{
/* 加个时间戳,再通过串口输出 */
char timestamp[32];
snprintf(timestamp, sizeof(timestamp), "[%u]", (unsigned int)(HAL_GetTick() / 1000));
uart_send_string(timestamp);
uart_send_string(buf);
uart_send_string("\r\n");
}
这套配置的好处是:
- 发布版只打印警告和错误,不影响性能
- 每条日志都带文件名、行号、函数名
- 有统一的时间戳,方便分析时序问题
- 输出目标可以随时切换,不改LVGL源码
嗯,日志系统就讲到这里。说白了,它就是你的第二双眼睛。配置好了,能帮你省下大把抓头发的时间。
下一章我们聊聊LVGL的内存管理——那个让无数人翻车的地方。
公众号:蓝海资料掘金营,微信deep3321