T153学习 -- LVGL大屏从0开发
本文介绍了基于全志T153开发板和LVGL框架的多页面UI大屏项目实现过程。项目从SDK自带的lv_monitor示例出发,构建了包含Home、Clock、Sys Info、Gallery四个页面的滑动框架,通过lv_tileview实现水平页面切换。重点解决了工程实践中的关键问题:脱离Buildroot独立编译、触摸输入配置、PNG背景图加载优化、内存管理以及性能调优。实现了顶部导航栏动态更新、
目录
- 硬件环境
- 背景
- 整体架构
- 1. 工程基础搭建
- 2. 页面管理器 page_manager
- 3. 顶部导航栏
- 4. 背景图加载与性能优化
- 5. Home 主界面
- 6. Clock 时钟页面
- 7. Sys Info 系统信息页面
- 8. Gallery 图片展示页面
- 9. lv_conf.h 关键配置
- 踩坑记录
- 验证结果
- 涉及文件
- 总结
- 后续
硬件环境
| 项目 | 说明 |
|---|---|
| 板卡 | TLT153-MiniEVM (NAND) |
| 主控 | 全志 T153(4x Cortex-A7 1.608GHz + RISC-V E907) |
| 内存 | DDR3 |
| 存储 | NAND |
| 屏幕 | 10 寸 HDMI 触摸屏,物理分辨率 1024x600,触摸走 USB HID (event9) |
| 桥接芯片 | LT8912B(MIPI DSI → HDMI),已适配 720p 输出 |
| 实际渲染分辨率 | 1280x720 (720p),屏幕内部缩放到 1024x600 |
| SDK | aiot-t153-linux-v1.0 |
| 工具链 | gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf |
| LVGL 版本 | v8.3.1 (SDK 自带) |
背景
我的想法不是单纯学 LVGL API,而是把一个 UI 大屏项目当作载体,实践 Linux 嵌入式开发的完整流程:从 SDK 里的 demo 代码出发,搭建自己的多页面框架,解决交叉编译依赖、显示驱动适配、触摸输入配置、图片资源加载、性能优化等实际工程问题。
出发点是 SDK 自带的 lv_monitor 示例(系统监控面板),在此基础上做了以下事情:
- 搭建基于
lv_tileview的多页面滑动框架 - 新增 Home、Clock、Sys Info、Gallery 四个页面
- 解决 PNG 背景图加载、内存不足、滑动卡顿等问题
- 实现顶部导航栏(页面标题 + 网络状态 + 时间 + Home 按钮)
- 脱离 Buildroot,直接用交叉编译链编译
整体架构
┌─────────────────────────────────────────────┐
│ 顶部导航栏 (top_bar) │
│ [Home] [标题] [Net] [HH:MM:SS] │
├─────────────────────────────────────────────┤
│ │
│ lv_tileview (水平滑动) │
│ │
│ tile 0 tile 1 tile 2 tile 3 │
│ Home Clock Sys Info Gallery │
│ (5个按钮) (时钟+日历) (板卡信息) (背景图) │
│ │
└─────────────────────────────────────────────┘
↑ 底层: scr 上的 lv_img 背景图 (共享)
页面顺序和滑动方向:
Home ←→ Clock ←→ Sys Info ←→ Gallery
(0) (1) (2) (3)
1. 工程基础搭建
1.1 脱离 Buildroot 独立编译
SDK 的 lv_monitor 本来通过 Buildroot 编译,依赖 TPlayer 等闭源库。为了快速迭代,改成直接用交叉编译链:
make CC=/path/to/arm-linux-gnueabihf-gcc
处理 TPlayer 依赖:在 Makefile 的 CFLAGS 加 -DNO_TPLAYER,代码中用 #ifndef NO_TPLAYER 包裹 TPlayer 相关调用,Makefile 中排除 player_int.c、media_file_play.c、recorder_int.c。
1.2 Makefile 改造
编译产物输出到 build/ 目录,源码目录保持干净:
BUILD_DIR ?= build
BIN = $(BUILD_DIR)/lv_monitor
# .o 文件映射到 build/ 下
AOBJS = $(addprefix $(BUILD_DIR)/,$(ASRCS:.S=$(OBJEXT)))
COBJS = $(addprefix $(BUILD_DIR)/,$(CSRCS:.c=$(OBJEXT)))
MAINOBJ = $(addprefix $(BUILD_DIR)/,$(MAINSRC:.c=$(OBJEXT)))
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -rf $(BUILD_DIR)
1.3 触摸输入配置
在 lv_drv_conf.h 中修改 evdev 设备路径:
#define EVDEV_NAME "/dev/input/event9" /* USB 触摸屏 */
2. 页面管理器 page_manager
核心思路:用 lv_tileview 实现水平滑动切页,每个 tile 是一个独立页面。
2.1 page_manager.h 接口设计
/* 页面索引常量 */
#define PAGE_IDX_HOME 0
#define PAGE_IDX_CLOCK 1
#define PAGE_IDX_SYSINFO 2
#define PAGE_IDX_GALLERY 3
void page_manager_init(void);
void page_manager_destroy(void);
lv_obj_t * page_manager_get_tile(uint32_t index);
void page_manager_goto(uint32_t index); /* 跳转到指定页面 */
2.2 tile 创建与滑动方向
/* tile 0: 主界面, 只能右滑 */
tiles[0] = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_RIGHT);
/* tile 1: 时钟, 左右都能滑 */
tiles[1] = lv_tileview_add_tile(tileview, 1, 0, LV_DIR_LEFT | LV_DIR_RIGHT);
/* tile 2: 系统信息, 左右都能滑 */
tiles[2] = lv_tileview_add_tile(tileview, 2, 0, LV_DIR_LEFT | LV_DIR_RIGHT);
/* tile 3: 图片展示, 只能左滑返回 */
tiles[3] = lv_tileview_add_tile(tileview, 3, 0, LV_DIR_LEFT);
2.3 页面跳转
通过 page_manager_goto() 实现按钮点击跳转(不仅是滑动):
void page_manager_goto(uint32_t index)
{
if (index < PAGE_COUNT && tileview) {
lv_obj_set_tile(tileview, tiles[index], LV_ANIM_ON);
lv_label_set_text(title_label, page_names[index]);
}
}
3. 顶部导航栏
50px 高的半透明黑色条,覆盖在 tileview 之上,包含四个元素:
| 位置 | 元素 | 说明 |
|---|---|---|
| 左侧 | 页面标题 | 随 tileview 滑动自动切换 |
| 右侧 | Home 按钮 | 点击跳转回主界面 |
| 右侧 | 网络状态 | 读取 /sys/class/net/eth0/operstate,绿色 up / 红色 down |
| 最右 | 当前时间 | HH:MM:SS,1 秒刷新 |
关键实现:
/* 导航栏置于 tileview 之上 */
lv_obj_move_foreground(top_bar);
/* 滑动时自动更新标题 */
static void tileview_changed_cb(lv_event_t *e)
{
lv_obj_t *act_tile = lv_tileview_get_tile_act(lv_event_get_target(e));
for (int i = 0; i < PAGE_COUNT; i++) {
if (act_tile == tiles[i]) {
lv_label_set_text(title_label, page_names[i]);
break;
}
}
}
4. 背景图加载与性能优化
这部分踩了不少坑,是实际工程中最典型的问题。
4.1 PNG 图片加载
LVGL 通过 POSIX 文件系统驱动加载 PNG:
/* lv_conf.h 配置 */
#define LV_USE_FS_POSIX 1
#define LV_FS_POSIX_LETTER 'A' /* 通过 "A:/path" 访问 */
#define LV_USE_PNG 1 /* 启用 lodepng */
#define LV_MEM_CUSTOM 1 /* 使用系统 malloc */
背景图路径:A:/root/res/bg_720p.png(1280x720 PNG)
4.2 内存不足导致 “no data”
首次加载 PNG 时屏幕纯白,左上角显示 “no data”。解码器能识别图片 header(1280x720, cf=5),但无法分配解码缓冲区。
原因:LVGL 默认内存池 LV_MEM_SIZE = 128KB,而 1280×720×4 字节 = 3.6MB。
解法:改 LV_MEM_CUSTOM = 1,让 LVGL 用系统 malloc/free,不受固定内存池限制。
4.3 滑动卡顿优化
第一版在每个 tile 里各放一张 PNG 背景图,滑动时需要同时解码渲染两张全屏 PNG,非常卡。
优化方案:
- 单张背景图共享:只在
scr底层放一张lv_img,所有 tile 设为透明 - 开启图片缓存:
LV_IMG_CACHE_DEF_SIZE = 4,解码一次后缓存
/* scr 底层唯一背景图 */
bg_img = lv_img_create(scr);
lv_img_set_src(bg_img, BG_IMAGE_PATH);
/* tileview 和所有 tile 透明 */
lv_obj_set_style_bg_opa(tileview, LV_OPA_TRANSP, 0);
lv_obj_set_style_bg_opa(tiles[i], LV_OPA_TRANSP, 0);
优化后滑动流畅,触摸响应正常。
** 背景图加载成功的效果
5. Home 主界面
仿车机主界面风格,5 个圆角彩色按钮水平等距排列。
5.1 按钮配色
参考车机 UI 的渐变色块:
| 按钮 | 颜色 | 文字 | 功能 |
|---|---|---|---|
| 1 | 紫色 (0x8B2FC9) | Clock | 跳转时钟页 |
| 2 | 靛蓝 (0x1A3A8A) | Sys Info | 跳转系统信息页 |
| 3 | 绿色 (0x2E9B3E) | 3 | 预留 |
| 4 | 深红 (0xA02040) | 4 | 预留 |
| 5 | 蓝灰 (0x3A5A7A) | 5 | 预留 |
5.2 按钮样式
每个按钮有垂直渐变、高光边框、阴影和按下反馈:
/* 渐变 */
lv_obj_set_style_bg_grad_color(btn,
lv_color_darken(lv_color_hex(btn_colors[i]), 80), 0);
lv_obj_set_style_bg_grad_dir(btn, LV_GRAD_DIR_VER, 0);
/* 按下效果 */
lv_obj_set_style_bg_color(btn,
lv_color_darken(lv_color_hex(btn_colors[i]), 40),
LV_STATE_PRESSED);
5.3 点击跳转
static void btn_click_cb(lv_event_t *e)
{
int idx = (int)(intptr_t)lv_event_get_user_data(e);
switch (idx) {
case 0: page_manager_goto(PAGE_IDX_CLOCK); break;
case 1: page_manager_goto(PAGE_IDX_SYSINFO); break;
}
}
Home 主界面 5 个按钮的实际效果
6. Clock 时钟页面
左侧模拟时钟 + 数字时间/日期/星期,右侧日历控件。
6.1 模拟时钟
用 lv_meter 实现,灰色底 (#505050),三根指针:
| 指针 | 颜色 | 粗细 |
|---|---|---|
| 时针 | 绿色 (0x00ff00) | 5px |
| 分针 | 黄绿 (0xe1ff00) | 3px |
| 秒针 | 红色 (0xff4444) | 2px |
刻度:61 个小刻度(每 5 个一个大刻度),映射 12 小时制。
6.2 日历控件
用 lv_calendar,灰色底 (#505050),白色日期文字,头部高亮色 #BBFF00。
6.3 1 秒定时器
clock_timer = lv_timer_create(clock_timer_cb, 1000, NULL);
回调中同时更新时钟指针、数字时间、日期、星期和日历。
* 时钟页面完整效果(时钟 + 日历)
7. Sys Info 系统信息页面
灰色圆角面板 (#404040),白色文字,显示以下信息:
| 类别 | 数据来源 | 刷新 |
|---|---|---|
| 板卡型号 | 硬编码 | 静态 |
| CPU 型号 | 硬编码 | 静态 |
| 内核版本 | /proc/version |
5 秒 |
| 运行时间 | /proc/uptime |
5 秒 |
| 内存使用 | /proc/meminfo |
5 秒 |
| CPU 温度 | /sys/class/thermal/thermal_zone0/temp |
5 秒 |
| 系统负载 | /proc/loadavg |
5 秒 |
面板支持滚动查看(内容超出面板高度时)。
系统信息页面完整效果
8. Gallery 图片展示页面
最简单的页面,只有底层共享背景图透出 + 一个 << swipe left 返回提示。用于验证背景图在纯透明 tile 上的显示效果,也作为后续图片浏览功能的预留位。
9. lv_conf.h 关键配置
从默认配置到可用,改了以下关键项:
| 配置项 | 默认值 | 修改后 | 原因 |
|---|---|---|---|
LV_MEM_CUSTOM |
0 | 1 | 使用系统 malloc,PNG 解码需要 3.6MB |
LV_USE_FS_POSIX |
0 | 1 | 通过 POSIX 文件系统加载图片 |
LV_FS_POSIX_LETTER |
- | ‘A’ | 文件路径前缀 |
LV_USE_PNG |
0 | 1 | PNG 图片解码 |
LV_USE_SJPG |
0 | 1 | JPEG 图片解码 |
LV_IMG_CACHE_DEF_SIZE |
0 | 4 | 图片缓存,避免重复解码 |
LV_USE_CALENDAR |
0 | 1 | 日历控件 |
LV_USE_METER |
0 | 1 | 仪表盘控件(模拟时钟) |
LV_FONT_MONTSERRAT_16 |
0 | 1 | 导航栏网络状态字体 |
LV_FONT_MONTSERRAT_28 |
0 | 1 | 按钮文字字体 |
LV_FONT_MONTSERRAT_36 |
0 | 1 | 数字时间字体 |
LV_FONT_MONTSERRAT_48 |
0 | 1 | 大屏数字时间字体 |
LV_FONT_SIMSUN_16_CJK |
0 | 1 | 中文字体(预留) |
实际运行视频
实际运行效果
仓库开源地址
https://gitee.com/zkppp/t153_-lvgl_-desktop
踩坑记录
坑 1:PNG 解码 “no data”,纯白屏幕
- 现象:
lv_img_decoder_get_info()返回成功 (1280x720, cf=5),但渲染出来是纯白 + 左上角 “no data” - 原因:
LV_MEM_SIZE = 128KB,解码 1280×720 ARGB32 需要约 3.6MB - 解法:
LV_MEM_CUSTOM = 1,让 LVGL 用系统malloc - 附带问题:改完后
lv_deinit()编译报错(该函数在LV_MEM_CUSTOM=1时不编译),直接去掉调用即可
坑 2:3 张 PNG 背景导致滑动卡顿
- 现象:每个 tile 各放一张 1280×720 PNG,滑动非常卡顿
- 原因:
LV_IMG_CACHE_DEF_SIZE = 0(无缓存),滑动时同时解码 2 张全屏 PNG - 解法:只在 scr 底层放 1 张背景图,所有 tile 透明;同时开启图片缓存
坑 3:交叉编译后 Exec format error
- 现象:推送到板子运行
./lv_monitor报cannot execute binary file: Exec format error - 原因:Makefile 里
CC ?= gcc默认用了宿主机的 x86 gcc - 解法:编译时显式指定
make CC=/path/to/arm-linux-gnueabihf-gcc
坑 4:中文标题乱码
- 现象:导航栏中文标题显示为方块
- 原因:Montserrat 字体不含中文字形
- 临时方案:标题改用英文;启用了
LV_FONT_SIMSUN_16_CJK(16px 中文字体),后续可用 LVGL 字体工具生成更大号中文字体
坑 5:lv_font_montserrat_xx 链接报错
- 现象:
undefined reference to 'lv_font_montserrat_28' - 原因:代码里用了某个字号但
lv_conf.h里对应宏是 0 - 解法:把用到的字号在
lv_conf.h里全部改成 1,make clean全量重编
验证结果
- 四个页面正常显示,滑动切换流畅
- 背景图加载成功,不再卡顿
- Home 按钮跳转 Clock 和 Sys Info 正常
- 导航栏标题随页面滑动自动切换
- 导航栏 Home 按钮可从任意页面返回主界面
- 网络状态实时显示 (eth0 up/down)
- 时间实时刷新
- 时钟指针和日历日期同步
- 系统信息动态刷新(温度/内存/负载)
- 触摸操作正常 (event9 USB 触摸屏)
涉及文件
| 文件 | 说明 |
|---|---|
main.c |
程序入口,初始化驱动,创建 4 个页面 |
page_manager.c/h |
页面管理器,tileview + 导航栏 + 跳转 API |
page_home.c/h |
主界面,5 个彩色按钮 |
page_clock.c/h |
时钟页面,模拟时钟 + 日历 |
page_sysinfo.c/h |
系统信息,灰色面板 + 读取 /proc /sys |
lv_conf.h |
LVGL 配置(内存/字体/文件系统/图片解码) |
lv_drv_conf.h |
驱动配置(evdev 触摸屏路径) |
Makefile |
编译脚本(build/ 输出、排除 TPlayer) |
res/bg_720p.png |
1280x720 背景图片资源 |
总结
lv_tileview是做多页面滑动切换的最简单方案,每个 tile 独立管理自己的 UI 元素,页面间解耦干净- LVGL 在嵌入式 Linux 上加载大图(>720p PNG)必须用系统 malloc(
LV_MEM_CUSTOM=1),默认 128KB 内存池完全不够 - 多页面共享同一张背景图的正确做法是把
lv_img放在scr最底层,上层 tileview 和 tile 全部透明,而不是每个 tile 各放一张 - 脱离 Buildroot 独立编译的核心是处理好闭源依赖(条件编译排除)和交叉编译器路径
- 导航栏放在 tileview 之上(
lv_obj_move_foreground),但要注意不拦截触摸事件,否则会影响下方的滑动操作 - 定时器(
lv_timer_create)是刷新动态数据的标准方式,时钟 1 秒刷新,系统信息 5 秒刷新
后续
- 为 Home 按钮 3~5 添加实际功能(音视频播放、摄像头预览等)
- 制作自定义大号中文字体(用 LVGL 字体转换工具从 TTF 生成)
- 将背景图从 PNG 转为 LVGL bin 格式,进一步减少加载时间
- 集成原有 lv_monitor 系统测试功能到 Sys Info 页面
- 尝试开机自启动和全屏无边框运行
更多推荐
** 背景图加载成功的效果
* 时钟页面完整效果(时钟 + 日历)![📷 **[待插入图片]**](https://i-blog.csdnimg.cn/direct/d9c8332fea8048a2ab7efd2fd9a01b09.jpeg)
所有评论(0)