ZYNQ平台LVGL 9.5.0移植实战:显示驱动与VDMA详解

发布时间:2026/8/31 4:12:13
ZYNQ平台LVGL 9.5.0移植实战:显示驱动与VDMA详解 在 ZYNQ 平台上跑 LVGL 9.x最近工程量不小。网上很多现成案例还是 LVGL 8.x 的写法直接搬到 9.5.0 上往往会遇到 API 不兼容、VDMA 帧缓冲上下层颜色格式不统一、触摸坐标方向错乱等问题。本文基于 LVGL 9.5.0 1024×600 分辨率 RGB 屏这套组合梳理从 LVGL 源码集成、显示驱动编写、Vivado 显示链路搭建到 Vitis 裸机工程落地的完整流程重点讲清楚“为什么这样做”以及“踩坑后如何定位”。如果你正在做 ZYNQ HMI、工业显控面板、仪器仪表界面或者单纯想把 LVGL 8 的老项目升级到 9.x这篇文章会比较贴合你的场景。1. 项目背景与整体方案1.1 为什么在 ZYNQ 上跑 LVGL 9.5.0ZYNQ 是少见的 PSProcessing SystemARM 处理器 PLProgrammable LogicFPGA 逻辑一体芯片。PS 端适合跑界面逻辑、通信协议、业务控制PL 端适合做显示时序生成、数据搬运、外设扩展。用 ZYNQ 驱动 LCD 屏跑 LVGL既能保留高性能 CPU 的灵活性又能用 FPGA 解决 RGB 时序、DMA 搬运这类“最适合硬件做”的事情。LVGL 9.5.0 是 LVGL 9.x 系列中的一个较新版本。相比 8.x它在绘制架构、颜色格式处理、显示设备管理上做了不少调整整体设计更模块化但代价是 API 兼容性并不好。如果之前写过 LVGL 8 的代码升级到 9.x 时不能只替换源码必须同步修改显示驱动和输入设备注册逻辑。1.2 1024×600 屏的接口选择1024×600 是 7 寸屏常见的分辨率。这类屏幕大致分两类接口RGB 并口像素时钟约 40MHz 到 60MHz信号线包含 R、G、B、DCLK、DEN、HSYNC、VSYNC部分屏可能没有 DEN需要用 HSYNC/VSYNC 同步。LVDS 接口需要额外的 RGB 转 LVDS 芯片比如常见的 TC358774、SN65LVDS 系列。如果屏幕是 LVDS 接口而 ZYNQ PL 引脚只引出了 RGB 信号就需要在板上增加转换芯片或者在 FPGA 内部例化转换逻辑。这个必须在选型阶段确认不要等到 PCB 打样后才想起来接口不匹配。本文以最常见的 RGB 接口为例。1.3 总体链路与两条技术路线整体链路可以理解为PS 端 CPU 运行 LVGL ↓ 绘制结果写入 DDR 帧缓冲 PL 端 VDMA 从 DDR 读取像素数据 ↓ 输出 AXI4-Stream 视频流 VTC 生成行场时序AVideoOut IP 把数据打包成 RGB 信号 ↓ LCD 屏幕显示在这个链路上软件侧需要关注 LVGL 初始化、帧缓冲地址、触摸屏驱动硬件侧需要关注 VDMA、VTC、Video Out IP 配置以及时钟。ZYNQ 跑 LVGL 有两种常见路线裸机 Vitis所有界面逻辑跑在 ARM 上不依赖操作系统适合中小型项目调试直观。Linux Framebuffer/DRM界面跑在 Linux 用户空间显示和触摸由内核驱动接管适合复杂业务系统和多进程场景。本文以裸机 Vitis 为主线因为这种方式能更清楚地看到帧缓冲、VDMA、Cache 一致性的细节。Linux 的思路可以类比迁移。2. 环境准备与 LVGL 9.5.0 源码配置2.1 软硬件环境ZYNQ 开发环境现在分为旧 Vitis 和新 Vitis Unified菜单和工程结构略有差异但裸机工程的基础逻辑差不多。版本需要根据你的开发板实际调整这里只列参考Vivado / Vitis建议使用 2023.x 及以上版本老版本对 ZYNQ-7000 一样支持但工程文件格式有差异。ZYNQ 芯片本文思路适用于 ZYNQ-7000 系列比如 XC7Z010、XC7Z020、XC7Z035也适用于 ZYNQ UltraScale只是软核 IP 名称略有区别。屏幕1024×600RGB 接口带触摸如 FT5x06、GT911。LVGL 源码直接从官方 GitHub 仓库获取 9.5.0 Release或者使用最新 9.x 分支。如果你的开发板自带 Vitis 平台工程比如*.xsa文件已经生成可以直接跳过 Vivado 部分重点看第 5 章 LVGL 移植代码。如果没有现成硬件工程需要从第 4 章开始搭建。2.2 LVGL 9.5.0 源码目录与工程集成LVGL 9.x 源码结构跟 8.x 相似核心代码集中在src目录。集成到 ZYNQ 裸机工程时通常有两种做法把整个lvgl仓库作为子目录复制到 Vitis 工程然后将src目录加入编译路径。只复制需要的文件但新手不建议手动裁剪容易出现找不到头文件或未实现函数的问题。推荐先把整个src目录加入工程后续再按 flash 空间需求裁剪。LVGL 9 支持通过LV_CONF_PATH指定配置文件路径也可以直接把lv_conf.h放到工程 include 路径中让源码自动找到。2.3 lv_conf.h 关键配置LVGL 9.x 默认自带一份lv_conf_template.h使用前复制为lv_conf.h。下面是一个适合 ZYNQ 裸机的最小配置片段/* 文件路径你的工程 include 目录/lv_conf.h */ #define LV_COLOR_DEPTH 16 #define LV_USE_LOG 1 #define LV_DEF_REFR_PERIOD 33 #define LV_USE_STDLIB_MALLOC LV_STDLIB_CLIB #define LV_USE_STDLIB_STRING LV_STDLIB_CLIB #define LV_USE_STDLIB_SPRINTF LV_STDLIB_CLIB #define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_MONTSERRAT_24 1几个关键项说明LV_COLOR_DEPTH必须与硬件帧缓冲颜色格式一致。RGB565 屏选 16RGB888 屏选 32。LV_DEF_REFR_PERIOD是 LVGL 内部刷新周期单位毫秒33ms 大约对应 30FPS。LV_USE_LOG打开后报错时能直接在串口看到关键日志调试 LVGL 9 非常有用。LV_USE_STDLIB_*在裸机环境中一般选择 C 标准库这样内存分配和字符串操作不会依赖外部实现。配置完成后编译时如果提示lv_conf.h找不到优先检查 include 路径是否包含了配置文件所在目录。3. LVGL 9.x 移植必读API 变化与初始化流程3.1 从 lv_disp 到 lv_displayLVGL 8.x 中使用lv_disp_drv_t、lv_disp_draw_buf_t来注册显示驱动。LVGL 9.x 把这些对象统一改成了lv_display_t创建方式也更直观lv_display_t *disp lv_display_create(1024, 600);创建之后需要通过一系列lv_display_set_*函数设置缓冲、颜色格式、刷新回调。这种设计更接近面向对象思路也避免了老的 “driver buffer display” 三段式理解成本。3.2 颜色格式与绘制缓冲LVGL 9 中颜色格式不再只靠LV_COLOR_DEPTH决定显示设备自身还有独立的颜色格式配置lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);这里要和lv_conf.h中的LV_COLOR_DEPTH保持一致。如果 LVGL 用 RGB565 绘制但 VDMA 按 ARGB8888 解析屏幕数据显示出来就是花屏或颜色完全错乱。绘制缓冲区可以用全屏大小也可以只用部分区域。全屏缓冲内存占用大但刷新逻辑简单对 1024×600 来说单帧 RGB565 约 1.2MB双缓冲约 2.4MB对 ZYNQ DDR 毫无压力。3.3 输入设备注册方式LVGL 9 中触摸输入设备也改成对象创建方式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);LVGL 8 里常见的lv_indev_drv_t结构体在 9.x 中已经改变如果需要把老代码迁移过来这一步是必须修改的。3.4 显示初始化最小示例把以上几个点组合起来ZYNQ 裸机上的 LVGL 9.5.0 初始化逻辑大致如下void app_lvgl_init(void) { lv_init(); lv_display_t *disp lv_display_create(1024, 600); lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565); lv_display_set_buffers(disp, fb1, fb2, FB_SIZE, LV_DISPLAY_RENDER_MODE_DIRECT); lv_display_set_flush_cb(disp, lvgl_flush_cb); lv_indev_t *indev lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, lvgl_touch_read_cb); lv_indev_set_display(indev, disp); create_demo_ui(); }其中fb1、fb2是两个全屏缓冲区FB_SIZE是单帧字节数后续会专门说明。4. ZYNQ 显示链路硬件工程VDMA Video Out4.1 Block Design 需要哪些 IP要在 Vivado 中搭建显示链路主要使用以下 IPIP 名称作用ZYNQ7 Processing SystemPS 配置提供 DDR、时钟、中断AXI VDMA从 DDR 读取帧缓冲数据转成 AXI4-Stream 视频流AXI4-Stream to Video Out把视频流转换成 RGB 信号时序Video Timing Controller (VTC)生成行场同步、DE 等显示时序AXI SmartConnectAXI 总线互联Processor System Reset复位系统Clocking Wizard生成像素时钟可选AXI IIC / AXI GPIO触摸与背光控制按需添加最简显示链路不一定要 AXI GPIO背光可以直接在约束中固定拉高触摸也可以复用 PS 端 MIO 的 I2C。为了通用性建议保留 IIC 和 GPIO方便后续调触摸方向、背光 PWM。4.2 VDMA、总线和时钟连接要点VDMA 是整条链路的核心。在 ZYNQ 上VDMA 的读写数据接口建议接到 PS 端高性能端口 S_AXI_HP 上因为 HP 口直通 DDR带宽高、延迟低。寄存器配置接口 S_AXI_LITE 则接到 PS 端 M_AXI_GP 口由 CPU 来控制。典型连接如下PS M_AXI_GP0 → SmartConnect → VDMA S_AXI_LITEVDMA M_AXI_MM2S → SmartConnect → PS S_AXI_HP0VDMA M_AXIS_MM2S → AXI4-Stream to Video Out S_AXISVTC 输出到 Video Out IP 的时序控制端口Video Out IP 输出 video_out 引脚组RGB、DEN、HSYNC、VSYNC、DCLKVDMA 帧缓冲数量建议先设置 3 帧。三缓冲虽然多占一帧内存但能在界面刷新时明显降低撕裂感1024×600 RGB565 三帧约 3.6MBDDR 完全能接受。4.3 1024×600 RGB 时序配置VTC 的时序参数不能凭空写必须从屏幕规格书里读取。不同型号屏幕的 HFP、HBP、VFP、VBP 差异很大。下面以常见 RGB 屏时序参数表为例说明字段含义参数含义Active Width有效水平像素1024Active Height有效垂直像素600H Back Porch行同步后肩H Front Porch行同步前肩H Sync Width行同步脉冲宽度V Back Porch场同步后肩V Front Porch场同步前肩V Sync Width场同步脉冲宽度在 VTC IP 配置界面把 Active Width/Height 填成 1024 和 600再填入屏幕厂商给出的 Blanking 参数同时根据原理图确认 HSYNC/VSYNC 极性是正极性还是负极性。很多 RGB 屏在 DE 模式下并不严格要求 HSYNC/VSYNC 位置只要 DCLK 和 DE 正确画面就能出来但完整的时序参数仍然建议配置准确。像素时钟由屏幕分辨率和刷新率共同决定。1024×600 一般在 40MHz 到 60MHz 之间。像素时钟不能随意设置如果偏得太多屏幕可能无显示或显示异常。4.4 XDC 引脚约束与背光控制RGB 屏的引脚约束与普通 LED、按键不同必须根据板卡原理图逐个映射。下面只是占位示例不能直接用于你的板卡set_property PACKAGE_PIN AE21 [get_ports {lcd_r[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {lcd_r[0]}] set_property PACKAGE_PIN AF21 [get_ports {lcd_r[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {lcd_r[1]}]在设计 XDC 时还要留意引脚附近的约束比如是否被 PS MIO 占用是否支持 LVCMOS33以及是否有串阻、端接电阻。背光如果由 GPIO 控制需要在代码里拉高背光使能引脚否则屏幕有信号但背光不亮看起来像黑屏。5. Vitis 裸机工程LVGL 9.5.0 移植实战5.1 创建裸机工程并添加源码在 Vitis 中基于导出的.xsa硬件平台创建 Application Project操作系统选择 standalone然后添加 LVGL 源码。工程结构建议如下project/ ├── src/ │ ├── main.c │ ├── lvgl_port.c │ ├── lvgl_port.h │ ├── lv_conf.h │ └── lvgl/ │ ├── lvgl.h │ └── src/编译时确保lvgl/src和lv_conf.h所在目录都在 include path 中。如果使用 SDK/Vitis 的 Makefile 工程需要手动把.c文件路径加入源文件列表如果使用 CMake则把 LVGL 源码目录加入add_subdirectory或target_sources即可。5.2 帧缓冲与 flush 回调在 ZYNQ 裸机工程中LVGL 的帧缓冲可以直接定义成两个全屏数组也可以放在链接脚本指定的 DDR 地址。这里给出静态数组方式/* 文件路径src/lvgl_port.c */ #include lvgl/lvgl.h #include xil_cache.h #include xaxivdma.h #include xparameters.h #define DISP_H_RES 1024 #define DISP_V_RES 600 #define DISP_COLOR_DEPTH 2 /* RGB565 */ #define FB_SIZE (DISP_H_RES * DISP_V_RES * DISP_COLOR_DEPTH) static uint8_t fb1[FB_SIZE] __attribute__((aligned(64))); static uint8_t fb2[FB_SIZE] __attribute__((aligned(64))); extern XAxiVdma g_vdma; /* 在 main 中初始化的 VDMA 实例 */关键点是aligned(64)对齐。VDMA 搬运数据一般要求地址对齐到总线位宽64 字节对齐可以避免很多边界问题。flush 回调实现如下void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { /* px_map 是 LVGL 绘制完成的屏幕缓冲区 这里已把它当作 VDMA 当前帧缓冲因此不需要拷贝 */ Xil_DCacheFlushRange((UINTPTR)px_map, FB_SIZE); XAxiVdma_SetFrmStoreAddr(g_vdma, XAXIVDMA_READ, 0, (UINTPTR)px_map); lv_display_flush_ready(disp); }这里有两个容易被忽略的细节Xil_DCacheFlushRange保证 PS 端的 D-Cache 数据真正写回 DDR。VDMA 是 PL 端外设看不到 PS 的 Cache 内容如果不做 flush显示画面可能出现局部花屏、旧数据残留。XAxiVdma_SetFrmStoreAddr是切换 VDMA 当前读地址。由于 LVGL 的缓冲区就是帧缓冲绘制完成后让 VDMA 从新缓冲区开始读取这样能避免频繁 memcpy。5.3 触摸输入设备注册触摸读取函数需要根据实际触摸芯片实现。下面以 I2C 触摸为例示意 read_cb 的逻辑void lvgl_touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { uint16_t x 0; uint16_t y 0; uint8_t pressed touch_get_point(x, y); if (pressed) { >int main(void) { init_vdma_and_display(); /* 初始化 VDMA、VTC、LCD 背光 */ lv_init(); app_lvgl_init(); while (1) { lv_timer_handler(); usleep(5000); } return 0; }lv_timer_handler()是 LVGL 的核心运行函数它处理界面刷新、动画、事件等任务。裸机环境下必须在 while 循环里频繁调用通常建议 5ms 到 10ms 调用一次。运行后如果硬件链路正确屏幕会先出现 LVGL 默认背景色然后出现create_demo_ui()创建的控件。如果屏幕没有显示优先检查串口日志、背光 GPIO、VDMA 初始化状态不要一上来就怀疑 LVGL 移植有问题。6. 屏参适配与常见坑位6.1 拿到新屏第一步读 datasheet很多 ZYNQ 驱动失败问题不在 LVGL而在于屏幕时序或接口设置不对。拿到一块新屏先确认以下信息接口类型是 RGB 还是 LVDS。引脚名称和极性比如 DCLK 是上升沿采样还是下降沿采样。HSYNC/VSYNC/DE 极性。是否需要 DE 信号有些屏必须在 DE1 时才有数据。触摸芯片型号和 I2C 地址。这些信息全部来自屏幕规格书建议先整理成表格再配置 VTC 和约束文件。6.2 触摸坐标校准ZYNQ 上最常见的触摸问题是坐标方向反了或者 X/Y 对调。这通常不是 LVGL 问题而是触摸芯片坐标系与显示坐标系不一致。如果触摸点总是左右镜像可以在 read_cb 中做一次坐标变换x_display 1024 - x_touch;如果 X/Y 轴反了可以对调x_display y_touch; y_display x_touch;如果触摸点整体偏移但方向正确则考虑触摸芯片的坐标范围是否覆盖整个屏面有的触摸控制器需要先做一次校准保存最小值和最大值。6.3 帧缓冲内存与 OCM 限制ZYNQ-7000 的 OCMOn-Chip Memory通常只有 256KB 左右这对不带 DDR 的设计来说很紧张。1024×600 RGB565 一帧就是约 1.2MB双缓冲约 2.4MBOCM 远远放不下。因此只要跑 LVGL 1024×600 全屏界面DDR 基本是必须的。如果项目方案中选用了不带 DDR 的 ZYNQ并且希望通过 OCM 加载代码运行 LVGL那么建议把分辨率降低到 480×272 以下或者使用局部缓冲 小尺寸控件。这样 OCM 才能勉强容纳帧缓冲。6.4 横竖屏旋转有些屏幕是竖屏安装但界面需要横屏显示。LVGL 9 支持显示旋转旋转后触摸坐标也需要同步映射。最简单的方式是在 LVGL 中设置旋转再在 read_cb 里做对应坐标变换更统一的做法是在驱动层通过 VTC/PLL 把屏的扫描方向调整成横屏但很多 RGB 屏不支持这种硬件旋转只能靠软件处理。无论用哪种方式都要保证 LVGL 的宽高与屏幕实际扫描方向一致否则会出现显示被裁切或者触摸位置偏移。7. 常见问题与排查思路7.1 问题速查表问题现象常见原因解决思路屏幕不亮背光未拉高检查背光 GPIO 和电源屏幕有光但无画面VTC 时序错或 VDMA 未启动查看 VDMA 寄存器状态检查时序参数花屏D-Cache 未 flush在 flush 回调中调用Xil_DCacheFlushRange颜色偏色LVGL 颜色格式与 VDMA 不一致统一使用 RGB565 或 ARGB8888触摸无反应I2C 地址错误或 read_cb 未注册确认触摸芯片地址读取坐标测试触摸方向反坐标映射未处理在 read_cb 中交换/翻转坐标界面刷新慢单缓冲 全屏拷贝改为双缓冲并让 LVGL 直接绘制到帧缓冲7.2 黑屏

相关新闻