《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验
  95kVyaJuybju 2023年11月12日 21 0

PS通过VDMA驱动LCD显示实验​

AXI VDMA是Xilinx专门针对视频应用提供的一种高带宽的解决方案,旨在实现AXI4-Stream视频接口和AXI4接口之间的高带宽接入,可以方便地实现双缓冲和多缓冲机制。

本章我们将在PL端搭建VDMA的使用框架,并通过VDMA将PS端需要显示的数据显示在LCD上。本章包括以下几个部分:

  1. 简介
  2. 实验任务
  3. 硬件设计
  4. 软件设计
  5. 下载验证


简介

AXI VDMAAXI Video Direct Memory Access,以下简称VDMA),是Xilinx提供的软核IP。其功能和AXI DMA(以下简称DMA)有些类似,都可以为存储器或者AXI4-Stream类目标外设之间提供高带宽直接存储器存取。和DMA相比,VDMA增加了帧缓存的缓冲机制和同步锁相(GenLock)等功能,是为了针对视频图像应用而做的升级版的DMA。VDMA集成了视频专用功能,如帧同步和2D DMA传输等,非常适合基于MPSOC架构上的图像和视频处理,缩短了开发周期。

DMA和VDMA的对比如所示。

23.1.1 DMA和VDMA的对比

对比项\IP

DMA

VDMA

功能

为存储器或者AXI4-Stream类目标外设提供高带宽存取

为存储器或者AXI4-Stream类目标外设提供高带宽存取;帧缓存和多种缓存机制;

帧同步等

资源占用

较低

较高

应用场合

提供高速数据的传输,如高速ADDA的数据收发等

主要针对视频类数据的高速传输,如摄像头图像显示等

这里我们引入了帧缓存的缓冲机制和动态同步锁相两个概念,接下来向大家详细介绍这两个概念。

帧缓存

首先我们来看下什么是帧缓存。帧缓存存储器(Frame Buffer),简称帧缓存,也常被称作显存,是为显示设备(如HDMI显示器、RGB LCD液晶屏等)提供数据缓存的一片存储区域。一般图像输入源和图像显示的传输速率不匹配(如图像输入源传输速度较快或者图像显示端传输速度较快),这个时候需要一片存储区域来缓存输入的数据,以便显示设备读取数据,同时也方便后续对视频数据做图像处理。帧缓存的每一个存储单元对应屏幕上的一个像素,整个帧缓存对应一帧图像。

在实际应用中,一般选择外置的DDR存储器作为帧缓存,而不是选择片内的BRAM,这是由于MPSOC片内的BRAM存储容量比较小。我们知道,XCZU2EG的BRAM存储容量为5.3MbitXCZU4EV的存储容量为4.5Mbit,假设RGB LCD屏的分辨率为800*480,那么存储一帧图像需要的存储容量为(如:RGB888数据格式=24位)800*480*24= 9216000bit≈8.79Mbit,整个BRAM的存储容量不足以存储单帧图像,更何况有时需要多个显存的情况,因此使用外置的DDR存储器作为帧缓存。

对于使用外置存储器来缓存图像数据来说,可以采用单帧缓存或者多帧缓存的方案。单帧缓存是指图像的输入和图像的显示都是通过读写同一片存储区域来实现的。虽然也可以实现显示设备显示图像的功能,但这会带来一个问题,即读出的单帧图像是输入的两帧图像或者更多帧图像数据叠加在一起的结果,可能会导致显示设备显示的图像出现割裂的现象。单帧缓存方案的读写示意图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存


23.1.1单帧缓存方案读写示意图

由上图可知,随着输入数据源的不断写入,单帧存储区域会出现单帧图像数据或者两帧图像数据叠加在一起的情况。当然,如果读出速度比写入速度慢很多的话,也可能出现更多帧图像叠加在一起,从而导致显示设备显示的图像出现割裂的现象。

对于本次RGB LCD屏彩条显示实验来说,由于彩条只需要写入一次,写入完成后,后续只需要进行读操作,因此本次实验使用单帧缓存的方案即可,那么采用DMA或者VDMA的方案区别不大。而对于需要频繁写入和读出的应用来说,比如摄像头的图像显示,如果仅仅使用单个帧缓存的话,势必会造成图像叠加的情况,这个时候就要采用VDMA的多帧缓存的方案了。本次实验旨在通过VDMA的RGB LCD屏显示彩条的实验,让大家熟悉并掌握VDMA IP核的使用方法,为后续学习摄像头的图像显示实验打下基础。

在VDMA IP核中,通过配置VDMA的同步锁相模式来配置帧缓存的缓冲进制,接下来我们向大家介绍VDMA的同步锁相模式。

