☰
中景园1.8寸ST7735屏移植LVGL完整指南:从底层驱动到界面点亮
2026/10/19 2:54:53 网站建设 项目流程

简介:将LVGL图形库移植到中景园1.8寸128×160显示屏的完整Keil工程,基于STM32F10x平台,面向需要为嵌入式设备快速添加图形界面的开发者,也适合正在学习LVGL移植原理的初学者。工程不仅完成LCD驱动适配和LVGL显示参数配置,还保留USER、HARDWARE、Middlewares、STM32F10x_FWLib等目录,可直接导入Keil MDK编译,并已生成可烧录的hex固件,节省从零搭建工程的时间。针对1.8寸屏幕的接口时序、颜色格式、分辨率均已在驱动中适配,开发者只需修改引脚定义即可使用。压缩包共412个文件,以204个C源码和183个头文件为主,另含mk构建脚本、TTF字体、Markdown说明文档、Keil工程配置文件及批处理脚本,整体体积仅3.86MB。已有811人浏览/学习。借助内置的LVGL字体资源、完整可运行代码和清晰目录模块,可大幅减少环境搭建与移植调试的步骤,既能当作学习样板,也能作为二次开发模板,快速实现带按钮、滑块、图表等控件的嵌入式界面。

1. 先别急着调GUI:一块中景园1.8寸小屏跑LVGL,痛点全在底层

如果你手里正好有一块中景园1.8寸128x160的TFT屏幕,又想让它在LVGL上跑出像样的界面,那这份移植代码值得你花十分钟拆一遍。很多人第一次干这件事时,都会按网上的Demo把SPI四根线接好、烧进去,结果屏幕上不是满屏雪花条纹就是干脆白屏,折腾一下午最后发现是SPI Mode不对或者RGB颜色顺序反了。这篇文章把中景园1.8寸128x160跑LVGL的完整链路捋清楚,从ST7735驱动芯片的初始化时序、像素输出格式,到LVGL的显示驱动注册、flush_cb回调、lv_conf.h配置,再到实际移植中容易踩的坑。适合手上有这块屏、想在MCU上做正经GUI交互的开发者,也适合刚接触LVGL、想从小屏起步的人。

2. 硬件平台与移植思路:ST7735、SPI和LVGL之间还隔着一层驱动

2.1 这块1.8寸屏到底是什么硬件

中景园1.8寸128x160的屏幕,市面上绝大多数用的是ST7735S驱动方案,极少数老批次会用ST7735R,两者指令集基本一致,代码可以通用。屏幕本身是TFT-LCD,接口只有一组SPI加几个控制脚,RGB565颜色格式,分辨率128x160,像素总数20480,一帧全屏数据量约40KB。对STM32G0/G4、ESP32这类MCU来说,跑LVGL的算力门槛其实不高,真正的瓶颈在内存和刷新带宽。

接线这件事虽然简单,但引脚选择会影响后续的移植体验。建议把DC(数据/命令选择)和RST(复位)放在普通GPIO上,CS也尽量用软件控制,这样底层驱动可以完全绕开不同MCU的SPI_NSS硬件差异。常见引脚分配如下表:

屏幕引脚功能说明推荐接法
VCC电源正3.3V
GND地GND
CS片选,低有效任意GPIO或硬件NSS
RESET硬件复位,低有效任意GPIO
DC命令/数据选择任意GPIO,必须软件控制
SDI(MOSI)SPI主机输出SPI_MOSI
SCKSPI时钟SPI_SCK
LED背光,可PWM3.3V或PWM引脚

这块屏不带触摸,所以LVGL侧只需要注册显示驱动,输入设备可以先用物理按键顶上。后面如果要做触摸,再单独加XPT2046之类的触摸控制芯片,LVGL侧的indev接口跟显示驱动是解耦的,不影响本轮移植。

2.2 LVGL移植的三大件:显示驱动、输入设备、心跳

LVGL的架构里,硬件相关的东西被抽象成三个部分:显示驱动(disp_drv)、输入设备驱动(indev_drv)和系统心跳(tick)。显示驱动负责把LVGL画好的像素数据送到底层屏幕,输入设备负责把按键和触摸事件上报给LVGL,而心跳则驱动LVGL内部的时间轴——动画、闪烁、长按检测全部依赖它。

