尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

LVGL 9.0移植到STM32F746G-DISCO并跑性能基准测试实战 这次把 LVGL 9.0 移植到 STM32F746G-DISCO 上重点是跑一次性能基准测试。很多从 V8 迁移过来的同学会踩 API 变化的坑而这块板子最合适用来验证 LVGL 9 在 Cortex-M7 加 LTDC 环境下的实际帧率。下面按我从 CubeMX 建工程到 benchmark 出数的完整过程拆一遍。先说结论LVGL 9.0 和 V8 在移植接口上差别不小不能直接把旧代码拖过来改个版本号就完事。但性能基准测试反而比 V8 更好做因为官方 demo 里已经带了 benchmark打开后会自动跑一堆典型绘制场景最后输出帧率和耗时。你真正要做的是先把显示、触摸、心跳这三条链路都打通再让 benchmark 自己跑。1. STM32F746G-DISCO 跑 LVGL 9.0先别急着改 lv_conf.h1.1 LVGL 9.0 的关键变化显示驱动和输入设备接口LVGL 9.0 在底层把显示驱动和输入设备驱动都重做了。V8 里常见的lv_disp_drv_t、lv_disp_draw_buf_t在 V9 里已经被新的lv_display_t对象替代。你不再需要定义一个lv_disp_drv_t结构体再调用lv_disp_drv_register而是直接通过lv_display_create创建显示对象。同样的触摸输入设备的注册方式也从lv_indev_drv_t结构体注册改成了先创建lv_indev_t再设置类型和 read 回调。这个改动直接影响所有移植代码所以如果你的工程里已经有 V8 的驱动文件请对照 V9 的示例重写不要复制粘贴。另外LVGL 9.0 的 flush 回调签名也变了。V8 里第三个参数一般是lv_color_t *color_pV9 里更常见的是uint8_t *px_map。你在写lv_display_set_flush_cb的时候要按 V9 的声明来处理像素数据。很多人第一次编译报错就是卡在这里。1.2 为什么这块板子适合做 LVGL 9.0 基准测试STM32F746G-DISCO 最大的特点是板载了 4.3 英寸 480x272 的 RGB 接口 LCD同时 MCU 自带的 LTDC 可以直接驱动屏幕不用外接转接板。这个分辨率做 GUI 非常合适不大不小跑性能测试时能看到明显的帧率差异。更重要的是这块开发板带外部 SDRAM也有 DMA2D 硬件加速。STM32F746 的主频是 216MHz比常见的 F407 高处理软件渲染时会有一定优势。虽然 LVGL 9 的主体渲染还是软件绘制但在显示缓冲区、帧缓冲、DMA2D 这几样东西配合到位之后性能会上一个台阶。还有一点很多人在搜“freertos 移植”“stm32 移植 lvgl”的时候容易把 FreeRTOS 和 LVGL 混在一起处理。我的建议是第一次移植先不要加 RTOSLTDC、触摸、LVGL 这三条链路全部裸机跑通再考虑要不要引入 FreeRTOS。不然性能测试时一旦卡住你很难判断是 LVGL 问题、驱动问题还是任务调度问题。2. 移植前的板级准备LTDC、SDRAM、触摸和 CubeMX 工程2.1 外设到底要怎么配用 STM32CubeMX 新建工程时如果找不到开发板模板可以先选 STM32F746NGH6 这颗芯片。随后要配置的外设包括以下几个LTDC负责 RGB LCD 显示输出需要设置屏幕的宽高、像素时钟、同步时序和像素格式。GPIOLTDC 的引脚、背光控制引脚、触摸复位引脚都要按开发板原理图配置。I2C板载触摸屏大多走 I2C 接口F746G-DISCO 的触摸芯片也是通过 I2C 读取坐标。FMC如果你打算把外部 SDRAM 用起来需要配置 FMC 的 SDRAM 控制器。DMA2DLVGL 9 在部分平台可以用 DMA2D 做加速但第一次移植可以先不开等裸机能显示之后再加。LTDC 时序参数不建议我这边给一套通用值就复制过去。每块屏幕的 HBP、HFP、VBP、VFP 都可能不一样最稳妥的做法是看开发板原理图对应屏幕型号从数据手册里查时序或者参考 ST 官方示例工程。CubeMX 生成的 LTDC 初始化代码只是给了一个基础模板你要做的是确认像素格式和分辨率一致。2.2 帧缓冲放在内部 RAM 还是外部 SDRAM这是第一次移植时容易纠结的问题。LVGL 绘制需要一块显示缓冲区LTDC 做屏幕刷新也需要一块帧缓冲。480x272 的屏幕如果用 RGB565一帧数据大概是 480 * 272 * 2 261120 字节。STM32F746 的内部 RAM 虽然比 F1、F4 大但如果你既要放程序运行数据又要放两个全屏缓冲还是会紧张。比较常见的做法是显示缓冲区放内部 RAM可以是单缓冲或双缓冲先跑通再说。LTDC 的帧缓冲放外部 SDRAM让 LTDC 直接从 SDRAM 读取像素输出到屏幕。LVGL 把界面内容写进显示缓冲区后通过 flush 回调把数据更新到 LTDC 帧缓冲。如果你的外部 SDRAM 配置不稳定屏幕会出现花屏、闪烁或者数据错乱。所以第一次跑的时候我建议先尽量用内部 RAM 把显示跑起来再把视频内存切到 SDRAM。不要一上来就把所有缓冲都塞进 SDRAM那样报错了不好定位。3. 添加 LVGL 9.0 源码并打通显示链路3.1 lv_conf.h 和 demo 源文件LVGL 9.0 的源码建议直接从官方仓库拉取版本号选择 release 分支避免用 main 分支里还没稳定发布的代码。工程中需要把lvgl/src目录全部加进来同时在工程根目录准备一个lv_conf.h。lv_conf.h一般从lvgl/lv_conf_template.h复制而来。这个文件是整个移植的配置中心颜色深度、字体、控件开关、内置 demo 开关都在这里控制。第一次移植至少要确认这几个宏#define LV_COLOR_DEPTH 16 #define LV_USE_LOG 1 #define LV_USE_PERF_MONITOR 1 #define LV_USE_SYSMON 1 #define LV_USE_DEMO_BENCHMARK 1LV_COLOR_DEPTH要和 LTDC 的像素格式匹配。如果你用 RGB565那就是 16如果你用 RGB888一般可以配置成 32但要注意内存占用会翻倍。LV_USE_DEMO_BENCHMARK是跑基准测试的关键忘了开的话lv_demo_benchmark这个函数会找不到或者直接不生效。注意LVGL 9 对 demo 源文件的编译条件是独立控制的。即使你开了LV_USE_DEMO_BENCHMARK也要确认lvgl/demos/benchmark目录下的源文件被加入工程。如果编译器提示找不到lv_demo_benchmark函数先检查是不是漏加了源文件。3.2 显示初始化代码LVGL 9 的显示初始化可以按下面这个思路写不过实际操作中要按你的工程文件来组织void lv_port_disp_init(void) { lv_display_t *disp lv_display_create(480, 272); lv_display_set_flush_cb(disp, disp_flush_cb); static lv_color_t buf1[480 * 60]; static lv_color_t buf2[480 * 60]; lv_display_set_buffers(disp, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL); }这里我用了两个 480x60 的缓冲区属于比较保守的配置。lv_display_set_buffers中的缓冲区大小参数是按字节传的而不是像 V8 某些写法直接传像素数量。这一点要特别注意传错之后屏幕上会缺行或者直接黑屏。flush 回调的代码重点是调用lv_display_flush_ready告诉 LVGL 这一块数据已经处理完static void disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { lcd_fill_area(area-x1, area-y1, area-x2, area-y2, px_map); lv_display_flush_ready(disp); }如果你的 LTDC 帧缓冲就是一块连续内存而 LVGL 的显示缓冲区也直接指向这块内存那么在 flush 回调里只需要调用lv_display_flush_ready不需要再手工拷贝。这种写法适合全屏单缓冲或双缓冲但也容易遇到撕裂因为 LTDC 扫描屏幕的同时LVGL 可能在改写同一块内存。3.3 心跳和主循环LVGL 需要一个毫秒级心跳用于内部定时器、动画和输入长按判断。最简单的方式是在 SysTick 中断或某个 Periodic Timer 中断里调用lv_tick_inc(1);主循环里则要周期调用lv_timer_handler让 LVGL 处理界面刷新和事件while (1) { lv_timer_handler(); HAL_Delay(5); }不要在主循环里加上大段阻塞延时或者长时间运行的业务逻辑否则界面响应会明显变慢。性能测试时尤其要保持主循环稳定这样 benchmark 的数据才有参考价值。4. 触摸屏接入FT5336 到 LVGL indev4.1 读取触摸坐标STM32F746G-DISCO 板载的触摸屏一般通过 FT5336 这类电容触摸芯片读取数据。FT5336 的输出本质上是通过 I2C 读取一组寄存器里面包含触点个数和触点坐标。你要做的就是把 I2C 的原始寄存器值解析成坐标。初始化时一般需要给触摸芯片复位引脚拉一下电平再确认 I2C 通信正常。很多人在这一步卡住不是因为 I2C 配置不对而是触摸芯片的复位时序或者中断引脚方向没处理好。如果 I2C 读出来的数据全是 0xFF 或者 0x00先查复位引脚和 I2C 地址再查读到的寄存器序号。4.2 坐标方向和点击状态在 LVGL 9 中注册触摸设备不再使用 V8 的lv_indev_drv_register方式而是先创建 indev 对象再设置类型和回调lv_indev_t *indev lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, touchpad_read_cb);read 回调里需要填两个关键信息坐标和按下状态。static void touchpad_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { uint16_t x, y; uint8_t pressed; ft5336_read(x, y, pressed); if (pressed) { >#define LV_USE_DEMO_BENCHMARK 1 #define LV_USE_PERF_MONITOR 1 #define LV_USE_LOG 1然后在main函数初始化 LVGL 和显示驱动后直接调用lv_demo_benchmark();lv_demo_benchmark会启动一组预置的绘制场景依次测试色块填充、矩形、边框、线条、圆弧、文字、图片、混合等典型操作。每个场景都会在屏幕上显示出来整个测试会自动切换不需要人工干预。如果你希望把性能日志打印到串口需要注册日志打印回调。LVGL 的日志输出默认可能是空实现你可以自己接一个串口输出函数这样电脑上能看到每轮耗时和帧率数据。没有串口也没关系屏幕上会显示最终结果。5.2 怎么读性能结果benchmark 跑完后屏幕上通常会出现一组统计数据包括平均 FPS、每帧耗时、CPU 占用等。不要只盯着最后那个平均帧率看因为不同绘制场景的耗时差异很大。比如纯色填充场景在 480x272 下很容易跑出高帧率而大量圆角矩形、文字抗锯齿、图片混合场景会把帧率拉低很多。我一般会把数据分成三档来看简单填充类场景如果帧率没有明显优势说明显示链路可能还有瓶颈。中等复杂场景比如大量矩形、文字最能反映日常 GUI 的真实流畅度。复杂混合场景比如半透明图片叠加主要看内存带宽和软件渲染能力。如果你发现某个场景掉帧严重不要急着说 LVGL 9 性能差。先确认该场景是否开了阴影、模糊或者大量抗锯齿这些效果在低端 MCU 上确实开销很大。5.3 实测中常见的性能假象有几种情况会让 benchmark 数据看起来很漂亮但实际使用却很卡缓冲区比较大简单场景跑得飞快但复杂场景频繁切换缓冲时会有撕裂或者等待。使用了外部 SDRAM 做帧缓冲连续大块绘制时 SDRAM 带宽被占用帧率会突然下降。开了LV_USE_PERF_MONITOR后性能监视器本身也会占用一点资源但影响通常不大。主循环里lv_timer_handler调用频率不稳定会导致最大帧率被拉低。所以性能测试时要保持同一个工程配置只改一个变量来对比。最忌讳的是同时加大 buffer、切换 SDRAM、打开 DMA2D、又改刷新周期最后数据变化了但根本不知道谁起的作用。6. 性能优化和故障排查6.1 先看瓶颈再调参数如果你的 benchmark 结果不理想不要立刻把缓冲区调大。先判断瓶颈是 CPU 渲染、内存带宽还是显示链路阻塞。CPU 渲染瓶颈的表现是复杂场景掉帧特别明显简单场景帧率很高CPU 占用接近满。这时候优先优化绘制算法、减少特效、关闭不需要的字体。内存带宽瓶颈的表现是大图片、大区域填充时掉帧明显数据越界或者撕裂出现。这时候可以看 SDRAM 和内部 RAM 的使用情况考虑把显示缓冲区放到内部 RAM或者降低颜色深度。显示链路阻塞的表现是帧率一直稳定在一个低值并且lv_display_flush_ready之后到下一次 flush 之间有明显空闲。这时候要检查 LTDC 时序是否太慢刷新周期是否过长或者 buffer 太小导致 LVGL 不断等待 flush 完成。6.2 常见问题和排查顺序移植过程中遇到问题先按顺序检查不要直接怀疑 LVGL 9 有 bug。黑屏先确认背光引脚和背光 PWM 输出。再确认 LTDC 初始化是否成功帧缓冲地址是否正确。最后确认lv_display_create和lv_display_set_buffers有没有被调用。花屏优先检查颜色深度LV_COLOR_DEPTH和 LTDC 像素格式是否一致。再检查显示缓冲区大小是否足够LVGL 9 按字节传大小容易在这里出错。如果用了 SDRAM还要检查 FMC 初始化顺序和 SDRAM 时序。触摸没反应先确认 I2C 通信读取触摸芯片 ID 是否正确。再确认触摸复位引脚有没有正确拉高。最后确认 read 回调是否被 LVGL 定期调用以及坐标是否映射到屏幕范围。跑 benchmark 时卡死或 HardFault先检查内存占用特别是内部 RAM 是否被局部大数组耗尽。再检查lv_conf.h是否同时打开了太多该板子资源撑不住的控件和字体。最后确认 LVGL 版本和移植代码是否匹配尤其是lv_display_set_buffers和lv_indev_set_read_cb的写法。6.3 后续扩展建议裸机跑通 benchmark 之后如果要把这个方案继续往实际项目推进有几个方向值得做。第一个方向是接 FreeRTOS。LVGL 的lv_timer_handler可以放到一个独立任务里心跳可以放到定时器中断触摸读取可以放到另一个任务。但先不要把系统调度和 LVGL 渲染混在一起保持任务边界清晰。第二个方向是优化 DMA2D 和显示缓冲。LVGL 9 在部分配置下可以启用硬件加速具体选项需要打开 lv_conf.h 里和 GPU、DMA2D 相关的功能并根据你下载的源码确认宏名称。启用后要验证 flush 回调的数据一致性因为 DMA2D 在快速搬运数据时如果和 LTDC 扫描冲突容易出现撕裂。第三个方向是整理串口日志。把 benchmark 的耗时数据通过串口输出后可以做一个简单的性能基线记录。以后每次修改代码都对比这组基线才知道优化到底有没有效果。最后留一个建议性能测试不是跑完一个 demo 就结束而是要控制变量。先跑默认配置记下一组 FPS再依次打开 DMA2D、加大 buffer、切换 SDRAM每次只改一个变量这样才知道性能到底提升在哪里。很多人卡在帧率上并不是 LVGL 9 不行而是把多个优化点一次性全开了出问题后根本定位不到原因。
返回列表