同步锁相(Genlock

很多的视频应用中,图像输入端和输出端的数据速率不匹配通常使用帧缓存来避免因速率不匹配而导致的潜在错误。为了解决单帧缓存区域带来的图像叠加问题,通过分配多个帧缓存区域保存数据,图像输入端在写入其中一个帧缓存时,输出端读取其它的帧缓存。

VDMA支持四种同步锁相模式,分别是Genlock Master(同步锁相主模式)、Genlock Slave(同步锁从模式)、Dynamic Genlock Master(动态同步锁相主模式)和Dynamic Genlock Slave(动态同步锁相从模式)。

VDMA有一个写通道(S2MM)和一个读通道(MM2S),用户通过写通道将输入端数据写入帧缓存,通过读通道将从帧缓存中读出数据。VDMA的每一个通道都可以选择以上四种模式中的一种,接下来我们分别向大家介绍这四种同步模式。

Genlock Master

当写通道(S2MM)或者读通道(MM2S)配置为Genlock Master时,该通道不会跳过或者重复任一帧缓存区域,按照帧缓存顺序读出数据。配置为Genlock Slave的通道应当紧跟Genlock Master通道变化,但有一定的延迟,延迟的大小在寄存器(*frmdly_stride[28:24])中配置。

Genlock Slave

当写通道(S2MM)或者读通道(MM2S配置为Genlock Slave时,该通道会通过跳过或者重复一些帧缓存区域的方式,尝试与Genlock Master通道同步

Dynamic Genlock Master

当写通道(S2MM)或者读通道(MM2S配置为Dynamic Genlock Master时,该通道会跳过Dynamic Genlock Slave通道正在操作的帧缓存,通过跳过或者重复一些帧缓存区域的方式来完成。

这里以分配三个帧缓存为例。当配置为Dynamic Genlock Master的通道访问帧缓存时,没有检测到Slave通道访问的帧缓存,那么它会循环访问帧缓存0 1 2 0 1 2;而如果检测到Slave访问的帧缓存区域,它们它会跳过该区域并开始访问下一帧缓存。因此,如果Slave通道长时间访问帧缓存1,则Master会循环访问帧缓存2和3。

Dynamic Genlock Slave

当写通道(S2MM)或者读通道(MM2S配置为Dynamic Genlock Slave时,该通道会操作 Dynamic Genlock Master通道上一周期操作的帧。

下面以配置为Genlock模式为例,写通道(S2MM)配置为Genlock Master模式,读通道(MM2S)配置为Genlock Slave模式,操作示意图如23.1.2所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_02


23.1.2 Genlock Master/Slave示意图

s2mm_frame_ref:写通道访问的帧缓存;

s2mm_fsync:写通道帧同步信号,下降沿表示VDMA开始进行写操作;

mm2s_frame_ref:读通道访问的帧缓存;

mm2s_fsync:读通道帧同步信号,下降沿表示VDMA开始进行读操作;

由上图可知,写通道在循环访问帧缓存0,1,2;而由于读通道的帧速率比写通道慢,读通道会紧跟写通道,在操作0之后,跳过1,而去处理2。

下面以配置为Dynamic Genlock模式为例,写通道(S2MM)配置为Dynamic Genlock Master模式,读通道(MM2S)配置为Dynamic Genlock Slave模式,操作示意23.1.3所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_03


23.1.3 Dynamic Genlock Master/Slave示意图

在动态同步锁相模式下,Master会跳过Slave当前操作的帧缓存,Slave工作在Master上一次操作过的帧缓存。在上图中,写通道循环访问0,1,2,当再次返回至0时,由于Slave在操作0,所以Master跳过0,而去访问1。而读通道操作Master上一次访问过的帧缓存。

由此我们可以得出结论,如果想要避开读通道和写通道同时访问同一帧缓存,那么VMDA必须配置成动态同步锁相的模式,且帧缓存数量要大于等于3。由于分配过多的帧缓存区域对效率的提升已经微乎其微,且会占用更多的存储空间和消耗CPU的时间,因此在摄像头图像显示的应用中,帧缓存空间一般设置为3,并采用动态同步锁相的模式。

需要注意的是,VDMA只是Xilinx提供的IP核,本身不提供存储的功能,帧缓存一般设置在外置的存储器(如DDR4)中,VDMA只是提供了用于访问DDR4的接口。

VDMA概述

VDMA用于将AXI Stream格式的数据流转换为Memory Map格式或将Memory Map格式的数据转换为AXI Stream数据流,也就是说VDMA内核旨在提供从AXI4域到AXI4-Stream域的视频读/写传输功能,反之亦然,从而实现系统内存(主要指DDR4)和基于AXI4-Stream的目标视频IP之间的高速数据移动。VDMA的框图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_04


23.1.4 VDMA的框图

AXI4-Lite可以对寄存器进行编程(配置),从而实现软件动态配置VDMA的功能。通过AXI4-Lite接口对寄存器进行编程后,控制/状态逻辑块会为DataMover生成适当的命令,以在AXI4主接口上启动写入和读取命令。可配置的异步line buffer用于在将像素数据写入AXI4-Memory Map接口或AXI4-Stream接口之前临时保存像素数据。

VDMA数据接口可以分为读、写两个通道,且写入和读取独立运行。用户可以通过写通道将AXI-Stream类型的数据流写入系统存储器(主要指DDR4)。在读通道中,VDMA使用AXI4主接口从系统存储器读取数据并在AXI4-Stream主接口上输出。可以看到,VDMA本质上是一个数据搬运的IP,可以看作是为视频图像处理做特殊优化的带有帧缓冲功能的高性能DMA,为数据进出系统存储器提供了一种便捷的方案。

VDMA内核不仅具有帧缓冲功能,而且集成了视频专用功能,如Gen-Lock和帧同步,用于完全同步的帧DMA操作和2D DMA传输。除了同步之外,还可以使用帧存储编号和scatter gather或寄存器直接模式操作,以便中央处理器控制。在本设计中,不使用VDMA scatter gather功能,因为可以使用VDMA的更简单的寄存器直接模式充分实现系统,从而避免实现scatter gather功能带来的面积成本。只有在系统需要对VDMA进行相对复杂的软件控制时,才应启用scatter gather

实验任务

本章的实验任务是PS写彩条数据至DDR内存中,然后通过VDMA IP核将彩条数据显示在RGB液晶屏上。

硬件设计

VDMA IP核为存储器和AXI4-Stream类目标外设之间提供高带宽直接存储器存取,本身不产生RGB LCD的接口时序,因此本次实验除添加VDMA IP核外,还需要添加用于产生RGB LCD接口时序的IP核。MPSOC中提供了AXI4-Stream to Video Out IP核,可以将VDMA输出的AXI4-Stream数据流转换成视频协议的数据流(包括并行数据、视频同步信号等),转换后的数据流符合RGB LCD接口的时序。另外,在添加AXI4-Stream to Video Out IP核之后,还需要添加Video Timing Controller(VTC)IP核,这个IP核为AXI4-Stream接口和视频输出接口提供了一个桥,用于控制视频输出的时序参数。

本次实验需要兼容正点原子推出的所有RGB LCD液晶屏,因此本次实验需要先获取LCD屏ID,然后对RGB LCD屏接口时序相关的IP核进行动态配置,以实现兼容所有RGB LCD屏的功能。

23.3.1为本次实验的系统框图,粉红色的走线是控制流,绿色的走线是数据流。我们先来看数据流,彩条数据由PS端产生,产生的数据写入系统存储器DDR4内存中;VDMA通过AXIAXI_HP端口进行连接,从而高效访问DDR4VDMA将从DDR4中读取的视频或图像数据传输给AXI4-Stream to Video Out IP核。AXI4-Stream to Video Out IP核在VTC IP核的控制下,把AXI4-Stream格式的数据转换成视频输出的数据格式(如RGB888),并将输出的视频数据流连接至RGB2LCD IP核(rgb2lcd)的输入端。RGB2LCD IP核是本次实验自定义的IP核,实现了获取LCD屏的ID,以及将LCD屏的引脚封装到总线接口上,以方便将LCD引脚引出至顶层模块端口上。

最后我们再来看控制流,AXI Interconnect实现M_AXI_HPM接口与外设配置接口的互联,从而实现PS控制PL端的外设。PS通过AXI GPIO IP核获取LCD屏ID,并根据获取到的ID,配置VDMA IP核的帧缓存空间大小、读通道等以及配置VTC IP核的输出的时序参数。由于不同分辨率的LCD屏其驱动时钟不一样,因此本次实验添加了Clocking Wizard(时钟)IP核,并使能了其时钟重配置的功能,PS根据LCD屏的ID配置时钟IP核输出不同的时钟。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_05


23.3.1 VDMA系统框

首先创建Vivado工程,工程名为“vdma_lcd”。

1 配置Zynq UltraScale+ MPSOC

添加Zynq UltraScale+ MPSOC,完成UART和DDR4的基本配置外,本实验我们还需要使用S_AXI_HP接口。

首先在PS-PL Configuration栏开启AXI HP0 FPD,如下图所示:


《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_06


23.3.2 开启AXI HP0 FPD接口

上图中AXI HP FPD和AXI HPC FPD都是高性能的接口,都可以满足本次实验的要求。其中AXI HPC FPD接口连接到了CCICache-coherent interconnect),可以访问L1和L2 Cache,也正是连接到了CCI,所以在访问DDR控制器时,相比于AXI HP FPD接口来说,延时会较大,所以一般选择AXI HP FPD接口。

接下来配置时钟PL0 配置为100Mhz。需要说明的是,VDMA端口的各种总线都有自己的时钟,这些时钟是异步的,可以使用同一个时钟,可以使用不同的时钟。为了降低设计的复杂度,本次统一使用100Mhz的时钟,对于一些有特殊需求的应用,可以使用不同的时钟。时钟配置界面如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_07


23.3.3 配置时钟

由上图可知,将PL0配置成100Mhz,但实际输出的时钟只有96.968727Mhz,和预期的时钟偏差较大。我们的开发板的PS输入时钟频率为33.333Mhz,MPSOC芯片内部会通过PLL对该时钟进行倍频和分频,从而得到其它频率的时钟,而上图中的PLL Options,就是PLL的倍频和分频系数,这些系数是由工具自动计算得到的,共输出5路时钟,名称分别为APLL、DPLL、VPLL、IOPLL和RPLL

这些不同的时钟应用于不同的功能模块,在UG1085手册“PS Clock Subsystem”章节中有着详细的介绍,关于这些时钟的描述如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_08


23.3.4 时钟功能描述

需要注意的是,由于时钟倍频和分频的系数是由Vivado软件自动生成的,所以有时这些系数会有差别,笔者第一次在做本章实验的时候,PLL Options的系数就和图中的系数有差别,不过这个影响不大,只要我们最终需要得到的时钟和实际产生的时钟偏差不大就行。当然也可以勾中上图中的“Enable Manual Mode”,来手动调整这些系数,但是通常不建议这么做,一方面在使用上会比较麻烦,另一方面调整有误的话,会发挥不出MPSOC芯片的性能。

再次回到PL0配置成100Mhz和实际输出的时钟偏差较大的问题,如果大家在配置时发现偏差很小,则可以直接使用,不用做任何修改;如果偏差较大的话,可以尝试修改Source一栏的时钟源,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_09


23.3.5 PL0时钟源修改

由上图可知,将时钟源改成IOPLL之后,实际输出的时钟频率和配置的时钟偏差非常小。Source选择不同的时钟源,表示PL0的时钟源来自于哪里,此处选择IOPLL

配置完成后,点击“OK”按钮,Zynq UltraScale+ MPSOC如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_10


23.3.6 MPSOC7 框图

2 添加并配置VDMA IP核。

2-1 添加VDMA IP。

点击“+”图标,在搜索框中输入“vdma”,添加该IP,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_11


23.3.7 添加VDMA IP

2-2 配置VDMA。

双击打开VDMA配置界面,在Basic界面,Address Width保持默认32即可。Frame Buffers选项可以选择AXI VDMA要处理的帧缓冲存储位置的数量。对于大于32位的地址空间,最多允许8个帧缓存。由于对于本次彩条显示实验来说,数据只需要写入一次,因此不需要设置多个帧缓存区域,这里我们设置为1。因为本实验是从DDR4中读取数据输出给LCD,所以只需要勾选Enable Read Channel就可以了,无需勾选Enable Write Channel。

Memory Map Data Width选项可以为MM2S通道选择所需的AXI4数据宽度。有效值为32、64、128、256、512和1024。此处保持默认64即可。

Read Burst Size用于指定突发读的大小,此处选择64

Stream Data Width选项可以选择MM2S通道的AXI4-Stream数据宽度。有效值是8的倍数,最大到1024。必须注意的是该值必须小于或等于Memory Map Data Width。此处因输出数据格式为RGB888,设置为24

Line Buffer Depth选项可以选择MM2S通道的行缓冲深度(行缓冲区宽度为stream data 的大小),此处设置为2048即可。

Advanced界面用于配置读写通道的同步锁相模式,由于本次实验只有一个帧缓存,且只开启了读通道,多以同步锁相模式无需配置,保持默认就可以了。VDMA的配置如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_12


23.3.8 配置VDMA

配置完成后,VDMA的框图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_13


23.3.9 AXI VDMA框图

AXI VDMA部分接口说明:

S_AXI_LITE:AXI-Lite接口,PS通过这个接口配置VDMA的寄存器;

s_axi_lite_aclkAXI VDMA AXI4-Lite接口时钟;

M_AXI_MM2S:读通道存储器端映射的AXI4接口,提供对存储器(DDR4)的访问;

m_axi_mm2s_aclkAXI VDMA MM2S时钟;

M_AXIS_MM2S:读通道AXI-Stream端映射的AXI4接口,用于输出到外设;

m_axis_mm2s_aclkAXI VDMA MM2S AXIS时钟;

mm2s_introut:读通道中断输出信号。

3 添加视频时序控制器Video Timing Controller

3-1 添加Video Timing Controller,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_14


23.3.10 添加视频时序控制器

3-2 配置Video Timing Controller

在配置界面,确保勾选Include AXI4-Lite Interface,该选项将使用AXI4-Lite接口,该接口可以动态访问程序并更改处理参数,也就是说可以动态配置Video Timing Controller。勾选Enable Generation,使能Video Timing Controller生成并输出视频时序。因为本实验通过PS动态配置生成时序,所以无需勾选Enable Detection使能检测,其它选项保持默认就可以了。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_15


23.3.11 配置视频时序控制器

配置完成后,Video Timing Controller的框图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_16


23.3.12 Video Timing Controller框图

Video Timing Controller部分接口说明:

ctrl:Video Timing Controller配置接口,PS通过这个接口配置VTC的寄存器;

clk:Video核时钟;

gen_clkenVideo Timing Generator时钟使能信号,高电平有效;

vtiming_out:Video时序输出接口。

4 添加视频输出控制器Video Out

4-1 添加Video Out,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_17


23.3.13 添加视频输出控制器

4-2 配置Video Out

Clock Mode时钟模式用于指定AXI4-Stream输入和视频输出信号是使用公共时钟还是独立时钟进行时钟控制,此处我们使用独立时钟进行控制,即勾选Independent,取消勾选Common,其它选项保持默认即可。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_18


23.3.14 配置视频输出控制器

配置完成后,Video Out的框图如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_19


23.3.15 AXI4-Stream to Video Out框图

AXI4-Stream to Video Out部分接口说明:

video_inAXI-Stream接口输入数据流;

vtiming_in:Video时序输入接口;

vid_io_out:Video数据流输出接口;

vtg_ce:VTC时钟使能信号。

5 添加自定义IP

由于RGB液晶屏有多种分辨率,所以我们需要一个获取LCD屏ID的模块,我们自定义的rgb2lcd IP核可以实现该功能,且该IP核把LCD引脚封装到一起,方便引出至端口。

该IP我们放在例程(vdma_lcd)的ip_repo目录,大家可以将ip_repo文件夹拷贝到自己的工程目录下,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_20


23.3.16 自定义IP存放目录

拷贝完成后将IP核添加至工程中。打开设置界面,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_21


23.3.17 点击设置按钮

在弹出的界面中,点击左侧的IP->Repository,单击右侧的“+”按钮,添加自定义IP库,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_22


23.3.18 修改宏定义

在弹出的界面中选择自定义IP所存放的目录,本实验为F:\MPSOC\Embedded_System\vdma_lcd\ip_repo,添加完成后,如下图所示,在弹出的“Add Repository”界面中直接单击“OK”按钮即可。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_23


23.3.19 确认添加

6 添加rgb2lcd IP,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_24


23.3.20 添加RGB转LCD输出的IP

双击打开配置界面,Vid Data Width用于指定输入的RGB总线宽度,为24位。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_25


23.3.21 rgb2lcd IP核配置

rgb2lcd IP核将rgb2lcd.v文件封装成自定义的IP核,其模块代码如下:

1 module rgb2lcd #(​
2 //parameter define​
3 parameter VID_DATA_WIDTH = 24​
4 )(​
5 //VID_OUT​
6 input [VID_DATA_WIDTH-1:0] rgb_data ,​
7 input rgb_vde ,​
8 input rgb_hsync,​
9 input rgb_vsync,​
10 ​
11 input pixel_clk,​
12 ​
13 //RGB LCD​
14 output lcd_pclk ,​
15 output lcd_rst ,​
16 output lcd_hs ,​
17 output lcd_vs ,​
18 output lcd_de ,​
19 output lcd_bl ,​
20 ​
21 //AXI GPIO(LCD ID)​
22 input [2:0] lcd_id_i, ​
23 input [2:0] lcd_id_t,​
24 output [2:0] lcd_id_o,​
25 ​
26 //LCD数据引脚为双向引脚,改成三态引脚的形式 ​
27 input [VID_DATA_WIDTH-1:0] lcd_rgb_i,​
28 output [VID_DATA_WIDTH-1:0] lcd_rgb_o,​
29 output [VID_DATA_WIDTH-1:0] lcd_rgb_t​
30 );​
31 ​
32 //reg define​
33 reg [2:0] lcd_id;​
34 ​
35 //*****************************************************​
36 //** main code​
37 //*****************************************************​
38 ​
39 //LCD信号赋值​
40 assign lcd_pclk = (lcd_id == 5) ? ~pixel_clk : pixel_clk; //如果是10寸屏,输出的时钟取反​
41 assign lcd_de = rgb_vde ;​
42 assign lcd_hs = rgb_hsync;​
43 assign lcd_vs = rgb_vsync;​
44 assign lcd_bl = 1'b1;​
45 assign lcd_rst = 1'b1;​
46 ​
47 //读取LCD ID​
48 assign lcd_id_o[0] = (lcd_id_t[0]==1'b1) ? lcd_rgb_i[23] : 1'bz; //R7:M0​
49 assign lcd_id_o[1] = (lcd_id_t[1]==1'b1) ? lcd_rgb_i[15] : 1'bz; //G7:M1​
50 assign lcd_id_o[2] = (lcd_id_t[2]==1'b1) ? lcd_rgb_i[7] : 1'bz; //B7:M2​
51 ​
52 assign lcd_rgb_o = (lcd_id_t[0]==1'b1) ? {VID_DATA_WIDTH{1'bz}} :​
53 rgb_data[VID_DATA_WIDTH-1:0];​
54 assign lcd_rgb_t = {VID_DATA_WIDTH{lcd_id_t[0]}};​
55 ​
56 always @(posedge pixel_clk) begin​
57 if(lcd_id_t == 3'b111)​
58 lcd_id = lcd_id_o;​
59 end​
60 ​
61 endmodule

这个模块的代码比较简单,这里有几点需要注意。首先LCD屏的数据引脚是双向的引脚,由于在封装IP核的时候不能直接定义成inout信号(软件最终会综合成输出的引脚),因此这里需要将LCD的数据引脚定义成三个引脚。lcd_rgb_i为输入的24位LCD数据信号,用于获取LCD屏的IDlcd_rgb_o为输出到LCD屏的像素数据;lcd_rgb_t用于控制LCD数据引脚的方向。这三个信号在自定义IP核的时候增加一个GPIO的接口,即可在端口上以一个双向引脚的形式存在。

lcd_id连接至AXI GPIO,PS通过AXI GPIO来读取LCD ID。由于lcd_id的引脚方向控制着lcd_rgb数据引脚的方向(输入或者输出),因此lcd_id也要写成三个引脚的形式。

程序中第40行至45行代码为LCD的信号进行赋值,其实就是将输入的信号直接赋值给输出,这里将LCD的端口信号统一在rgb2lcd IP核中输出,方便后续对接口进行封装。

而获取LCD的ID实际上只用到了三个数据引脚,分别是lcd_rgb_i各颜色分量的高位,M0~M2分别对应lcd_rgb_i[23]lcd_rgb_i[15]lcd_rgb_i[7]

需要注意的是,由于10寸屏对时序的要求更为严格,需要调整输出的lcd时钟和数据的相位,此处如果检测到开发板连接的是10寸屏,直接对输出的时钟进行区分,相当于时钟和数据相位偏移180度。

7 添加Clocking Wizard时钟IP

不同分辨率的LCD屏,其驱动时钟频率不同,所以需要使用一个动态时钟IP核来控制时钟的输出,本章实验我们选择使用Vivado软件自带的时钟IP核,并使能其动态可配置功能,IP核的添加方式如下图所示。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_26


23.3.22 添加时钟IP

双击打开Clocking Wizard IP核,开始进行配置,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_27


23.3.23 Clocking Options配置页面

在Clocking Options选项页中,勾中“Dynamic Reconfig”选项,即使能时钟的动态重配置功能,选中之后,左侧IP核的端口中会多出一个s_axi_lite接口,PS通过这个接口来对时钟IP核进行配置,从而开发板连接不同的LCD屏,时钟IP核可以输出不同的时钟,该选项页的其它配置保持默认即可。

Output Clocks配置页面中,使能clk_out1的时钟输出,时钟选择33.3333Mhz,这里是设置时钟IP核默认输出的时钟频率,当连接了LCD屏之后,会根据LCD的ID,对时钟IP核输出的时钟频率进行重配置,配置完成后点击“OK”按钮,如下图所示。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_28


23.3.24 Output Clocks配置页面

8 添加GPIO IP

PS通过AXI GPIO获取LCD屏的ID,接下来添加AXI GPIO IP核。点击Add IP按钮,在搜索框中输入“AXI GPIO”,双击AXI GPIO IP核,将IP核添加进来,该IP核配置界面如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_29


23.3.25 配置AXI GIPIO

GPIO设置为输入,位宽设置为3位即可。

9 连线。

因为Vivado的自动连线功能不可能完全按照我们的需求进行连接,我们在进行自动连线之前,先手动连接部分接口,避免Vivado错误连接,手动连线如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_30


23.3.26 手动连线

这里是将时钟IP核输出的时钟连接至LCD屏时序控制相关的IP核,另外将rgb2lcd IP核的lcd_id端口连接至AXI GPIO IP核,以供PS读取LCD屏的ID

接下来点击界面上方“Run Connection Automation”,在弹出的对话框左侧确认勾选All Automation,然后点击“OK”按钮。

此时系统会自动生成AXI Interconnect和AXI Smartconnect。AXI Interconnect(ps8_0_axi_periph)用于桥接MPSOC处理器M_AXI_HPM0_LPD总线和外部低速外设的AXI_LITE总线;AXI Smartconnect(axi_smc)用于连接MPSOC处理器的S_AXI_HP0_FPD接口和VDMA的M_AXI_MM2S 总线。另外系统也自动生成了1个reset模块(rst_ps8_0_99M),用于复位AXI_Lite总线上的外设。

Vivado自动连线完成后,有部分信号Vivado无法自动连接,需我们手动连接,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_31


23.3.27 需手动连接的信号

10 引出rgb2lcd的输出引脚。由于在硬件上,LCD复位信号和PL复位信号直接连接在一起,因此LCD的复位端口信号不用引出,其余的LCD端口信号需要引出,如所示。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_32


23.3.28 引出引脚

整体系统架构图如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_33


23.3.29 整体系统架构连接图

这里我们的Block Design设计完成了,在Diagram窗口空白处右击,然后选择Validate Design”验证设计。验证完成后弹出对框提示Validation Successful”表明设计无误点击“OK”确认。最后按快捷键Ctrl + S”保存设计

下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。

11 添加引脚约束。

引脚约束如下:

#RGB LCD​
set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[0]}]​
set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[1]}]​
set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[2]}]​
set_property -dict {PACKAGE_PIN Y13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[3]}]​
set_property -dict {PACKAGE_PIN W12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[4]}]​
set_property -dict {PACKAGE_PIN Y12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[5]}]​
set_property -dict {PACKAGE_PIN W11 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[6]}]​
set_property -dict {PACKAGE_PIN AA12 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[7]}]​
set_property -dict {PACKAGE_PIN AC14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[8]}]​
set_property -dict {PACKAGE_PIN AD15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[9]}]​
set_property -dict {PACKAGE_PIN AC13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[10]}]​
set_property -dict {PACKAGE_PIN AD14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[11]}]​
set_property -dict {PACKAGE_PIN AE15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[12]}]​
set_property -dict {PACKAGE_PIN AA13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[13]}]​
set_property -dict {PACKAGE_PIN AE14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[14]}]​
set_property -dict {PACKAGE_PIN AB13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[15]}]​
set_property -dict {PACKAGE_PIN AG14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[16]}]​
set_property -dict {PACKAGE_PIN AB15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[17]}]​
set_property -dict {PACKAGE_PIN AH14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[18]}]​
set_property -dict {PACKAGE_PIN AB14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[19]}]​
set_property -dict {PACKAGE_PIN AG13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[20]}]​
set_property -dict {PACKAGE_PIN AE13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[21]}]​
set_property -dict {PACKAGE_PIN AH13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[22]}]​
set_property -dict {PACKAGE_PIN AF13 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb_tri_io[23]}]​

