news 2026/8/31 20:59:55

STM32F746G-DISCO移植LVGL 9.0性能基准测试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F746G-DISCO移植LVGL 9.0性能基准测试实战

之前在 STM32 项目里做 GUI 界面,大部分资料都停留在 LVGL 8.x 版本。等我把工程切到 LVGL 9.0 后,发现原来的lv_disp_drv_tlv_disp_buf_t初始化方式已经变了,网上很多代码直接编译不过。加上 STM32F746G-DISCO 板卡带 RGB 接口 LCD 和 SDRAM,很多读者都想知道这块板子跑 LVGL 9.0 到底能到多少帧、CPU 占用大概什么水平,这正好是一个很适合做性能基准测试的平台。

本文会围绕 STM32F746G-DISCO 上的 LVGL 9.0 移植流程展开,从工程准备、LVGL 9.0 显示驱动接入、心跳与刷新回调处理,到基准测试方法与常见坑位,都会完整梳理一遍。无论你是准备在 STM32 上入门 LVGL,还是想评估新版本性能后再决定是否升级,这篇实战笔记都能给你一个可复用的参考路径。

需要说明的是,LVGL 版本迭代比较快,不同开发板的 LCD 时序和引脚配置也有差异,所以下面代码会以“思路 + 关键示例”为主,具体引脚和屏幕参数以你自己生成的工程为准。

1. 移植背景与核心概念

1.1 STM32F746G-DISCO 为什么适合跑 LVGL

STM32F746G-DISCO 是 ST 官方 Discovery 系列开发板,主控芯片是 STM32F746NGH6,基于 ARM Cortex-M7 内核,主频最高可以到 216MHz。Cortex-M7 相比老一代 Cortex-M4,在流水线、分支预测和内存访问上有明显改进,而且 STM32F746 内部带了硬件浮点单元 FPU,这对图形运算里大量使用的坐标计算、颜色混合、透明度处理都有帮助。

这块板子还有一个很关键的特性:板载了 RGB 接口的 TFT-LCD,分辨率是 480x272,同时板上有外部 SDRAM 可以用来做显存和 LVGL 缓冲区。RGB 接口 LCD 和 SPI 接口屏不太一样,RGB 屏需要一个持续的像素时钟来刷新画面,通常由 LTDC 外设驱动,而 LTDC 可以从 SDRAM 中直接读取显存数据并输出到屏幕。因此,LVGL 负责“画好每一帧画面”,LTDC 负责“把画面送到屏幕”,SDRAM 则承担 framebuffer 和 LVGL 渲染缓冲区的存放。

这套组合非常适合做 LVGL 性能测试,因为显存路线是全内存映射的方式,渲染完成后再由硬件刷新,不存在 SPI 屏那种逐字节发送的瓶颈,测出来的 FPS 更接近 MCU 的“真实渲染能力”。

1.2 LVGL 9.0 相比 8.x 的主要变化

LVGL 9.0 是一个大版本升级,API 变化比较明显。如果以前用的是 8.x,升级到 9.0 时不能只替换源码,很多初始化逻辑都需要调整。

比较明显的变化包括:

  • 显示设备接口从lv_disp_drv_t变成了lv_display_t,创建显示器的入口变成了lv_display_create()
  • 缓冲区注册从lv_disp_drv_register()改成了lv_display_set_buffers()
  • flush 回调使用lv_display_set_flush_cb()来注册,并且在刷新完成以后调用lv_display_flush_ready()
  • 颜色格式支持更灵活,比如LV_COLOR_FORMAT_RGB565LV_COLOR_FORMAT_ARGB8888,并且不再像 8.x 那样只依赖LV_COLOR_DEPTH一种全局颜色深度。
  • 输入设备接口也做了调整,从lv_indev_drv_t变成了lv_indev_create()的方式。
  • 内置了大量重构后的绘制单元(Draw Unit)架构,为后续多线程渲染和硬件加速做了铺垫。

这些变化意味着,网上大量基于 8.x 的移植代码在 9.0 上会直接报错。本文后面的示例全部基于 LVGL 9.0 的 API 来写。

