做ZYNQ+LVGL高刷新UI这段时间,我最大的感受是:显示这个东西,从“能亮”到“能看”,再到“跟手”,中间隔着一整条数据链路。前六讲把环境、工程结构、基础控件都过了一遍,这一讲集中解决系列教程里最核心的问题——高刷新。很多朋友拿着ZYNQ跑LVGL,发现分辨率一上去帧率就掉、滑动就撕裂、动画就卡顿,其实不是LVGL不行,而是你还没搞清楚它在这颗异构芯片上到底该怎么跑。这讲我会从渲染、刷新、搬运、部署四个层面,把ZYNQ+LVGL高刷新从原理到实操完整串一遍,适合正在做仪器界面、工业HMI、车载仪表这类对流畅度有硬要求的开发者。
1. 高刷新UI设计整体思路拆解
1.1 为什么LVGL跑在ZYNQ上会卡
先说一个很多人容易误解的地方:ZYNQ的PS侧是双核ARM Cortex-A9,主频能到667MHz甚至1GHz,听起来不算弱,但你要清楚它是什么年代的架构。相比现在手机里那些动辄2GHz起步、带乱序执行的Coretx-A系列,A9在老一辈里算能打,放到今天就比较吃力了。LVGL的软件渲染全靠CPU逐像素计算,如果你的分辨率是1080P、颜色深度32bit、界面上还堆了渐变、阴影、圆角、字体抗锯齿,那CPU每帧的绘制量是非常恐怖的。
我举个例子,一块1920x1080的屏幕,32位色,一帧原始数据量是192010804字节,约等于8.3MB。如果要跑到60帧,光是把这些像素从内存搬到显示器,带宽需求就接近500MB/s。DDR3/DDR4的带宽本身够,但问题在于这500MB/s只是搬运,还没算LVGL在内存里做绘制、混合、裁剪、旋转这些操作的内存带宽开销。一相加,你再看看A9实际上能跑出多少有效带宽,卡顿就一点也不奇怪了。
所以高刷新的第一步不是埋头调LVGL参数,而是先建立一个全局观念:LVGL只是负责“画”,画完以后还要有人把画好的内容“送”到屏幕上。“画”和“送”都高效了,高刷新才有可能。ZYNQ这颗芯片最大的价值,就是它给了你两个世界——PS侧用ARM跑系统、跑LVGL逻辑,PL侧用FPGA做并行搬运和加速,这是纯MCU方案完全不具备的优势。
1.2 从“能显示”到“流畅显示”要打通的三段链路
我把ZYNQ+LVGL的高刷新UI拆成三条链路,一条也不能短:
第一条是渲染链路。LVGL把按钮、标签、图表、动画这些控件画到内存缓冲区里,这个过程主要由CPU完成。渲染链路的瓶颈在算法效率和绘制面积。你动画越大、控件越复杂、用了越多半透明混合,CPU单帧绘制时间就越长。
第二条是刷新链路。绘制完成后,缓冲区里的像素数据要交给显示控制器。如果你用的是传统的RGB接口屏幕,ZYNQ需要通过LCD控制器或者你自己在PL里写的驱动,按照行场时序把数据持续不断地推给屏幕。这一环最容易出问题的是撕裂——你一边往缓冲区写新画面,显示控制器一边从同一个缓冲区读数据,读了一半你改了内容,屏幕上下两半就会错位。
第三条是搬运链路。LVGL绘制在DDR里,显示控制器也要从DDR里读,中间的搬运工作最理想的是交给DMA引擎,而不是让CPU去一点点拷。ZYNQ里的AXI VDMA就是干这个的,配置好以后,CPU只需要把画面画到指定的内存地址,VDMA会自动按帧率把数据搬给显示接口,完全不占CPU。
这三条链路是一条流水线,任何一环慢,整体帧率就慢。后面几节就顺着这三条链路逐个攻坚。
2. 双缓冲与DMA:高刷新的地基
2.1 没有双缓冲,谈高刷新就是耍流氓
如果你现在还在一块显存上边画边显示,那高刷新基本和你无缘。单缓冲模式下,LVGL绘制新帧和显示控制器读取旧帧共用同一块内存,一定会互相踩踏,轻则闪烁,重则画面撕裂。
解决撕裂的标准方案就是双缓冲。分配两块帧缓冲,PS侧的LVGL往Buffer A绘制,显示控制器读Buffer B;等Buffer A画完了,通过VDMA的帧完成中断做一次软切换,显示控制器转而读Buffer A,LVGL往Buffer B画下一帧。两块缓冲乒乓交替,画面永远读到的是完整的一帧,撕裂问题从根上就解决掉了。
LVGL里面对应的是lv_disp_buf_init初始化两个缓冲区。这里有个取舍:缓冲区越大,LVGL一次能重绘的区域就越大,绘制效率越高,但内存占用也越大。我的经验是,如果内存允许,直接给两个全屏大小的缓冲区,也就是真正的全双缓冲。如果内存紧张,至少也得给1/10屏幕以上的缓冲区,配合局部刷新机制,效果也不会太差。但记住一点——你给LVGL的buffer只是它绘制用的,VDMA真正送显的帧缓冲还要单独分配,两个概念别混了。
2.2 VDMA配置与带宽计算
VDMA是ZYNQ里做帧缓冲搬运的核心IP。用Vivado搭建硬件时,在Block Design里加入AXI VDMA,把它配置成Write/Read双通道模式,它的写通道可以接收PS侧写入的帧数据,读通道按你设定的时序把数据送给显示接口。
配置VDMA有几个关键参数直接影响刷新效果:
第一个是帧缓冲地址。VDMA支持多帧缓冲轮询,你给它两个地址,它在读到帧尾后自动跳到另一个地址去读,这就是硬件层面的双缓冲切换。你需要把这两个地址对应到DDR里物理连续且对齐好的内存区域,地址对齐至少4KB,最好1MB对齐,否则Cache和DMA协作时容易出奇怪问题。
第二个是行宽和像素格式。行宽要和屏幕分辨率严格对应,比如1920宽、RGB888格式,每行就是1920*3字节。如果行宽没设对,画面会出现斜切或者整体偏移。像素格式必须和LVGL的颜色深度一致,LVGL里常见的LV_COLOR_DEPTH是16位或32位,而显示接口那边可能是RGB565或RGB888,中间如果做了转换,带宽和颜色效果都会变化。
第三个是刷新率和时空开销的计算。60Hz刷新率下,一帧16.6ms。假设1080P RGB888,VDMA每秒搬运的数据量约等于192010803*60,大约373MB/s。DDR3-1066的带宽理论上有8.5GB/s,实际可用打个六七折也远够用。但你还要同时承载CPU绘制、DMA搬运、系统运行,所以要尽量让VDMA的搬运走高性能端口,避免和CPU抢占同一个DDR控制器通道。
2.3 Cache一致性:最容易翻车的隐藏坑
这一节必须单独拿出来说,因为我在这个坑里折腾过好几个通宵。ZYNQ的PS侧CPU带有L1和L2 Cache,CPU写内存时,数据可能先留在Cache里,没有立刻写回到DDR。而VDMA读的是DDR里的物理数据,它不会去看CPU的Cache。结果就是:LVGL明明已经把一帧画完了,VDMA从DDR里读出来的却是旧数据,画面就出现一块一块的“花屏”或者更新的内容迟迟不出现。
解决方向有两个。一个是在驱动里对帧缓冲地址做内存映射时,直接使用不带Cache的映射属性。Linux下用dma_alloc_coherent分配的内存天然就是consistent的,CPU写完后对DMA立即可见,省心。另一个是用带Cache的普通内存,但每次帧绘制完成后,在启动DMA搬运之前,主动调用Cache刷新操作。实测下来,对于高频刷新的UI,推荐直接用dma_alloc_coherent分配帧缓冲,简单可靠,性能损失在可接受范围内。
另外提醒一句:ZYNQ的L2 Cache是A9共用的,如果PS侧还跑了Linux,多个进程、DMA都在操作同一段内存,你要格外注意内存属性是否一致。我见过有人把帧缓冲分配成普通匿名内存,然后又用VDMA直接去读,结果画面时好时坏,排查了很长时间才发现是Cache没有刷。
3. LVGL渲染层优化:把每一毫秒都抠出来
3.1 lv_conf.h里的关键开关
LVGL的性能表现,很大程度在编译期就决定了。lv_conf.h里那几个宏,每一个都值得你认真对待。
第一个是LV_COLOR_DEPTH。用16位还是32位,对渲染效率和内存占用影响非常直接。16位色下像素大小是2字节,一帧需要的数据量只有32位的一半,内存带宽压力小很多,渲染也更快。如果你的屏幕是RGB565接口,并且UI设计对色彩精度要求不高,优先选16位。但如果你的界面用了很多半透明效果、需要显示图片,32位的色彩混合效果会明显更好,这个要根据项目实际情况取舍。
第二个是LV_MEM_SIZE。LVGL默认使用内部动态内存池,太小了控件一多就会分配失败,界面直接崩掉或者控件显示不全。ZYNQ上的内存相对充足,可以大方一点,但也不要无脑设大,因为LVGL会一次性开辟这段内存。
第三个是LV_DISP_DEF_REFR_PERIOD,默认是30ms,对应约33帧每秒的刷新节律。如果你想追求更高刷新,可以把它缩短到16ms甚至更短。但这个值不是越小越好,因为LVGL的刷新机制是靠lv_timer_handler周期性驱动的,如果刷新周期太短而CPU绘制能力跟不上,反而会导致任务堆积、系统卡顿。建议先保持30ms跑一遍基准,再逐步往下压。
3.2 局部刷新和脏矩形机制
LVGL默认开启局部刷新,也就是脏矩形机制。它会把需要重绘的区域打包成一个个矩形区域,只重绘这些区域,而不是每帧都全屏清理。这个特性对你的高刷新目标特别重要,因为大多数UI场景中,每帧真正变化的区域其实很小,比如一个滑块的进度条在动,旁边的大面积静态背景根本不需要重画。
但是局部刷新有一个硬件配合要求:你的显示控制器必须支持从内存任意位置读取数据,或者说,VDMA要能正确响应非全屏区域的刷新。在ZYNQ平台,通常的做法是把LVGL的刷新回调函数disp_flush_cb里拿到的区域信息,直接交给一个支持区域更新的显示驱动。如果你的方案是VDMA整帧搬运,那局部刷新的收益会被部分抵消,因为你最后还是把整帧搬给了屏幕。
所以这里有个实践方向供参考:如果追求极致的刷新效率,可以跳过LVGL默认的整帧搬运,让disp_flush_cb只把脏矩形区域写入帧缓冲对应的位置,然后由显示控制器做区域同步。但这样驱动复杂度会高不少,对大部分项目来说,全双缓冲加整帧搬运已经能达到很好的流畅度,不必过度设计。我自己的做法是先全双缓冲跑通,再加局部刷新优化,一步一步来。
3.3 动画、样式和字体的性能取舍
LVGL的动画非常丝滑,但那是在资源充足的平台上。ZYNQ的A9核跑复杂动画时,你要有意识地做减法,毕竟目标是以最低的开销换最高的流畅度。
动画方面,LVGL自带的lv_anim是基于软件定时器逐步更新属性的,本身不会像游戏引擎那样按帧渲染。但如果你同时在界面上挂了几十个控件各自的lv_anim,每个动画每次触发都会让LVGL标记对应区域为脏区,脏区一多,局部刷新就退化成接近全屏刷新了。我踩过一个项目,界面上一排指示灯全部用动画循环呼吸,CPU占用率直接飙升,后来改成只有焦点状态变化时才开启动画,平时保持静态,占用率瞬间降下来。
样式方面,LVGL的阴影、圆角、渐变都是实时计算的,绘制开销从高到低大致是阴影、渐变、圆角、纯色填充。如果你的UI追求高刷新,非必要的阴影和渐变尽量少用。我自己习惯是先做一版纯色扁平风格,把帧率基线拉上去,再逐个加样式,观察帧率变化,超过预期就砍掉,这样能明确知道是哪一两个样式拖了后腿。
字体方面,中文字体是内存和渲染的大户。一个完整的中文字库字模体积非常大,LVGL加载到内存后占用的空间也很可观。更关键的是,字体渲染涉及到抗锯齿计算,用的字符越多,绘制越慢。建议对UI里实际用到的文字做子集化,只打包需要的汉字,能显著降低内存占用和渲染时间。LVGL官方提供了字体转换工具,支持从TTF生成子集,实际项目中我都是把界面文案先整理成一个清单,再生成对应的字体文件,效果立竿见影。
4. 硬件加速与渲染策略进阶
4.1 用PL做2D加速的思路
LVGL的软件渲染吃CPU,这是架构决定的,想彻底改观,有一条路是用PL侧做硬件加速。ZYNQ的FPGA可以让你把固定的2D操作,比如位块搬运、颜色填充、Alpha混合,做成硬件模块,然后通过AXI总线挂到PS侧,LVGL的绘制函数可以调用这些硬件模块来替代CPU计算。
具体实现上,可以自己在PL里写一个简单的2D加速器,也可以基于Xilinx提供的Video Framebuffer相关IP来扩展。核心思路并不复杂:CPU只负责设定绘制的目标地址、源地址、画布大小和颜色,然后启动硬件模块,由FPGA并行完成像素级的填充或混合,完成后通过中断通知CPU。这样一来,CPU的负载就从“逐像素计算”降级为“下发指令”,绘制速度有质的提升。
有人会拿全志T113这类带G2D的芯片来对比,其实逻辑完全一样。G2D就是一个2D图形加速硬件,LVGL可以对接它来加速矩形填充和图像混合。ZYNQ的PL就是你的G2D,而且是可以随意修改的G2D。如果你关注过LVGL的draw接口,会发现它预留了自定义绘制回调,把底层绘制函数替换成FPGA加速模块,是标准玩法。
不过要说清楚,自己写PL加速器的工作量不小,适合对性能有极致要求、并且有一定FPGA开发经验的团队。如果只是想快速交付一个流畅的UI,优先把双缓冲、DMA、LVGL配置优化到位,一般就能满足需求。PL加速属于进阶加分项,不建议项目前期就陷进去。
4.2 毛玻璃与高斯模糊的落地办法
LVGL的社区里经常有人问毛玻璃效果怎么做。这种在手机系统上看起来很炫的半透明模糊背景,放在MCU级别的UI框架里,是一个性能杀手。LVGL自带的功能里,半透明背景可以用opacity实现,但高斯模糊没有原生支持。
在ZYNQ上实现毛玻璃效果,我建议按性能代价从低到高排列几种方案:
第一种是预渲染静态模糊。把毛玻璃效果要用的背景在PC上先用图像处理工具模糊好,作为一张静态图片资源加载到LVGL里,背景变化时切换不同图片。这种方案几乎不消耗运行时性能,缺点是没有真正的动态模糊,背景内容变化时效果会失真。
第二种是低分辨率模糊再拉伸。先把背景图像缩小到1/4甚至1/8分辨率,做模糊处理后,再放大覆盖到毛玻璃区域。因为模糊计算量跟像素数量成正比,分辨率降低之后计算量大幅减少。LVGL里可以通过图片缩放功能实现,但需要占用一部分DMA或CPU时间来缩放和混合。
第三种是用PL做模糊。高斯模糊本质上是卷积运算,非常适合FPGA并行处理。把背景图像通过AXI总线送到PL里做卷积,再把结果返回给PS显示。这种方案的效果最接近真实毛玻璃,响应也最快,但开发成本和前面PL加速一样,投入不小。
我的实际建议是:在ZYNQ这个平台上,除非产品宣传特别需要,否则UI设计尽量避免大面积动态毛玻璃。与其花大代价做模糊,不如用半透明纯色加轻微噪点纹理来模拟质感,视觉效果接近,代价小一个数量级。设计是为体验服务的,不是为技术炫技服务的。
4.3 RTOS与Linux两种环境下的刷新实时性
做ZYNQ的LVGL,你迟早要面对一个选择:下面跑的是FreeRTOS这类RTOS,还是Linux。这个选择直接影响高刷新UI的实现方式。
如果跑FreeRTOS,LVGL独占CPU,你可以把刷新周期控制得非常精确。FreeRTOS是裸机环境的延伸,LVGL的lv_timer_handler直接在你的主循环或者高优先级任务里周期调用,延迟很小,几乎不会被其他任务抢占。对于极致的刷新体验,这种方案更可控。我之前在ZYNQ上搭过FreeRTOS加LVGL,显示这一路没有Linux那套复杂的内存管理,Cache一致性问题也更简单,直接用裸机驱动库就行。
如果跑Linux,走的是标准的FrameBuffer或DRM/KMS显示路径,LVGL作为一个用户态程序运行在操作系统之上。Linux的好处是生态好,文件系统、网络、调试工具完善,适合UI功能复杂的项目。但代价是显示链路的调度不确定性增加,刷新周期没那么精确,如果系统负载高,LVGL的刷新可能被其他进程影响。
从高刷新的角度,我的经验做法是:对刷新率要求极端严格的产品,特别是仪表类、工业控制类,优先考虑FreeRTOS + LVGL,或者干脆裸机运行。如果必须用Linux做复杂业务逻辑,那就需要把LVGL进程的CPU亲和性绑到一个核上,同时提高它所在线程的实时优先级,尽量保证刷新的稳定性。另外,Linux下显示缓冲区建议用DRM的硬件plane来做双缓冲切换,配合VDMA效果更好,但驱动配置也会更复杂。
顺带一提,Linux下跑LVGL还是Qt,也是一个常见纠结。我的看法是,界面元素少、逻辑轻、资源受限,就选LVGL,它在同样硬件条件下能跑出的帧率明显高于Qt。界面复杂到需要大量控件和高级特效,或者需要很强的业务集成能力,那Qt可能是更合适的选择,但代价是资源和启动时间都上去了。
5. 部署到ZYNQ:从编译到SD卡启动
5.1 PetaLinux 2025.1生成三件套
ZYNQ上跑Linux,目前最顺手的构建工具还是PetaLinux,2025.1版本对新的ZYNQ器件支持已经比较完善。整个流程的核心产出,就是启动Linux必须的三件套:BOOT.BIN、boot.scr、image.ub。
BOOT.BIN里面包含ZYNQ的启动引导,主要组成是FSBL(First Stage Boot Loader)、bitstream(PL配置)和U-Boot。很多人第一次接触时会碰到一个报错,提示需要一个有效的FSBL文件才能完成Flash操作,这个基本就是因为你没有先编译FSBL,或者在Vivado导出硬件时没有把FSBL工程一起生成。解决办法是在Vivado的SDK/Vitis里创建一个FSBL工程,编译成功后,PetaLinux打包时才能正确把它拼进BOOT.BIN。
boot.scr是U-Boot的启动脚本,它告诉U-Boot从哪里加载内核和设备树。在PetaLinux的U-Boot配置里,需要确保环境变量里的启动命令正确指向image.ub。image.ub则是Linux内核、设备树和根文件系统的统一打包镜像,PetaLinux会自动把它生成到一个镜像目录里。
5.2 SD卡分区与镜像烧写
ZYNQ从SD卡启动,SD卡的分区结构有讲究。一张可启动的SD卡,至少需要两个分区:第一个分区必须是FAT32,用来存放BOOT.BIN、boot.scr、image.ub这类启动文件;第二个分区可以是ext4或者FAT32,主要取决于你想把根文件系统放在哪。
我的习惯是第一个分区256MB,FAT32格式,启动文件就三个,空间完全够了。第二个分区放根文件系统,用ext4格式,容量按项目的应用需求来定。在Linux主机上,用fdisk或者gparted创建好分区后,把BOOT.BIN、boot.scr、image.ub拷到FAT32分区,然后用dd命令把PetaLinux生成的根文件系统镜像写入ext4分区,一张可以启动的SD卡就做好了。
没有Linux主机的话,Windows下可以用一些分区工具来操作,但步骤繁琐一些,而且dd写镜像这个动作在Windows下不太好实现。我个人还是强烈建议准备一台Linux的虚拟机或者WSL环境,效率会高很多。
开发过程中还有个小技巧:常态调试时不要频繁烧SD卡。先把系统跑起来,通过NFS或者TFTP加载内核镜像和文件系统,每次修改LVGL应用程序,只是一个交叉编译加拷贝文件的操作,比反复插拔SD卡快太多。等调试稳定了,再固化到SD卡里做最终验证。
5.3 LVGL交叉编译与运行
LVGL跑在Linux环境下,可以静态编译成单文件,也可以编译成动态库。对于部署简单性,我建议静态链接。交叉编译的工具链在PetaLinux安装目录里自带,直接用它来编译LVGL的工程即可。
需要注意的一个大坑是:LVGL的显示输出目标,不同版本的Linux显示框架差异很大。如果你的系统用的是传统FrameBuffer接口,LVGL可以通过linux_fbdev驱动直接写/dev/fb0。如果系统走的是DRM/KMS,则需要自己适配DRM的显示接口。在做环境适配之前,先确认你的PetaLinux内核配置里打开了哪些显示支持,再决定LVGL的驱动方式。
另一个实用建议是,先用PC模拟器调试好 LVGL 的界面逻辑和布局,等到ZYNQ平台上再适配显示驱动。LVGL官方提供了基于SDL的PC模拟器,鼠标键盘可以直接模拟触摸操作,调试UI交互非常方便。用模拟器把界面结构、动效、页面切换都理顺了,再上板子,能省掉大量在板端反复编译烧写的等待时间。
6. 高频问题排查与避坑实录
6.1 花屏、撕裂和闪烁问题
花屏这个现象,在ZYNQ+LVGL项目里至少有一半概率是内存问题。最常见的就是前面说的Cache一致性问题——DMA读到的数据和CPU写的不一致,表现出来就是画面部分区域更新不及时,或者出现不规则的色块。排查方法很简单,先检查帧缓冲内存是不是用一致性映射分配的,是的话再看Cache刷新时机对不对。
撕裂现象,绝大多数是单缓冲导致的。我曾经见一个朋友为了省内存,只给了一块帧缓冲,结果屏幕刷新率越高撕裂越明显。解决方法就是上双缓冲,没有别的捷径。注意切换缓冲的时机要放在垂直消隐期间,也就是VBlank中断里做,否则切换瞬间还是可能有一两行撕裂。
闪烁则要区分是LVGL绘制闪烁还是显示时序闪烁。绘制闪烁通常是脏矩形处理不当,LVGL在重绘边缘区域时出现色差,可以检查lv_disp_flush_is_last的相关逻辑。显示时序闪烁则大概率是像素时钟或者行场参数配置不对,用示波器看一下LCD接口的时序波形,和屏幕数据手册对照一遍就清楚了。
6.2 刷新率上不去的常见原因
如果无论怎么调,刷新率都卡在二三十帧上不去,我建议按优先级排查这几个地方:
第一,LVGL刷新周期配置是否合理,是不是默认的30ms没改,或者改太短导致CPU过载。第二,CPU是否在做大量非必要绘制,动画、阴影、圆角、全屏透明这些高性能消耗操作有没有压制。第三,脏矩形机制有没有真正生效,如果每次都触发全屏刷新,那渲染开销会成倍放大。第四,VDMA搬运有没有和CPU绘制竞争内存带宽,如果你有很多PL外设同时在高速搬运数据,DDR带宽可能会成为瓶颈。
还有一个很多人会忽略的问题:你的屏幕接口是不是真的支持高刷新率。有些RGB屏幕虽然标称60Hz,但实际时序配置里像素时钟偏低,或者行消隐、场消隐设置过长,导致实际刷新率只有40多赫兹。这个用屏幕的时钟计算公式倒推一下就能算准,别让屏幕硬件限制了你的刷新目标。
6.3 开发中遇到的几个“玄学”问题
做嵌入式开发,总有那么几个问题让你怀疑人生。ZYNQ上跑LVGL,我也遇到过几个典型的“伪玄学”问题,这里诚实地分享一下。
一个是偶发性UI错乱。界面整体工作正常,但偶尔某个控件莫名其妙显示异常,复位一下又好了。这种情况大概率不是LVGL的bug,而是内存踩踏。检查一下有没有数组越界、指针错误、或者某个驱动里DMA写越界了,把LVGL的内存池用保护模式开起来,能帮你快速抓到非法访问。
另一个是触摸响应不跟手。高刷新UI做得再流畅,触摸一卡就全毁了。LVGL的输入设备驱动是在独立线程里做的,如果触摸事件采样率太低,或者事件队列处理延迟高,界面自然显得不跟手。建议把触摸的采样频率提到100Hz以上,中断优先级调高,同时确保lv_timer_handler不会被长时间阻塞。
最后一个是关于调试手段。如果板子上跑Linux,可以开启LVGL的性能监控,它会直接在显示画面上叠加帧率、CPU占用等调试信息,这在优化阶段特别有用。理论上LVGL的性能监控对最终UI会有轻微影响,但调优阶段这点开销完全可以接受,上线前关掉就行。
做高刷新UI,核心思路其实就是八个字:减少绘制、加速搬运。绘制端用LVGL的机制做减法,搬运端靠DMA和双缓冲做加法,两边一配合,ZYNQ这套平台能跑出的流畅度,是远超你最初预期的。我在实际项目中跑过的经验是,一套设计得当的ZYNQ+LVGL方案,稳定的40到50帧刷新是完全可以实现的。如果你的项目卡在某个奇怪的显示问题上出不来,不妨把显示链路从头到尾捋一遍,渲染、刷新、搬运、时序,一层层排查,一定会找到问题所在。