set_property -dict {PACKAGE_PIN J11 IOSTANDARD LVCMOS33} [get_ports lcd_hs]​
set_property -dict {PACKAGE_PIN K12 IOSTANDARD LVCMOS33} [get_ports lcd_vs]​
set_property -dict {PACKAGE_PIN J10 IOSTANDARD LVCMOS33} [get_ports lcd_de]​
set_property -dict {PACKAGE_PIN J12 IOSTANDARD LVCMOS33} [get_ports lcd_bl]​
set_property -dict {PACKAGE_PIN K13 IOSTANDARD LVCMOS33} [get_ports lcd_clk]​
set_property -dict {PACKAGE_PIN F10 IOSTANDARD LVCMOS33} [get_ports lcd_rst]

最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。

在生成Bitstream之后菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择Tools> Launch Vitis,启动Vitis软件。

软件设计

在将硬件导出至 Vitis,并打开 Vitis 开发环境后,创建应用工程的步骤都是一样的,这里不再赘述,新创建的空白应用工程命名为vdma_lcd

Vitis软件建一个空的应用工程,应用工程名为“vdma_lcd”。Vitis中需要添加的源代码较多,大家可以从提供的例程中拷贝Vitis的源文件,需拷贝的文件如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_34


23.4.1 Vitis源文件

