zynq 7000 的HDMI 显示实验
用了很多年的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
IP準備
這個實驗用了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,使它脫離連接。
添加ip 目錄?
在主窗口左側邊欄 Project Manager 下點擊 Project Settings 選項, 向工程中添加這兩個自定義 IP,其實是把 IP 存放目錄加上去如下圖所示:
可以Project Manager -> IP Catalog 看到我們添加的IP。?
原理圖添加并配置zynq
新建一個原理圖, 加入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 IP?
如下圖所示:
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 模塊
添加 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 模塊
添加 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 模塊
添加 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 模塊
添加 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??聪聢D所示:
連接后是這樣的,
硬件設計完成了,如果有?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 600frame[linesStart + xcoi ] = Pixel_800_600[pixelIdx++]; // Blueframe[linesStart + xcoi + 1] = Pixel_800_600[pixelIdx++]; // Greenframe[linesStart + xcoi + 2] = Pixel_800_600[pixelIdx++]; // Red*//*// 1280 x 720frame[linesStart + xcoi ] = Pixel_1280_720[pixelIdx++];frame[linesStart + xcoi + 1] = Pixel_1280_720[pixelIdx++];frame[linesStart + xcoi + 2] = Pixel_1280_720[pixelIdx++];*//*// 1280 x 1024frame[linesStart + xcoi ] = Pixel_1280_1024[pixelIdx++];frame[linesStart + xcoi + 1] = Pixel_1280_1024[pixelIdx++];frame[linesStart + xcoi + 2] = Pixel_1280_1024[pixelIdx++];*/// 1920 x 1080frame[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也能顯示。
做這個實驗還是花很多時間才完成,有點難。成功顯示后還有點成就感,再做一遍,并寫上此文。
總結
以上是生活随笔為你收集整理的zynq 7000 的HDMI 显示实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 非标自动化机械设计行业未来发展趋势
- 下一篇: 七段显示器显示整数C语言答案,C语言程序