赞
踩
用了很多年的zynq 7000,一直就没做hdmi 显示实验。前几天终于做了这个实验,也就做一个总结。
我的实验是在微相的z7-lite下根据他们的教程完成的。平台是windows 10 , Vivado 2018.3。如果硬件设计不一样,主要是替换rgb2dvi 模块和gpio 中断部分。
工程资料下载,
链接:https://pan.baidu.com/s/11-RLOYtl1AyxcQ_XGbw2YQ
提取码:zvnc
这个下载里里三个文件,一个hdmi_out 全工程67M,下了这个就包含其他2个了,如果只是ip ,可以下hmdiip(375k),如果只是源代码 hdmisrc( 15M),主要是图形数据占空间。
如果你有微相的资料,那就是 z7_Lite\03_SDK_Demo\17_hdmi_out
这个实验用了2个IP,可以从 https://github.com/Digilent/vivado-library 下载。但我实验中用的是微相提供的是rgb2dvi_v1_2,这个ip 在工程的ip_repo目录里。
该链接内有 Digilent 提供的很多个自定义 IP,但这个实验只用2个。其中 axi_dynclk 是时钟发生模块,会根据不同的屏幕分辨率,自动生成相应的像素时钟和串行时钟,而rgb2dvi 模块会将图像的红绿蓝信号转换成MDS 信号, 送往 HDMI 端口发出。网上版本比较新,我本想用新版本做测试的,结果sdk的时候报错,只好用微相提供的版本。IP文件目录如下图所示:
在这里特别注意: if 文件目录也要复制,就是if 目录,其中包含了tmds 目录。ip目录下的 axi_dynclk, rgb2dvi 。图中内容复制到你的你的ip_repo,就是集中放IP的目录,我这目录下就放了其他IP。
开始的时候,我习惯只把ip 下的目录复制过来,结果通过不了。
在 Vivado 下新建一个工程,名字为 hdmi_out。整个硬件设计是比较复杂的,我把它分为几个部分:
添加ip目录,添加并配置zynq,添加 VDMA IP,AXI-Stream Subset Converter 模块,AXI4-Stream to Video Out 模块,Video Timing Controller 模块,rgb2dvi 模块,中断合并。
图像数据的流向是:zynq的DDR中, VDMA 读取,AXI-Stream Subset Converter转换,AXI4-Stream to Video Out 模块,rgb2dvi 模块输出。
Run Automation Connect的时候,一般只勾选当时介绍的IP, 如果多勾了,与后面指定操作冲突时,可以选择脚,右键,Disconnect Pin,使它脱离连接。
在主窗口左侧边栏 Project Manager 下点击 Project Settings 选项, 向工程中添加这两个自定义 IP,其实是把 IP 存放目录加上去如下图所示:
可以Project Manager -> IP Catalog 看到我们添加的IP。
新建一个原理图, 加入zynq ,原理图是这样的:
双击zynq进行设置:
点击 PS-PL Configuration 选项, 在 HP Slave AXI interface 下勾选 S AXI HP0 interface, 本节实验要用这个端口获取 DDR 中存储的图像数据。
使能 UART0,这与开发板有关,z7_lite对应的是14,15。
点击 Clock Configuration 选项,在 PL Fabric Clocks 窗口,勾选 FCLK_CLK0和 FCLK_CLK1,并将其分别设为 100, 140MHz。 其中 FCLK_CLK0 将作为 Zynq配置各个模块的时钟, 而 FCLK_CLK1 将作为图像数据流的时钟,
再点击 DDR Configuration, 选择跟开发板一致的 DDR 型号.z7_lite 是 MT41K256M16 RE-125,16 Bit
再点击 Interrupt 选项,激活 IRQ_F2P[15:0]选项,点击 OK,配置完成。
手动连接 FCLK_CLK0 到 MAXI_GPO_ACLK, 连接 FCLK_CLK1 到 S_AXI_HP0_ACLK,
检查并对比,原理图是否一致。
如下图所示:
VDMA 添加完成如下图所示:
双击上图 axi_vdma_0, 进行参数配置。 首先 Basic 选项, Address Width设为 32 bits, 寻址空间可以达到 4GB。 Frame Buffers 设为 1,取消 Enable WriteChannel 选项,因为本实验 VDMA 只从 DDR 中读取图像数据。 在 Enable Read Channel 下, 将 Line Buffer Depth 改为 4096, 其它默认, 如下图所示:
再点击 Advanced 选项卡, 将 GenLock Mode 改为 Master,点击 OK 完成配置。
现在原理图是如下样子,点击 Run Connection Automation,自动生成 AXI 互联总线及系统复位电路,在弹出的对话框中勾选 All Automation, 点击 OK。
然后手动将 m_axis_mm2s_aclk 和 m_axi_mm2s_aclk 两个时钟连在一起。
添加 AXI-Stream Subset Converter 模块
添加完成后如下图所示:
双击上图的 axis_subset_converter_0,如下配置。
TDATA 输入为 32-bit,即 4-Byte,输出为 24-Bit, 即 3-Byte。
将 TKEEP、 TLAST 修改为 yes,并将 USERWidth 置为 1,
这里要重点说一下 TDATA Remap String 项,因为要显示的图像在 DDR 中的存储方式为 24-bit 默认方式 B, G, R,分别以[7:0], [15:8], [23:16]组成 24-bit,而本例中使用的产生 TMDS 信号模块 rgb2dvi 的像素排序为 R, B, G,所以需要在 TDATA Remap String 这一项做顺序调整。调整字符串为:tdata[23:16],tdata[7:0],tdata[15:8]
配置如下图所示:
配置好了,点OK。
手 动 连 接 axis_subset_converter_0 的 S_AXIS 端 口 到 axi_vdma_0 的M_AXIS_MM2S 端口。
并将其 aclk 连接到 FCLK_CLK1 时钟网络。 如下图所示:
增加一个常量输出模块 Constant, 将 axis_subset_convert_0 的复位端口 aresetn 拉高,使其始终处于工作状态,
双击刚刚添加的 xlconstant_0,保持默认的 1bit 位宽输出高电平 1 的配置,点击 OK,
将 xlconstant_0 的 dout 端口连到 axis_subset_converter_0 的 aresetn端口,
添加 AXI4-Stream to Video Out 模块。本例用这个模块将 VDMA 从 DDR 读出的 AXI4-Stream 转换成 RGB 图像数据。
双击 v_axi4s_vid_out_0 进行配置。
将 FIFO Depth 改为 4096,
Clock Mode 设为 Independent,
Timing Mode 设为 Master,点击 OK。
手动将 v_axi4s_vid_out_0 的 video_in 端口连接到 axis_subset_converter_0的 M_AXIS 端口,
再将其 aclk 连接到 FCLK_CLK1 时钟网络,如下图,
添加 Video Timing Controller 模块。本例使用这个模块,来产生不同的分辨率下时序控制信号。
双击 v_tc_0 进行配置。取消掉 Enable Detection 选项的勾选,其它保持默认,点击 OK,
点击 Diagram 窗口的 Run Connection Automation,自动生成 v_tc_0 模块的相关连接。
勾选 All Automation, 点击“ OK”。
连接 v_tc_0 的 vtiming_out 端口到 v_axi4s_vid_out_0 的 vtiming_in 端口,
连接 v_tc_0 的 gen_clken 端口到 v_axi4s_vid_out_0 的 vtg_ce 端口,
添加 rgb2dvi 模块。此模块将 RGB 信号转换为 DVI 信号,即 TMDS 格式。
双击 rgb2dvi_0 进行配置。 这里取消勾选 Reset active high
和 Generate SerialClk internally from pixel clock,
信号频率设置选择<120MHz(720p)这一项。
连接 v_axi4s_vid_out_0 的 vid_io_out 端口到 rgb2dvi_0 端口,
右键单击 rgb2dvi_0 的 TMDS 端口, 选择 Make External 生成外部引脚,
添加时钟生成模块,点击上方工具栏“ +”,在搜索框输入 dynclk, 回车添加模块。
此模块使用默认参数, 点击 Run Connection Automation
将 axi_dynclk_0的像素时钟 PXL_CLK_O 端口连接到 rgb2dvi_0 的 PixelClk 端口,
并将其串行时钟PXL_CLK_5X_O 端口连接到 rgb2dvi_0 的SerialCLk 端口,
再将 axi_dynclk_0 的 LOCKED_O 端口连接到 rgb2dvi_0 的 aRst_n 端口,
这样当锁相环锁定,即时钟信号稳定输出时,rgb2dvi 模块开始工作,
做到这我没截图,所以下面图中还包含了一些下一步的连线。
将 v_tc_0 的 clk 端口连接到 axi_dynclk_0 的 PXL_CLK_O 端口上。同样,
将 v_axi4s_vid_out_0 的 vid_io_out_clk 也连接到 PXL_CLK_O 这一网络上,
添加 GPIO 模块,用作 HDMI 的热拔插检测信号 HPD
双击 axi_gpio_0 进行配置,将其设为 1 位输入, 使能 Interrupt,点击 OK,
将端口名称修改为“ HDMI_HPD”,
添加一个 concat 模块,将所有中断信号集中起来,然后再连接到 Zynq 处理器的中断输入端口。
双击 xlconcat_0 进行配置。本例中有 3 个中断信号, 设置端口数为 3, 点击 OK, 如下图所示:
将 axi_gpio_0 的 中 断 端 口 ip2intc_irpt , axi_vdma_0 的 中 断 端 口mm2s_introut, v_tc_0 的中断端口 irq,分别连接到 xlconcat_0 的 In0, In1, In2上,并将 xlconcat_0 的输出端口 dout 连接到 Zynq 的 IRQ_F2P 上,
由于图比较大,可能脚比较远,也可以选择要连接的端口之一,比如axi_vdma_0 的 中 断 端 口mm2s_introut,然后右键选择 Make Connetion,出现可能的连线,然后做一个选择,下图中选择xlconc_0 的In1。看下图所示:
连接后是这样的,
硬件设计完成了,如果有 Run Block Automation 提示,那就点击 Run Block Automation,在弹出的对话框中全选所有信号端口,点击“ OK”。
点击 Regenerate Layout 生成标准布局如下图, 再点击 Validate Design 验证设计:
验证成功后, 弹窗点击 OK, Ctrl+S 保存设计。
我的在验证时出现如下错误:
[BD 41-1343] Reset pin /v_tc_0/resetn (associated clock /v_tc_0/clk) is connected to reset source /rst_ps7_0_100M/peripheral_aresetn (synchronous to clock source /processing_system7_0/FCLK_CLK0).
This may prevent design from meeting timing. Please add Processor System Reset module to create a reset that is synchronous to the associated clock source /axi_dynclk_0/PXL_CLK_O.
我对照一下,点击pin /v_tc_0/resetn, 然后右键 Disconnect Pin,再验证就好了。
来到 Source 窗口, 生成设计代码和顶层文件, 右键点击 hdmi_out 选择Generate Output Producs, 在弹出的窗口点击 Generate,点击 OK。 右键点击hdmi_out 选择 Create HDL Wrapper,在弹出的窗口点击 OK。
添加管脚约束,这里可以直接添加一个约束文件,然后把下面内容复制过去就可。
- set_property IOSTANDARD LVCMOS33 [get_ports {HDMI_HPD_tri_i[0]}]
- set_property PACKAGE_PIN P19 [get_ports {HDMI_HPD_tri_i[0]}]
- set_property PACKAGE_PIN U18 [get_ports TMDS_0_clk_p]
- set_property PACKAGE_PIN V20 [get_ports {TMDS_0_data_p[0]}]
- set_property PACKAGE_PIN T20 [get_ports {TMDS_0_data_p[1]}]
- set_property PACKAGE_PIN N20 [get_ports {TMDS_0_data_p[2]}]
这个约束文件很简单,看电路图,都用了脚对,虽然4个脚,实际是8个脚。
原文中有设置约束文件的方法,不错,值得学习。但复制约束文件比较简单。
产生比特流,输出硬件(要包含比特流),然后打开SDK,就开始软件设计部分了。
在打开的 SDK 软件内点击 File > New > Application Project, 工程名填入“ hdmi_out”, 工程模板选择 Empty Application 工程, 点击 Finish。
创 建 完 成 后 , 打 开 资 料 目 录hdmi_out/hdmi_out.sdk/hdmi_out/src 文件夹,复制文件夹内的所有文件,粘贴到当前工程同样的目录下(注意,不要覆盖本工程下的 lscript.ld), 右键单击工程 hdmi_out
选择 Refresh,则开始自动编译。双击打开 src 目录底下的 display_demo.c, 这就是 SDK 主程序。
接下来讲解代码,展开工程下的 src 可以看到文件结构如下图所示, 其中display_ctrl 文件夹包含不同视频分辨率情况下的时序控制, dynclk 包含不同分辨率情况下的像素时钟和串行时钟生成。
打开 display_demo.c 文件,这就是本实验的主程序,我们分段讲解各部分的作用。
- #include <stdio.h>
- #include <math.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include "xil_types.h"
- #include "xil_cache.h"
- #include "xparameters.h"
- #include "display_demo.h"
- #include "display_ctrl/display_ctrl.h"
- #include "display_ctrl/vga_modes.h"
-
- // Image data for each resolution
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- //#include "pic_800_600.h"
- //#include "pic_1280_720.h"
- //#include "pic_1280_1024.h"
- #include "pic_1920_1080.h"
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-
- /*
- * XPAR redefines
- */
- #define DYNCLK_BASEADDR XPAR_AXI_DYNCLK_0_BASEADDR
- #define VGA_VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID
- #define DISP_VTC_ID XPAR_VTC_0_DEVICE_ID
-
- /* ------------------------------------------------------------ */
- /* Global Variables */
- /* ------------------------------------------------------------ */
-
- /*
- * Display Driver struct
- */
- DisplayCtrl dispCtrl;
- XAxiVdma vdma;
-
- /*
- * Frame buffers for video data
- */
- u8 frameBuf[DISPLAY_NUM_FRAMES][DEMO_MAX_FRAME];
- u8 *pFrames[DISPLAY_NUM_FRAMES]; // array of pointers to the frame buffers
- /* ------------------------------------------------------------ */
- /* Procedure Definitions */
- /* ------------------------------------------------------------ */
-
- int main(void)
- {
- int i;
- int Status;
- XAxiVdma_Config *vdmaConfig;
-
- /*
- * Initialize an array of pointers to the 3 frame buffers
- */
- for (i = 0; i < DISPLAY_NUM_FRAMES; i++)
- {
- pFrames[i] = frameBuf[i];
- }
-
- /*
- * Initialize VDMA driver, get the hardware VDMA configurations
- */
- vdmaConfig = XAxiVdma_LookupConfig(VGA_VDMA_ID);
- if (vdmaConfig == NULL)
- {
- xil_printf("No video DMA found for ID %d\r\n", VGA_VDMA_ID);
- }
-
- /*
- * Use hardware VDMA configurations to initialize the driver
- */
- Status = XAxiVdma_CfgInitialize(&vdma, vdmaConfig, vdmaConfig->BaseAddress);
- if (Status != XST_SUCCESS)
- {
- xil_printf("VDMA Configuration Initialization failed %d\r\n", Status);
- }
-
- /*
- * Initialize the Display controller and start it
- */
-
- // Video Mode for each resolution
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- //VideoMode VMODE = VMODE_800x600;
- //VideoMode VMODE = VMODE_1280x720;
- //VideoMode VMODE = VMODE_1280x1024;
- VideoMode VMODE = VMODE_1920x1080;
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-
- Status = DisplayInitialize(&dispCtrl, &vdma, DISP_VTC_ID, DYNCLK_BASEADDR, pFrames, DEMO_STRIDE, VMODE);
- if (Status != XST_SUCCESS)
- {
- xil_printf("Display Ctrl initialization failed during demo initialization%d\r\n", Status);
- }
-
- Status = DisplayStart(&dispCtrl);
- if (Status != XST_SUCCESS)
- {
- xil_printf("Couldn't start display during demo initialization%d\r\n", Status);
- }
-
- DemoPrintTest(dispCtrl.framePtr[dispCtrl.curFrame], dispCtrl.vMode.width, dispCtrl.vMode.height, dispCtrl.stride);
-
- return 0;
- }
-
- void DemoPrintTest(u8 *frame, u32 width, u32 height, u32 stride)
- {
- u32 xcoi, ycoi;
- u32 linesStart = 0;
- u32 pixelIdx = 0;
-
- for(ycoi = 0; ycoi < height; ycoi++)
- {
- for(xcoi = 0; xcoi < (width * 4); xcoi+=4)
- {
-
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-
- /*
- // 800 x 600
- frame[linesStart + xcoi ] = Pixel_800_600[pixelIdx++]; // Blue
- frame[linesStart + xcoi + 1] = Pixel_800_600[pixelIdx++]; // Green
- frame[linesStart + xcoi + 2] = Pixel_800_600[pixelIdx++]; // Red
- */
-
- /*
- // 1280 x 720
- frame[linesStart + xcoi ] = Pixel_1280_720[pixelIdx++];
- frame[linesStart + xcoi + 1] = Pixel_1280_720[pixelIdx++];
- frame[linesStart + xcoi + 2] = Pixel_1280_720[pixelIdx++];
- */
-
- /*
- // 1280 x 1024
- frame[linesStart + xcoi ] = Pixel_1280_1024[pixelIdx++];
- frame[linesStart + xcoi + 1] = Pixel_1280_1024[pixelIdx++];
- frame[linesStart + xcoi + 2] = Pixel_1280_1024[pixelIdx++];
- */
-
- // 1920 x 1080
- frame[linesStart + xcoi ] = Pixel_1920_1080[pixelIdx++];
- frame[linesStart + xcoi + 1] = Pixel_1920_1080[pixelIdx++];
- frame[linesStart + xcoi + 2] = Pixel_1920_1080[pixelIdx++];
-
- //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- }
-
- linesStart += stride;
- }
-
- /*
- * Flush the frame buffer memory range to ensure changes are written to the
- * actual memory, and therefore accessible by the VDMA.
- */
- Xil_DCacheFlushRange((unsigned int) frame, DEMO_MAX_FRAME);
- }
这部分提供了 4 个不同分辨率图像的像素数据, 这些数据以头文件的形式包含在工程目录中,比如 pic_800_600.h。 以 800x600 图像像素为例,如下图,每个像素以三基色表示,一共 800x600x3 = 1440000 个数据点。屏幕左上角坐标(0,0)处第一个像素的蓝色,绿色,红色数值依次是 0xF1, 0xDC, 0xDB, 其它点的像素值依次类推。
pic_800_600.h 文件内容,其实就是定义图形常量:
- const unsigned char Pixel_800_600[1440000] = {
- 0XF1,0XDC,0XDB,0XF1,0XDC,0XDB,0XF3,0XDE,0XDD,0XF4,0XDF,0XDE,0XF5,0XE0,0XDF,0XF6,0XE1,0XE0,
- 0XF7,0XE2,0XE1,0XF7,0XE2,0XE1,0XFC,0XE7,0XE6,0XFB,0XE7,0XE6,0XFB,0XE7,0XE6,0XFC,0XE8,0XE7,
-
- ...
-
- };
主程序的开始部分是4个全局变量的定义
- /*
- * Display Driver struct
- */
- DisplayCtrl dispCtrl;
- XAxiVdma vdma;
-
- /*
- * Frame buffers for video data
- */
- u8 frameBuf[DISPLAY_NUM_FRAMES][DEMO_MAX_FRAME];
- u8 *pFrames[DISPLAY_NUM_FRAMES]; // array of pointers to the frame buffers
显示控制结构体 DisplayCtrl 和 VDMA 例化(读者可以按住 Ctrl 键的同时鼠标左键点进去看看这两个结构体包含的内容)。
然后是显示数据定义。
main 函数:
先初始化pFrames(frame buffers 的指针)
get the hardware VDMA configurations :vdmaConfig
初始化驱动
显示启动。
最后通过函数DemoPrintTest把显示数据复制到显示缓存,图像就显示了
程序结束
程序代码是针对1600x1200的,如果要更改方式,有3个地方需要修改:
1,#include "pic_1920_1080.h"
2:VideoMode VMODE = VMODE_1920x1080;
3:DemoPrintTest函数中
// 1920 x 1080
frame[linesStart + xcoi ] = Pixel_1920_1080[pixelIdx++];
frame[linesStart + xcoi + 1] = Pixel_1920_1080[pixelIdx++];
frame[linesStart + xcoi + 2] = Pixel_1920_1080[pixelIdx++];
这3个地方都有//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
包括了,应该容易找到。
编译好了,就可以验证显示了。
连接电脑 USB 到开发板 JTAG 口,连接电脑另一个 USB 到开发板 UART口,确保开发板启动模式设置为 JTAG,连接开发板 HDMI 口到 HDMI 显示器,给开发板上电。按照之前实验的操作方式连接串口(在本实验中,如果未发送错误则串口不打印信息)。其实每阶段运行完了,给个阶段显示更好,可以加点print。
Xilinx->program FPGA下载比特流,然后Run-> Run Configuration,做如下选择,然后Run
第一次的时候,我直接 Run As ->Launch on Hardware,结果没显示,按上面方法才显示,可能要等的时间长一点。我新做这个实验, Run As -> Launch on Hardware也能显示。
做这个实验还是花很多时间才完成,有点难。成功显示后还有点成就感,再做一遍,并写上此文。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。