在硬件设计部分我们添加了时钟Clocking Wizard IP核,这里我们通过C代码对该IP核输出的时钟频率进行重配置,从而控制该IP核生成不同频率的时钟信号以驱动不同分辨率的LCD。display_ctrl文件夹来自于Digilent的开源文件,由于其默认的VDMA配置只支持VDMA的读通道,不支持写通道,为了兼容后面摄像头的图像显示实验,本次实验对开源文件做了简单的修改,把文件中VDMA相关的代码删除,将VDMA相关API函数放在单独的vdma_api文件夹内。除此之外,display_ctrl文件夹内的函数也实现了获取LCDID的功能。

vdma_api文件夹存放了对VDMA相关操作的API函数,该文件来源于Xilinx官方提供的VDMA模板,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_35


23.4.2 VDMA模块例程

VDMA模板提供的函数默认同时打开了写通道和读通道,由于本次试验不需要打开写通道,所以对文件内的函数作了修改,通过函数输入的形参来判断是否打开读通道或者写通道。

main.c文件中的宏定义、全局变量等相关内容,代码如下:

1 #include <stdio.h>​
2 #include <stdlib.h>​
3 #include <string.h>​
4 #include "xil_types.h"​
5 #include "xil_cache.h"​
6 #include "xparameters.h"​
7 #include "xgpio.h"​
8 #include "xaxivdma.h"​
9 #include "xaxivdma_i.h"​
10 #include "display_ctrl/display_ctrl.h"​
11 #include "vdma_api/vdma_api.h"​
12 #include "clk_wiz/clk_wiz.h"​
13 ​
14 //宏定义​
15 #define BYTES_PIXEL 3 //像素字节数,RGB888占3个字节​
16 #define CLK_WIZ_ID XPAR_CLK_WIZ_0_DEVICE_ID //时钟IP核器件ID​
17 #define VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID //VDMA器件ID​
18 #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID //VTC器件ID​
19 #define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO 0(lcd_id)器件ID​
20 #define AXI_GPIO_0_CHANEL 1 //AXI GPIO(lcd_id)通道1​
21 ​
22 //函数声明​
23 void colorbar(u8 *frame, u32 width, u32 height, u32 stride);​
24 ​
25 //全局变量​
26 XAxiVdma vdma;​
27 DisplayCtrl dispCtrl;​
28 XGpio axi_gpio_inst; //PL端 AXI GPIO 驱动实例​
29 VideoMode vd_mode;​
30 //frame buffer的起始地址​
31 unsigned int const frame_buffer_addr = (XPAR_PSU_DDR_0_S_AXI_BASEADDR + 0x1000000);​
32 unsigned int lcd_id=0; //LCD ID

