s苦瓜大王

LVGL的移植思路

LVGL 移植与使用完全指南

从源码获取、目录结构、显示/输入设备移植、任务集成到常用控件与性能调优,系统化掌握 LVGL 在嵌入式平台上的落地方法。

  • 面向嵌入式开发者
  • 基于 LVGL v8 / v9
  • 支持裸机 / RTOS / Linux

目录

  1. LVGL 是什么
  2. 源码获取与目录结构
  3. 移植三要素
  4. 显示设备移植
  5. 输入设备移植
  6. 心跳与任务调度
  7. 第一个 UI 程序
  8. 常用控件速查
  9. 样式与主题
  10. 性能优化与踩坑
  11. PC 模拟器调试
  12. 常见问题 FAQ

1. LVGL 是什么

LVGL(Light and Versatile Graphics Library) 是一个用 C 语言编写的轻量级嵌入式图形库,可以在资源极其有限的 MCU 上提供丰富的 UI 能力。

  • 跨平台: 支持裸机、FreeRTOS、RT-Thread、ThreadX、Linux 等,无 OS 依赖。
  • 资源占用低: 最小 ~64KB Flash、~8KB RAM 即可运行。
  • 控件丰富: 30+ 内置控件(按钮、列表、图表、键盘、动画等)。
  • 图形能力: 支持抗锯齿、透明度、渐变、动画、图片解码(BMP/PNG/JPG/SVG)。
  • 国际化: UTF-8、阿拉伯/希伯来语渲染、CJK 字库支持。

典型应用场景:智能家居面板、工业 HMI、医疗设备、穿戴设备、汽车仪表、消费电子等。


2. 源码获取与目录结构

2.1 获取源码

推荐从 GitHub 拉取稳定版本:

# 拉取 v8.3 最新稳定版
git clone -b release/v8.3 https://github.com/lvgl/lvgl.git

# 或下载压缩包
wget https://github.com/lvgl/lvgl/archive/refs/tags/v8.3.10.zip

LVGL v8 与 v9 在 API 上有少量差异,本文示例基于 v8.x 编写(更通用),v9 差异点会单独标注。

2.2 关键目录

目录 作用
src/ 核心源码,所有 .c 文件
src/core 对象系统、事件、布局、刷新机制
src/widgets 按钮、标签、滑块等所有控件
src/draw 渲染后端(SW / GPU / DMA2D)
src/font 内置字体文件(lv_font_montserrat_* 等)
src/extra 主题、布局、文件系统、图片解码等扩展
examples/ 官方示例代码(必看)
demos/ 完整功能演示(包含多页面、动画)
lv_conf_template.h 配置文件模板,需重命名为 lv_conf.h
lvgl.h 主头文件(include path 需包含 src)

2.3 配置 lv_conf.h

lv_conf_template.h 复制为 lv_conf.h,核心宏:

// 颜色深度,根据屏幕与 RAM 选择
#define LV_COLOR_DEPTH 16   // 16 = RGB565, 32 = ARGB8888

// 水平/垂直分辨率,仅影响缓冲区分配,运行时可改
#define LV_HOR_RES_MAX          480
#define LV_VER_RES_MAX          320

// 缓冲区大小(像素),推荐屏幕宽度的 1/10
#define LV_MEM_SIZE     (32 * 1024U)

// 使能 FreeRTOS 支持(若使用 RTOS)
#define LV_USE_OS        LV_OS_FREERTOS

// 使能/关闭不需要的控件减小体积
#define LV_USE_BTN         1
#define LV_USE_LABEL       1
#define LV_USE_CHART       0   // 不需要就关掉

提示: 关闭未使用的控件、字体、动画、主题可以显著减小 bin 体积。生产版本建议在 lv_conf.h 中把调试宏全部关闭。


3. 移植三要素

LVGL 与硬件解耦,移植时只需向它"喂"三样东西:

