☰
LVGL中文手册V1.3:STM32移植与部件实战指南
2026/10/29 3:47:00 网站建设 项目流程

简介:这份资源是正点原子编写的《LVGL开发指南》中文V1.3版PDF,面向使用STM32开发板进行嵌入式图形界面开发的工程师与学习者,帮助读者从零掌握LVGL在资源受限微控制器上的移植与UI构建方法。内容覆盖初识LVGL与移植要求、无操作系统移植全流程、基于FreeRTOS的带系统移植、PC模拟器使用及移植相关知识,并延伸到UI元素创建、动画交互与性能优化等应用开发要点,目录按基础篇分章组织,便于按阶段查阅。资源包共1个PDF文件,大小约9.54MB,单文件即完整教程,适合离线阅读与随查随用。目前已有764人学习下载,可作为嵌入式GUI入门到实践的参考手册,帮助读者理清移植步骤、理解配置选项与例程验证思路,减少在STM32平台上搭建图形界面的试错成本。

1. 拿到一份 LVGL 中文手册,先别急着翻目录

上周有个做工业面板的同行发来一张截图:STM32F407 上跑 LVGL,界面能显示,但触摸坐标整体偏移了半个屏幕,他对着lv_conf.h改了三天没找到原因。我让他把移植章节的显示接口部分翻出来,对照disp_drv.hor_res和实际面板分辨率核一遍,十分钟定位到是旋转配置和触摸映射没对齐。这类问题在嵌入式 GUI 里太常见了——不是库难,是移植链路上每个环节都有一两个参数在等你翻车。

这份《LVGL 开发指南》中文 V1.3 就是冲着这条链路写的。它面向 STM32 平台,从裸机移植、FreeRTOS 移植、PC 模拟器,一路讲到显示接口、输入设备、样式系统、文件系统和字库,再往下是 arc、bar、btn 这些具体部件的逐个拆解。适合两类人:一是手上有一块 STM32 开发板、想把 LVGL 跑起来但卡在移植阶段的人;二是已经能点亮屏幕、但写界面全靠试参数、想系统补齐对象模型和样式机制的人。它不是 API 字典,而是一份带实验步骤的移植与部件实战记录,V1.3 相比前两版更新了例程功能并修正了部分描述,拿来做对照复现比啃英文文档省事。

2. 移植链路拆解:从裸机到 FreeRTOS 的两条路

移植是这份手册花笔墨最多的地方,也是大多数人第一次接触 LVGL 时最容易卡住的地方。手册把移植拆成两条线:无操作系统的裸机移植和带 FreeRTOS 的移植。两条线的核心差异不在 LVGL 本身,而在“谁来周期性调用lv_timer_handler()”。裸机靠主循环或 SysTick,RTOS 靠一个独立任务加互斥保护。先把这条主线理清楚,后面加文件、改配置才有方向。

2.1 裸机移植:文件怎么加、工程怎么改

手册的移植准备部分先让你确认三件事:屏幕驱动能正常刷色块、有一个毫秒级时基、堆空间够 LVGL 分配对象。这三条不满足,后面加再多文件也是白搭。我一般会先写一个最简的刷屏测试,确认 LCD 的写窗口和写像素函数没问题,再动 LVGL。

向工程添加文件这一步,手册给的是“核心库 + 配置文件 + 端口文件”的结构。实际落地时,目录组织建议按下面这样分,避免后面加部件时找不到头文件:

# 工程目录建议结构(以 Keil 工程为例,路径按自己习惯调整) Middlewares/LVGL/ ├── lvgl/ # LVGL 核心源码,整目录拷入 │ ├── src/ │ └── lvgl.h ├── lv_conf.h # 配置文件,放在 lvgl 同级或工程根目录 └── lv_port_disp.c # 显示端口:刷屏回调、缓冲区注册 lv_port_indev.c # 输入端口:触摸/按键注册

lv_conf.h的位置有讲究。LVGL 在lvgl.h里通过#include "lv_conf.h"找它,所以要么把它放在编译器能搜到的 include 路径下,要么在lvgl.h里改路径。手册里提到把lv_conf_template.h改名后使用,这一步别偷懒直接改模板文件本身,复制一份再改,升级库的时候不会互相覆盖。

工程文件修改的重点是三个宏和两个函数。宏是LV_COLOR_DEPTH、LV_MEM_SIZE、LV_TICK_CUSTOM;函数是刷屏回调和时基。刷屏回调的签名在 V1.3 对应的 LVGL 版本里是void (*flush_cb)(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p),实现时把area换算成 LCD 的窗口坐标,把color_p里的像素逐个写进去,最后必须调用lv_disp_flush_ready(disp_drv),漏了这句屏幕就只刷第一帧。