1.3 性能基准测试到底在测什么

LVGL 性能基准测试并不是简单看一个“帧率数字”就够了。真正有参考价值的测试,至少要包含三个维度:

第一是 FPS,也就是每秒能刷新多少帧画面。FPS 越高,界面滑动和动画越流畅。但 FPS 会受场景复杂度影响很大,全屏图片缩放和纯色矩形填充的 FPS 完全不同。

第二是 CPU 占用率,也就是lv_timer_handler()执行一轮所花的时间占整个调度周期的比例。如果 FPS 很高但 CPU 已经 90% 以上,说明系统已经没有余量处理业务逻辑。

第三是单帧渲染时间。通过记录lv_timer_handler()前后时间戳,可以算出每一帧的平均渲染耗时和最大耗时。这个指标对判断动画掉帧最直接。

后续第 5 节会专门说明如何基于 LVGL 9.0 的 perf monitor 和自定义计时方式来做这些测量。

2. 环境准备与工程结构

2.1 硬件与软件清单

在开始移植之前,先把环境准备好。

硬件方面:

  • STM32F746G-DISCO 开发板。
  • 一根 USB 线,用于供电和下载调试。
  • 可选:逻辑分析仪或者串口,用于输出测试数据。

软件方面:

  • STM32CubeIDE 或者 Keil MDK,两者都可以。
  • STM32CubeMX,用于生成基础工程,尤其是 SDRAM、LTDC、GPIO 的初始化代码。
  • LVGL 9.0 源码,建议直接从 GitHub 官方仓库获取 release 分支。
  • STM32F7 HAL 库,CubeMX 生成工程时会自动带入。

版本方面建议统一:如果你的开发环境是 STM32CubeIDE,就使用它自带的 HAL 版本;LVGL 9.0 建议使用最新的 9.x release,因为 9.0 初期版本还有一些小问题,后续的 9.1、9.2 会有修复。本文写作时以 9.x 主线 API 为准,具体版本号请以你拉取代码时为准。

2.2 获取 LVGL 9.0 源码

LVGL 源码建议直接克隆官方仓库:

git clone --branch release/v9.2 https://github.com/lvgl/lvgl.git

如果 GitHub 访问不方便,可以从官方镜像或者本地包管理工具获取。这里版本号只是示例,你可以选择你需要的 release。拿到源码以后,LVGL 本体只需要几个关键目录:

  • src目录,这是 LVGL 的核心源码。
  • lvgl.h,总头文件。
  • lv_conf_template.h,配置模板,复制后改名为lv_conf.h

注意不要把整个仓库全部加入编译,只要把src目录加入 include path 和编译路径即可。

2.3 最小工程结构

这里建议的工程结构如下:

STM32F746G_LVGL9/ ├── Core/ │ ├── Inc/ │ ├── Src/ │ └── Startup/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32F7xx_HAL_Driver/ ├── LVGL/ │ ├── lv_conf.h │ ├── lvgl.h │ ├── src/ │ └── ... ├── App/ │ ├── app_lvgl.c │ ├── app_lvgl.h │ ├── lcd_driver.c │ ├── lcd_driver.h │ └── ... └── STM32F746G_DISCO.ioc

App目录用来放我们自己写的 LVGL 适配层,比如lcd_driver.c负责底层 LCD 操作,app_lvgl.c负责 LVGL 初始化和主循环调度。这样把“硬件驱动”和“LVGL 应用”分离开,后续做性能测试和代码维护都更方便。

3. LVGL 9.0 移植核心配置

3.1 lv_conf.h 的基本配置

LVGL 的裁剪和功能开关都集中在lv_conf.h文件里。把lv_conf_template.h复制一份到工程中,改名为lv_conf.h,然后确保在编译选项或者头文件路径中能找到它。

lv_conf.h中几个关键项需要重点关注:

// LVGL 总开关 #define LV_USE_OS LV_OS_NONE // 颜色深度 #define LV_COLOR_DEPTH 16 // 开启性能监视器 #define LV_USE_PERF_MONITOR 1 // 开启内置 Benchmark 示例 #define LV_USE_DEMO_BENCHMARK 1 // 内存配置 #define LV_MEM_SIZE (64U * 1024U)

