
这次我们来看一块具体的开发板STM32F746G-DISCO跑最新版本的 LVGL 9.0并且把“能显示”这件事推进到“可量化的性能基准测试”。做嵌入式 GUI 的朋友都清楚界面能点亮只是第一步真正要关心的是移植代码怎么写、刷新要占多少 CPU、FPS 能到多少、哪些配置能明显改变流畅度。这篇文章就是用 F746G-DISCO 这套硬件把整个流程从头到尾过一遍。先说结论STM32F746G-DISCO 是一块非常适合 LVGL 9.0 移植和性能测试的板卡。它搭载的是 Cortex-M7 内核的 STM32F746NGH6主频最高 216MHz板载 4.3 英寸 480x272 RGB LCD由 STM32 内置 LTDC 控制器直接驱动同时板上有 SDRAM 可以放帧缓冲还带了电容触摸屏。最关键的是LVGL 9.0 这次是大版本升级显示驱动和输入驱动接口全部改成了对象化方式如果直接把 8.x 的老代码复制过来编译会报一堆错误。所以这篇文章会重点讲清楚9.0 和 8.x 的驱动代码差别在哪里如何把 LVGL 9.0 移植到 F746G-DISCO 上以及怎么用 LVGL 自带的 benchmark demo 做性能基线测试。无论你是准备把老工程从 LVGL 8.x 升级到 9.0还是第一次在 STM32F7/GD32F7 这类 M7 平台上搭建 GUI这篇都建议收藏后慢慢看。1. 核心能力速览先整理一份速查表方便快速判断这套方案适不适合你的项目。能力项说明开发板型号STM32F746G-DISCODiscovery 系列主控芯片STM32F746NGH6Cortex-M7最高 216MHz板载屏幕4.3 英寸 RGB LCD分辨率 480x272LTDC 驱动板载存储有 SDRAM可做 LVGL 帧缓冲和渲染缓冲具体容量以板卡手册为准触摸输入板载电容触摸屏通过 I2C 接入LVGL 侧按指针型输入设备处理LVGL 版本9.0.x以你实际拉到 tag 为准渲染方式LVGL 软件渲染 STM32F746 内置 DMA2D 加速可选帧缓冲模式单缓冲 / 双缓冲 / 局部渲染均可配置基准测试LVGL 自带 benchmark demo自动跑场景并统计 FPS调试手段板载 ST-Link串口输出 LVGL LOG 和 benchmark 结果移植难度中等。难点不在 LVGL 本身而在 LTDC、SDRAM、触摸驱动与 9.0 新 API 的衔接表里的“渲染方式”和“帧缓冲模式”需要后续在代码里自行配置能力都在但默认不会自动跑到最优效果。2. 为什么用 F746G-DISCO 跑 LVGL9.02.1 硬件资源匹配F746G-DISCO 这块板子做 LVGL 测试有个天然优势屏幕、SDRAM、触摸、调试器全都在板子上不需要自己画转接板。LTDC 是 STM32F746 内置的显示控制器能直接驱动 RGB 接口的 LCD不占用 CPU 去做行场扫描。SDRAM 则解决了一个很重要的问题LVGL 在刷新界面时需要同时存在帧缓冲和渲染缓冲如果只靠 F746 内部的 320KB SRAM480x272x2 字节的 RGB565 帧缓冲就要占掉 261KB留给 LVGL 对象堆的空间会很紧张。有了 SDRAM就可以把帧缓冲、渲染缓冲全部挪到外部内存内部 SRAM 留给 LVGL 对象和任务栈。从 Cortex-M7 的性能来看216MHz 主频配合 TCM 和 Cache跑 LVGL 9.0 的软件渲染是够用的。当然具体 FPS 取决于你画了多少复杂对象、开了哪些特效、颜色深度是多少。这也是这篇文章要带你用 benchmark 去实测的原因。2.2 LVGL 9.0 的关键变化LVGL 9.0 相比 8.x最大的变化是显示驱动和输入驱动改为对象化 API。在 8.x 时代我们习惯用lv_disp_drv_t、lv_disp_drv_register、lv_indev_drv_t这套结构体加注册函数到了 9.0直接变成lv_display_t和lv_indev_t对象通过lv_display_create()、lv_indev_create()创建。此外9.0 把图像解码器也整理了一轮PNG、JPG、BMP 等解码能力被集成到基础库中配置宏和依赖方式与 8.x 不同。对于只跑 GUI 的嵌入式开发来说这两点是最直接的感知。如果项目代码还是按 8.x 写的升级到 9.0 时需要重点检查显示驱动、输入设备驱动和颜色格式相关代码。3. 环境准备与工程初始化3.1 工具链选型建议使用 STM32CubeMX 生成基础工程然后导入 Keil MDK 或 STM32CubeIDE 编译。F746G-DISCO 是老牌 Discovery 板卡ST 官方有对应例程CubeMX 里也可以直接选板卡模板。具体工具版本不强制但考虑到 STM32F746 的固件包和 LVGL 9.0 源码建议使用较新的 CubeMX 版本和编译器避免出现编译环境过老导致 C99 语法支持不全的问题。3.2 CubeMX 外设配置要点在 CubeMX 里需要初始化以下几类外设时钟树配置 PLL让 CPU 跑到 216MHz同时保证 LTDC 像素时钟和 SDRAM 时钟满足要求。LTDC配置 480x272 的 RGB LCD 时序参数具体的前肩、后肩、同步脉冲需要看板载屏的数据手册。CubeMX 中选好 F746G-DISCO 板卡模板后通常会有默认参数但仍需核对。FMC-SDRAM初始化 SDRAM 控制器设置时序和 Bank 地址。帧缓冲会放在这块区域时序不对会出现花屏或数据偶发错误。DMA2D图形加速 DMA如果要在 flush 回调里做快速像素拷贝或颜色格式转换需要使能 DMA2D 时钟。I2C用于触摸芯片数据读取。USART接到板载 ST-Link VCP输出 LVGL LOG 和 benchmark 结果。GPIO触摸复位、LCD 背光控制等具体引脚按板卡实际设计定义。3.3 下载 LVGL 9.0 源码访问 LVGL 官方 GitHub 仓库选择 9.0.x 的 release tag 下载源码不建议直接在 main 分支上开发因为 main 分支可能夹带新功能和破坏性修改。下载后解压源码目录结构大致如下。lvgl/ lvgl.h lv_conf.h // 用户配置头文件需手动生成 lv_conf_template.h src/ core/ draw/ display/ indev/ fonts/ ... demos/ benchmark/ music/ widget/ ...3.4 添加源码到工程推荐把lvgl文件夹放到工程的Middlewares目录下然后在编译器中添加lvgl/src的 include path。如果后续要跑 benchmark还需要添加lvgl/demos目录下对应模块的 include path并把lvgl/demos/benchmark里的.c文件加入编译。同时把lvgl/src下所有.c文件加入工程编译量会比较大但这也是 LVGL 的常规用法。不建议手动精简源码因为 LVGL 9.0 内部模块之间依赖复杂少加一个文件就会出现难以排查的链接错误。4. LVGL 9.0 移植代码改动4.1 配置头文件 lv_conf.h在 LVGL 9.0 中如果没有定义LV_CONF_SKIP编译器会优先使用 include path 下的lv_conf.h。将lv_conf_template.h复制一份重命名为lv_conf.h放到工程根目录并确保该目录被加入 include path。以下是一份适合 F746G-DISCO 起步的配置片段#define LV_COLOR_DEPTH 16 #define LV_MEM_SIZE (64U * 1024U) #define LV_DEF_REFR_PERIOD 30 #define LV_USE_PERF_MONITOR 1 #define LV_USE_MEM_MONITOR 1 #define LV_USE_LOG 1 #define LV_USE_DEMO_BENCHMARK 1说明LV_COLOR_DEPTH 16使用 RGB565帧缓冲占用减半是嵌入式 GUI 的常见选择。LV_MEM_SIZELVGL 对象堆大小根据实际应用调整。F746 内部 SRAM 有限如果只跑 demo64KB 起步。LV_USE_PERF_MONITOR开启后左上角会显示实时 FPS 和 CPU 占用性能调试很有用。LV_USE_DEMO_BENCHMARK启用官方 benchmark demo。4.2 显示驱动对接LVGL 9.0 的显示驱动是用lv_display_create创建的。下面给出一段通用模板注意flush_cb中的像素拷贝需要根据你实际的 LTDC 帧缓冲地址和缓冲模式实现。#include lvgl.h #include main.h static lv_display_t *disp; static uint8_t buf1[480 * 100 * 2]; // RGB565每行 480 点100 行 static uint8_t buf2[480 * 100 * 2]; // 双缓冲可提高效率 static void my_flush_cb(lv_display_t *display, 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); uint32_t offset area-x1 area-y1 * 480; /* 把 px_map 渲染结果拷贝到 LTDC 正在扫描的帧缓冲地址 */ ltdc_fb_copy(offset, px_map, w, h); /* 必须调用通知 LVGL 该区域刷新完成 */ lv_display_flush_ready(display); } void lvgl_display_init(void) { disp lv_display_create(480, 272); lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL); lv_display_set_flush_cb(disp, my_flush_cb); }这段代码里ltdc_fb_copy是自定义函数需要根据你的 SDRAM 地址和 DMA2D 配置实现。如果 LTDC 直接持续扫描一块 SDRAM 地址作为帧缓冲那么 flush 回调里真正要做的事情就是把 LVGL 渲染完的这个小区域贴到 LTDC 帧缓冲的对应位置。4.3 触摸驱动对接触摸在 LVGL 9.0 中属于LV_INDEV_TYPE_POINTER类型。以下是一个标准对接流程。static void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) { int16_t x 0; int16_t y 0; bool pressed false; touch_get_point(x, y, pressed); if (pressed) { >void SysTick_Handler(void) { lv_tick_inc(1); }主循环中反复调用lv_timer_handler()LVGL 内部会处理刷新、动画、输入设备等任务。while (1) { lv_timer_handler(); }在裸机工程中这个循环看起来有点“空转”但实际上lv_timer_handler内部会做大量工作。如果后续接入 FreeRTOS可以单独建一个lvgl_task用信号量或消息队列唤醒。5. 性能基准测试与效果验证5.1 开启官方 benchmark在lv_conf.h中确认已经打开了LV_USE_DEMO_BENCHMARK然后把lvgl/demos/benchmark目录下的源文件加入工程调用一次lv_demo_benchmark();这个函数会自动创建一组测试场景包括随机矩形、圆形、圆弧、文本、图像、带透明度混合等常见绘制负载每跑完一个场景会在左上角显示 FPS同时通过 LVGL LOG 输出耗时数据。这样做的好处是所有场景都是 LVGL 官方定义的换一块屏幕或者换一个主控之后跑出的结果有横向对比价值。5.2 观察 FPS 和 CPU 占用启动后打开LV_USE_PERF_MONITOR屏幕左上角会显示类似FPS: 30、CPU: 45%的信息。这里的 CPU 占用率并不是 CPU 总使用率而是 LVGL 刷新任务占用定时器周期内的时间比例但在不用 RTOS 的裸机工程里已经能反映刷新压力大小。观察 benchmark 场景切换时要注意是否有明显掉帧、闪烁或撕裂。如果出现闪烁优先检查缓冲模式如果出现撕裂优先检查 LTDC 扫描和 LVGL flush 是否在同一个帧周期内打架。5.3 手工量化DWT 计时如果你不想依赖 LVGL 的 perf monitor也可以用 Cortex-M7 的 DWT 时钟计数器做精确耗时统计。用下面的代码包住待测代码段可以在串口打印出耗时微秒数。#include core_cm7.h static inline void dwt_start(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static inline uint32_t dwt_stop_us(void) { uint32_t cycles DWT-CYCCNT; return cycles / (SystemCoreClock / 1000000U); } void measure_demo(void) { dwt_start(); lv_demo_benchmark(); uint32_t us dwt_stop_us(); printf(benchmark start cost: %lu us\r\n, (unsigned long)us); }这个方法更适合测量某一段特定绘制代码而不是整个界面系统的平均 FPS。比如你想知道一个复杂仪表盘界面刷新一帧要多久可以在lv_timer_handler前后加计时把结果打印出来。5.4 判断移植是否成功的标准一次完整的移植验证至少要满足以下条件屏幕能够稳定显示 LVGL 桌面或 benchmark 测试界面没有花屏、黑屏、闪屏。触摸点击能够触发 LVGL 控件事件坐标方向正确。lv_conf.h中开启 perf monitor 后FPS 数值能稳定刷新。benchmark demo 可以完整跑完不出现 HardFault 或死机。串口能输出 LVGL LOG方便查看版本信息和错误提示。如果这五项都通过说明移植已经达到了可测试状态接下来才进入性能优化阶段。6. 性能分析与优化方向6.1 颜色深度同样的 480x272 分辨率RGB565 的帧缓冲只有 ARGB8888 的一半大小。F746G-DISCO 的 LTDC 支持 RGB565 输出LVGL 侧把LV_COLOR_DEPTH设为 16可以明显降低内存带宽压力。如果你的界面不需要透明通道优先选 RGB565。6.2 缓冲模式与大小LVGL 9.0 的lv_display_set_buffers支持单缓冲和双缓冲。单缓冲省内存但刷新时容易出现撕裂双缓冲能显著改善流畅度但内存占用翻倍。在 F746G-DISCO 上建议把缓冲放到 SDRAM大小可以根据屏幕高度按行数切分比如先放 100 行大小不够再加。注意缓冲区过小会导致 LVGL 需要多次刷屏FPS 反而下降。6.3 DMA2D 加速F746 内置 DMA2D可以在 flush 回调里做快速的位块拷贝和像素格式转换。如果 LTDC 帧缓冲和 LVGL 渲染缓冲都在 SDRAM直接使用 DMA2D 的 Memory-to-Memory 模式搬运即可。这样 CPU 不需要逐像素拷贝刷新耗时会有明显改善。需要留意 DMA2D 与 CPU 对 SDRAM 的访问带宽争用最好在刷新期间关闭中断或使用 DMA 中断完成信号再调用lv_display_flush_ready。6.4 渲染模式与刷新周期在 9.0 中lv_image_set_scale、lv_obj_set_style_transform这类变换操作会额外增加 CPU 计算量。性能敏感的场景里要尽量避免频繁缩放大尺寸图片。LV_DEF_REFR_PERIOD默认是 30ms如果你的界面需要对触摸快速响应可以适当调小但刷新频率越高 CPU 占用也越高。建议先保持默认跑完一轮 benchmark 拿到基线数据后再调。6.5 内存分配策略LVGL 内部对象、样式、动画都从LV_MEM_SIZE指定的内存池分配。如果LV_MEM_SIZE太小会出现对象创建失败、控件显示不完整的问题如果设得太大内部 SRAM 剩余空间不足又会影响任务栈和系统堆。实际项目可以保留一部分 LVGL 对象池在内部 SRAM把显示缓冲、帧缓冲、图像解码缓冲放到 SDRAM达到平衡。7. 常见问题与排查方法以下表格整理了 F746G-DISCO 移植 LVGL 9.0 过程中最容易遇到的问题。问题现象可能原因排查方式解决方案编译报错lv_disp_drv_t未定义代码还在用 8.x 显示驱动 API查看报错文件搜索lv_disp_改用 9.0 的lv_display_create/lv_display_set_buffers编译报错lv_indev_drv_t未定义输入设备驱动还是 8.x 写法检查 indev 相关代码改用lv_indev_create/lv_indev_set_read_cb屏幕花屏或条纹LTDC 时序不对或 SDRAM 读写时序不稳对照板卡手册检查 LTDC 参数缩小帧缓冲区域做测试调整 LTDC 像素时钟和同步参数检查 SDRAM 控制器时序启动后屏幕黑屏无输出帧缓冲地址未被 LTDC 正确读取或背光未打开检查 LTDC 层配置和背光 GPIO确认 LTDC Layer 的帧缓冲地址是 SDRAM 实际地址打开背光触摸无反应I2C 地址错误坐标范围不对或 indev 未注册打印触摸原始坐标确认触摸芯片地址和坐标最大值调整>