/* 显示端口刷屏回调的最小实现,STM32 + 并口/SPI 屏通用骨架 */ static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); LCD_SetWindow(area->x1, area->y1, area->x2, area->y2); /* 设写窗口 */ LCD_WritePixels((uint16_t *)color_p, w * h); /* 批量写像素 */ lv_disp_flush_ready(disp_drv); /* 必须调用,通知 LVGL 本帧完成 */ }

这段代码里LCD_SetWindow和LCD_WritePixels是你自己屏幕驱动里的函数,手册不提供具体实现,因为不同开发板驱动不一样。参数上要注意LV_COLOR_DEPTH设成 16 时lv_color_t是 16 位,直接强转成uint16_t*批量写没问题;设成 32 位就得按 32 位写,强转会导致颜色错乱。缓冲区大小在lv_port_disp.c里通过lv_disp_draw_buf_init注册,手册建议至少给屏幕十分之一大小的行缓冲,内存紧张时用单缓冲,流畅度要求高再上双缓冲。

2.2 FreeRTOS 移植:任务、时基与互斥

带操作系统的移植,手册的思路是把 LVGL 的心跳放进一个独立任务。裸机时lv_timer_handler()在主循环里跑,RTOS 下就变成一个while(1)里带vTaskDelay的任务。这里有个参数要算:lv_timer_handler()的调用周期决定动画和事件的响应粒度,手册例程一般给 5ms 到 10ms,给太长动画会卡顿,给太短任务切换开销上来。

/* LVGL 任务:周期调用 timer handler,注意优先级和栈大小 */ void lvgl_task(void *pvParameters) { while (1) { lv_timer_handler(); /* 处理定时器、动画、输入事件 */ vTaskDelay(pdMS_TO_TICKS(5)); /* 5ms 周期,按 CPU 负载调整 */ } } /* 创建任务时栈别给太小,LVGL 内部有递归绘制 */ xTaskCreate(lvgl_task, "lvgl", 2048, NULL, 3, NULL);

栈大小 2048 字(不是字节,FreeRTOS 里单位是 StackType_t)是常见起点,界面复杂、字体大时容易栈溢出,表现是随机 HardFault,这种问题查起来就是血泪经验——先怀疑栈,再怀疑内存。优先级给 3 左右,别给最高,否则刷屏时会饿死其他任务。

RTOS 下另一个坑是时基。LVGL 需要毫秒级 tick,裸机可以用 SysTick 直接喂lv_tick_inc(1),RTOS 下如果 SysTick 被系统占用,就得在系统 tick 钩子里调,或者用LV_TICK_CUSTOM让 LVGL 直接读一个毫秒计数器。手册在时基一节有说明,但没强调互斥:如果多个任务都调 LVGL 的 API,必须加互斥锁,否则对象链表会被踩坏,现象是界面随机花屏或死机。我一般只在 LVGL 任务里操作 UI,其他任务通过消息队列发请求,从根上避开这个问题。

2.3 PC 模拟器:先在电脑上把界面调好

手册第四章专门讲 PC 模拟器,这一章的价值被很多人低估。嵌入式开发最耗时的不是写界面,是每次改一行样式就编译烧录等半分钟。模拟器让你在电脑上跑同一套 LVGL 代码,调好布局和颜色再往板子上搬。手册给的模拟器工程基于常见 IDE,运行例程后能看到官方 demo 的控件效果。

用模拟器有个前提:lv_conf.h要和板子上保持一致,尤其是LV_COLOR_DEPTH和屏幕分辨率。模拟器上颜色正常、板子上偏色,八成是这两项不一致。另外模拟器里的输入是鼠标,映射到lv_indev的指针设备,和板子上的触摸驱动不是一套,移植时输入端口要重写,别把模拟器的 indev 代码直接拷过去。

3. 显示与输入接口:缓冲区、旋转和触摸映射

移植跑通只是第一步,界面能不能用、触摸准不准,全看显示和输入接口这几个参数。手册第五章把这块单独拎出来讲,包括绘制缓冲区、注册显示驱动、屏幕旋转、输入设备注册和时基。这一章是整份文档里技术密度最高的部分之一,值得逐节对照代码看。

3.1 绘制缓冲区:单缓冲、双缓冲和部分刷新

