嵌入式也要图形界面
几年前的嵌入式设备还只是几个 LED、数码管和按键。现在的智能家电、工控面板、手环、温控器,全都是带彩屏的"小型计算机"。屏一旦上来,你就不能再靠 printf 调试,得正经做图形界面。这就是 LVGL 这类嵌入式 GUI 框架要解决的事。
屏的成本在掉,但人对界面的期待一直在涨。一块 240×320 的彩屏加触控,已经是几百块成本就能搞定的配置。
这篇不讲怎么用 LVGL 的所有控件(那要看官方文档),讲的是把 LVGL 跑起来、跑顺、跑稳的关键步骤。
LVGL 是什么,为什么选它
LVGL 是一个开源的嵌入式图形库,C 写的,专为资源受限的 MCU 设计。几 KB 的 Flash、几十 KB 的 RAM 就能跑起来。它提供按钮、滑块、图表、键盘、弹窗等几十种控件,自带事件系统、动画、抗锯齿字体。
- LVGL开源、轻量、控件全,MCU 首选。
- TouchGFXST 官方,性能强但只跑 STM32。
- Qt for MCUs专业级,资源占用高。
如果你只是要个干净的控件库、不要做深度定制,LVGL 是最省心的选择。
移植到 MCU:显示与输入驱动
LVGL 本身不碰硬件。你要做两件事:把 LVGL 画好的帧送到屏幕、把屏幕上的触摸坐标告诉 LVGL。这就是显示驱动和输入驱动两个回调。
// 显示刷新回调:LVGL 准备好一块区域的像素后调用
void disp_flush(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) {
lcd_draw_bitmap(area->x1, area->y1, area->x2, area->y2, px_map);
lv_display_flush_ready(disp); // 通知 LVGL 可以画下一块
}
// 触摸读回调:LVGL 每 N 毫秒调一次
void touch_read(lv_indev_t * indev, lv_indev_data_t * data) {
tp_point_t p;
if (tp_read(&p) == 0 && p.pressed) {
data->point.x = p.x;
data->point.y = p.y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
这两个回调写好,LVGL 就能在你的屏上动起来。剩下的是 LVGL 自己的事。
控件布局与刷新回调
LVGL 用对象树管理控件:屏幕是根,按钮、标签、滑块都是它的子节点。布局靠父容器自动排布,不用手动算坐标。
lv_obj_t * scr = lv_screen_active();
lv_obj_set_style_bg_color(scr, lv_color_hex(0x1E5FA8), 0);
lv_obj_t * btn = lv_button_create(scr);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_size(btn, 120, 40);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "开始");
lv_obj_center(label);
事件绑定:按钮按下触发什么函数,用 `lv_obj_add_event_cb`。刷新回调必须周期性调用 `lv_timer_handler()`,通常放在 10ms 一次的定时器里。
内存、帧率与优化
MCU 上跑 GUI 最紧的是内存。LVGL 默认配的双缓冲可能直接吃掉 100KB RAM。优化思路:
- 调低 LVGL 分辨率,不要一上来就全屏刷新。
- 开启部分刷新(只刷新脏区域),不要全屏刷。
- 把 LVGL 的内存池从堆里划一块静态数组,避免 malloc 碎片。
- 动画帧率从 60 降到 30,视觉差别不大,CPU 占用腰斩。
字体、图片与资源管理
LVGL 默认只带英文字体。中文界面要用它的字体转换工具把字库编译成 C 文件,烧进 Flash。常用做法:只转界面用到的那几十个汉字,不要整个中文字库塞进去。
- 字体子集化只保留界面用到的汉字。
- 图片转 C 数组RGB565 比 RGB888 省一半空间。
- Flash 分页存放大图片放外部 SPI Flash。
一个可跑的 Demo 骨架
最后给一个最小可跑骨架,照着接你的硬件。
// main.c
#include "lvgl.h"
int main(void) {
hw_init(); // 屏、触摸、时钟
lv_init();
disp_init(); // 注册显示回调
touch_init(); // 注册触摸回调
ui_build(); // 你的界面
while (1) {
lv_timer_handler();
delay_ms(5);
}
}
LVGL 学起来比想象中简单,难的不是 API,是把内存和刷新调顺。
跑通这个骨架,再去看官方控件示例,基本就能自己搭界面了。