flowchart LR APP[LVGL 应用层<br/>控件/事件/动画] --> CORE[LVGL Core] CORE --> DISP[显示设备驱动<br/>flush_cb] CORE --> INPUT[输入设备驱动<br/>read_cb] CORE --> TICK[心跳时钟<br/>lv_tick_inc] DISP -->|像素| LCD[SPI/8080 LCD] INPUT -->|触摸/按键| TP[Touch IC / Key] TICK -->|SysTick| MCU[主循环]
  1. 显示设备:实现 flush_cb,把 LVGL 渲染的像素刷到 LCD。
  2. 输入设备:实现 read_cb,周期性读取触摸/按键上报给 LVGL。
  3. 心跳时钟:周期调用 lv_tick_inc(ms) 提供时间基准。

4. 显示设备移植

4.1 注册一个显示设备

// 全局缓冲区(双缓冲可提高并行效率)
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LV_HOR_RES_MAX * 40];
static lv_color_t buf2[LV_HOR_RES_MAX * 40];   // 可选第二缓冲

// flush 回调:LCD 刷屏函数
static void my_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p)
{
    int32_t x1 = area->x1, y1 = area->y1;
    int32_t x2 = area->x2, y2 = area->y2;
    lcd_set_window(x1, y1, x2, y2);   // 设置 LCD 写入窗口
    lcd_write_pixels((uint16_t *)color_p, (x2 - x1 + 1) * (y2 - y1 + 1));
    lv_disp_flush_ready(drv);          // 通知 LVGL 刷新完成
}

void lv_port_disp_init(void)
{
    lv_disp_drv_t disp_drv;
    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 40);
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res  = 480;
    disp_drv.ver_res  = 320;
    disp_drv.flush_cb = my_flush_cb;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register(&disp_drv);
}

关键点: lv_disp_flush_ready() 必须在 LCD 真正刷完或 DMA 发起后才调用,否则会出现"重影"或画面错乱。若使用 DMA 异步刷屏,应在 DMA 完成中断里调用。

4.2 flush 加速策略

方式 做法 适用场景
SPI 轮询 flush 中直接 SPI 写,完成后调用 flush_ready 小屏、刷新率要求低
SPI DMA flush 启动 DMA,在 DMA 完成中断调用 flush_ready 中等尺寸 SPI 屏(主流方案)
RGB 并口 LVGL 写入图层 buffer,LTDC 硬件自动扫描 STM32F4/H7 + 800x480
GPU 加速 实现 gpu_blend_cb / gpu_fill_cb Chromium GPU、ESP32-S3 DMA2D

5. 输入设备移植

5.1 触摸屏(以电阻/电容屏为例)

static void touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
    static int16_t last_x, last_y;
    bool pressed = tp_get_state(&last_x, &last_y);   // 读 IC 寄存器

    data->point.x = last_x;
    data->point.y = last_y;
    data->state   = pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
}

void lv_port_indev_init(void)
{
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type    = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touch_read_cb;
    lv_indev_drv_register(&indev_drv);
}

5.2 多输入设备(触摸 + 编码器 + 按键)

LVGL 支持同时注册多个输入设备,只需在 indev_drv.type 区分:

type 典型硬件 LVGL 行为
LV_INDEV_TYPE_POINTER 触摸屏、鼠标 坐标定位
LV_INDEV_TYPE_KEYPAD 实体按键矩阵、遥控器 焦点切换,需配合 group
LV_INDEV_TYPE_ENCODER 旋转编码器(带按键) 上下/确认,需配合 group
LV_INDEV_TYPE_BUTTON 独立 GPIO 按键 屏幕外按钮,可触发指定区域点击

5.3 焦点组(无触摸场景必须)

// 创建焦点组并把控件加进来
lv_group_t *g = lv_group_create();
lv_group_add_obj(g, btn1);
lv_group_add_obj(g, btn2);
lv_indev_set_group(my_indev_keypad, g);  // 绑定到 keypad/encoder 设备

