ZYNQ图像处理(1)——vdma_hdmi显示环境搭建

1、引言

FPGA是一种现场可编程逻辑门阵列,其并行的特点让其在图像处理、数字通信等领域有广泛的应用。FPGA缺点是不擅长流程控制,对于IIC、SPI等通信方式,往往需要用到状态机。ZYNQ7000是赛灵思推出的一款带有ARM核的FPGA,包含了两个ARM A9的核以及FPGA资源,分为PS端和PL端。在运用ZYNQ做图像处理之前,有必要先搭建环境。我用的是ZYNQ7010,vivado是2018.3版本的。

2、显示环境硬件搭建

2.1 工程框架

利用ZYNQ7010显示图像,其工程的框架我们可以参照赛灵思应用手册xapp742,手册中有这样一张图,描述了 整个工程的各个模块。它是基于FPGA K7开发板搭建的,其实对于ZYNQ也相似。VDMA是video direct memory access,是一种可以将数据从地址映射转为数据流的IP核,RGB to YCrCb、OSD等式图像处理模块,这里未用到,VTC和Stream to video bridge是一起使用的,VTC是动态时钟管理,用于给Stream to video bridge提供时钟信号,Stream to video bridge则是将VDMA的数据流转换成RGB格式可以供LCD屏或者HDMI屏幕显示,具体可以看手册。
在这里插入图片描述

2.2 设计目标和工程建立

这里我们需要先在PS端往DDR中写入数据,之后通过VDMA在hdmi显示屏上显示一个彩条图案。在知道了设计的基本框架和目标后,我们画出这个工程的简图,之后创立vivado工程。
这里我自己画了一个工程的简化后的框图,PS端主要完成了DDR的配置,以及串口通信还有往DDR中写入彩条数据。PL端与PS端通过GP和HP接口进行交互,HP接口读取DDR中数据,传输到VDMA,VDMA实现地址映射到数据流转换,VIDEO OUT实现axi数据流到显示屏可以显示的数据转换。这里GP接口是配置,VDMA的一些参数,VTC和PLL分别是时钟配置和锁相环。
在这里插入图片描述
工程这边就按照自己的开发板型号来建立即可,之间创建block design,第一步添加ZYNQ IP核如下。需要对他做一些简单的配置,包括DDR、UART等,DDR和串口根据自己板子型号设置即可,这边为了能让传输快一些,将FCLK时钟频率改为了100M,此外,还添加了HP接口来作为数据的传输接口。
在这里插入图片描述
修改完成之后的zynq IP核如下图所示。
在这里插入图片描述

2.3 VDMA ip核简介和配置

创建完了工程后,根据框架简图,首先添加VDMA IP核如下图所示。关于VDMA IP核的使用可以参考塞琳思的手册pg020。
在这里插入图片描述
AXI VDMA IP核提供了一种高带宽的地址映射到数据流的转换,这个IP的基本框图如下图所示,左边是地址映射接口,可以将DDR数据读取出来,右边是数据流格式,AXI4-Lite可以对IP核进行配置。
在这里插入图片描述

2.4 VTC ip核与Video OUT ip核简介与配置

这两个IP核的参考手册分别是PG016和PG044。针对手册内容,不在过多叙述,这里直接vivado添加了这两个IP。Video out ip如下图所示,其主要接口为video_in:接入vdma输出的数据流,vtiming_in:用于接收VTC IP给出的时序,此外,还有两组时钟和复位信号。主要的输出端口vid_io_out:输出LCD屏幕可以显示的信号,包括行同步、场同步等。
在这里插入图片描述
在这里插入图片描述
此外,还需要添加VTC模块如下图所示。其主要输入接口包括了时钟、复位、使能,输出包括时序信号输出等。对它的配置如下,首先是取消勾选了AXI接口,因这里没有用到;然后关闭了detection。之后,因为这这边显示屏是768p的,所以选择1024×768。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在添加完了IP后,继续在block design中连线完成设计,连接完的框图如下图所示。将VDMA的流数据接到Video out,VTC为Video out提供显示的时序信号。Video out的aclk为100M,此外还注意到,video out和VTC还有一个显示屏显示时钟,这个时钟得根据不同的显示屏来确定。
在这里插入图片描述

2.5 PLL ip和DVI ip添加和配置

因为显示屏显示是需要具体的时钟信号,所以我们这里手动添加了一个PLL ip核,用于给VTC和Video out提供显示时钟。除此之外,Video out的时序是LCD的,还需要添加一个自定义的IP核来完成lcd到hdmi的转换。添加的两个IP如下图所示。这边用锁相环产生了两个时钟,一个是65MHz,对应我的显示屏,另一个是325MHz,是65MHz的5倍,满足hdmi的时序对时钟信号的要求。最后完成连线如下图所示,之后对工程综合实现生成bit流导入到sdk进行软件设计。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、显示环境软件程序

整个程序也十分简单,在sdk中导入vdma显示的例程。利用run_triple_frame_buffer函数初始化VDMA,初始化的参数包括了VDMA实例、VDMA的ID、宽度、高度、vdma起始地址。之后写入彩条数据即可。

#include "stdio.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "vdma_api.h"
#include "xil_cache.h"

#define VDMA_ID	XPAR_AXIVDMA_0_DEVICE_ID
#define BASE_ADDR	XPAR_PS7_DDR_0_S_AXI_BASEADDR
#define WIDTH	1024
#define DEPTH	768

int vdma_addr=(BASE_ADDR+0x1000000);