在代码第27行, 我们定义了一个DisplayCtrl 结构体变量 dispctrl,DisplayCtrl是在displya_ctrl.h文件下定义的结构体,包含 vtc结构体,视频分辨率video mode等信息,用于显示控制,具体定义如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_36


23.4.3 DisplayCtrl 结构体

在代码的第29行,定义了一个VideoMode结构体变量vd_modeVideoMode是在lcd_modes.h文件下定义的结构体,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_37


23.4.4 VideoMode结构体

不同分辨率的LCD屏,需要为这个结构体所包含的变量赋不同的值,具体变量的赋值方法在注释中已给出,如hps的值为LCD水平方向有效像素+行显示前沿。为了方便赋值,我们在lcd_modes.h文件下针对不同分辨率的LCD屏,分别赋值了不同的参数,在获取到LCD屏的ID直接选择对应的参数即可,如7寸LCD屏(分辨率800*480)的参数如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_38


23.4.5 7寸LCD屏(分辨率800*480)的参数

在代码的第30行定义了VDMA的帧缓存区域位于DDR4中的起始地址(frame_buffer_addr),写彩条和VDMA的读通道都是从这个起始地址开始操作的。

main.c文件中的函数代码如下:

34 int main(void)​
35 {​
36 XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID); //GPIO初始化​
37 XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x07); //设置AXI GPIO为输入​
38 lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL); //获取LCD的ID​
39 XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x00); //设置AXI GPIO为输出​
40 xil_printf("LCD ID: %x\r\n",lcd_id);​
41 ​
42 //根据获取的LCD的ID号来进行video参数的选择​
43 switch(lcd_id){​
44 case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率​
45 case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率​
46 case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率​
47 case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率​
48 case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率​
49 default : vd_mode = VMODE_800x480; break;​
50 }​
51 ​
52 //配置VDMA​
53 run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,​
54 frame_buffer_addr,0, 0,ONLY_READ);​
55 ​
56 //设置时钟IP核输出的时钟频率​
57 clk_wiz_cfg(CLK_WIZ_ID,vd_mode.freq);​
58 //初始化Display controller​
59 DisplayInitialize(&dispCtrl, DISP_VTC_ID);​
60 //设置VideoMode​
61 DisplaySetMode(&dispCtrl, &vd_mode);​
62 DisplayStart(&dispCtrl);​
63 ​
64 //写彩条​
65 colorbar((u8*)frame_buffer_addr, vd_mode.width,​
66 vd_mode.height, vd_mode.width*BYTES_PIXEL);​
67 return 0;​
68 }