6. 心跳与任务调度

LVGL 是单线程协作式的,它本身不会创建任何任务或定时器,需要主循环周期性"喂"它时间。

6.1 裸机主循环

int main(void)
{
    hal_init();            // 硬件初始化
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();
    lv_user_gui_create();  // 用户 UI

    while (1) {
        lv_timer_handler();     // 处理 LVGL 任务(约 5ms 一次)
        __WFI();                // 空闲休眠
    }
}

6.2 SysTick 提供心跳

// 在 SysTick 中断中调用(每 1ms)
void SysTick_Handler(void)
{
    lv_tick_inc(1);
    HAL_IncTick();
}

6.3 RTOS 下独立任务

void lvgl_task(void *p)
{
    while (1) {
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

// 单独定时器提供 tick
void tick_task(void *p)
{
    while (1) {
        lv_tick_inc(5);
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

7. 第一个 UI 程序

void lv_user_gui_create(void)
{
    lv_obj_t *scr = lv_scr_act();

    // 背景
    lv_obj_set_style_bg_color(scr, lv_color_hex(0x003a57), 0);

    // 标签
    lv_obj_t *label = lv_label_create(scr);
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_set_style_text_color(label, lv_color_white(), 0);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, -30);

    // 按钮
    lv_obj_t *btn = lv_btn_create(scr);
    lv_obj_set_size(btn, 120, 50);
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40);
    lv_obj_t *btn_lbl = lv_label_create(btn);
    lv_label_set_text(btn_lbl, "Click me");
    lv_obj_center(btn_lbl);

    // 事件回调
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
}

static void btn_event_cb(lv_event_t *e)
{
    lv_obj_t *btn = lv_event_get_target(e);
    lv_obj_t *label = lv_obj_get_child(btn, 0);
    lv_label_set_text(label, "Clicked!");
}

编译下载,即可在屏幕上看到带按钮的界面,点击按钮文字会变化,这就是一个完整的 LVGL 应用。


8. 常用控件速查

控件 创建函数 典型用途
Label lv_label_create() 文字显示、字体颜色
Button lv_btn_create() 交互按钮
Arc lv_arc_create() 进度圆环、旋钮
Slider lv_slider_create() 音量、亮度滑条
Switch lv_switch_create() 开关切换
Dropdown lv_dropdown_create() 下拉选择
List lv_list_create() 菜单列表
Table lv_table_create() 表格
Chart lv_chart_create() 折线/柱状图
Keyboard lv_keyboard_create() 软键盘
Canvas lv_canvas_create() 自定义绘图(类似 framebuffer)
Image lv_img_create() 显示 PNG/JPG/BMP/SVG
Textarea lv_textarea_create() 多行输入框
Tabview lv_tabview_create() 多页签容器
Tileview lv_tileview_create() 可滑动的卡片视图

9. 样式与主题

LVGL v8 引入样式表(样式状态机)机制,比 v7 的纯函数链式 API 更易复用。

9.1 基础样式

static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x0e7c66));
lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
lv_style_set_radius(&style_btn, 10);
lv_style_set_pad_all(&style_btn, 12);

lv_obj_add_style(btn, &style_btn, 0);   // 默认状态
lv_obj_add_style(btn, &style_btn_pressed, LV_STATE_PRESSED);

9.2 切换主题

lv_theme_t *th = lv_theme_default_init(
    NULL, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
    true, LV_FONT_DEFAULT);
lv_disp_set_theme(NULL, th);

LVGL 还内置了 lv_theme_materiallv_theme_monolv_theme_zen 等可直接使用。


10. 性能优化与踩坑

