使用 HDMI 监视器进行 ZYNQ-VDMA 彩条显示测试
学习内容
本文使用带有HDMI接口的显示器,构建图像视频显示的测试工程,利用VDMA进行传输图像视频数据,进行彩条显示的测试。
开发环境
vivado 18.3&SDK,PYNQ-Z2开发板。
准备工作
所用到的IP:VDMA、video out IP、Video Timing Controller、动态时钟配置 IP和DVI IP。
详细介绍参考前文:
- ZYNQ-AXI_VDMA IP简介
- ZYNQ-Video out IP和Video Timing Controller IP简介
系统框图
通过控制器(MCU)把彩条数据写入DDR,缓存到VDMA。然后通过AXI-stream to video out IP和VTC IP将视频数据转换为普通的视频接口的时序信号,然后通过GP0控制视频时序输出,,由显示器显示输出彩条图片。
硬件平台搭建
新建工程,创建 block design。
配置ZYNQ7
添加ZYNQ7 IP,对zynq进行初始化配置,
勾选M_AXI_GP0和S_AXI_HP0,
并添加时钟和复位信号。
完成配置后点击OK,配置后入下图:
配置VDMA IP
并添加VDMA ip核,如下图:
双击vdma ip核打开,进行配置ip核的相关信息。因为这里仅仅做显示测试,所以不需要进行帧缓存和调用写通道的资源,对于传输数据的位宽等信息根据自己将要显示图像大家进行合理配置。
配置Video out IP
添加 AXI4-Stream to Video Out IP,双击ip核打开,进行配置ip核的相关信息。按着图示配置即可,
配置VTC(Video Timing Controller ) IP
添加 Video Timing Controller IP,勾选AXI-Lite ,取消勾选监视器。
配置根据使用的显示器进行显示分辨率的设计,这里我就配置默认生成是1080p的
添加动态时钟配置 IP和DVI IP
迪芝伦公司提供的IP,IP 链接,用于动态生成视频时序信号的时钟。
连接数据通路
完成视频时序信号和视频信号的连接,
完成连接FCLK_CLK1信号连接,
完成动态时钟输出的视频时钟的连接,
完成连接后点击自动连接,勾选全部即可。
完成连接最终系统设计
完成连接最终系统设计如下:
然后我们进行generate output product 然后生成HDL封装。接着就对应引脚进行引脚约束即可(PYNQ的粉色开发板可以直接引用这个约束):
set_property -dict { PACKAGE_PIN L17 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_clk_n }]; #IO_L11N_T1_SRCC_35 Sch=hdmi_tx_clk_n
set_property -dict { PACKAGE_PIN L16 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_clk_p }]; #IO_L11P_T1_SRCC_35 Sch=hdmi_tx_clk_p
set_property -dict { PACKAGE_PIN K18 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_n[0] }]; #IO_L12N_T1_MRCC_35 Sch=hdmi_tx_d_n[0]
set_property -dict { PACKAGE_PIN K17 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_p[0] }]; #IO_L12P_T1_MRCC_35 Sch=hdmi_tx_d_p[0]
set_property -dict { PACKAGE_PIN J19 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_n[1] }]; #IO_L10N_T1_AD11N_35 Sch=hdmi_tx_d_n[1]
set_property -dict { PACKAGE_PIN K19 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_p[1] }]; #IO_L10P_T1_AD11P_35 Sch=hdmi_tx_d_p[1]
set_property -dict { PACKAGE_PIN H18 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_n[2] }]; #IO_L14N_T2_AD4N_SRCC_35 Sch=hdmi_tx_d_n[2]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD TMDS_33 } [get_ports { tmds_tmds_data_p[2] }]; #IO_L14P_T2_AD4P_SRCC_35 Sch=hdmi_tx_d_p[2]
set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS33 } [get_ports { tmds_oen }]; #IO_0_34 Sch=hdmi_tx_hpdn
完成约束后进行综合布局布线,等待生成bit流文件。bit文件生成后在FILE处,点击导出硬件资源(包含bit流文件),接着launch SDK。
SDK软件部分
打开SDK后,新建application project。首先导入相关的IP的驱动文件
导入VDMA API驱动
这里打开system.mss,点击axi_vdma这里的导入示例,
这里可以把vdma_api.c进行封装,添加头文件,把要使用的函数进行声明。
#ifndef VDMA_API_H_
#define VDMA_API_H_
/* Include File Definitions */
#include "xaxivdma.h"
#include "xparameters.h"
#include "xil_exception.h"
/* General Type Declarations */
typedef enum
{
ONLY_READ=1, //
ONLY_WRITE, //
BOTH //
}vdma_run_mode;
/* Procedure Declarations */
int run_vdma_frame_buffer(XAxiVdma* InstancePtr, int DeviceId, int hsize,
int vsize, int buf_base_addr, int number_frame_count,
int enable_frm_cnt_intr,vdma_run_mode mode);
#endif /* VDMA_API_H_ */
因为系统生成的示例的API是同时开启了Vdma的读写通道,这里正点原子对函数进行了简单的修改,声明了一个结构体。这样可以在进行帧缓存的时候仅使用单独的读或者写通道也可以确保函数的正常运行。
在vdma_api.c,的函数Function Prototypes部分,也要修改添加参数。
对于StartTransfer函数的修改:
对于run_vdma_frame_buffer函数的修改:
之前叫run_triple_frame_buffer
导入display_ctrl_hdmi和dynclk驱动
在迪芝伦的提供的IP中有他们已经封装好的驱动代码,这里直接导入我们的SDK工程文件夹即可。
完成驱动导入后,在main.c中键入下面代码:
#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_hdmi/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);
int main(void)
{
xil_printf("HDMI Display 1920*1080 \r\n");
//设置video参数,分辨率:1920*1080
vd_mode = VMODE_1920x1080;
//配置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");
}
运行效果
Reference
- 正点原子ZYNQ开发视频