3、lv_conf.h配置详解:颜色深度、内存大小、帧缓冲、显示驱动、输入设备驱动的宏定义配置
说实话,很多新手拿到LVGL的第一件事就是直接复制一份lv_conf.h,改两下就编译。结果呢?花屏、死机、触摸没反应,折腾一整天。我当年也干过这事,后来发现——这个文件才是整个移植工作的核心。你配置错了,后面写再多代码都是白搭。
这一章,咱们就把lv_conf.h里那几个要命的宏定义掰开揉碎讲清楚。你跟着我的思路走一遍,以后移植任何平台心里都有底。
3.1 颜色深度:选错了就是花屏
颜色深度这个配置,说白了就是告诉LVGL:你的屏幕每个像素用几个bit来表示颜色。最常见的选项是LV_COLOR_DEPTH 16和LV_COLOR_DEPTH 32。
我个人习惯先看屏幕驱动芯片的手册。比如常见的ILI9341、ST7789,它们内部是16位色(RGB565)的,那你就老老实实配16。你配32也不是不行,但LVGL内部会做一次颜色转换,白白浪费CPU周期。
关键宏定义:
/* 颜色深度:支持 8、16、32 */
#define LV_COLOR_DEPTH 16
/* 颜色交换:某些屏幕需要高低字节交换 */
#define LV_COLOR_16_SWAP 0
这里有个坑——LV_COLOR_16_SWAP。我在项目里遇到过一块屏幕,配了16位色,显示出来的红色变成了蓝色。折腾了半天,原来是屏幕的RGB字节顺序跟LVGL默认的不一样。把这个宏改成1就好了。
我的经验:如果你发现颜色不对(比如红色显示成蓝色),先别急着怀疑代码逻辑。试试把LV_COLOR_16_SWAP改成1,八成能解决。
3.2 内存大小:给少了死机,给多了浪费
LVGL是一个需要动态内存的GUI库。你给它分配的内存大小,直接决定了你能画多少个控件、开多少个窗口。
先看两个核心宏:
/* LVGL内部使用的堆大小,单位字节 */
#define LV_MEM_SIZE (32 * 1024) /* 32KB */
/* 自定义内存分配函数(可选) */
#define LV_MEM_CUSTOM 0
我刚开始做移植的时候,给了一个STM32F103配了16KB内存。结果界面稍微复杂一点就死机。后来用LVGL自带的监控功能一看,内存碎片化严重,实际可用连10KB都不到。
警告:不要以为你的MCU有64KB RAM,就能给LVGL分50KB。你还要留空间给系统栈、中断处理、协议栈(比如LWIP)。我一般建议LVGL内存不超过总RAM的40%~50%。
怎么估算需要多少内存?给你个参考:
| 界面复杂度 | 推荐LV_MEM_SIZE | 适用场景 |
|---|---|---|
| 简单(几个按钮+标签) | 8KB ~ 16KB | 传感器数据显示、简单菜单 |
| 中等(列表+图表+动画) | 32KB ~ 64KB | 智能家居面板、仪表盘 |
| 复杂(多窗口+大量控件) | 64KB ~ 128KB+ | HMI人机界面、工控屏 |
调试技巧:在lv_conf.h里开启LV_MEM_MONITOR,然后调用lv_mem_monitor(),就能看到当前内存使用率和碎片率。我曾经靠这个发现一个控件泄漏了2KB内存。
3.3 帧缓冲:单缓冲还是双缓冲?
帧缓冲的配置,直接决定了你的界面刷新有没有撕裂感。LVGL提供了三种模式:
/* 1 = 单缓冲(最省内存,但有撕裂) */
/* 2 = 双缓冲(平滑,但费内存) */
/* 3 = 全屏缓冲(性能最好,内存大户) */
#define LV_DISP_BUF_SIZE (LV_HOR_RES_MAX * 10) /* 10行像素 */
我个人最常用的是单缓冲+部分刷新。什么意思呢?就是只分配一个能存10行像素的缓冲区,LVGL每次只刷新屏幕的一部分。这样内存占用小,而且对于大多数嵌入式屏幕来说,肉眼基本看不出撕裂。
什么时候用双缓冲?如果你的屏幕分辨率很高(比如480x320以上),而且界面有大量动画,那就老老实实上双缓冲。代价是内存翻倍。
配置示例(单缓冲,320x240屏幕):
/* 水平分辨率 */
#define LV_HOR_RES_MAX 320
/* 垂直分辨率 */
#define LV_VER_RES_MAX 240
/* 缓冲区大小:10行像素 */
#define LV_DISP_BUF_SIZE (320 * 10)
/* 显示缓冲区定义 */
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[LV_DISP_BUF_SIZE];
lv_disp_draw_buf_init(&draw_buf, buf, NULL, LV_DISP_BUF_SIZE);
注意:如果你用双缓冲,第二个缓冲区指针不能传NULL。我曾经犯过这个错,结果屏幕刷新到一半就卡死了。正确做法是分配两个缓冲区:
static lv_color_t buf1[LV_DISP_BUF_SIZE];
static lv_color_t buf2[LV_DISP_BUF_SIZE];
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_DISP_BUF_SIZE);
3.4 显示驱动:把像素刷到屏幕上
显示驱动是LVGL和硬件之间的桥梁。你需要实现一个flush_cb回调函数,LVGL会把要显示的像素数据通过这个函数传给你。
先看配置:
/* 显示驱动结构体 */
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
/* 设置分辨率和回调 */
disp_drv.hor_res = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
disp_drv.flush_cb = my_flush_callback;
/* 注册显示驱动 */
lv_disp_drv_register(&disp_drv);
这个my_flush_callback怎么写?说白了就是调用你屏幕驱动芯片的写像素函数。以ILI9341为例:
static void my_flush_callback(lv_disp_drv_t *drv,
const lv_area_t *area,
lv_color_t *color_p)
{
/* 设置写入区域 */
ili9341_set_window(area->x1, area->y1,
area->x2, area->y2);
/* 批量写入像素数据 */
ili9341_write_pixels(color_p,
(area->x2 - area->x1 + 1) *
(area->y2 - area->y1 + 1));
/* 通知LVGL刷新完成 */
lv_disp_flush_ready(drv);
}
性能优化:我在项目里发现,如果每次只刷一小块区域,频繁调用SPI传输,效率很低。后来我加了一个判断:如果刷新区域太小(比如小于10个像素),就合并到下一次刷新再处理。帧率提升了将近30%。
3.5 输入设备驱动:让触摸和按键活起来
输入设备驱动和显示驱动类似,你需要实现一个read_cb回调。LVGL会定期调用这个函数,问你当前有没有输入事件。
/* 输入设备驱动结构体 */
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
/* 设置类型和回调 */
indev_drv.type = LV_INDEV_TYPE_POINTER; /* 触摸屏 */
indev_drv.read_cb = my_touchpad_read;
/* 注册输入设备 */
lv_indev_drv_register(&indev_drv);
触摸屏的read_cb实现:
static void my_touchpad_read(lv_indev_drv_t *drv,
lv_indev_data_t *data)
{
static int16_t x, y;
static uint8_t pressed;
/* 读取触摸控制器(比如FT6236) */
touch_read(&x, &y, &pressed);
if (pressed) {
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
} else {
data->state = LV_INDEV_STATE_REL;
}
}
输入设备类型对照表:
| 类型宏 | 适用设备 | read_cb返回值 |
|---|---|---|
LV_INDEV_TYPE_POINTER |
触摸屏、鼠标 | 坐标 + 按下/释放 |
LV_INDEV_TYPE_KEYPAD |
矩阵键盘、GPIO按键 | 键值 + 按下/释放 |
LV_INDEV_TYPE_ENCODER |
旋转编码器 | 差值 + 按键状态 |
LV_INDEV_TYPE_BUTTON |
外部物理按钮 | ID + 按下/释放 |
我曾经踩过的坑:触摸屏的坐标和屏幕的显示方向不一致。比如屏幕是横屏,触摸控制器默认是竖屏坐标。结果你点左上角,LVGL以为你点的是右下角。解决办法是在read_cb里做坐标映射:
/* 横屏时交换X/Y并翻转 */
data->point.x = LV_VER_RES_MAX - y;
data->point.y = x;
3.6 配置检查清单
每次改完lv_conf.h,我建议你按这个清单过一遍:
- 颜色深度:和屏幕驱动芯片一致吗?颜色交换宏试过了吗?
- 内存大小:不超过总RAM的50%吗?开了内存监控吗?
- 帧缓冲:缓冲区大小够10行像素吗?双缓冲的第二个指针不是NULL吧?
- 显示驱动:flush_cb里调了
lv_disp_flush_ready吗? - 输入设备:坐标方向对吗?类型选对了吗?