缓冲区决定刷新策略。手册讲了两种模式:单缓冲和双缓冲。单缓冲是一块 buffer,LVGL 画完一帧再刷,刷的时候 CPU 等着;双缓冲是两块,一块在刷、一块在画,理论上能重叠。但嵌入式上真正影响体验的不是单双缓冲,是“部分刷新”还是“全屏刷新”。

部分刷新时,LVGL 只把发生变化的区域交给flush_cb,area参数就是脏矩形。这时候你的LCD_SetWindow必须严格按area设窗口,不能图省事每次全屏刷,否则部分刷新的优势全没了。全屏刷新则每次area都是整屏,适合内存小、驱动简单的场景。手册例程默认走部分刷新,缓冲区给的是若干行的高度。

/* 缓冲区初始化:行缓冲方案,适合内存紧张的 STM32 */ static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /* 10 行缓冲 */ void lv_port_disp_init(void) { static lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, MY_DISP_HOR_RES * 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.draw_buf = &draw_buf; disp_drv.flush_cb = disp_flush; disp_drv.hor_res = MY_DISP_HOR_RES; disp_drv.ver_res = MY_DISP_VER_RES; lv_disp_drv_register(&disp_drv); }

MY_DISP_HOR_RES * 10这个 10 是行数,给多少取决于 RAM。320 宽、16 位色,10 行就是 6.4KB,STM32F103 这类小内存片子给 5 行也能跑,只是刷新次数变多。给双缓冲时第二个参数传buf_2,大小和buf_1一致。注意lv_disp_draw_buf_init的第四个参数是缓冲区总像素数,不是字节数,单位搞错会越界写,现象是随机花屏。

3.2 屏幕旋转:改分辨率还是改刷屏坐标

屏幕旋转是移植里最容易翻车的点之一。手册在显示接口一节提到旋转,但实际项目里旋转涉及两个层面:LVGL 的逻辑分辨率和 LCD 驱动的物理扫描方向。常见做法有两种,选哪种决定了你后面触摸怎么映射。

第一种是改disp_drv.hor_res和ver_res,让 LVGL 按旋转后的分辨率布局,然后在flush_cb里把area坐标做一次变换再写给 LCD。第二种是 LCD 驱动本身支持扫描方向寄存器,直接改硬件方向,LVGL 这边分辨率不变。第一种灵活但每次刷屏多一次坐标计算,第二种省事但依赖屏幕控制器。

我一般优先用第二种,因为触摸坐标也跟着硬件方向走,映射关系简单。如果屏幕控制器不支持,才用第一种,这时候触摸的read_cb里必须做同样的坐标变换,否则就是开头那个同行遇到的问题——显示转了、触摸没转,点哪哪不对。

