软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述本文对 Xilinx 提供的一款 IP 核—AXI VDMA(Video Direct Memory Access) 进行详细讲解,首先分析 VDMA 应用意义;然后详细介绍 VDMA 的特点、寄存器作空间;最后阐述如何使用 VDMA,包括 IP 核的配置方法、代码编写流程等。 VDMA IP可以从PS DDR中读取数据,然后以AXI-Stream流的方式输出。虽然使用DMA和FDMA也可以从PS DDR中读取数据,但是使用VDMA输出的优势在于LINUX下使用的时候,具有现成的驱动可以使用。 本文在PS DDR中开辟了一段缓存地址用于VDMA的视频缓存,通过PS的ARM写入测试图形数据,测试图形就会在显示器或者液晶屏上显示。 PS DDR中的数据也可以通过HDMI通道输出,考虑到2CG板卡并没有自带的HDMI输出接口,所以本文仅介绍使用VDMA输出至LCD屏以及HDMI输出两种输出方式。如果用户在学习的过程中想使用HDMI输出,可以参考我们的教程购买HDMI子卡自行修改Demo。 本文实验目的: 1:掌握基于VDMA IP的图像显示系统的搭建方法可以参考“附录2” 2:了解点缓存方式图像的撕裂问题 3:掌握VDMA polled模式的基本使用 4:掌握米联客 HDMI IP 在 ZYNQ 中的使用
5:掌握米联客7寸液晶屏驱动显示 2系统框图
如下图所示,该方案中 PS 的 CPU 往 DDR 中绘制测试图形,VDMA 自主从 PS DDR 中读取绘制的图形数据,发送给 AXI4-Stream to Video Out IP(简称 VID OUT),VID OUT IP 把 Stream 流数据转为 RGB 时序给 HDMI 输出 IP,这样就能驱动 HDMI 显示器了。
这里还用到了 Video Timing Controller IP(简称 VTC),该 IP 产生 RGB 视频时序,和VID OUT IP 配合实现 AXIStream 视频数据流转为 RGB 视频数据。
如下图所示,该方案中PS 的CPU往DDR中绘制测试图形,VDMA自主从PS DDR中读取绘制的图形数据,发送给AXI4-Stream to Video Out IP(简称VID OUT),VID OUT IP 把Stream流数据转为RGB时序给LVDS输出IP,这样就能驱动LCD显示器了。 这里还用到了Video Timing Controller IP(简称VTC),该IP产生RGB视频时序,和VID OUT IP配合实现AXI-Stream视频数据流转为RGB视频数据。 米联客7寸液晶屏可以支持LVDS(必须修改BANK电压支持LVDS通信,如果有不清楚请咨询米联客技术支持)接口或者RGB接口,比如LVDS接口的系统框图中也是把RGB时序转为LVDS驱动液晶屏。
如果是使用RGB接口可以直接用RGB时序驱动液晶屏.
3硬件电路分析硬件接口和子卡模块请阅读“附录1” 配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。 4搭建SOC系统工程详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 4.1Zynq IP PS部分设置本文中的PS设置内容是新增加的配置部分,关于DDR、MIO、CPU时钟等设置请参考“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 1:PS复位设置
2:设置 PS GT Master 接口和 HP Slave 接口
3:设置PL到PS的中断Interurptsà勾选 Fabric Interrupt,勾选IRQ_F2P[15:0]。
4:设置 PL 的时钟勾选FCLK_CLK0,设置为100,即PS的PLL提供本系统的时钟100MHZ。
5:ZYNQ IP设置完成后下图中IRQ_F2P[0:0]会根据xconcat IP自动扩展
4.2添加IP
以实现 HDMI 输出为演示 demo 说明,需要添加的 IP 如下。以下 IP 是已经完成配置后的形态:
1:VDMA IPAXI Video Direct Memory Access该IP简称VDMA IP 可以用于AXI-Stream流视频输入和输出,以下对使用到的配置参数做一些简单介绍,更多关于VDMA IP的介绍阅读“附录:2”介绍 本方案中只用到Read通道,因此只需要使能read 通道。 Address Width 设置AXI4总线可以访问的地址范围,对于32bit系统设置32即可 FrameBuffers 设置3,最大可以支持3帧缓存 Memory Map Data Width 用于设置AXI4接口的数据位宽,位宽越大通信速度越高,但是总线占用率也越高 Read Burst Size 设置读通道的AXI4 Burst长度 Stream Data Width 设置AXI4-Stream接口的数位宽,这里是32bit对齐 Line Buffer Depth设置FIFO大小,这里设置512 对于写通道的定义一样,这里没用到,也就不介绍了。
这里写通道没有使用依然不设置,Fsync Options设置
2:VTC IPVideo Timming Controller该IP简称VTC IP用于产生HS VS等视频时序给VID OUT IP用 VTC IP可以支持AXI-LITE接口通过PS动态修改分辨率,但是我们这里只设置固定的分辨率
HDMI 液晶屏这里的分辨率设置 720P
米联客的7寸液晶屏 分辨率是1024x600设置如下:
3:VIDOUT IP AXI4-Stream to Video Out该IP简称VID OUT IP 用于把AXI-Stream 视频流转为RGB视频时序输出,该IP设置如下 Pixels Per Clock 设置1个像素多少个时钟 Video Format 设置为RGB AX4S Video Input Component Width设置视频格式的数据位宽,对于RGB 888 这里是8 Native Video Output Component Width 主要参数设置RGB接口,FIFO深度1024,T
4:uihdmitx IP
该 IP 为米联客自定义的用 FPGA 自带 serdes 完成 HDMI 输出的 IP
5:AXI Interconnect IP用于互联AXI4总线接口的IP,本方案用的2个interconnect ip 设置一样
6:Clocking Wizard IP
以下内容介绍 HDMI 显示器 720P 以及米联客 7 寸液晶屏的分辨率对应时钟设置
对于 HDMI 液晶屏,需要设置 2 个时钟,并且是 5 倍关系,设置 720P 分辨率时钟用 74.25M
对于7寸液晶屏,分LVDS接口用法和RGB接口用法,1024*640分辨率RGB接口时钟设置如下:
对于7寸液晶屏,分LVDS接口用法和RGB接口用法,1024*640分辨率LVDS接口时钟设置如下:
4.3PL图形编程
1:7寸液晶RGB接口FPGA IO BANK 电压为 3.3V 使用 FEP-BASE-CARD-3.3V 接口卡
2:7寸液晶LVDS接口FPGA IO BANK 电压为 2.5V 或者 1.8V,需要匹配使用 2.5V 的 FEP-BASE-CARD-2.5V 接口卡或者 1.8V 的 FEP- BASE-CARD-1.8V 接口卡
3:HDMI 输出编程
4地址空间分配
5编译并导出平台文件1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:生成Bit文件。 4:导出到硬件: FileàExport HardwareàInclude bitstream 5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
5搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 5.1创建SDK Platform工程
5.2创建vdma APP测试工程Vdma_out_test APP工程为单缓存方式
6程序分析本方案路径下也提供了7寸液晶屏的方案源码。 6.1vdma_out_test程序- /********************MILIANKE**************************
- *Company : MiLianKe Electronic Technology Co., Ltd.
- *WebSite:https://www.milianke.com
- *TechWeb:https://www.uisrc.com
- *tmall-shop:https://milianke.tmall.com
- *jd-shop:https://milianke.jd.com
- *taobao-shop1: https://milianke.taobao.com
- *Create Date: 2021/10/15
- *File Name: vdma_out_test.c
- *Description:
- *Declaration:
- *The reference demo provided by Milianke is only used for learning.
- *We cannot ensure that the demo itself is free of bugs, so users
- *should be responsible for the technical problems and consequences
- *caused by the use of their own products.
- *Copyright: Copyright (c) MiLianKe
- *All rights reserved.
- *Revision: 1.0
- ****************************************************/
- #include "xil_exception.h"
- #include "xil_printf.h"
- #include "xil_cache.h"
- #include "vdma_pl.h"
- #include "sleep.h"
- extern XAxiVdma VDMA; //定义VDMA
- extern XAxiVdma_DmaSetup WriteCfg; //定义VDMA写通道结构体
- extern XAxiVdma_DmaSetup ReadCfg; //定义VDMA
- u32 GFrame[BUFFERSIZE] __attribute__ ((__aligned__(256))); //定义图像缓存,自动分配地址
- int main()
- {
- u8 mode_s =0;
- u32 vcnt,hcnt;
- u32 *ARGB = (u32*) GFrame;
- sleep(1);
- ReadCfg.FrameStoreStartAddr[0] = (UINTPTR)GFrame;//获取缓存地址,这里只设置1帧缓存
- MM2S_ReadSetup(XPAR_AXIVDMA_0_DEVICE_ID, &VDMA, ReadCfg);//设置读通道参数
- xil_printf("VDMA Generic Video start! r\n");
- //产生测试图形
- while(1)
- {
- for(vcnt =0 ; vcnt < 720 ; vcnt++)
- {//video color test
- for(hcnt = 0; hcnt < 1280; hcnt++)
- {
- switch(mode_s)
- {
- case 0: ARGB[hcnt+vcnt*1280] = 0x000000ff;break; //纯蓝
- case 1: ARGB[hcnt+vcnt*1280] = 0x0000ff00;break; //纯绿
- case 2: ARGB[hcnt+vcnt*1280] = 0x00ff0000;break; //纯红
- case 3: ARGB[hcnt+vcnt*1280] = 0x00ff0000;break; //纯红
- case 4: //彩条
- if(hcnt<200 ) ARGB[hcnt+vcnt*1280] = 0x000000ff;
- else if(hcnt<400 ) ARGB[hcnt+vcnt*1280] = 0x0000ff00;
- else if(hcnt<600 ) ARGB[hcnt+vcnt*1280] = 0x00ff0000;
- else if(hcnt<800 ) ARGB[hcnt+vcnt*1280] = 0x00ff00ff;
- else if(hcnt<1000) ARGB[hcnt+vcnt*1280] = 0x0000ffff;
- else if(hcnt<1200) ARGB[hcnt+vcnt*1280] = 0x00ffff00;
- else if(hcnt<1400) ARGB[hcnt+vcnt*1280] = 0x00ffffff;
- break;
- case 5: //黑白方格
- if((hcnt&0x10)^(vcnt&0x10))
- ARGB[hcnt+vcnt*1280] = 0x00000000;
- else
- ARGB[hcnt+vcnt*1280] = 0xffffffff;
- break;
- default:mode_s = 0;break;
- }
- }
- }
- //更新cache 数据到DDR
- Xil_DCacheFlushRange((u32)(GFrame), 1280*720*4);
- if(mode_s <5 ) mode_s ++;
- else mode_s =0;
- sleep(1);
- }
- return XST_SUCCESS;
- }
复制代码
这个程序中定义了1个图像缓冲区,缓存区的大小是1280*720*4,由于我们在VDMA IP中设置了1个缓存,所以这里我们可以把上面定义的1个缓存的地址赋值给ReadCfg.FrameStoreStartAddr参数: ReadCfg.FrameStoreStartAddr[0] = (UINTPTR)GFrame;//获取缓存地址,这里只设置1帧缓存 以下我们重点看下vdma_pl.c程序。
6.2vdma_pl程序分析这个程序中2个函数MM2S_ReadSetup和S2MM_WriteSetup分别代表了VDMA读操作配置和VDMA写操作配置,这两个程序配置过程一样,我们这里也只使用到MM2S_ReadSetup函数用于初始化VDMA的读通道。 - int MM2S_ReadSetup(u16 DeviceID, XAxiVdma * InstancePtr , XAxiVdma_DmaSetup ReadCfg)
- {
- int Status;
- XAxiVdma_Config *Config;
- ReadCfg.VertSizeInput = MM2S_V; /*设置视频图形的垂直像素大小*/
- ReadCfg.HoriSizeInput = MM2S_H*4; /*设置视频图形的水平像素大小*/
- ReadCfg.Stride = MM2S_H*4; /*设置视频图形的水平Stride大小*/
- ReadCfg.FrameDelay = 0; /*设置帧延迟*/
- ReadCfg.EnableCircularBuf = 0; /* 是否使能circular 缓存模式 */
- ReadCfg.EnableSync = 1; /* 使能Gen-Lock模式 */
- ReadCfg.PointNum = 1; /* 与Master同步*/
- ReadCfg.EnableFrameCounter = 0; /* 帧计数器使能 */
- Config = XAxiVdma_LookupConfig(DeviceID);
- if (NULL == Config) {
- xil_printf("XAxiVdma_LookupConfig failure\r\n");
- return XST_FAILURE;
- }
- /* 从SDK参数定义中获取配置参数 */
- Status = XAxiVdma_CfgInitialize(InstancePtr, Config, Config->BaseAddress);
- if (Status != XST_SUCCESS) {
- xil_printf("XAxiVdma_CfgInitialize failure\r\n");
- return XST_FAILURE;
- }
- /*设置VDMA寄存器*/
- Status = XAxiVdma_DmaConfig(InstancePtr, XAXIVDMA_READ, &ReadCfg);
- if (Status != XST_SUCCESS) {
- xil_printf("Read channel config failed %d\r\n", Status);
- return XST_FAILURE;
- }
- /* Set the buffer addresses for transfer in the DMA engine
- * The buffer addresses are physical addresses
- */
- Status = XAxiVdma_DmaSetBufferAddr(InstancePtr, XAXIVDMA_READ,ReadCfg.FrameStoreStartAddr);
- if (Status != XST_SUCCESS) {
- xil_printf("Read channel set buffer address failed %d\r\n", Status);
- return XST_FAILURE;
- }
- //start vdma
- Status = XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_READ);
- if (Status != XST_SUCCESS) {
- xil_printf(
- "Start read transfer failed %d\r\n", Status);
- return XST_FAILURE;
- }
- return XST_SUCCESS;
- }
复制代码
1:参数配置除了main函数中对地址空间的参数做了配置,以下几个参数分别是行像素大小、垂直像素大小、Stride参数大小。以及用于MM2S VDMA 控制寄存器中相关参数的初始化,包括延迟中断使能设置为0、Circular_Park模式设置0代表VDMA的帧地址无法自动切换,这种设置可以在1个缓存或者使用parked模式下使用。GenlockEn设置为1,由于这里只有VDMA读通道实际这个也起不到作用。 由于这里只有读通道,也不涉及2个VDMA通道之间的同步,所以帧延迟设置为0
2:Config = XAxiVdma_LookupConfig(DeviceID)函数这个函数通过VDMA IP中配置获取VDMA的配置参数。
3:XAxiVdma_CfgInitialize(InstancePtr, Config, Config->BaseAddress)函数以用Config参数来配置XDMA的指针参数。别看这么一堆参数,实际上就是赋值参数到VDMA指针,后面主要用VDMA指针来管理这些参数信息。
4:XAxiVdma_DmaConfig(InstancePtr, XAXIVDMA_READ, &ReadCfg)函数先追踪下这个函数,如下图:
Channel = XAxiVdma_GetChannel(InstancePtr, Direction)函数其实干上面事情,就是判断下之前的XDMA参数中是否有设置读通道,利用XILINX的库函数就是有这种显得非常累赘做法。低效代码非常多。真要吐槽下。
XAxiVdma_ChannelConfig(Channel, (XAxiVdma_ChannelSetup *)DmaConfigPtr)函数,这个函数中才有一些货真价实的东西。我们看下关键的三个寄存器,分别是MMS控制寄存器,MMS_Hsize寄存器,MMS_Stride寄存器
5:XAxiVdma_DmaSetBufferAddr(InstancePtr, XAXIVDMA_READ,ReadCfg.FrameStoreStartAddr)函数
XAxiVdma_ChannelSetBufferAddr(Channel, BufferAddrSet,Channel->NumFrames)函数中设置帧缓存地址,这里最大支持3帧,我们前面的main函数也定义三帧图像的起始地址。
6:XAxiVdma_DmaStart(InstancePtr, XAXIVDMA_READ)函数这个函数启动VDMA传输,
XAxiVdma_ChannelStart(Channel)函数首选判断通道是否已经启动,通过写控制寄存器启动VDMA.
之后通过更新MMS_vsize寄存器,实现VDMA的MMS启动。这里注意MMS_vsize寄存器需要最后一个写。
7:XAxiVdma_StartParking(&VDMA, mode_s,XAXIVDMA_READ)函数这个函数中通过PARK寄存器设置那一帧输出到MMS
7 HDMI方案演示本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF) 7.1硬件准备本方案中,没有使用到 TF 卡
7.2实验结果
8液晶屏LVDS方案演示MLK-H3-CZ08-7100FC 开发板默认 FEP 扩展接口使用 1.8V 接口,配 1.8V 的扩展模块。 8.1硬件准备
下图中,SD 卡不是必须的
8.2实验结果
|