显示驱动的注册流程比较固定:先定义一个lv_disp_draw_buf_t绘图缓冲区,再定义一个lv_disp_drv_t显示驱动结构体,把flush_cb回调和绘图缓冲区塞进去,最后调用lv_disp_drv_register完成注册。之后LVGL每次刷新界面,都会把需要更新的区域(lv_area_t)和像素数据通过flush_cb丢给我们,我们只需要在这个回调里把数据怼到ST7735上。这里最容易理解错的一点是:LVGL不要求你实现整屏刷新,它会自动计算脏区域,flush_cb拿到的area可能只是屏幕上的一个小矩形。

tick的部分则更简单,通常用一个定时器中断,每1ms调用一次lv_tick_inc(1),或者在主循环里用lv_tick_set_cb注册一个获取毫秒数的函数。很多翻车现场就是tick没喂,结果界面能显示但按钮按了没反馈、动画完全不动。

2.3 版本选择:为什么我推荐v8.3而不是v9.x

LVGL目前主流的稳定分支是v8.3,v9.x虽然已经发布,但API改动不小,比如显示驱动的flush_cb函数签名、lv_disp_drv_init这些初始化方式都有变化。网上大量的教程、示例工程和ST7735驱动代码都是基于v8.x写的,遇到问题搜到的答案基本都能直接套用。对一块128x160的小屏来说,v8.3的功能完全足够,而且内存占用比v9更可控。

我一般的做法是直接到LVGL官方仓库把v8.3.x的源码拉下来,只保留src目录,然后把lv_conf_template.h重命名为lv_conf.h,按屏幕参数做了三处修改:LV_COLOR_DEPTH设为16,LV_HOR_RES和LV_VER_RES分别设为128和160,再把不需要的模块裁剪掉。v8.3的源码目录结构清晰,src下有core、draw、extra、font等子目录,编译时整个src目录加进去就好,不需要手动挑文件,省心很多。

3. 核心移植代码拆解:从ST7735底层驱动到LVGL的flush_cb

3.1 ST7735底层:先让屏幕能显示一个像素

ST7735的驱动逻辑很简单:向它发命令和数据,命令模式下DC拉低,数据模式下DC拉高。初始化时按数据手册的时序走一遍:上电后拉低RST再拉高,等待120ms以上,然后依次发SWRESET、SLPOUT、COLMOD、DISPON等命令,把颜色格式设成16-bit RGB565,最后开启显示。下面是一段精简初始化代码:

static void st7735_write_cmd(uint8_t cmd) { ST7735_CS_LOW(); ST7735_DC_LOW(); // DC低电平 = 命令模式 spi_transmit(&cmd, 1); ST7735_CS_HIGH(); } static void st7735_write_data(uint8_t data) { ST7735_CS_LOW(); ST7735_DC_HIGH(); // DC高电平 = 数据模式 spi_transmit(&data, 1); ST7735_CS_HIGH(); } static void st7735_init_cmds(void) { st7735_write_cmd(0x01); // SWRESET 软件复位 delay_ms(150); st7735_write_cmd(0x11); // SLPOUT 退出睡眠 delay_ms(200); st7735_write_cmd(0x3A); // COLMOD 设置像素格式 st7735_write_data(0x05); // 0x05 = 16bit/pixel (RGB565) st7735_write_cmd(0x20); // INVOFF 关闭颜色反转 st7735_write_cmd(0x2A); // CASET 列地址范围 st7735_write_data(0x00); st7735_write_data(0x00); st7735_write_data(0x00); st7735_write_data(0x7F); // 0-127 st7735_write_cmd(0x2B); // RASET 行地址范围 st7735_write_data(0x00); st7735_write_data(0x00); st7735_write_data(0x00); st7735_write_data(0x9F); // 0-159 st7735_write_cmd(0x29); // DISPON 开启显示 }

spi_transmit是对MCU SPI外设的封装,用HAL库就是HAL_SPI_Transmit,用ESP-IDF就是spi_device_polling_transmit,底层实现因平台而异。上面的命令序列里,CASET和RASET把显示窗口限定在128x160内,范围之外的像素不会显示,这为后面的区域刷新打下了基础。最后COLMOD的0x05是RGB565的关键,如果错设成0x06的18-bit模式,LVGL推过来的16位像素数据就会错位,屏幕上全是噪点。

3.2 让LVGL接管屏幕:disp_drv注册和flush_cb

LVGL不直接操作屏幕,它只负责往缓冲区里画,画完把区域和像素指针交给flush_cb。我们可以选择全屏缓冲也可以局部缓冲,中景园这块屏全屏需要40KB,对大多数MCU来说太奢侈,所以一般用局部缓冲,比如一次刷40行,缓冲区大小就是128x40x2 = 10KB。核心注册代码如下:

static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[128 * 40]; // 10KB 局部缓冲 void lv_port_disp_init(void) { lv_disp_draw_buf_init(&draw_buf, buf, NULL, 128 * 40); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = 128; disp_drv.ver_res = 160; disp_drv.flush_cb = disp_flush_cb; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); } void disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { st7735_fill_rect(area->x1, area->y1, area->x2, area->y2, (uint16_t *)&color_p->full); lv_disp_flush_ready(drv); // 告诉LVGL这一帧数据已经送完 }

flush_cb里st7735_fill_rect做的事是:先设置窗口(CASET/RASET),再把color_p里连续排列的RGB565像素直接写入SPI。这里有个关键点必须说明:LVGL传给flush_cb的color_p是一个连续内存块,它的排列顺序和area区域的像素从左到右、从上到下完全一致,所以底层驱动可以无脑把整块数据丢给SPI,不需要关心具体坐标转换。lv_disp_flush_ready必须在数据送完后调用,否则LVGL会认为屏幕还在忙,下一帧永远不会开始绘制,后果是界面卡在第一帧。

3.3 lv_conf.h里决定生死的配置项

lv_conf.h是LVGL的全局配置头文件,所有功能开关、内存策略都在这里定义。对中景园1.8寸这块屏的移植来说,有几个配置项直接决定能不能跑起来、跑得顺不顺:

配置项推荐值说明
LV_COLOR_DEPTH16必须和ST7735的RGB565一致
LV_HOR_RES_MAX128编译期水平分辨率上限
LV_VER_RES_MAX160编译期垂直分辨率上限
LV_MEM_SIZE32KB以上LVGL内部动态内存池大小
LV_TICK_CUSTOM1使用外部tick源,避免依赖LVGL内部时钟
LV_USE_LOG0正式移植时关掉,否则串口刷屏拖慢速度

LV_COLOR_DEPTH如果是32或者8,LVGL内部就会用ARGB8888或者L8格式绘制,flush_cb收到的颜色数据根本没法直接给ST7735用,需要额外做格式转换,性能损失巨大。LV_TICK_CUSTOM置1后,LVGL会调用我们提供的lv_tick_get_cb函数来获取当前毫秒数,这样tick不再依赖内部定时器,精确度更高。LV_MEM_SIZE则要结合界面复杂度调整,如果只是显示几个label和进度条,32KB足够;如果开了图表、图片解码之类的大件,建议直接64KB以上。

3.4 主循环与tick配合:为什么动画会卡

主循环的写法决定了整个GUI的响应速度,常见做法是在while里不断调用lv_timer_handler,同时保持tick的持续供给:

void lv_tick_inc(uint32_t tick_period); // 在定时器中断里调用,周期1ms int main(void) { lv_init(); lv_port_disp_init(); lv_port_indev_init(); // 无触摸屏时可以只注册物理按键 while (1) { lv_timer_handler(); delay_ms(5); } }

lv_timer_handler是LVGL的事件循环入口,它内部会处理所有已注册定时器产生的事件,比如动画更新、控件重绘、输入设备事件派发。每次调用之间间隔5~10ms是比较健康的节奏,太快会白占CPU,太慢则动画会肉眼可见地掉帧。tick和timer_handler是两套独立的机制,tick负责时间计量,timer_handler负责事件处理,两者缺一动画都不会走。如果发现按钮点击后有200ms左右的延迟才反弹,多半是主循环里串了阻塞式的延时,LVGL对这类情况很敏感。

4. 编译烧录与避坑:从白屏到能点亮的排查路线

4.1 一个最小可跑的工程结构

移植时不需要把LVGL官方Demo全搬进来,一个最小工程只需要四块:LVGL源码、lv_conf.h、ST7735底层驱动、LVGL的显示端口适配层。目录可以这样组织:

app/ lvgl/ // LVGL v8.3源码,只保留src和lvgl.h lv_conf.h // LVGL配置 user/ main.c // 初始化LVGL、注册驱动、主循环 st7735.c/h // ST7735底层SPI驱动 lv_port_disp.c/h // LVGL显示驱动适配层 lv_port_indev.c/h // 按键输入适配层

编译时把src目录下的所有.c文件加入工程,include路径加上lvgl目录和lv_conf.h所在目录。用STM32CubeIDE就是新建一个空的C工程这么加,用ESP-IDF则直接在CMakeLists里加源文件路径。烧录后如果屏幕点亮并显示了LVGL自带的Demo界面,说明移植成功。建议第一步先跑官方demo而不是直接画自己的界面,这样可以排除是LVGL配置问题还是自己业务代码的问题。

4.2 避坑:中景园1.8寸移植最容易翻车的5个地方

白屏完全不亮。现象是背光正常发亮但屏幕没有任何内容,黑屏或白屏。原因通常不在LVGL,而在ST7735初始化序列没走完,尤其是RST复位时序不对。有的代码把RST拉高就立刻发命令,芯片其实还在复位状态。解决方法是RST拉低后至少延时10ms再拉高,拉高后再延时120ms再送第一条命令,同时确认SLPOUT之后的200ms延时没省。

满屏彩色雪花条纹。现象是屏幕上全是杂乱无章的花点或条纹,没有可辨识的图形。原因多半是SPI参数配置错误,ST7735要求SPI工作Mode 0,也就是CPOL=0、CPHA=0,数据在时钟上升沿采样。如果MCU默认配成了Mode 1或Mode 3,数据采样点正好错过,读到的全是错位比特。解决方法是把SPI初始化结构体里的CPOL和CPHA都设为0,同时把波特率压到10MHz左右,小屏SPI走太快容易受排线干扰。

颜色发蓝或者发红,看起来像照片底片。现象是界面能显示但颜色严重偏色,比如红色显示成蓝色。原因是ST7735的数据手册默认有RGB和BGR两种颜色顺序,部分批次的屏幕在出厂时被设成了BGR顺序,而LVGL按RGB565输出。解决方法是修改MADCTL命令参数,在初始化序列里加入st7735_write_cmd(0x36)并参数设为0x08,把颜色顺序切回RGB。

画面左右颠倒或整体偏移。现象是LVGL的坐标和屏幕实际显示区域对不上,比如左侧一部分画面跑到右边去了。原因是不同批次中景园屏幕的偏移量不同,有些ST7735R屏幕的显示区域是132x162,需要额外设置CASET/RASET的偏移寄存器。解决方法是打开ST7735驱动里的OFFSET开关,一般提供COL_OFFSET和ROW_OFFSET宏,根据屏幕实测填入偏移值,常见的是2或0。

动画不跑、界面卡死。现象是界面能显示一帧,但按钮按了没反应,进度条动画纹丝不动。原因大概率是tick没喂。LVGL内部所有动画和事件派发都依赖tick做时间判断,tick一直为0时它认为时间没前进,自然不会推进任何动画。解决方法是确认定时器中断里周期调用lv_tick_inc(1),或者用lv_tick_set_cb绑定一个毫秒级系统时钟源。

4.3 验证移植结果:用LVGL自带Demo替代自定义页面

工程跑通后不要急着写业务界面,先用LVGL自带的demo来做冒烟测试。把lv_conf.h里的LV_USE_DEMO_WIDGETS设为1,在main函数里调用lv_demo_widgets(),然后烧录观察。这个demo会把按钮、滑块、进度条、list等常用控件铺满全屏,任何一个控件渲染异常都能直观看到。如果Demo画面稳定、滑动顺畅、没有撕裂感,说明底层驱动和LVGL配置都过关了,再开始做自己的界面心里有底。

5. 二次开发:从点亮Demo到做出自己的产品界面

屏点亮、Demo跑顺之后,真正的工程问题才开始。首先是输入设备,中景园这块屏不带触摸,常见做法是加两个物理按键做菜单上下翻页。LVGL侧只需要实现一个indev驱动,轮询GPIO电平并上报按键事件:

static void keypad_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />

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

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

立即咨询