LV_COLOR_DEPTH设置为 16 表示使用 RGB565 颜色格式,这种格式占内存小,渲染也快。如果你的界面需要更丰富的颜色表现,可以改成 32,但是帧率和内存占用会明显增加。

LV_USE_PERF_MONITOR是 LVGL 自带的性能显示工具,开启后界面上会显示 FPS 和 CPU 占用率。这个适合开发调试阶段,量产时可以关闭。

LV_USE_DEMO_BENCHMARK是 LVGL 自带的 Benchmark 演示程序,用来测试多项图形绘制性能,后面性能测试章节会用到。

3.2 心跳 Tick 实现

LVGL 需要一个毫秒级的时间基准,用来处理动画、时间相关事件。在裸机环境下,我们可以用任意一个定时器来实现,比如 SysTick。

假设你已经有一个HAL_GetTick()或者自定义的毫秒计数函数:

static uint32_t my_tick_get(void) { return HAL_GetTick(); } void app_lvgl_init(void) { lv_init(); lv_tick_set_cb(my_tick_get); }

这里lv_tick_set_cb()是 LVGL 9.0 设置 tick 来源函数。注意这里传的是一个“获取当前毫秒数”的回调函数,而不是直接传一个 tick 值。

3.3 创建 Display 与缓冲区设置

LVGL 9.0 不再使用lv_disp_drv_t这个旧结构体,而是通过lv_display_create()来创建一个显示器对象。