/* 触摸读取回调:旋转后必须和显示做一致的坐标变换 */ static void touch_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { uint16_t x, y; TP_ReadXY(&x, &y); /* 读原始触摸坐标 */ /* 假设屏幕旋转 90 度,做坐标交换和翻转,按实际方向调整 */ >/* 样式设置:先初始化、再设属性、最后添加到对象 */ static lv_style_t style_btn; lv_style_init(&style_btn); lv_style_set_bg_color(&style_btn, lv_color_hex(0x1E90FF)); lv_style_set_bg_opa(&style_btn, LV_OPA_COVER); lv_style_set_radius(&style_btn, 8); lv_obj_t *btn = lv_btn_create(lv_scr_act()); lv_obj_add_style(btn, &style_btn, LV_PART_MAIN | LV_STATE_DEFAULT);

lv_style_t必须是静态或全局生命周期,不能是函数内的局部变量,因为 LVGL 存的是指针不是拷贝。函数返回后样式内存失效,界面表现就是颜色随机、属性乱套。这个坑我踩过一次,查了两天才发现是样式变量作用域问题。另外样式可以继承,给容器设字体,子对象默认继承,但背景色不继承,别指望设了父对象背景子对象就自动有。

4.3 部件实战:arc、bar、btn 的参数边界

手册从第九章开始每个部件一章,结构统一:组成、相关知识、API、实验。拿圆弧lv_arc举例,关键参数是范围值、当前值、角度范围和模式。范围值用lv_arc_set_range设,当前值用lv_arc_set_value,角度用lv_arc_set_angles设起始和终止角度,模式分普通和对称。这些参数之间有约束:当前值超出范围会被截断,角度设反了圆弧会画不出来。

进度条lv_bar类似,方向、范围、当前值、模式(普通/对称)。按钮lv_btn相对简单,但要注意它默认带点击态样式,自定义样式时如果只设了默认态,按下时还是默认主题的颜色,看起来像没生效。解决办法是给LV_STATE_PRESSED也加一条样式,或者直接改主题。

部件实验部分每个都有硬件设计、软件设计、下载验证三步,这部分适合照着复现。我的习惯是先跑通手册例程,确认部件行为符合预期,再在这个基础上改参数观察变化,比直接从头写快。改参数时一次只改一个,改完看现象,不然出了问题不知道是哪个参数导致的。

5. 避坑清单:移植和调试里最常见的五个翻车点

这一章单独列出来,是因为下面这几条在手册里分散在各章,但实际项目里往往同时出现,混在一起排查很折磨人。每条按现象、原因、解决写,照着对号入座能省不少时间。

5.1 屏幕只刷第一帧或局部花屏

现象:上电后屏幕显示第一屏内容,之后不再更新;或者只有部分区域刷新,其余区域是花屏或残留。

原因:flush_cb里漏调lv_disp_flush_ready,LVGL 以为上一帧还没刷完,不再发起新的刷新;局部花屏多半是LCD_SetWindow的坐标没按area设,或者缓冲区大小和lv_disp_draw_buf_init传的像素数不一致导致越界。

解决:先确认flush_cb末尾有lv_disp_flush_ready;再打印area的四个边界,和LCD_SetWindow实际写入的窗口对比;最后核对缓冲区数组长度和初始化参数是否一致,单位是像素不是字节。

5.2 触摸坐标偏移或方向相反

现象:点击位置和响应位置差一个固定偏移,或者 X/Y 互换、上下左右颠倒。

原因:显示做了旋转但触摸没做同样变换;触摸芯片的原始坐标范围和屏幕分辨率不一致,没做缩放;read_cb里state返回值写反,按下报释放。

解决:在屏幕上四角各放一个测试按钮,逐个点,根据偏移方向确定是交换还是翻转;用串口打印原始触摸值和变换后的值,和屏幕坐标对照;确认LV_INDEV_STATE_PRESSED和RELEASED没写反。

5.3 界面随机死机或 HardFault

现象:运行一段时间后死机,或者操作某个按钮时必现 HardFault,调用栈指向 LVGL 内部。

原因:多任务同时调 LVGL API 没加锁,对象链表被踩;样式变量是局部变量,函数返回后指针失效;任务栈太小,绘制递归时栈溢出;删除对象后继续访问指针。

解决:UI 操作收敛到单任务,跨任务用消息队列;样式变量改静态或全局;把 LVGL 任务栈加大一倍观察是否还死;删除对象用lv_obj_del_async,删完不再碰指针。

5.4 颜色偏色或显示发灰

现象:PC 模拟器上颜色正常,板子上偏色;或者整体发灰、对比度低。

原因:LV_COLOR_DEPTH和 LCD 实际色深不一致,比如库配 32 位、屏是 16 位 RGB565;字节序不对,16 位色高低字节反了;LCD 初始化时 gamma 或色彩模式没配对。

解决:先统一LV_COLOR_DEPTH和屏幕色深;检查flush_cb里写像素的字节序,必要时做高低字节交换;对照屏幕控制器手册确认 RGB565 的位序。

5.5 字库显示乱码或中文不显示

现象:英文正常,中文显示成方块或乱码;或者自定义字库部分字符缺失。

原因:源文件编码不是 UTF-8,LVGL 按 UTF-8 解析;字库生成时没包含对应汉字,或者范围设小了;文件系统读取字库时路径或读取回调有问题。

解决:把源文件统一转 UTF-8;用字库转换工具时把常用汉字范围加进去,别只加几个测试字;文件系统方案先用小字库验证读取链路,再换大字库。

6. 字库与文件系统:把中文界面真正落地

界面做到最后,绕不开中文显示和外部资源加载。手册第七章讲文件系统移植,第八章讲字库使用,这两章合起来解决的是“界面上的字和图标从哪来”。很多项目卡在这一步,不是不会调 API,是资源生成和挂载的链路没打通。

6.1 字库的三种用法与选择

手册把字库分三种:内置图标字体、内部字库、自定义字库。内置图标字体是 LVGL 自带的符号字体,用LV_SYMBOL_XXX直接引用,适合按钮图标。内部字库是编译进固件的 C 数组,读取快、不依赖文件系统,但占 Flash。自定义字库可以走 C 数组,也可以走文件系统从外部存储读,后者省 Flash 但多一次 IO。

选择上有个简单判断:界面字符集固定、Flash 够,用 C 数组字库,省去文件系统这层不确定性;字符集大、要支持多语言切换,走文件系统,字库文件放外部 Flash 或 SD 卡。手册在自定义字库一节两种都给了例程,C 数组方案适合先跑通,文件系统方案适合产品化。

字库生成时最容易错的是范围。只加“测试”两个字,跑起来发现其他中文全是方块,这是范围没覆盖。常见做法是把 GB2312 常用字区整个加进去,或者按项目实际用词生成。生成工具输出的 C 文件很大时,编译会变慢,可以拆成多个文件按需引用。

6.2 文件系统移植:驱动接口和路径映射

LVGL 的文件系统不是自己实现一套,而是通过一组回调对接你现有的文件系统,比如 FatFS。手册在文件系统移植一节讲了要实现的几个函数:打开、读取、关闭、定位。核心是把 LVGL 的路径映射到你挂载的盘符。

/* 文件系统驱动注册:把 LVGL 的读写回调接到 FatFS 上 */ static lv_fs_drv_t fs_drv; lv_fs_drv_init(&fs_drv); fs_drv.letter = 'S'; /* 盘符,LVGL 里用 "S:/xxx" 访问 */ fs_drv.open_cb = fs_open; fs_drv.read_cb = fs_read; fs_drv.close_cb = fs_close; fs_drv.seek_cb = fs_seek; lv_fs_drv_register(&fs_drv);

letter这个盘符要和代码里引用的路径前缀一致,注册成'S'就用"S:/font.bin",写成"0:/"会打不开。read_cb的返回值要按 LVGL 的约定填LV_FS_RES_OK或对应错误码,返回 0 会被当成读到文件尾。文件系统这层出问题,现象通常是字库加载失败、图片显示空白,排查时先用f_open直接测 FatFS 能不能读到文件,再查 LVGL 回调。

6.3 资源加载的验证顺序

字库和文件系统一起用时,建议按这个顺序验证:先用 C 数组字库确认中文能显示,排除编码和字库本身的问题;再接文件系统,用一个小文本文件测lv_fs读写;最后把字库换成文件系统加载。每一步只引入一个变量,出问题能快速定位是哪一层。

编码这块再强调一次:源文件、字库生成工具、LVGL 解析三方都按 UTF-8 走。有一环是 GBK,中文就乱。手册在字库一章开头就讲启用 UTF-8 编码,这一步别跳过。

7. 从手册到项目:一套可复用的移植检查流程

手册读完、例程跑通,真正到项目里还是会遇到新问题,因为项目硬件和例程板不可能完全一样。我的习惯是把移植过程固化成一份检查清单,每次新板子都走一遍,比凭记忆靠谱。下面这套流程是从这份手册的章节结构里提炼的,配合手册对照看,能覆盖大部分移植场景。

阶段检查项对应手册章节失败现象
硬件准备LCD 刷色块正常、毫秒时基可用、堆空间足够移植要求花屏、动画不动、创建对象失败
文件组织核心库、lv_conf.h、端口文件路径正确无系统移植编译报头文件找不到
配置色深、分辨率、内存大小与硬件一致lv_conf.h解析偏色、越界、内存不足
显示flush_cb设窗口正确、调用了flush_ready显示接口只刷一帧、局部花屏
输入坐标变换与显示旋转一致、状态值正确输入设备点击偏移、按下无响应
时基tick 稳定递增、handler 周期合理时基与任务处理动画卡顿、定时器不准
资源编码 UTF-8、字库范围覆盖、文件系统路径正确字库与文件系统中文乱码、图片空白

这张表里每一行都对应手册的一个小节,出问题时先定位到阶段,再翻对应章节对照参数。比从头读一遍快得多。

再补一个调试技巧:LVGL 自带日志,在lv_conf.h里把LV_USE_LOG打开,级别设到LV_LOG_LEVEL_WARN,很多配置错误和内存告警会直接打出来。配合lv_mem_monitor看内存碎片,界面反复创建删除后碎片率高,说明对象没回收干净,回头查删除逻辑。这两个工具手册没重点讲,但实际排查时比盲猜有效。

最后说个习惯。我现在每次移植新板子,都会先只跑一个最小 demo:一个屏幕、一个按钮、一个标签,触摸能点、文字能显示,就算移植通过。这个最小集跑通之前不碰复杂界面,因为复杂界面出问题时你分不清是移植问题还是界面代码问题。最小集跑通后再逐个加部件,每加一个验证一次。这套流程看着慢,实际比反复返工快。手册的章节顺序基本也是这个思路,从移植到对象到部件,照着走不容易乱。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询