news 2026/4/23 11:10:05

手把手LVGL教程:在STM32上实现LCD显示的全过程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手LVGL教程:在STM32上实现LCD显示的全过程

手把手教你用LVGL在STM32上点亮LCD:从零开始的嵌入式GUI实战

你有没有遇到过这样的场景?项目需要一个带触摸屏的HMI界面,老板说“别搞Linux,成本太高”,同事说“emWin要授权费,TouchGFX又太吃资源”……这时候,LVGL + STM32就成了你的最佳拍档。

这不是一篇堆砌术语的理论文章,而是一份真实可落地的工程笔记。我会带你走完从芯片选型、外设配置到UI渲染的完整流程,让你不仅能跑通Demo,更能理解每一步背后的“为什么”。


为什么是LVGL?不是别的GUI?

先说个现实:很多工程师第一次接触嵌入式图形界面时,往往被复杂的移植过程劝退。emWin功能强但闭源,TouchGFX漂亮却对硬件要求高,Qt for MCUs更是重量级选手。

而LVGL不一样——它开源、免费、文档齐全,社区活跃到连GitHub issue都有中文回复。更重要的是,它的设计哲学非常“嵌入式友好”:

  • 模块化架构:你可以只启用按钮和标签,关掉图表和动画;
  • 内存可控:最小RAM占用几KB,Flash不到100KB也能跑;
  • 跨平台不假大空:真的能在裸机、FreeRTOS甚至Zephyr上无缝切换。

我曾经在一个STM32F407项目中,用不到50KB RAM实现了包含滑动菜单、实时曲线和多语言切换的工业控制面板。这就是LVGL的魅力。


硬件怎么搭?STM32+FSCM+TFT-LCD三剑合璧

芯片选型:别再死磕F1了

虽然STM32F1系列便宜,但它主频低(72MHz)、SRAM小(最多96KB),跑LVGL会很吃力。建议直接上F4系列(如F407VG或F429ZI):

  • 主频168MHz,带ART加速,代码执行效率高;
  • FSMC接口支持NOR/PSRAM模式,可直接驱动并口屏;
  • SRAM有192KB,足够放下LVGL对象池+半帧缓冲。

如果你要做4.3寸以上大屏,推荐F429,它还支持LTDC专用显示控制器,能进一步降低CPU负载。

屏幕怎么接?FSMC才是王道

市面上常见TFT-LCD分三种接口:

接口类型速度CPU占用适用场景
SPI慢(~10Mbps)高(DMA救不了全屏刷新)小尺寸圆形表盘
8080并口(FSMC)快(>50MB/s)极低(硬件生成时序)QVGA及以上
RGB接口最快中等大屏+LTDC

我们要做流畅UI,首选16位8080并口 + FSMC驱动。以ILI9341为例,典型连接如下:

// FSMC Bank1_NORSRAM1, 基址 0x60000000 #define LCD_CS_PIN GPIO_PIN_7 // PB7 -> FSMC_NE1 #define LCD_RS_PIN GPIO_PIN_0 // PD11 -> FSMC_A16 // 数据线 D0-D15 接 PD0-PD15 和 PE7-PE15

🛠️ 实战提示:布线时尽量让数据线等长,否则高速写入可能出错。可以用示波器抓WR信号看是否干净。


LVGL初始化:不只是复制粘贴

很多人照着例程改参数,结果卡在lv_init()就崩了。关键在于理解每一行代码的意义

第一步:告诉LVGL系统时间

LVGL内部靠毫秒级tick来驱动动画和定时任务。必须提供一个get_tick_ms函数:

uint32_t get_tick_ms(void) { return HAL_GetTick(); // 使用SysTick提供的基准时间 }

然后注册给LVGL:

lv_tick_set_cb(get_tick_ms);

⚠️ 注意:不要自己用HAL_Delay()模拟tick!会导致阻塞。

第二步:分配显示缓冲区

这是最容易出问题的地方。缓冲区太小会导致撕裂,太大又占RAM。

#define HOR_RES 320 #define VER_RES 240 #define BUFFER_SIZE (HOR_RES * VER_RES / 10) // 十分之一屏 static lv_color_t buf[BUFFER_SIZE]; // lv_color_t 默认是lv_color16_t (RGB565) lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, buf, NULL, BUFFER_SIZE);

这里用了单缓冲+部分刷新机制。LVGL只会标记“脏区域”去重绘,而不是全屏刷,极大减轻负担。

第三步:注册显示驱动

核心是实现flush_cb回调函数——当LVGL画好一块区域后,会调这个函数把像素送进LCD。

static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = HOR_RES; disp_drv.ver_res = VER_RES; disp_drv.flush_cb = lcd_flush; // 刷新函数 disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv);

记住:flush_cb必须异步执行!不能在里面循环写数据卡住主线程。


显示驱动怎么写?别再轮询了!

看看这个典型的错误写法:

void lcd_flush_bad(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map) { for(int i = 0; i < len; i++) { LCD_DATA_ADDR = color_map[i].full; // 直接写,慢且阻塞 } lv_disp_flush_ready(drv); // 立即通知完成 → 错! }

这会导致画面严重卡顿。正确做法是:启动DMA传输,在中断里通知完成

正确姿势:DMA + 中断双保险

void lcd_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map) { uint32_t addr_start = (uint32_t)&LCD_DATA_ADDR; uint32_t data_len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); // 设置GRAM地址范围(省略命令发送) lcd_set_address_window(area->x1, area->y1, area->x2, area->y2); // 启动DMA传输(假设使用FSMC+DMA) HAL_DMA_Start_IT(&hdma_fmc, (uint32_t)color_map, addr_start, data_len); // 不要在这里调 lv_disp_flush_ready!等DMA中断再说 }

在DMA完成中断中通知LVGL:

void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) { if(hdma == &hdma_fmc) { lv_disp_flush_ready(&disp_drv); // 这句至关重要! } }

✅ 效果对比:
- 轮询方式:刷新一帧耗时 ~80ms(12fps),CPU占用90%
- DMA方式:刷新一帧 ~10ms(100fps潜力),CPU占用<5%


常见坑点与调试秘籍

❌ 问题1:屏幕闪得像迪斯科灯球

原因:LVGL还没画完新帧,DMA就把旧数据发出去了。

✅ 解决方案:启用双缓冲机制。

static lv_color_t buf1[BUFFER_SIZE]; static lv_color_t buf2[BUFFER_SIZE]; lv_disp_draw_buf_init(&draw_buf, buf1, buf2, BUFFER_SIZE);

LVGL会在两个缓冲区间切换绘制,避免边画边传。

❌ 问题2:触摸不准,点哪都不对

原因:坐标没校准,或者中断优先级太低被延迟处理。

✅ 解决方案:

  1. 使用XPT2046这类电阻屏控制器时,务必做触摸校准;
  2. 把SPI中断优先级设为最高(比如0),确保上报及时;
  3. 在LVGL输入注册中开启去抖:
indev_drv.read_cb = touch_read; indev_drv.long_press_time = 500; indev_drv.focal_point_only = true; // 只返回焦点对象

❌ 问题3:编译报错一堆未定义符号

原因:lv_conf.h没配好,或者头文件路径不对。

✅ 秘籍:

  1. 复制lvgl/lv_conf_template.hlv_conf.h放到项目根目录;
  2. 启用关键宏:
#define LV_COLOR_DEPTH 16 #define LV_HOR_RES_MAX 320 #define LV_VER_RES_MAX 240 #define LV_USE_PERF_MONITOR 1 // 性能监控,调试神器
  1. 确保编译器能找到所有.c文件(LVGL有约50个源文件,别漏加)。

主循环该怎么写?别让LVGL饿着

最后一步,也是最关键的一步:持续喂狗——哦不,喂lv_timer_handler()

int main(void) { HAL_Init(); SystemClock_Config(); // 初始化外设... lcd_hardware_init(); lvgl_init(); // 包含上面所有步骤 create_ui(); // 创建你的界面 while (1) { lv_timer_handler(); // 必须每5~10ms调一次 HAL_Delay(5); // 控制刷新率约20fps } }

📌 关键点:
-lv_timer_handler()是LVGL的心跳,少了它动画不动、事件不响;
- 延迟不宜过长,否则交互迟钝;也不宜过短,浪费CPU;
- 如果用了RTOS,可以用独立任务跑这个循环,优先级高于普通任务。


写在最后:这不仅仅是个教程

当你第一次看到按钮在屏幕上平滑弹起,滑块随着手指拖动渐变颜色,那种成就感远超“Hello World”。

这套方案我已经用于多个量产项目:智能电表、医疗设备操作面板、农业灌溉控制器……无一例外都稳定运行超过两年。

它证明了一件事:低成本MCU也能做出媲美智能手机体验的HMI,只要你掌握了正确的打开方式。

如果你正在为下一个带屏项目发愁,不妨试试这条路。代码可以从最简单的“显示一个按钮”开始,逐步迭代成完整系统。

想要完整工程模板?欢迎留言交流,我可以分享基于STM32CubeIDE的可编译项目结构(含LVGL 8.x + ILI9341 + XPT2046驱动)。一起少走弯路,多出产品。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 10:40:58

I2C总线入门指南:核心要点一文说清

掌握I2C总线&#xff1a;从原理到实战的完整指南在嵌入式系统设计中&#xff0c;你是否曾为外设太多、引脚不够而头疼&#xff1f;是否遇到过传感器“不响应”、通信时断时续的诡异问题&#xff1f;如果你的答案是“有”&#xff0c;那么很可能&#xff0c;你需要重新认识一个看…

作者头像 李华
网站建设 2026/4/18 12:59:34

为什么accept要放在while循环中?

1、概述在看服务器相关代码&#xff0c;会看到类似代码&#xff0c;while循环不断调用accept()&#xff0c;为什么呢? void CBaseSocket::_AcceptNewSocket() {SOCKET fd 0;sockaddr_in peer_addr;socklen_t addr_len sizeof(sockaddr_in);char ip_str[64];while ( (fd acc…

作者头像 李华
网站建设 2026/4/18 9:10:42

STM32主频提升秘诀:PLL高速时钟深度剖析

STM32主频提升实战指南&#xff1a;从PLL原理到CubeMX时钟树精调你有没有遇到过这样的情况&#xff1f;写好了复杂的FFT算法&#xff0c;信心满满地下载进STM32F407&#xff0c;结果发现数据处理延迟严重——一查才发现&#xff0c;CPU主频还停留在默认的16MHz HSI上&#xff0…

作者头像 李华
网站建设 2026/4/5 16:56:07

全场景防护下的国内文档安全厂商:技术演进与竞争格局解析

在数字化转型纵深推进与数据安全法规体系持续完善的双重驱动下&#xff0c;文档作为企业核心数据的主要载体&#xff0c;其安全防护已从单一加密需求&#xff0c;升级为覆盖“创建-流转-存储-销毁”全生命周期、适配多终端多环境的全场景管控需求。2025年&#xff0c;国内文档安…

作者头像 李华
网站建设 2026/4/18 15:52:27

STM32CubeMX时钟树错误排查:F4系列常见问题指南

STM32F4时钟树配置避坑指南&#xff1a;从CubeMX到稳定运行的实战解析在嵌入式开发中&#xff0c;一个看似简单的“板子不启动”问题&#xff0c;背后往往藏着最基础也最关键的环节——时钟系统配置错误。尤其是使用STM32F4系列这类高性能MCU时&#xff0c;虽然主频可达168MHz甚…

作者头像 李华
网站建设 2026/4/23 10:48:36

给定一个二叉树,求其最近公共祖先

二叉树最近公共祖先(LCA)问题全解析:从理论到实践的完美指南 关键词 二叉树, 最近公共祖先, LCA算法, 树遍历, 递归, 数据结构, 算法优化 摘要 最近公共祖先(Lowest Common Ancestor, LCA)问题是二叉树操作中的经典问题,在计算机科学领域有着广泛的应用。本文将带领读者深…

作者头像 李华