10.1 渲染性能

  • 缓冲区越大越好? 缓冲区越大,单次 flush 数据量越大,SPI 屏反而会拖慢。经验值取屏幕宽度的 1/10 ~ 1/4 行。
  • 双缓冲(DMA):一个缓冲给 LVGL 渲染,另一个给 DMA 传输,充分利用总线带宽。
  • 裁剪:LVGL 自动只刷新变化区域,避免调用 lv_obj_invalidate() 全屏重绘。
  • 关闭动画:lv_anim_delete_all() 或减少动画时长。
  • 颜色深度:LV_COLOR_DEPTH 1632 快且省 RAM,但渐变过渡会变差。

10.2 内存优化

配置项 作用 建议
LV_MEM_SIZE 动态内存池大小 先用默认值,实测峰值后再调小
LV_USE_BIG_ENDIAN 大小端配置 按 MCU 选错会乱码
字体 每多一种字号都增加 Flash 生成工具 lv_font_conv 按需生成
图片 lv_img_dsc_t 内置数组 在线转换 img2lvgl 工具

10.3 常见踩坑

踩坑 1: 触摸坐标上下颠倒 — 多数是 LCD 旋转与 TP 扫描方向不一致,通过 lv_disp_set_rotation() 或 TP 驱动里加翻转系数解决。

踩坑 2: 控件不响应点击 — 检查是否启用了 LV_USE_GROUPindev.set_group(无触摸场景),或触摸坐标是否被裁剪。

踩坑 3: 屏幕刷新"卡死" — 大概率 lv_disp_flush_ready() 在中断外被调用太晚,或 SPI 时序不对。

踩坑 4: 多任务下 UI 错乱 — LVGL 不是线程安全的,所有 UI 操作必须放在同一任务,或使用 lvgl_mutex(v8.3+ 提供 lv_lock/ lv_unlock)。


11. PC 模拟器调试

强烈推荐先用 PC 模拟器把 UI 调好,再烧到板子。官方仓库提供 SDL2 模拟器:

# Linux
sudo apt install -y build-essential cmake libsdl2-dev
git clone https://github.com/lvgl/lv_port_pc_eclipse.git
cd lv_port_pc_eclipse
cmake -B build && cmake --build build -j
./build/main

Windows 下可用 lv_port_win_codeblocks(Code::Blocks 工程)或 VSCode + CMake,直接双击运行就能看到窗口化的 LVGL 界面,断点、打印均可用。

调试技巧:

  • lv_conf.h 中打开 LV_USE_LOG 并设置 LV_LOG_LEVEL = LV_LOG_LEVEL_TRACE
  • 使用 lv_obj_dump_tree() 打印对象树,排查"为什么控件没显示"。
  • 使用 lv_mem_monitor() 查看内存占用与泄漏。

12. 常见问题 FAQ

问题 可能原因与解法
屏幕全白 / 全黑 检查 LCD 复位与背光;flush_cb 是否被调用;SPI 时序/线序
画面撕裂 启用 DMA 双缓冲,确认 flush_ready 在 DMA 完成中断调用
触摸不灵敏 触摸 IC 初始化时序;触摸校准参数;坐标方向
中文显示成方块 未启用 CJK 字库,使用 lv_font_conv 生成包含中文的字体
占用 Flash 太大 关闭未用控件/主题/动画,启用 LTO,字体子集化
多任务崩溃 UI 操作未在同一个任务;未使用 LVGL 互斥锁
动画卡顿 缓冲区过小 / 刷屏速度慢;降低动画时长或减少同时动画数

参考资料

  1. LVGL 官方文档(英文,持续更新,v8 / v9 双版本)
  2. LVGL GitHub 仓库(源码、示例、移植参考)
  3. LVGL 官方移植教程与平台集成指南
  4. LVGL PC 模拟器(SDL2 调试环境)
  5. LVGL 中文社区论坛与 B 站教程
  6. lv_font_conv 字体/图片转换工具(子集化,减小体积)
  7. SquareLine Studio(可视化 UI 设计器,导出 LVGL 代码)

posted on 2026-07-14 23:33  s苦瓜大王  阅读(36)  评论(0)    收藏  举报