在main函数中,我们首先需要获取LCD屏的ID,才能对VDMA、VTC和时钟做配置,如代码中第36行至第40行代码所示。PS通过AXI GPIO获取LCD屏的ID,因此先对AXI GPIO做初始化,然后将AXI GPIO设置成输入,LTDC_PanelID_Read()函数返回的值即为LCD屏的IDLTDC_PanelID_Read()函数根据读取到的AXI GPIO的引脚数据映射成16位的值,以方便指示当前的LCD屏尺寸和分辨率。当AXI GPIO引脚为000时,函数返回0x4342,表示LCD屏为4.3寸屏,分辨率为480*272;001对应0x7084;010对应0x7016;100对应0x4384;101对应0x1018。在获取到LCD ID后,将AXI GPIO设置成输出,此时可以开始驱动LCD屏。

在main函数的第42行至第50行代码,根据不同的LCD屏ID,为vd_mode结构体变量赋不同的值。接下来通过run_vdma_frame_buffer()函数配置VDMA,包括配置帧缓存地址、读通道配置、开启读通道等,如程序中第53行和第54行代码所示。需要说明的是,该函数输入的最后一个参数用来配置VDMA的读写通道,当该参数为ONLY_READ时表示只开启读通道;ONLY_WRITE表示只开启写通道;BOTH表示既开启读通道,也开启写通道。

