嵌入式也要图形界面

几年前的嵌入式设备还只是几个 LED、数码管和按键。现在的智能家电、工控面板、手环、温控器,全都是带彩屏的"小型计算机"。屏一旦上来,你就不能再靠 printf 调试,得正经做图形界面。这就是 LVGL 这类嵌入式 GUI 框架要解决的事。

屏的成本在掉,但人对界面的期待一直在涨。一块 240×320 的彩屏加触控,已经是几百块成本就能搞定的配置。
LVGL 图形界面配图:开发板屏幕上彩色 UI 控件与齿轮
LVGL 让几百 KB RAM 的 MCU 也能做出现代感的界面

这篇不讲怎么用 LVGL 的所有控件(那要看官方文档),讲的是把 LVGL 跑起来、跑顺、跑稳的关键步骤。

LVGL 是什么,为什么选它

LVGL 是一个开源的嵌入式图形库,C 写的,专为资源受限的 MCU 设计。几 KB 的 Flash、几十 KB 的 RAM 就能跑起来。它提供按钮、滑块、图表、键盘、弹窗等几十种控件,自带事件系统、动画、抗锯齿字体。

嵌入式 GUI 常见选择
  • 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 一次的定时器里。