static lv_color_t buf1[LVGL_BUF_SIZE]; static lv_color_t buf2[LVGL_BUF_SIZE]; void app_lvgl_init(void) { // 先初始化 LVGL lv_init(); lv_tick_set_cb(my_tick_get); // 创建显示器 lv_display_t *disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT); lv_display_set_buffers(disp, buf1, buf2, LVGL_BUF_SIZE * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL); lv_display_set_flush_cb(disp, lcd_flush_cb); // 之后可以创建 UI 对象 }

lv_display_set_buffers()的第三个参数是缓冲区大小,单位是字节。LVGL 9.0 的 API 定义中,缓冲区大小参数是uint32_t buf_size,指的是缓冲区的字节大小,而不是颜色数量。为了安全,建议写成sizeof(buf1)的形式。

LV_DISPLAY_RENDER_MODE_PARTIAL是部分渲染模式,LVGL 只渲染屏幕上需要更新的区域。对于中小内存的单片机来说,这种模式更实用。如果改成LV_DISPLAY_RENDER_MODE_FULL,LVGL 会一次渲染整帧,需要较大的缓冲区。

3.4 Flush 回调实现

Flush 回调是 LVGL 与 LCD 驱动之间的桥梁。LVGL 渲染完成后会调用这个回调,把图像数据交给 LCD 驱动。

void lcd_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { uint32_t w = lv_area_get_width(area); uint32_t h = lv_area_get_height(area); // 把 px_map 中的数据拷贝到 LTDC 当前层显存对应区域 lcd_copy_area(area->x1, area->y1, w, h, (uint16_t *)px_map); // 通知 LVGL 刷新完成 lv_display_flush_ready(disp); }

注意:lv_display_flush_ready()必须在真正完成数据拷贝或交给 DMA 后调用。如果你的底层拷贝是异步的,比如使用 DMA2D,那么应该在 DMA 传输完成中断里调用lv_display_flush_ready()。如果拷贝是同步的,则可以在拷贝函数返回后直接调用。

3.5 输入设备接入(可选)

STM32F746G-DISCO 板载了触摸屏,但触摸 IC 的驱动需要根据具体型号编写。LVGL 9.0 的输入设备接入方式如下:

lv_indev_t *indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, touch_read_cb);

touch_read_cb需要填充lv_indev_data_t结构体:

static void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { static lv_point_t last_point = {0, 0}; if (touch_get_point(&last_point)) { >// 文件路径:App/app_lvgl.c #include "app_lvgl.h" #include "lvgl.h" #include "lcd_driver.h" #define LCD_WIDTH 480 #define LCD_HEIGHT 272 #define LVGL_BUF_SIZE (LCD_WIDTH * 40) static lv_color_t buf1[LVGL_BUF_SIZE]; static lv_color_t buf2[LVGL_BUF_SIZE]; void app_lvgl_init(void) { lv_init(); lv_tick_set_cb(HAL_GetTick); lv_display_t *disp = lv_display_create(LCD_WIDTH, LCD_HEIGHT); lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL); lv_display_set_flush_cb(disp, lcd_flush_cb); }

这里缓冲区设置为LCD_WIDTH * 40,也就是 40 行像素大小。这个值不是固定的,后续性能测试可以尝试不同大小的缓冲区。

4.3 接入 LTDC 层显存地址

Flush 回调中的关键操作,是把 LVGL 渲染好的区域拷贝到 LTDC 当前正在显示的画面层。

对于 STM32F746G-DISCO,LTDC 的 Layer0 通常配置为使用 SDRAM 的某一段地址作为帧缓冲。每次 flush 时,需要把 LVGL 输出的像素数据覆盖到帧缓冲的对应区域。

// 文件路径:App/lcd_driver.c #define LCD_FB_ADDR 0xC0000000 // 以实际工程配置为准 #define LCD_PIXEL_WIDTH 480 #define LCD_PIXEL_HEIGHT 272 void lcd_copy_area(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h, uint16_t *pixels) { uint16_t *dst = (uint16_t *)(LCD_FB_ADDR + (y1 * LCD_PIXEL_WIDTH + x1) * 2); uint16_t *src = pixels; for (uint16_t row = 0; row < h; row++) { memcpy(dst, src, w * 2); dst += LCD_PIXEL_WIDTH; src += w; } }

这种逐行拷贝方式实现简单,但性能一般。如果开启了 DMA2D,可以改用 DMA2D 的内存到内存拷贝模式,把拷贝过程交给硬件执行,具体会在第 7 节说明。

4.4 主循环调用 lv_timer_handler

LVGL 的运行依赖于周期性调用lv_timer_handler()。在裸机环境中,主循环可以这样写:

// 文件路径:main.c int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_FMC_Init(); MX_LTDC_Init(); lcd_init(); app_lvgl_init(); // 创建一个测试界面 create_demo_ui(); while (1) { lv_timer_handler(); HAL_Delay(5); } }

这里lv_timer_handler()会处理 LVGL 内部的所有任务,包括界面更新、动画、输入设备检测等。加上HAL_Delay(5)是为了降低 CPU 占用,但也会影响最高帧率。性能测试时,可以考虑改成 CPU 空转或者使用更短的延时。

4.5 HelloWorld 验证

LVGL 初始化完成后,先创建一个最简单的界面,验证移植是否成功。

void create_demo_ui(void) { lv_obj_t *label = lv_label_create(lv_screen_active()); lv_label_set_text(label, "LVGL 9.0 on STM32F746"); lv_obj_center(label); }

如果屏幕正常显示文字,说明 LVGL 的 tick、display、flush、缓冲区配置基本都是通的。这个时候可以继续做性能测试。

5. 性能基准测试实战

5.1 性能指标与测试方法

性能基准测试需要先明确几个指标的计算方式。

帧率 FPS 的测量方法:在lcd_flush_cb中每调用一次说明 LVGL 完成了一次局部画面刷新,但局部刷新不等于完整帧。因此更合理的做法是:统计lv_timer_handler()一次完整执行中,画面从开始渲染到 flush 完成的过程耗时,或者直接借助 LVGL 内部的 perf monitor。

CPU 占用率测量方法:在一段时间内统计lv_timer_handler()执行耗时占比。比如每 100ms 统计一次,假如lv_timer_handler()累计执行了 60ms,那么 CPU 占用率大约是 60%。LVGL 自带的 perf monitor 已经实现了这个逻辑。

单帧渲染时间测量方法:在调用lv_timer_handler()之前记录一个时间戳,调用完成后再次记录,两者相减就是该轮渲染耗时。注意lv_timer_handler()并不一定每轮都会触发渲染,如果界面上没有变化,它处理完定时器任务后就会快速返回。

5.2 启用 LVGL 自带 Perf Monitor

LVGL 9.0 内置了性能监视器,只要在lv_conf.h中开启:

#define LV_USE_PERF_MONITOR 1

编译运行后,在屏幕左上角或右下角会显示类似以下信息:

FPS: 45 CPU: 32%

这个数字是 LVGL 自己统计的,包含界面渲染和内部定时任务开销。调试阶段这个功能非常方便,不需要额外写代码就能看到大概性能水平。

不过要注意,perf monitor 本身也会消耗一定绘制资源,而且它在屏幕上绘制文字和背景本身也会增加一点点渲染负载。测出来的数据用于相对比较没有问题,但如果是发布版本,建议关闭。

5.3 使用官方 Benchmark Demo

LVGL 官方提供了 benchmark 演示,用来测试不同绘制场景的性能。在lv_conf.h中开启:

#define LV_USE_DEMO_BENCHMARK 1

然后调用:

lv_demo_benchmark();

这个 demo 会自动运行一系列测试场景,包括矩形、边框、圆弧、文字、图片缩放、阴影等。每一轮完成后会给出一个分数,并且可以统计平均渲染时间。

使用 benchmark demo 的一个好处是测试场景标准化,同一个 demo 在不同开发板、不同配置之间跑出来的数据具有可比性。缺点是它测的是 LVGL 渲染性能的“上限”,实际业务界面的复杂度千差万别。

5.4 自定义多场景对比测试

除了官方 benchmark,建议针对自己的业务场景做几组对比测试。常见的对比维度有:

第一,缓冲区大小对比。分别设置缓冲区为 10 行、20 行、40 行、全屏高度,测量相同界面的 FPS 和 CPU 占用。

第二,颜色格式对比。在 RGB565 和 ARGB8888 之间切换,测量全屏图像填充和半透明控件的渲染耗时。

第三,渲染模式对比。对比LV_DISPLAY_RENDER_MODE_PARTIALLV_DISPLAY_RENDER_MODE_FULL,观察帧率和画面撕裂情况。

第四,DMA2D 开关对比。没有 DMA2D 时,Flush 使用 CPU 循环拷贝;开启 DMA2D 后,Flush 将拷贝交给硬件,对比 CPU 占用率的变化。

自定义测试代码示例:

// 测试某个操作的单帧耗时 uint32_t t0 = DWT->CYCCNT; lv_obj_t *obj = lv_obj_create(lv_screen_active()); lv_obj_set_size(obj, 200, 100); lv_obj_center(obj); lv_timer_handler(); uint32_t t1 = DWT->CYCCNT; float cost_ms = (float)(t1 - t0) / 216000000.0f * 1000.0f;

注意 DWT->CYCCNT 需要在启动时使能:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

这种方法统计的是 CPU 周期数,精度比HAL_GetTick()高很多,适合做短时间片段的性能对比。

5.5 测试结果记录与对比思路

建议做一张测试记录表,这样后续优化效果一目了然:

测试场景颜色格式缓冲区大小渲染模式平均 FPSCPU 占用最大单帧耗时
官方 BenchmarkRGB56540 行PARTIAL待测待测待测
官方 BenchmarkARGB888840 行PARTIAL待测待测待测
自定义页面 ARGB565全屏FULL待测待测待测

这里的数值因工程而异,不建议直接套用网上的成绩。不同 CubeMX 版本、不同编译器优化选项、不同 LTDC 像素时钟都会影响结果。关键是保持测试条件一致,每次只改变一个变量,这样优化才有参考意义。

6. 常见问题与排查

6.1 白屏或者花屏

白屏通常意味着 LTDC 没有正常工作,或者显存地址没有数据。排查时按下面顺序来:

先确认 SDRAM 是否正常。向显存地址写入一个纯色值,比如全红0xF800,看屏幕是否显示红色。如果还是白屏,优先检查 FMC SDRAM 的初始化时序。

再确认 LTDC 层配置。LTDC 需要正确配置 Layer 的窗口位置、像素格式、显存地址和颜色格式。STM32F746G-DISCO 的 RGB 屏颜色格式通常是 RGB888,而 LVGL 的缓冲区可能是 RGB565,两者不一致会导致显示颜色错乱或花屏。

最后确认像素时钟。如果屏幕规格是 60Hz 刷新率,像素时钟和时序参数由 CubeMX 计算。手动改错一个参数就可能导致画面偏移或者花屏。

6.2 刷新闪烁和撕裂

闪烁一般是由于 LVGL 缓冲区太小,导致渲染和显示不同步。可以通过开启双缓冲区减轻:

lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);

双缓冲区下,LVGL 可以在一个缓冲区渲染的同时,把另一个缓冲区的内容交给 LCD 刷新。

撕裂则是 LTDC 正在读取显存时,LVGL 修改了同一块显存区域造成的。要彻底避免撕裂,需要做到刷新和渲染同步,通常可以等待垂直同步信号,或者在 LTDC 配置中开启 FIFO Underrun 中断。对于大部分工业界面,轻微的撕裂影响不大,优先保证帧率。

6.3 内存不足或者 HardFault

LVGL 的lv_conf.hLV_MEM_SIZE默认可能比较小,如果创建了大量控件或者图片,会出现内存分配失败。现象是运行一段时间后界面卡死或者直接 HardFault。

排查方法:开启 LVGL 内存监控函数lv_mem_monitor(),在串口输出内存使用情况。

lv_mem_monitor_t mon; lv_mem_monitor(&mon); printf("used: %d / %d\n", mon.used_size, mon.total_size);

HardFault 也可能发生在 Flush 回调中,比如缓冲区地址没有对齐,或者 SDRAM 地址越界写入。建议在 HardFault_Handler 中解析故障栈,确认 PC 指针是否落在lcd_copy_areamemcpy中。

6.4 API 版本不兼容报错

LVGL 9.0 升级后,旧代码编译报错很常见。最典型的是找不到lv_disp_drv_tlv_disp_buf_t,或者调用了lv_disp_drv_register()

解决办法是统一按照 9.0 的新 API 来写。不要混用 8.x 的旧接口,也不要把不同版本的源码混在一个工程里。如果从旧项目升级,优先参考官方 migration guide,检查lv_conf.h中是否有已经不存在的宏。

还有一种情况是lv_conf.h找不到,这个一般是头文件路径没配好。LVGL 源码和lv_conf.h需要同时加入编译器的 include path。

6.5 Cortex-M7 Cache 一致性问题

STM32F746 属于 Cortex-M7,带有 D-Cache。如果开启了 D-Cache,并且 DMA2D 或 LTDC 在访问 SDRAM 中的显存,CPU 写进缓存的数据可能没有及时同步到内存,导致画面显示错乱。

解决方案有两个方向:

一是对显存所在区域配置 MPU,设置为 Non-cacheable 属性。这种方式最简单,但 SDRAM 读写性能会下降。

二是手动维护 Cache 一致性。在 DMA 传输前调用SCB_CleanDCache()清除数据缓存,在 DMA 读回数据后调用SCB_InvalidateDCache()使缓存失效。

对于性能测试来说,建议先不开 D-Cache,或者关闭 SDRAM 区域的 Cache,等整体跑通后再根据性能瓶颈决定是否启用。

7. 性能优化建议与工程实践

7.1 合理设置缓冲区大小与渲染模式

缓冲区大小对性能影响很明显。缓冲区太小,LVGL 需要把一帧画面拆成很多小块分别渲染,每块都要经历一次 Flush,渲染次数增多。缓冲区太大,又会导致内存占用过高,甚至无法分配。

实际项目中可以从“屏幕总像素数”的角度估算:480x272 的 RGB565 全屏缓冲区需要480 * 272 * 2 = 261120字节,约 255KB。如果全部放在内部 RAM,已经接近芯片极限,所以一般放在外部 SDRAM,或者在内部 RAM 中只放少量行缓冲。

一个值得尝试的配置是 1/4 屏缓冲区,也就是 480x68 的 RGB565 缓冲,约 65KB,放在外部 SDRAM 中。这个容量在流畅度和内存占用之间比较均衡。

7.2 颜色格式选择

RGB565 是嵌入式 GUI 中性价比最高的颜色格式,占内存小,渲染时颜色处理简单,性能通常比 ARGB8888 高出不少。

如果界面必须使用半透明效果和抗锯齿,可以考虑 ARGB8888,但要注意开启半透明后,LVGL 的混合计算量会显著增加。对于 STM32F746G-DISCO 这种 Cortex-M7 平台,复杂界面上 ARGB8888 的帧率明显低于 RGB565。

建议:默认使用 RGB565,只在特定页面需要丰富色彩时再局部使用 ARGB8888 或者图片资源,而不是全工程统一切换。

7.3 使用 DMA2D 加速拷贝和填充

DMA2D 是 STM32 系列中专门用来做 2D 图形数据传输的外设,支持内存到内存拷贝、内存填充、像素格式转换等功能。在 LVGL 的 Flush 回调中,可以把memcpy替换为 DMA2D 操作。

一个最简单的 DMA2D 拷贝示例:

void lcd_copy_area_dma2d(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h, uint16_t *pixels) { uint32_t dst = LCD_FB_ADDR + (y1 * LCD_PIXEL_WIDTH + x1) * 2; uint32_t src = (uint32_t)pixels; DMA2D->CR = DMA2D_CR_MODE_MEM2MEM; DMA2D->FGMAR = src; DMA2D->BGMAR = dst; DMA2D->FGOR = 0; DMA2D->BGOR = LCD_PIXEL_WIDTH - w; DMA2D->FGPFCC = DMA2D_INPUT_RGB565; DMA2D->BGPFCC = DMA2D_INPUT_RGB565; DMA2D->NLR = (h << 16) | w; DMA2D->CR |= DMA2D_CR_START; while (DMA2D->CR & DMA2D_CR_START); }

这种方式把“内存拷贝”从 CPU 搬运到 DMA2D。不过这里还有一个细节:在 Flush 回调中使用阻塞等待while会卡住 CPU,虽然拷贝本身没有占用 CPU 计算,但等待期间 CPU 也不能做其他事。更好的做法是使用 DMA2D 中断,在传输完成中断里调用lv_display_flush_ready()

7.4 结合 FreeRTOS 提升响应性

如果系统里还有其他业务逻辑,比如网络协议栈、传感器读取、按键扫描,建议把 LVGL 放到 FreeRTOS 任务中。LVGL 9.0 支持配置操作系统接口层:

#define LV_USE_OS LV_OS_FREERTOS

开启后,LVGL 可以使用 FreeRTOS 的互斥锁和信号量来保护内部状态,也便于后续多核或并行渲染扩展。

在实际项目中,可以单独创建一个lvgl_task,优先级适中,循环调用lv_timer_handler()并设置合适的延时。业务逻辑放在其他任务中,通过 LVGL 的消息队列与界面任务通信,避免直接在中断或非 LVGL 上下文中操作控件。

要注意的是,LVGL 并不是线程安全的。如果多个任务同时调用 LVGL API,可能会出现不可预料的崩溃。即使用了 FreeRTOS 适配层,也建议所有界面操作统一放到 LVGL 任务中执行。

7.5 工程化建议

在正式做性能测试和移植时,有几个工程实践可以坚持一下。

第一,组件裁剪。LVGL 9.0 默认开启的功能很多,如果只是做简单仪表盘,完全用不到图表、日历、键盘等组件。在lv_conf.h中关闭不需要的组件,可以减少内存占用和编译时间。

比如:

#define LV_USE_CHART 0 #define LV_USE_CALENDAR 0 #define LV_USE_KEYBOARD 0

第二,编译器优化等级。开发调试阶段建议使用-O0,性能测试阶段建议切换到-O2-Os。优化等级对 LVGL 渲染性能影响非常明显,有时候帧率能提升 20% 以上。

第三,日志分级。调试时打开 LVGL 日志:

#define LV_USE_LOG 1 #define LV_LOG_LEVEL LV_LOG_LEVEL_WARN

发布时关闭日志,避免日志打印占用串口和 CPU 时间。

第四,备份测试记录。每次优化后及时保存 FPS、CPU、内存使用数据,配合章节 5.5 的表格记录,避免优化方向反复横跳。

8. 总结与下一步

这篇文章从 STM32F746G-DISCO 的硬件特点说起,梳理了 LVGL 9.0 的新接口变化,完整演示了从 CubeMX 创建工程、LCD 点亮、LVGL 初始化、心跳和 Flush 回调接入的全过程。然后围绕性能基准测试,介绍了 FPS、CPU 占用、单帧耗时三个核心指标,以及如何使用 perf monitor、官方 Benchmark demo 和自定义计时来做测量。

在移植过程中最值得留意的几个坑,一个是 LVGL 9.0 的 API 与 8.x 差异大,不要直接复用旧代码;另一个是 SDRAM 显存区域的 Cache 一致性,Cortex-M7 的 D-Cache 使用不当会带来画面错乱;还有 Flush 回调应尽量缩短执行时间,有条件时用 DMA2D 或双缓冲来改善显示体验。

性能优化是一条需要反复测试和验证的路。建议先把官方 Benchmark 跑通,记录基准数据,再依次调整颜色格式、缓冲区大小、渲染模式、DMA2D 加速和编译器优化等级,每一步都用同一测试场景做对比。不要盲目追求高分,先搞清楚当前界面是 CPU 渲染瓶颈还是数据拷贝瓶颈,再针对性地优化。

如果你也正在做 STM32 + LVGL 9.0 的移植,建议先从一个最简 Demo 跑通,再做性能优化。这样后续遇到问题,至少能缩小排查范围。觉得这篇文章对你有帮助的话,可以先收藏备用,等实际测试中遇到问题再回来对照排查思路。

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

YOLO室内生物特征采集左手掌右手掌目标检测数据集-3739张

YOLO室内生物特征采集左手掌右手掌目标检测数据集 大规模左手掌检测&#xff1a;YOLO26 训练与评估 &#x1f4ca; 数据集基本信息 目标类别&#xff1a; [‘palmarleft’, ‘palmarright’]中文类别&#xff1a;[‘左手掌’, ‘右手掌’]训练集&#xff1a;2618 张验证集&…

作者头像 李华
网站建设 2026/8/31 20:51:27

彻底搞懂checkout:Git命令与GitHub Actions的区别与实战

在日常开发中&#xff0c; checkout 这个词几乎每天都会出现&#xff0c;但它经常让新手甚至有一定经验的开发者感到困惑&#xff1a;在本地终端里敲 git checkout 是在切换分支、恢复文件&#xff1b;在 GitHub Actions 工作流里写 actions/checkoutv4 又是在拉取仓库代…

作者头像 李华
网站建设 2026/8/31 20:51:26

Spring Boot服装生产管理系统:从设计到部署完整解析

简介&#xff1a;本资源是一套面向本科或高职院校计算机相关专业毕业生的Spring Boot实战项目——服装生产管理系统&#xff0c;聚焦制造业数字化管理场景&#xff0c;解决服装厂在样板、质检、仓储、人事及订单五大核心环节中的信息化协同问题。压缩包为标准ZIP格式&#xff0…

作者头像 李华
网站建设 2026/8/31 20:47:22

PANDA原型锚定对齐:解决医学多模态部分未配对难题的方法解析

先聊一个很多人做医学多模态时都会遇到的痛点&#xff1a;你手里有 MRI 影像&#xff0c;也有病理切片&#xff0c;甚至还有基因表达数据&#xff0c;理论上多模态能提供比单模态更丰富的疾病信息。可真到了训练模型那天&#xff0c;你才发现这批数据“凑不齐”——有的患者只有…

作者头像 李华