在main函数的第56行至第62行代码对时钟和VTC进行配置,并启动VTC开始运行。在main函数的第65和第66行,通过colorbar()函数向帧缓存区域中写入彩条数据,写彩条的函数同样位于main.c文件中,代码如下:

70 //写彩条函数(彩虹色)​
71 void colorbar(u8 *frame, u32 width, u32 height, u32 stride)​
72 {​
73 u32 color_edge;​
74 u32 x_pos, y_pos;​
75 u32 y_stride = 0;​
76 u8 rgb_r, rgb_b, rgb_g;​
77 ​
78 color_edge = width * BYTES_PIXEL / 7;​
79 for (y_pos = 0; y_pos < height; y_pos++) {​
80 for (x_pos = 0; x_pos < (width * BYTES_PIXEL); x_pos += BYTES_PIXEL) {​
81 if (x_pos < color_edge) { //红色​
82 rgb_r = 0xFF;​
83 rgb_g = 0;​
84 rgb_b = 0;​
85 } else if ((x_pos >= color_edge) && (x_pos < color_edge * 2)) { //橙色​
86 rgb_r = 0xFF;​
87 rgb_g = 0x7F;​
88 rgb_b = 0;​
89 } else if ((x_pos >= color_edge * 2) && (x_pos < color_edge * 3)) { //黄色​
90 rgb_r = 0xFF;​
91 rgb_g = 0xFF;​
92 rgb_b = 0;​
93 } else if ((x_pos >= color_edge * 3) && (x_pos < color_edge * 4)) { //绿色​
94 rgb_r = 0;​
95 rgb_g = 0xFF;​
96 rgb_b = 0;​
97 } else if ((x_pos >= color_edge * 4) && (x_pos < color_edge * 5)) { //青色​
98 rgb_r = 0;​
99 rgb_g = 0xFF;​
100 rgb_b = 0xFF;​
101 } else if ((x_pos >= color_edge * 5) && (x_pos < color_edge * 6)) { //蓝色​
102 rgb_r = 0;​
103 rgb_g = 0;​
104 rgb_b = 0xFF;​
105 } else if ((x_pos >= color_edge * 6) && (x_pos < color_edge * 7)) { //紫色​
106 rgb_r = 0x8B;​
107 rgb_g = 0;​
108 rgb_b = 0xFF;​
109 }​
110 frame[x_pos + y_stride + 0] = rgb_b;​
111 frame[x_pos + y_stride + 1] = rgb_g;​
112 frame[x_pos + y_stride + 2] = rgb_r;​
113 }​
114 y_stride += stride;​
115 }​
116 Xil_DCacheFlush(); //刷新Cache,数据更新至内存​
117 xil_printf("show color bar\r\n");​
118 }

colorbar()函数实现了一个彩虹色(红橙黄绿青蓝紫)的七色彩条。在函数的最后通过调用Xil_DCacheFlush()函数将缓存在DataCache中的数据刷新到DDR4中,以便VDMA进行读取

最后我们来着重介绍下,如果对时钟IP核进行动态重配置。在Block Design设计界面中,我们勾选了时钟IP核的动态重配置功能,此时该IP核的接口中多了一个AXI4-Lite的从机接口,通过AXI互联连接到了PS的M_AXI_HPM0_LPD接口,我们在PS编写的C代码,就是通过对时钟IP核的寄存器进行修改,最终通过AXI接口实现对时钟IP核的重配置功能。由于Vitis并没有提供对时钟进行重配置的库函数,因此需要对时钟IP核的寄存器比较熟悉才行,关于时钟IP核寄存器的详细介绍,请参考Xilinx官方手册pg065(pg065-Clocking Wizard LogiCORE IP Product Guide)。

clk_wiz.h源代码中定义了对时钟进行重配置的寄存器地址偏移量,以及函数的声明,代码如下:

1 #ifndef CLK_WIZ_H_​
2 #define CLK_WIZ_H_​
3 ​
4 #include "xil_types.h"​
5 ​
6 #define CLK_SR_OFFSET 0x04 //Status Register​
7 #define CLK_CFG0_OFFSET 0x200 //Clock Configuration Register 0​
8 #define CLK_CFG2_OFFSET 0x208 //Clock Configuration Register 2​
9 #define CLK_CFG23_OFFSET 0x25C //Clock Configuration Register 23​
10 ​
11 void clk_wiz_cfg(u32 clk_base_addr,double freq);​
12 ​
13 #endif /* CLK_WIZ_H_ */

由代码中第6至9行代码可知,程序中只配置了4个寄存器,即可实现对时钟进行重配置,寄存器描述如下:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_39


23.4.6 寄存器描述1

Software Reset Register (SRR)的基地址偏移量为0x00,用于实现对时钟IP核软复位的功能。

Status Register (SR)的基地址偏移量为0x04,用于判断时钟IP核输出的时钟是否稳定,即Locked信号是否为1。当Locked信号等于1时,表示时钟输出稳定;而当Locked等于0时,表示时钟IP核正在进行重配置。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_40


23.4.7 寄存器描述2

Clock Configuration Register 0的基地址偏移量为0x200,用于设置输出时钟的分频/倍频系数。其中Bit[7:0]表示分频系数(DIVCLK_DIVIDE);Bit[15:8]表示倍频系数的整数部分(CLKFBOUT_MULT);Bit[25:16]表示倍频系数的小数部分(CLKFBOUT_FRAC)。需要注意的是,该寄存器设置的分频/倍频系数,对时钟IP核输出的所有时钟都有效。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_41


23.4.8 寄存器描述3

Clock Configuration Register 2的基地址偏移量为0x208,用于设置CLKOUT0时钟的分频系数。Bit[7:0]表示分频系数的整数部分(CLKOUT0_DIVIDE);Bit[17:8]表示倍频系数的小数部分(CLKOUT0_FRAC);该寄存器仅用于配置clkout0的分频系数,即clkout0最终输出的时钟频率是先在Clock Configuration Register 0的基础上进行分频/倍频,再通过Clock Configuration Register 2进行分频得到的。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_42


23.4.9 寄存器描述4