int main()
{
	int i,j;
	u8* vdma_buffer;
	XAxiVdma vdma_inst;
	vdma_buffer=(u8*) vdma_addr;

	run_triple_frame_buffer(&vdma_inst,VDMA_ID, WIDTH,DEPTH,vdma_addr,0,0);
	for(j=0;j<DEPTH;j++)
	{
		for(i=0;i<WIDTH;i++)
		{
			if(i>=0 && i<WIDTH/3)
			{
				*(vdma_buffer+j*WIDTH*3+3*i+0)=0x00;
				*(vdma_buffer+j*WIDTH*3+3*i+1)=0x00;
				*(vdma_buffer+j*WIDTH*3+3*i+2)=0xff;
			}
			else if(i>=WIDTH/3 && i<WIDTH/3*2)
			{
				*(vdma_buffer+j*WIDTH*3+3*i+0)=0x00;
				*(vdma_buffer+j*WIDTH*3+3*i+1)=0xff;
				*(vdma_buffer+j*WIDTH*3+3*i+2)=0x00;
			}
			else if(i>=WIDTH/3*2 && i<WIDTH)
			{
				*(vdma_buffer+j*WIDTH*3+3*i+0)=0xff;
				*(vdma_buffer+j*WIDTH*3+3*i+1)=0x00;
				*(vdma_buffer+j*WIDTH*3+3*i+2)=0x00;
			}
		}
	}
	Xil_DCacheFlush();
	return 0;
}

4、VDMA彩条显示效果图

在这里插入图片描述

5、添加动态时钟IP核的工程

上面的一个工程针对的是某个固定的屏幕,当我们想要更换屏幕时,底层的模块就要重新配置,然后再重新生成比特流,比较麻烦。因此,下述的框架对上面的工程做了修改,首先是删除了锁相环IP核,添加了动态时钟配置模块;其次,打开VTC模块的AXI接口,使其可以通过PS端进行配置;然后VTC、Video out以及DVI模块的时钟由动态时钟模块提供。最后完成所有连线。
在这里插入图片描述
最后进行SDK软件设计,其代码如下所示,配置VDMA以及彩条显示与上面的代码相似。差别是添加了一些Display controller的函数对VTC模块和Dyclk模块进行配置和初始化。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "display_ctrl/display_ctrl.h"
#include "vdma_api/vdma_api.h"

//宏定义
#define BYTES_PIXEL        3                          //像素字节数,RGB888占3个字节
#define DYNCLK_BASEADDR    XPAR_AXI_DYNCLK_0_BASEADDR //动态时钟基地址
#define VDMA_ID            XPAR_AXIVDMA_0_DEVICE_ID   //VDMA器件ID
#define DISP_VTC_ID        XPAR_VTC_0_DEVICE_ID       //VTC器件ID

//函数声明
void colorbar(u8 *frame, u32 width, u32 height, u32 stride);

//全局变量
XAxiVdma     vdma;
DisplayCtrl  dispCtrl;
VideoMode    vd_mode;
//frame buffer的起始地址
unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);
unsigned int lcd_id=0;        //LCD ID

int main(void)
{
	vd_mode=VMODE_1024x768;

	//配置VDMA
	run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
							frame_buffer_addr,0, 0,ONLY_READ);

    //初始化Display controller
	DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
    //设置VideoMode
	DisplaySetMode(&dispCtrl, &vd_mode);
	DisplayStart(&dispCtrl);

	//写彩条
	colorbar((u8*)frame_buffer_addr, vd_mode.width,
			vd_mode.height, vd_mode.width*BYTES_PIXEL);
    return 0;
}

//写彩条函数(彩虹色)
void colorbar(u8 *frame, u32 width, u32 height, u32 stride)
{
	u32 color_edge;
	u32 x_pos, y_pos;
	u32 y_stride = 0;
	u8 rgb_r, rgb_b, rgb_g;

	color_edge = width * BYTES_PIXEL / 7;
	for (y_pos = 0; y_pos < height; y_pos++) {
		for (x_pos = 0; x_pos < (width * BYTES_PIXEL); x_pos += BYTES_PIXEL) {
			if (x_pos < color_edge) {                                           //红色
				rgb_r = 0xFF;
				rgb_g = 0;
				rgb_b = 0;
			} else if ((x_pos >= color_edge) && (x_pos < color_edge * 2)) {     //橙色
				rgb_r = 0xFF;
				rgb_g = 0x7F;
				rgb_b = 0;
			} else if ((x_pos >= color_edge * 2) && (x_pos < color_edge * 3)) { //黄色
				rgb_r = 0xFF;
				rgb_g = 0xFF;
				rgb_b = 0;
			} else if ((x_pos >= color_edge * 3) && (x_pos < color_edge * 4)) { //绿色
				rgb_r = 0;
				rgb_g = 0xFF;
				rgb_b = 0;
			} else if ((x_pos >= color_edge * 4) && (x_pos < color_edge * 5)) { //青色
				rgb_r = 0;
				rgb_g = 0xFF;
				rgb_b = 0xFF;
			} else if ((x_pos >= color_edge * 5) && (x_pos < color_edge * 6)) { //蓝色
				rgb_r = 0;
				rgb_g = 0;
				rgb_b = 0xFF;
			} else if ((x_pos >= color_edge * 6) && (x_pos < color_edge * 7)) { //紫色
				rgb_r = 0x8B;
				rgb_g = 0;
				rgb_b = 0xFF;
			}
			frame[x_pos + y_stride + 0] = rgb_b;
			frame[x_pos + y_stride + 1] = rgb_g;
			frame[x_pos + y_stride + 2] = rgb_r;
		}
		y_stride += stride;
	}
	Xil_DCacheFlush();     //刷新Cache,数据更新至DDR3中
	xil_printf("show color bar\r\n");
}

最后显示的效果如下图,从左到右依次红橙黄绿青蓝紫。
在这里插入图片描述

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年2月25日 下午10:16
下一篇 2023年2月25日 下午10:18

相关推荐