ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32CubeIDE+LVGL 8.3移植SPI屏避坑指南:彻底解决花屏问题

STM32CubeIDE+LVGL 8.3移植SPI屏避坑指南:彻底解决花屏问题 搞嵌入式的老哥们都懂做产品最怕的不是逻辑写不出来而是辛辛苦苦写的界面一上真机就花屏。屏幕上全是彩色噪点、撕裂横纹要不就是颜色完全错乱那种感觉比单纯跑飞一个变量还让人崩溃。我这次用 STM32CubeIDE 把 LVGL 8.3 完整移植到一块 240x320 的 SPI 屏上前后踩了不少坑从源码下载、配置裁剪、显示驱动对接到最后彻底解决花屏问题整个过程全部记录在这篇手记里。文章里没有虚的全是实际跑的代码和排查思路适合刚入坑嵌入式 GUI 或者准备给产品加界面却一直被显示问题困扰的朋友直接照着抄。先交代一下背景主控用的是 STM32F401RET6屏幕是常见的 ILI9341 SPI 接口分辨率 240x320开发环境就是标题里说的 STM32CubeIDE。LVGL 用的是 8.3 系列这是目前 8.x 里最稳的版本API 不会再像 7.x 到 8.0 那样翻个底朝天。整个移植过程从新建工程到跑起官方 demo 差不多用了一个晚上但其中花屏问题占了大半时间。下面我会把每个环节的关键点拆开讲尤其是那些一不留神就让你抓狂的细节。1. 移植方案选型为什么是STM32CubeIDELVGL 8.31.1 开发环境的取舍先聊聊工具链。很多人习惯用 Keil 或者 IAR 做 STM32 开发这没问题但我个人强烈推荐 STM32CubeIDE尤其是做这种带外部库的图形项目时。有几个实际原因第一STM32CubeIDE 免费且跨平台Windows、Linux 都能跑没有破解或授权的麻烦第二它内置了 CubeMX 图形化配置工具时钟树、引脚分配、外设初始化都能可视化操作生成代码的质量很高第三它对 C/C 工程的源码管理和编译配置非常透明比如添加一个源码目录、设置宏定义、调整链接脚本都很容易操作。相比之下 Keil 添加一大堆分散的 .c 文件路径时一旦目录层级不对就很容易编译报错。不过也提醒一句STM32CubeIDE 的 Eclipse 内核决定了它的索引和编译速度不如 Keil 快尤其是在老电脑上打开大的工程会有明显延迟。如果电脑配置一般建议关掉自动构建或者把工程放在 SSD 路径下能稍微缓解这个问题。1.2 硬件资源和屏幕选型底线LVGL 8.3 不是一个轻量到随便哪个单片机都能跑的库它的运行需要一定的内存和 Flash 资源。根据我实测的情况LVGL 核心代码编译后大概占 30~60KB Flash看开多少控件和字体运行时的 RAM 消耗则取决于屏幕分辨率、颜色深度、缓冲策略以及所创建的控件数量。以 240x320 RGB565 为例一帧完整画面需要 240x320x2 153600 字节也就是 150KB所以想把整帧缓冲全部放在内部 RAM 里至少得上 STM32F407 这种 192KB RAM 的芯片。如果不是整帧缓冲而是用局部缓冲区方案那 RAM 要求会大幅下降。LVGL 默认推荐用 1/10 屏高的并行缓冲比如 240 宽 x 32 高的缓冲区只需要 240x32x2 15360 字节也就是 15KB。这样 STM32F103RCT648KB RAM也能勉强跑起来但控件多了之后内存紧张建议用 F103RET6 甚至 F401 以上的型号。屏幕方面常见的选择是 ILI9341、ST7789、GC9A01 这些带 GRAM 的 SPI/并口屏选 SPI 接口的原因很简单引脚占用少接线方便而且 MCU 的 SPI 外设加 DMA 能很大程度减轻 CPU 负担。硬件底线总结一下RAM 至少 20KB 以上Flash 至少 128KB推荐 256KB屏幕分辨率 320x240 以内色彩格式尽量支持 RGB565如果屏幕分辨率超过 480x272那就不建议再用普通 SPI 屏了上 RGB 接口或 EDPI 接口屏幕更合适。2. 工程搭建与LVGL源码导入2.1 创建STM32CubeIDE工程STM32CubeIDE 建工程的步骤比较固定我简单列一遍重点讲几个容易忽略的配置点。新建工程File - New - STM32 Project在弹窗里输入芯片型号比如 STM32F401RET6选择封装后进入图形化配置界面。调试接口在 System Core - SYS 里把 Debug 选成 Serial Wire否则板子下载一次程序后就提示无法连接这点新手经常踩。时钟树在 Clock Configuration 页面里输入外部晶振频率然后把 HCLK 拉满到芯片支持的最高值。STM32F401 最高是 84MHzF103 最高 72MHz。时钟不够快会影响 LVGL 的计算速度但对显示刷新影响不明显因为瓶颈在 SPI 传输。SPI 外设选一个空闲的 SPI配置成 Transmit Only Master时钟分频先别太激进。我最初用 8 分频SPI 时钟 10.5MHz后面为了排除花屏问题降到 32 分频SPI 时钟 2.6MHz事实证明先降速排除干扰再提速是个好策略。GPIO 配置屏幕的 CS、DC、RST、BLK 这几个控制脚都配置成推挽输出具体电平逻辑看屏幕数据手册不同厂家的屏模块时序略有差异。配置完成后保存STM32CubeIDE 会自动生成初始化代码。注意如果生成了代码之后又要改外设配置直接在 .ioc 文件里重新配置并保存代码会自动重新生成但你自己写的业务代码不要放在生成区域内否则会被覆盖。2.2 下载并加入LVGL 8.3源码LVGL 源码去 GitHub 官方仓库下载注意要选 8.3.x 的 release 分支不要直接拉最新 master因为 LVGL 9.x 已经出了API 变化较大跟网上一堆教程都对不上。解压之后以lvgl命名放进工程目录目录里最关键的部分是src文件夹里面包含了核心库、控件库、字体、绘图引擎、硬件抽象层这些所有源码。往 STM32CubeIDE 工程里添加源码有两种方式。第一种直接把lvgl/src整个文件夹作为一个源码目录加进工程工程会自动递归编译下面所有 .c 文件。操作方法右键工程 - Properties - C/C General - Paths and Symbols - Source Location - Add Folder选择lvgl/src即可。第二种手动把 src 下的 .c 文件拖进工程但目录多容易漏我不推荐。添加完源码还要配置头文件路径。同样在 Paths and Symbols 的 Includes 标签页添加lvgl和lvgl/src两个路径编译时才能找到lvgl.h和内部头文件。然后还需要一个关键操作复制lvgl/lv_conf_template.h到工程根目录或者任意已经在 include 路径中的目录重命名为lv_conf.h并在编译宏里加上LV_CONF_INCLUDE_SIMPLE。这样 LVGL 源码里#include lv_conf.h就能直接搜到不用再修改任何源码路径。2.3 LCD底层驱动的正确姿势屏幕底层驱动是整个移植的地基地基没打好后面全是瓦砾。ILI9341 这类屏幕的初始化大体流程是复位、延时、发送一串初始化命令、开启显示。网上能找到大量成熟的 ILI9341 驱动代码可以直接抄但抄之前要看懂几个关键点。复位时序必须严格RST 拉低保持至少 10ms拉高后再延时 120ms再开始发命令。有些屏模块的复位脚直接连到了 MCU 的复位脚那样初始化时可以不用单独复位但稳妥起见还是独立接一个引脚。命令发送时 DC 引脚要区分命令和数据命令字节 DC 低电平数据字节 DC 高电平CS 全程拉低。发送数据时要注意字节顺序ILI9341 默认是大端 RGB565也就是一个像素的两个字节高字节在前低字节在后。这一点直接关系到后面的LV_COLOR_16_SWAP配置非常重要。初始化命令表不用死记参考厂商驱动即可但一定要确认最后一个命令是0x29Display ON并且开显示之后延时至少 100ms 再画数据。我的经验是如果初始化完成后屏幕是全白或全黑但能看到电平在跳那初始化时序基本没问题问题通常出在颜色格式或窗口坐标上。3. lv_conf.h配置决定花不花屏的关键3.1 颜色深度和字节序lv_conf.h里第一个要修改的就是LV_COLOR_DEPTH这个参数必须和屏幕硬件实际支持的色彩格式一致。ILI9341 在 SPI 模式下常用 RGB565 格式也就是 16 位色所以设置LV_COLOR_DEPTH 16。如果你设置成 24 或 32LVGL 内部会按 RGB888 处理然后你在 flush 回调里如果直接把它按 RGB565 发给屏幕那就必然花屏颜色错乱、黄色变蓝、白色带噪点典型的“色彩通道对调”症状排查起来很费时间。比颜色深度更容易踩坑的是LV_COLOR_16_SWAP。这个参数控制 RGB565 颜色值在内存中的字节序。如果屏幕需要高字节在前比如常见的是 SPI 大端模式但 LVGL 默认是小端存储那发出去的每个像素的高低位就反了整个屏幕会出现明显的偏色和花屏。排查方法非常直接把LV_COLOR_16_SWAP在 0 和 1 之间切换一次看画面是否恢复正常。如果切换之后颜色对了说明之前配置反了。这不是技术难题但很容易被忽略我在帮朋友看问题时就遇到过好几次这种低级但致命的配置错误。3.2 内存池与显示缓冲区LV_MEM_SIZE定义了 LVGL 内部使用的动态内存池大小它不是直接用 malloc而是在一个静态数组上做内存分配。这个值设置太小会导致控件创建失败、画面元素消失或者随机崩死设置太大又会浪费宝贵的 RAM。项目初期我建议先给一个保守值比如(64U * 1024U)也就是 64KB然后根据实际运行情况往上减或往上涨。需要注意的是显示缓冲区并不占用这个内存池缓冲区是独立定义的静态数组。显示缓冲区定义在驱动文件里常见的写法是这样#define MY_DISP_HOR_RES 240 #define MY_DISP_VER_RES 320 static lv_disp_draw_buf_t draw_buf; static lv_color_t buf_1[MY_DISP_HOR_RES * 40]; // 40行缓冲 static lv_color_t buf_2[MY_DISP_HOR_RES * 40]; // 第二个缓冲区两个缓冲区可以设置成一块用作单缓冲也可以设置成双缓冲。我强烈建议至少开双缓冲尤其是配合 DMA 传输时一个缓冲区在被 SPI 发送的同时另一个缓冲区可以被 LVGL 用来渲染下一帧极大提高效率。缓冲区行数决定了每次刷新屏幕的数据量行数越多刷新效率越高但内存占用也越大。240x40 的 RGB565 缓冲区占用 240x40x2 19200 字节两块就是 38400 字节加上 LVGL 内核内存池 64KB差不多 100KB RAMF401RE 的 96KB 就有点悬了。所以实际项目里我常常把内存池降到 32KB缓冲区调到 240x32这样 96KB RAM 的芯片也能跑得比较从容。3.3 字体、控件和调试开关lv_conf.h里默认把很多字体都编译进去了这会导致 Flash 占用急剧膨胀。我的建议是只保留你实际用到的字体比如 Montserrat 12 和 14其他全关掉。字体开关长这样#define LV_FONT_MONTSERRAT_12 1 #define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_DEFAULT lv_font_montserrat_14如果界面需要显示中文那不能直接依赖 LVGL 自带的字体需要通过字体转换工具把 ttf 字体转换成数组或者 bin 文件再带进工程。LVGL 8.3 推荐用官方在线转换工具或者lv_font_conv命令行工具转换时需要注意设置好字符范围和输出格式这里先不展开后面有空单独写一篇。调试方面建议在项目初期打开日志功能LV_USE_LOG 1日志等级调到LV_LOG_LEVEL_WARN或LV_LOG_LEVEL_INFO。通过串口或调试器能看到 LVGL 内部的报错信息比如内存不足、控件指针无效等等这对定位问题非常有帮助。正式发布时再关掉日志即可。另外还有一个小功能很实用LV_USE_PERF_MONITOR 1。它会在屏幕上显示实时的 FPS 和 CPU 利用率移植验证时打开它能直观看到刷新性能省得猜想了。4. 显示接口对接flush_cb的写法直接决定画面质量4.1 显示驱动结构体初始化LVGL 8.3 的显示驱动注册流程比旧版清爽核心就三步初始化 draw_buf、初始化 disp_drv、register。代码结构大概长这样static lv_disp_driver_t disp_drv; void lv_port_disp_init(void) { lv_disp_draw_buf_init(draw_buf, buf_1, buf_2, MY_DISP_HOR_RES * 40); lv_disp_drv_init(disp_drv); disp_drv.hor_res MY_DISP_HOR_RES; disp_drv.ver_res MY_DISP_VER_RES; disp_drv.flush_cb my_disp_flush_cb; disp_drv.draw_buf draw_buf; lv_disp_drv_register(disp_drv); }有个细节值得注意hor_res和ver_res是屏幕物理分辨率而不是旋转后的逻辑分辨率。如果要做横屏和竖屏切换可以用LV_DISP_ROT_90这类旋转宏但要在 register 之前配置而且旋转后 flush 回调里也要对应调整窗口坐标这个复杂度比较高新人建议先固定横屏或竖屏不要急着加旋转功能。4.2 手写flush_cb同步与DMA版本flush 回调是 LVGL 和屏幕驱动之间最核心的接口。LVGL 每次需要刷新屏幕某个区域时会调用这个回调把区域坐标和颜色数据传进来你要做的就是把这块数据写到屏幕的对应窗口里。最基本、调通最省心的方式是同步发送也就是阻塞式 SPI 传输static void my_disp_flush_cb(lv_disp_driver_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t w lv_area_get_width(area); uint32_t h lv_area_get_height(area); lcd_set_window(area-x1, area-y1, area-x2, area-y2); lcd_push_colors((uint8_t *)color_p, w * h * 2); lv_disp_flush_ready(disp_drv); }这样 LVGL 会等整个区域的数据发送完之后再继续渲染逻辑最简单不容易出错。缺点就是 CPU 在 SPI 发送期间被占死刷新效率比较低但用于功能验证完全够。实测 240x320 全屏刷一次大概要几十到上百毫秒看 SPI 速率鼠标拖动滑块会有肉眼可见的迟滞但至少画面不花。如果要做流畅的界面必须上 DMA。核心思路是不让 CPU 在 SPI 收发期间空等而是把缓冲区地址交给 DMA让 DMA 搬运数据CPU 继续去干别的事。DMA 版本的 flush 回调长这样volatile bool dma_tx_busy false; static void my_disp_flush_cb(lv_disp_driver_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t w lv_area_get_width(area); uint32_t h lv_area_get_height(area); lcd_set_window(area-x1, area-y1, area-x2, area-y2); dma_tx_busy true; HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)color_p, w * h * 2); }DMA 版本最关键的一点千万不能在 DMA 传输还没结束时就调用lv_disp_flush_ready。一旦提前调用LVGL 会认为这块缓冲区已经用完了立刻开始往同一块缓冲区里写入下一帧数据而此时 DMA 还在读这块缓冲区的数据往外发结果就是屏幕上出现花屏、残余物、撕裂像是随机闪过的噪点。正确的做法是在 SPI 的 DMA 传输完成中断里调用lv_disp_flush_readyvoid HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { dma_tx_busy false; lv_disp_flush_ready(disp_drv); } }这里要注意disp_drv必须是全局的这样回调里才能访问到。我就是在这里踩了最久的一个坑一开始总是想省事在 flush_cb 里直接发完数据就 ready结果图片和文字全部花掉后来才意识到 DMA 还没收工。4.3 心跳Tick和定时器刷新LVGL 需要一个基准时钟来驱动动画和鼠标滑块的逻辑这个时钟通常是毫秒级的 tick。在 LVGL 8.3 里最简单的做法是在SysTick_Handler里调用lv_tick_inc(1)void SysTick_Handler(void) { HAL_IncTick(); lv_tick_inc(1); }HAL_IncTick()是 HAL 库本来就有的函数里加一行lv_tick_inc(1)即可。如果你不想动 SysTick也可以在lv_conf.h里设置LV_TICK_CUSTOM 1这样 LVGL 会自己调用HAL_GetTick()获取时钟这种方案更适合与 RTOS 配合因为不需要在中断里额外调用 LVGL 函数。主循环里要定时调用lv_timer_handler()来驱动 LVGL 处理事件、刷新屏幕、运行动画。最简单的方式while (1) { lv_timer_handler(); HAL_Delay(5); }每 5ms 调一次LVGL 会按LV_DEF_REFR_PERIOD默认 30ms的节奏刷屏流畅度和 CPU 占用比较均衡。不要在一个循环里连续多次调用lv_timer_handler()那并不会提升帧率反而会白白浪费 CPU。如果和 RTOS 配合可以创建一个独立任务然后在任务循环里调用延时同样设在 5~10ms 左右。5. 花屏排查一次讲清楚所有常见根因5.1 花屏特征速查表花屏这个现象看着吓人但其实每种表现都有对应的根因。我整理了一张表格方便你对照现象快速定位方向。写这张表的时候我是以自己的踩坑经历为主也参考了一些网友在各种社区分享的问题每一条都是真刀真枪验证过或排查过的。现象可能原因排查方向白屏或黑屏无任何内容LCD初始化失败、背光未开、RST引脚电平不对检查初始化命令表和RST时序测量背光引脚颜色完全错乱蓝红对调RGB565字节序错误切换 LV_COLOR_16_SWAP 的 0/1画面出现横条纹或撕裂感flush_ready 调用过早DMA与渲染竞争同一缓冲区等待DMA完成后再调 flush_ready整屏彩色雪花电压不稳SPI速率过高信号完整性差降低SPI分频系数检查信号线长度画面显示位置偏移或重复LCD_SetWindow窗口坐标设置错误核对 area 坐标到屏幕坐标的映射随机死机或部分控件消失LV_MEM_SIZE 太小内存分配失败调大内存池打开日志查看报错刷新时有明显残影屏幕刷新率低或缓冲区行数太少增加缓冲区行数优化SPI为DMA5.2 SPI时序和初始化顺序SPI 通信最大的坑在于时钟极性和相位也就是 CPOL 和 CPHA。不同屏幕模块对这两项的配置要求不一样ILI9341 通常支持 SPI Mode 0 和 Mode 3但有些仿制的屏幕对时序要求特别严格用错模式就会表现为花屏或者只有半屏有内容。排查方法很简单先看你的驱动代码是怎么初始化 SPI 的然后尝试切换 Mode 0 和 Mode 3 看效果。STM32 的 HAL 库在SPI_InitTypeDef里直接配置CLKPolarity和CLKPhase测试时改两个参数重新编译下载即可。SPI 时钟速率也是花屏的高发区。屏幕频率标称值再高实际布线、杜邦线长度、电源纹波都会影响信号质量。我排查花屏时发现 10MHz 下画面正常但偶尔有噪点而把时钟稍微降一点到 6.5MHz 就完全稳定说明高速传输时信号边沿已经不够干净了。另一块仿制屏则正好反过来Mode 0 下怎么都花换成 Mode 3 加降速之后立刻稳定。板子设计不同表现差异很大不能只看屏幕芯片型号就断定参数。初始化顺序也很容易被忽略。有些屏模块上电瞬间会处于不确定状态如果代码在复位期间就开始送命令命令会丢失屏幕处于半初始化状态表现为花屏或亮屏只显示噪点。正确的顺序应该是上电后先拉低 RST 保持 10~20ms然后拉高延时 120ms再开始发初始化命令。这个等待时间不能省尤其不要在主频很高、MCU 跑得飞快的情况下想当然地省延时。5.3 缓冲区刷新和DMA竞态缓冲区相关的花屏我前面提过最核心的一点flush 回调里必须在数据发送完成后才通知 LVGL 缓冲区可复用。这里再补充一个实战代码用信号量或标志位来保证时序适合 DMA 方式下的标准写法static void my_disp_flush_cb(lv_disp_driver_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { lcd_set_window(area-x1, area-y1, area-x2, area-y2); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)color_p, lv_area_get_width(area) * lv_area_get_height(area) * 2); } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { lv_disp_flush_ready(get_disp_drv()); } }另一个容易踩的坑是缓冲区地址没有对齐。LVGL 内部对颜色缓冲有对齐要求尤其开启某些 DMA 优化后如果缓冲区地址不是按 32 位对齐的DMA 读取时可能出现奇怪的字节错位表现为偶尔几个像素花掉或者一条竖线错乱。解决办法是把缓冲区定义成全局静态数组编译器一般会自动对齐不要用局部的 malloc 出来。还有一种独立缓冲区问题发生在 SPI 数据长度上。HAL_SPI_Transmit 的第三个参数是数据长度单位取决于 SPI 实例的DataSize。如果 SPI 配置成 8 位数据帧那么参数就是字节数直接把w*h*2传进去没问题但如果配成 16 位数据帧第三个参数就应该按半字数量来算很多人在这里算错一半导致发送出去的数据长度不对屏幕底部多出一截彩色条纹。5.4 内存、堆栈和中断优先级内存不足导致的花屏比 SPI 时序问题更隐蔽。LVGL 在没有足够内存时控件可能创建失败界面元素缺失或者渲染到一半发现内存不够画面出现局部空白。打开日志后你会看到lv_mem_alloc相关的报错或者out of memory提示。如果不想开日志也可以用lv_mem_monitor()函数调试它能输出内存池使用率和峰值方便精确掌握内存需求。如果项目里跑了 FreeRTOS而且 LVGL 在一个独立任务里运行一定要给任务留足栈空间。LVGL 的渲染过程有较深的函数调用链涉及递归和临时变量栈需求比普通逻辑任务大得多。我实测下来默认 128 字的任务栈肯定不够至少给到 1024 字才比较稳如果页面复杂或者要加载图片建议 2048 字。栈溢出有时不会立刻崩溃而是表现为随机位置的花屏、HardFault、或者过一段时间才死机这种问题最磨人。可以把 FreeRTOS 的栈检测功能打开跑一段时间确认峰值栈用量。中断优先级也影响画面稳定性。SPI 的 DMA 完成中断如果优先级太低会被其他高频中断抢占延迟了lv_disp_flush_ready的调用可能导致 LVGL 认为刷新超时继而驱动内部状态错乱。更常见的坑是 SysTick 中断优先级高于 SPI 中断导致 tick 频繁抢占 SPI 传输影响时序。我的建议是在裸机环境下把 SysTick 优先级设为最低SPI DMA 中断设为中等优先级在 RTOS 环境下则要遵循 RTOS 的要求来设置 PendSV 和 SysTick。总之中断优先级设置的原则是紧急且短小的中断优先占用时间长的任务放在低优先级慢慢跑。6. 跑Demo验证移植再谈优化6.1 用官方demo验证移植到底成没成别自己画几个圆形矩形就完事直接跑 LVGL 官方自带的 widget demo 最省事。在lv_conf.h里打开LV_USE_DEMO_WIDGETS 1然后在main函数中初始化完 LVGL 和显示驱动之后调用lv_demo_widgets();这个 demo 里面包含了仪表盘、滑块、进度条、文本、按钮、日历等大量控件。如果它能够流畅显示出来说明移植基本成功如果出现某个控件显示异常或者花屏你再按我前面写的排查思路去定位。头一回跑通的时候我就盯着仪表盘转了好几分钟确实有种舒坦的感觉。如果嫌 demo 页面太复杂可以用更简单的lv_demo_benchmark或者lv_demo_music来测试。benchmark 适合测极限性能music demo 则适合测试动画流畅度。注意这些 demo 编译都会增大固件体积跑通验证后记得关掉项目实际使用只保留你要的界面代码。6.2 性能观察和优化方向打开LV_USE_PERF_MONITOR后屏幕角落会显示 FPS 和 CPU 占用率。对于 240x320 RGB565 的屏幕STM32F401 跑到 84MHz 主频、SPI 18MHz 加 DMA 双缓冲时界面静止时 CPU 占用率很低有动画时 FPS 大概在 25~40 之间具体取决于画面复杂度。如果 FPS 掉到 20 以下主要优化方向有这么几个增大显示缓冲区行数减少一次 LVGL 渲染与屏幕刷新的耦合次数确保LV_COLOR_16_SWAP配置正确避免 LVGL 内部做无谓的字节交换开启LV_DISP_DEF_REFR_PERIOD调整让 LVGL 刷新节奏更合理把屏幕驱动里的LCD_SetWindow和批量数据发送函数写得精简一点减少不必要的函数调用开销如果屏幕支持用 SPI 的 DMA 循环发送或者使用内存映射的并口屏效果会跨越式提升。缓冲区大小是性能和内存的权衡点。我实测 240x320 屏幕用 40 行双缓冲普通界面足够流畅降到 10 行双缓冲滑动页面时能看到明显的刷新块甚至隐约有屏幕从上到下刷新过程可见用户体验大幅下降。内存允许的话尽量把缓冲区加到 40 行以上性价比很高。6.3 继续扩展RTOS接入和触屏如果项目里已经跑了 FreeRTOSLVGL 的集成方式推荐独立任务。流程是初始化 LVGL 后创建一个lvgl_task优先级中等偏低任务主体就是前面写的无限循环内部调用lv_timer_handler()和延时。注意不要从多个任务同时调用 LVGL 的 APILVGL 8.3 默认不是线程安全的建议所有界面操作都放到这一个任务里其他任务通过队列或信号量把界面修改请求发过来。如果必须要跨任务直接调用可以在任务间加互斥锁但那样容易引入死锁和优先级反转的问题尽量从设计上避免。触屏这类输入设备接进来之后要在lv_port_indev.c里实现读坐标的回调。大部分电阻屏和电容屏的驱动都大同小异通过 SPI 或 I2C 读取触摸坐标然后在回调里通过lv_indev_set_state报告按下或释放。坐标校准是电容屏最烦的一环如果触点位置和显示位置对不上点按钮时完全点偏那种挫败感不亚于花屏。建议用官方推荐的校准流程或者移植一个简单的三点校准算法进去。最后再分享一个小技巧整个移植过程中验证 LVGL 逻辑是否正常、屏幕驱动是否正常其实可以把两者分开测。屏幕驱动单独初始化好后先往屏幕里直接写满几个纯色块测试确认颜色和坐标都正确后再接 LVGL。我一直用这个思路好多次花屏一拆问题就清楚了到底是驱动的问题还是 LVGL 配置的问题拉出来遛遛就明白了。希望这篇手记能帮你少走点弯路。
返回列表