Clock Configuration Register 23的基地址偏移量为0x25C,用于加载动态重配置的参数,在配置完分配/倍频系数后,必须通过此寄存器加载重配置的参数才行,否则配置的参数无法生效。当Bit[0]等于1时,表示加载重配置的参数。而Bit[1]用于选择重配置的参数,当Bit[1]等于1时,重配置的参数来源于前面的配置寄存器;当Bit[1]等于0时,重配置的参数来源于Clocking Wizard GUI界面。

对于本次实验来说,只需要了解以上寄存器,即可实现对时钟的重配置功能。配置步骤为:

  1. 先配置整体时钟的分频/倍频系数,通过Clock Configuration Register 0寄存器进行配置;
  2. 再配置所需时钟的分频系数(本次实验只用到了一个时钟,即CLKOUT0),通过Clock Configuration Register 2寄存器进行配置;
  3. 接下来加载重配置的参数,由Clock Configuration Register 23寄存器进行加载;
  4. 最后判断时钟是否重配置完成,由Status Register (SR)寄存器的Bit[0](Locked)进行判断。

在熟悉了时钟IP核的重配置寄存器之后,再来看时钟配置的代码就比较容易了,clk_wiz.c代码如下:

1 #include "xclk_wiz.h"​
2 #include "clk_wiz.h"​
3 #include "xparameters.h"​
4 ​
5 #define CLK_WIZ_IN_FREQ 100 //时钟IP核输入100Mhz​
6 ​
7 XClk_Wiz clk_wiz_inst; //时钟IP核驱动实例

"xclk_wiz.h"是Vitis提供的关于时钟IP核相关的库函数头文件,"clk_wiz.h"是我们自己编写的用于定义时钟相关寄存器的头文件。程序中第5行代码定义了Clocking Wizard输入源的时钟频率,为100Mhz;第7行代码声明了结构体的变量,表示时钟IP核的驱动实例。

9 //时钟IP核动态重配置​
10 //参数1:时钟IP核的器件ID​
11 //参数2:时钟IP核输出的时钟 单位:MHz​
12 void clk_wiz_cfg(u32 clk_device_id,double freq){​
13 double div_factor = 0;​
14 u32 div_factor_int = 0,dviv_factor_frac=0;​
15 u32 clk_divide = 0;​
16 u32 status = 0;​
17 ​
18 //初始化XCLK_Wiz​
19 XClk_Wiz_Config *clk_cfg_ptr;​
20 clk_cfg_ptr = XClk_Wiz_LookupConfig(clk_device_id);​
21 XClk_Wiz_CfgInitialize(&clk_wiz_inst,clk_cfg_ptr,clk_cfg_ptr->BaseAddr);​
22 ​
23 if(freq <= 0)​
24 return;​
25 //配置时钟倍频/分频系数​
26 XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG0_OFFSET,0x00000a01); //10倍频,1分频​
27 //计算分频系数​
28 div_factor = CLK_WIZ_IN_FREQ * 10 / freq;​
29 div_factor_int = (u32)div_factor;​
30 dviv_factor_frac = (u32)((div_factor - div_factor_int) * 1000);​
31 clk_divide = div_factor_int | (dviv_factor_frac<<8);​
32 //配置分频系数​
33 XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG2_OFFSET,clk_divide);​
34 //加载重配置的参数​
35 XClk_Wiz_WriteReg(clk_cfg_ptr->BaseAddr,CLK_CFG23_OFFSET,0x00000003);​
36 //获取时钟IP核的状态,判断是否重配置完成​
37 while(1){​
38 status = XClk_Wiz_ReadReg(clk_cfg_ptr->BaseAddr,CLK_SR_OFFSET);​
39 if(status&0x00000001) //Bit0 Locked信号​
40 return ;​
41 }​
42 }

时钟IP核动态重配置函数共有两个输入参数,分别为时钟IP核的器件ID和时钟IP核输出的时钟。程序中第19行至21行代码根据时钟IP核的器件ID,对时钟IP核进行初始化,这个操作和初始化其它外设是一样的。

程序中第26行代码通过Clock Configuration Register 0寄存器配置时钟的分频/倍频系数,此处配置成10倍频,1分频。由于输入的时钟源为100Mhz,此处相当于先对时钟做10倍频,即1000Mhz。

程序中第28至31行代码根据1000Mhz和需要输出的时钟频率,计算需要配置的分频系数,并得到分频系数的整数部分和小数部分。程序中第33行代码通过Clock Configuration Register 2寄存器配置CLKOUT0的分频系数。程序中第35行代码通过Clock Configuration Register 23寄存器加载重配置的参数。

程序的最后,通过Status Register (SR)寄存器来获取status的值,判断时钟的重配置是否完成,当status的最低位等于1时,表示重配置完成。

程序设计完成后,对代码进行编译。编译完成控制(Console)出现提示信息“Build Finished”,同时在应用工的Binaries目录下可以看到生成的elf文件。

下载验证

首先我将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接然后使用USB连接线将USB UART接口(PS_PORT)与电脑连接,用于口通信。接下来使用FPC排线一端与RGB LCD液晶屏上的接口连接,另一端连接开发板上的RGB LCD接口,如23.5.123.5.2所示。连接时,将黑色翻盖的两侧同时往上拉,将 FPC 排线蓝色面朝黑色翻盖方向插入,最后将黑色盖两侧压下以固定 FPC 排线。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_43


23.5.1 RGB LCD液晶屏

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_IP_44


23.5.2 开发板连接图

打开Vitis Terminal终端,设置连接串口。然后下载本次实验程序下载完成后,在下方的Terminal中可以看到应用程序打印的信息,如下图所示:

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_ide_45


23.5.3 Terimnal打印的信息

由于本次实验连接的是7寸液晶屏(分辨率:800*480),因此串口打印的LCD ID为7084。接下来观察RGB LCD液晶屏,可以看到,LCD上显示出彩条,彩条颜色从左到右依次为红橙黄绿青蓝紫,与写入的彩虹色数据相同,如23.5.4所示。

《DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南》第二十三章 PS通过VDMA驱动LCD显示实验_缓存_46


23.5.4 RGB LCD液晶屏显示彩条

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月12日 0

暂无评论

推荐阅读
  Vn37uiKQIsdv   2023年12月06日   13   0   0 网络层链路IP
  n4CHhQlV5v8U   2023年11月19日   13   0   0 服务器sip运营商IP
  tprTMCWDkFAR   2023年12月06日   23   0   0 用户名APIIP
  xWYnr39PTA9E   2023年11月19日   15   0   0 服务器UserIP
IP
  xWYnr39PTA9E   2023年11月19日   19   0   0 子网掩码子网IP
95kVyaJuybju