1. 程式人生 > >4. MIZ7035 HDMI測試【PCIE視訊傳輸】

4. MIZ7035 HDMI測試【PCIE視訊傳輸】

1 MIZ7035的HDMI工程建立

將上次用到的MIG_AXI工程拿來進行HDMI的工程建立。
不像ZCU102的開發板那樣用GT收發器,MIZ7035的HDMI介面是靠PL的邏輯來實現輸入輸出的。所以要寫RTL程式碼來做HDMI的編解碼。

測了幾天MIZ7035的這個板子,遇到了個問題,總結一下。

1 BUFx時鐘問題

寫這個文件時候我已經測試了一天了,就先將一個比較重要的問題提前丟擲來。米聯的MIZ7035的原理圖可以下載到,可以自行下載去檢視。
首先是HDMI的TMDS的訊號FPGA並不能夠直接進行連線,所以需要進行訊號轉換、改變耦合方式、加偏置等方法對訊號進行處理,這裡就不討論了。
再來看HDMI的訊號連線到了哪個BANK。可以看到兩個HDMI都連線到了FPGA的BANK 35。那麼問題來了,這樣會有什麼問題。
Digilent出了很多帶雙路FPGA的開發板,看了幾塊都是講HDMI的輸入輸出放在了不同的BANK。如下圖的zybo_z7開發板
1.png
Zynq7000的PL的BANK最多隻能有一個CMT,而每一個CMT中包含一個MMCM和一個PLL,兩個都可以產生高速時鐘。HDMI輸入輸出的RTL都是通過使用IOSERDES、IODELAY的源語來實現串並、並串轉換的。
它們的CLK、CLKDIV輸入的來源有2個

  1. 片上BUFx的輸出
  2. MMCM/PLL經BUFx的輸出

不管用哪種方式,其實都是必須經過BUFx進行緩衝的。

BUFx可用的組合方式可以看xapp585,
2.png
而XC7Z035-2的速率如下圖。
3.png
BUFG:710MHz,BUFIO:800MHz,BUFR:540MHz,BUFH:710MHz。
我們測試時使用的最大影象解析度為1920*1080,他的畫素時鐘為148.5MHz,TMDS上使用DDR方式則穿行時鐘是5倍的畫素時鐘為742.5MHz。

若使用BUFx作為SERDES的輸入時,我們僅僅只有BUFIO+BUFR這個組合能夠滿足晶片要求,而其他的肯定會有警告或是錯誤。(實際也確實是這樣,板子帶的程式碼沒用唯一可選的組合,而是選了兩個BUFG來實現,實現完成後即可看到有一個時鐘週期不滿足要求的時序錯誤。)但是實際測試HDMI輸出是可以正常使用的,畢竟官方標的BUFG 710MHz是嚴格的測試標準下得到的資料,肯定還有一部分餘量。但是為了更加的可靠,還是將這個時序錯誤避免掉比較好。

但是,看BUFIO加BUFR的這個組合只能夠使用MMCM來實現連線,而一個BANK中只有一個MMCM,所以在MIZ7035中,BUFx這個方法肯定不能滿足了。。假設一個HDMI通道用了MMCM+BUFIO+BUFR,則另外一個通道只能有以下情況:

  1. MMCM+BUFG+BUFG,因為BUFIO不能跨時鐘域
  2. PLL+BUFH/BUFG+BUFH/BUFG,因為這個BANK只剩下一個PLL了

不管使用上面的那種方式,都不可能滿足742.5MHz的DDR模式的。
看來BUFx的任何組合是不能夠滿足要求了!那麼我們只能接收這個時序錯誤了,假設BUFG或BUFH的上限710MHz是能夠接受742.5MHz訊號,並應該沒有問題的。。
這個坑自己設計原理圖時候可以考慮一下,最好將它們放在不同的BANK中,並且都使用MMCM+BUFIO+BUFR就好了。

2 HDMI&MIG問題

除了前面這個問題,還有個問題就是HDMI和MIG各自的BANK是緊鄰的。
因為HDMI的輸入、輸出還要連線vid、vtc、fifo等IP,會佔用挺多的資源,跟MIG這邊一紮堆,很容易造成AXI Lite介面不滿足時序要求等情況。

3 HDMI RX的HPD

HDMI的HPD訊號是由接收裝置傳輸給輸出裝置的。MIZ7035的HDMI RX連線到電腦上,HPD訊號是由開發板傳給電腦的,高電平會指示電腦有顯示器連線上、低電平指示沒有顯示器連線。高電平在網上可以查到說至少要大於+2.4V,具體從哪出來的這個要求,我還沒找到源頭。
再來看MIZ7035的原理圖,HDMI RX的聯結器下拉10K電阻到地,然後連線了TXS0108。這就有問題了!因為TXS系列的電平轉換晶片我經常用,我知道這個電平轉換晶片是雙向的,而且每個引腳上有一個上拉電阻,檢視datasheet可以看到這個上拉電阻是40K(低電平時)或是4K(高電平時)。
17.png
那麼當我們用FPGA驅動輸出高電平時,H1_HPD這個訊號的輸出將是3.3V的分壓,3.3*10/(4+10)=2.357V,不滿足+2.4V的要求。用萬用表實際測量,這個電壓為2.262V,電源為3.321V。後面測試時插上電腦時候,確實電腦沒有輸出任何訊號,看來得改一下板子的電路了。
可以選擇將這個電阻換大一點的阻值,如30K時可以讓高電平達到2.9V,來試試吧,在手頭找了一個相近的34.8K的電阻,把板子上的R53換掉。重新測了一下HPD電壓,為2.838V,可以滿足要求了。而且插上電腦,電腦確實閃爍了。雖然這樣看起來可以了,但還是有問題啊。只能說HPD需要一個強的驅動,TXS卻不能提供一個預設低電平的輸出。

同樣是TXS0108內部上拉,當我們的配置不使用LED相關引腳時,上拉電阻會將該晶片管腳拉高,並使LED燈點亮。。而且這個點亮是高電平驅動的,,但怎麼說呢這個晶片不適合輸出電流去驅動LED,至少我以前用都是這樣的。

4 demo移植

接著進行工程建立。
先從github上下載Digilent的IP,最新的IP加了ila,禁止ila的話有bug。就用之前的一個版本吧:https://github.com/digilent/vivado-library/tree/7827858e39f0a02b8ec579150110c073050aaada
下載Zybo Z7-20的HDMI demo,https://github.com/Digilent/Zybo-Z7-20-hdmi
參考zybo的demo,加入各IP進入到我們的MIG_AXI工程中,主要包括了rgb2dvi、dvi2rgb、vtc、vid。而demo中的axi_dynclk就不加入工程了。我們固定使用1080p的視訊輸出。
各IP配置如下
dvi2rgb
4.png
rgb2div
5.png
輸入輸出各自的vtc、vid參考demo即可。demo中的vdma ip我們不去使用。
影象資料與DDR的互動使用了tpg、mix、frmbuf_wr,跟ZCU102中的配置相同,區別在於為了跨不同BANK,我們在兩個AXI4-Stream Register Slice與其相連的IP間插入了一個AXI4-Stream Data FIFO。
整體框圖如下
6.png
hdmi_rx_ss
7.png
hdmi_tx_ss
8.png
其中的clk_wiz配置如下
9.png
10.png
clk_wiz的fb輸入輸出直接相連線,沒有經過任何BUF,因為我們用到的pclk和sclk的相位關係在HDMI TX中並不重要,axi_dynclk的原始碼中有寫。
v_dma_ss
11.png
zynq_ss,v_dma_ss過來的HP訊號同時接入了PS和MIG,這樣就能夠我們自行選擇用哪一邊的DDR了。但是PL端的DDR在這個設計中PS是沒有定址的,所以訪問不了。
12.png
addree配置,pl ddr定位到了0xC000000,1GB空間。
13.png
約束

set_property LOC PLLE2_ADV_X1Y6 [get_cells MIG_AXI_i/hdmi_tx_ss/clk_wiz/inst/plle2_adv_inst]
set_property LOC MMCME2_ADV_X1Y6 [get_cells MIG_AXI_i/hdmi_rx_ss/dvi2rgb/U0/TMDS_ClockingX/DVI_ClkGenerator]


create_clock -name clk100m_i -period 10.00 [get_ports clk100m_i]
#set_property VCCAUX_IO DONTCARE [get_ports clk100m_i]
set_property IOSTANDARD SSTL15 [get_ports clk100m_i]
set_property PACKAGE_PIN C8 [get_ports clk100m_i]

#rst_key, active low
set_property PACKAGE_PIN A10 [get_ports rst_key]
set_property IOSTANDARD LVCMOS15 [get_ports rst_key]

#led 0
set_property PACKAGE_PIN B9 [get_ports pl_ddr_init_done]
set_property IOSTANDARD LVCMOS15 [get_ports pl_ddr_init_done]
#led 1
set_property PACKAGE_PIN K10 [get_ports hdmi_rx_clk_lock]
set_property IOSTANDARD LVCMOS15 [get_ports hdmi_rx_clk_lock]

#HDMI RX
create_clock -name HDMI_RX_clk_p -period 6.734 [get_ports HDMI_RX_clk_p]
set_property PACKAGE_PIN D15 [get_ports HDMI_RX_clk_p]
set_property IOSTANDARD LVDS [get_ports HDMI_RX_clk_p]
set_property PACKAGE_PIN E16 [get_ports HDMI_RX_data_p[0]]
set_property IOSTANDARD LVDS [get_ports HDMI_RX_data_p[0]]
set_property PACKAGE_PIN F15 [get_ports HDMI_RX_data_p[1]]
set_property IOSTANDARD LVDS [get_ports HDMI_RX_data_p[1]]
set_property PACKAGE_PIN C17 [get_ports HDMI_RX_data_p[2]]
set_property IOSTANDARD LVDS [get_ports HDMI_RX_data_p[2]]

set_property PACKAGE_PIN B10 [get_ports RX_DDC_OUT_scl_io]
set_property IOSTANDARD LVCMOS15 [get_ports RX_DDC_OUT_scl_io]
set_property PACKAGE_PIN B15 [get_ports RX_DDC_OUT_sda_io]
set_property IOSTANDARD LVCMOS18 [get_ports RX_DDC_OUT_sda_io]
set_property PACKAGE_PIN B16 [get_ports RX_HPD_OUT]
set_property IOSTANDARD LVCMOS18 [get_ports RX_HPD_OUT]

#HDMI TX
set_property PACKAGE_PIN J14 [get_ports HDMI_TX_clk_p]
set_property IOSTANDARD LVDS [get_ports HDMI_TX_clk_p]
set_property PACKAGE_PIN K13 [get_ports HDMI_TX_data_p[0]]
set_property IOSTANDARD LVDS [get_ports HDMI_TX_data_p[0]]
set_property PACKAGE_PIN G14 [get_ports HDMI_TX_data_p[1]]
set_property IOSTANDARD LVDS [get_ports HDMI_TX_data_p[1]]
set_property PACKAGE_PIN K15 [get_ports HDMI_TX_data_p[2]]
set_property IOSTANDARD LVDS [get_ports HDMI_TX_data_p[2]]

set_property PACKAGE_PIN H13 [get_ports TX_DDC_OUT_scl_io]
set_property IOSTANDARD LVCMOS18 [get_ports TX_DDC_OUT_scl_io]
set_property PACKAGE_PIN G15 [get_ports TX_DDC_OUT_sda_io]
set_property IOSTANDARD LVCMOS18 [get_ports TX_DDC_OUT_sda_io]

set_property PACKAGE_PIN A17 [get_ports FMCH_IIC_scl_io]
set_property IOSTANDARD LVCMOS18 [get_ports FMCH_IIC_scl_io]
set_property PACKAGE_PIN B17 [get_ports FMCH_IIC_sda_io]
set_property IOSTANDARD LVCMOS18 [get_ports FMCH_IIC_sda_io]

編譯
資源佔用
14.png
時序錯誤,bufh不能達到742.5MHz
15.png
15a.png
資源分佈
16.png

生成並輸出hdf with bit。

2 工程測試

1 TPG測試

先寫一個截斷MIX,從TPG直接產生pattern輸出的驅動。測試程式碼:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"

#include "sleep.h"
#include "xgpio.h"
#include "xvidc.h"
#include "xv_tpg.h"
#include "xv_mix_l2.h"
#include "xv_frmbufwr_l2.h"

XGpio   Gpio_VDMA_resetn;
XGpio   Gpio_RXHPD;
XV_tpg  Tpg;
XV_Mix_l2       Mix;
XV_FrmbufWr_l2  FrmbufWr;

int init_gpio(void)
{
    int Status;
    Status = XGpio_Initialize(&Gpio_VDMA_resetn, XPAR_V_DMA_SS_AXI_GPIO_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: GPIO VDMA device not found\r\n");
        return(XST_FAILURE);
    }

    XGpio_SetDataDirection(&Gpio_VDMA_resetn, 1, 0);
    XGpio_DiscreteWrite(&Gpio_VDMA_resetn, 1, 0x0);
    usleep(1000);
    XGpio_DiscreteWrite(&Gpio_VDMA_resetn, 1, 0x7);
    usleep(1000);

    Status = XGpio_Initialize(&Gpio_RXHPD, XPAR_AXI_GPIO_VIDEO_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: GPIO HPD device not found\r\n");
        return(XST_FAILURE);
    }

    XGpio_SetDataDirection(&Gpio_RXHPD, 1, 0);
    XGpio_DiscreteWrite(&Gpio_RXHPD, 1, 0x0);
    usleep(1000);
    XGpio_DiscreteWrite(&Gpio_RXHPD, 1, 0x1);
    usleep(1000);

    return (XST_SUCCESS);
}

int init_tpg(void)
{
    int Status;
    Status = XV_tpg_Initialize(&Tpg, XPAR_V_DMA_SS_V_TPG_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: TPG device not found\r\n");
        return(XST_FAILURE);
    }

    //Stop TPG
    XV_tpg_DisableAutoRestart(&Tpg);

    XV_tpg_Set_height(&Tpg, 1080);
    XV_tpg_Set_width(&Tpg, 1920);
    XV_tpg_Set_ovrlayId(&Tpg, 0);
    XV_tpg_Set_colorFormat(&Tpg, XVIDC_CSF_RGB);
    XV_tpg_Set_bckgndId(&Tpg, XTPG_BKGND_TARTAN_COLOR_BARS);

    //move box
    XV_tpg_Set_ovrlayId(&Tpg, 1);
    XV_tpg_Set_boxSize(&Tpg,80);
    //if in YUV mode, R->Y,G->U,B->V,wrong ,it is g b r
    XV_tpg_Set_boxColorR(&Tpg,255);
    XV_tpg_Set_boxColorG(&Tpg,255);
    XV_tpg_Set_boxColorB(&Tpg,255);
    XV_tpg_Set_motionSpeed(&Tpg,1);

    //Start TPG
    XV_tpg_EnableAutoRestart(&Tpg);
    XV_tpg_Start(&Tpg);
    xil_printf("INFO: TPG configured\r\n");

    return (XST_SUCCESS);
}

int main()
{
    init_platform();

    print("Hello World\n\r");

    init_gpio();
    init_tpg();

    cleanup_platform();
    return 0;
}

將HDMI TX連線到顯示器,將HDMI RX連線到電腦。下載bit、elf,執行程式,首先可以看到顯示器顯示出了測試影象,上面的方形白色的色塊也可以正常移動;然後電腦螢幕閃了一下,看來是發現新的顯示器了,開啟NVIDIA控制面板,可以看到我們的新顯示裝置了DigilentDVI-3。
18.png
能讀到顯示器的配置,證明DDC介面的EDID資訊能夠正常傳輸。這個顯示裝置的名稱,我們可以在dvi2rgb的IP目錄中修改對應的資料即可。
再觀察一下開發板上的LED,

  1. LED0亮了,表示pl_ddr_init_done有效,MIG模組初始化PL DDR完成
  2. LED1亮了,表示HDMI RX IP: dvi2rgb模組接收到了HDMI輸入時鐘訊號,並已經倍頻鎖定。

2 MIX測試

首先官方的standalone mix bsp有個bug..,在xv_mix_l2.c檔案中。

 WinResInRange = ((Win->Width  > (XVMIX_MIN_STRM_WIDTH-1))  &&
                  (Win->Height > (XVMIX_MIN_STRM_HEIGHT-1)) &&
                  (Win->Width  < MixPtr->Config.LayerMaxWidth[LayerId-1]) &&
                  (Win->Height <= MixPtr->Config.MaxHeight));

中間有一句:Win->Width < MixPtr->Config.LayerMaxWidth[LayerId-1]
是小於號而不是等於號,其實是不對的,判斷條件應該是小於等於才對,否則我們將不能配置最大1920寬度的影象輸入。可以選擇修改bsp裡面的原始碼,或者修改mix ip核的配置。為了方便,我直接在SDK的安裝目錄裡面對bsp的程式碼進行了修改。

測試程式碼:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xil_cache.h"

#include "sleep.h"
#include "xgpio.h"
#include "xvidc.h"
#include "xv_tpg.h"
#include "xv_mix_l2.h"
#include "xv_frmbufwr_l2.h"

XGpio   Gpio_VDMA_resetn;
XGpio   Gpio_RXHPD;
XV_tpg  Tpg;
XV_Mix_l2       Mix;
XV_FrmbufWr_l2  FrmbufWr;

extern unsigned char Logo_R[];
extern unsigned char Logo_G[];
extern unsigned char Logo_B[];
extern unsigned char Logo_A[];

#define DEMO_PATTERN_0 0
#define DEMO_PATTERN_1 1
#define TPG_PASSTHROUGH_DISABLE 0
#define TPG_PASSTHROUGH_ENABLE 1

int init_gpio(void)
{
    int Status;
    Status = XGpio_Initialize(&Gpio_VDMA_resetn, XPAR_V_DMA_SS_AXI_GPIO_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: GPIO VDMA device not found\r\n");
        return(XST_FAILURE);
    }

    XGpio_SetDataDirection(&Gpio_VDMA_resetn, 1, 0);
    XGpio_DiscreteWrite(&Gpio_VDMA_resetn, 1, 0x7);
    usleep(10000);
    XGpio_DiscreteWrite(&Gpio_VDMA_resetn, 1, 0x0);
    usleep(10000);
    XGpio_DiscreteWrite(&Gpio_VDMA_resetn, 1, 0x7);
    usleep(10000);

    Status = XGpio_Initialize(&Gpio_RXHPD, XPAR_AXI_GPIO_VIDEO_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: GPIO HPD device not found\r\n");
        return(XST_FAILURE);
    }

    XGpio_SetDataDirection(&Gpio_RXHPD, 1, 0);
    usleep(10000);
    XGpio_DiscreteWrite(&Gpio_RXHPD, 1, 0x0);
    usleep(10000);
    XGpio_DiscreteWrite(&Gpio_RXHPD, 1, 0x1);
    usleep(10000);

    return (XST_SUCCESS);
}

int init_tpg(int IsPassThrough)
{
    int Status;
    Status = XV_tpg_Initialize(&Tpg, XPAR_V_DMA_SS_V_TPG_DEVICE_ID);
    if(Status != XST_SUCCESS) {
    xil_printf("ERR:: TPG device not found\r\n");
        return(XST_FAILURE);
    }

    //Stop TPG
    XV_tpg_DisableAutoRestart(&Tpg);

    XV_tpg_Set_height(&Tpg, 1080);
    XV_tpg_Set_width(&Tpg, 1920);
    XV_tpg_Set_ovrlayId(&Tpg, 0);
    XV_tpg_Set_colorFormat(&Tpg, XVIDC_CSF_RGB);
    XV_tpg_Set_bckgndId(&Tpg, XTPG_BKGND_TARTAN_COLOR_BARS);

    //move box
    XV_tpg_Set_ovrlayId(&Tpg, 1);
    XV_tpg_Set_boxSize(&Tpg,80);
    //if in YUV mode, R->Y,G->U,B->V,wrong ,it is g b r
    XV_tpg_Set_boxColorR(&Tpg,255);
    XV_tpg_Set_boxColorG(&Tpg,255);
    XV_tpg_Set_boxColorB(&Tpg,255);
    XV_tpg_Set_motionSpeed(&Tpg,1);

    if (IsPassThrough) {
        XV_tpg_Set_enableInput(&Tpg, IsPassThrough);
        XV_tpg_Set_passthruStartX(&Tpg,0);
        XV_tpg_Set_passthruStartY(&Tpg,0);
        XV_tpg_Set_passthruEndX(&Tpg,1920);
        XV_tpg_Set_passthruEndY(&Tpg,1080);
    }

    //Start TPG
    XV_tpg_EnableAutoRestart(&Tpg);
    XV_tpg_Start(&Tpg);
    xil_printf("INFO: TPG configured\r\n");

    return (XST_SUCCESS);
}

int init_mix(void)
{
    int Status;
    Status  = XVMix_Initialize(&Mix, XPAR_V_DMA_SS_V_MIX_DEVICE_ID);
        if(Status != XST_SUCCESS) {
        xil_printf("ERR:: Mixer device not found\r\n");
        return(XST_FAILURE);
    }

    XVidC_VideoStream VidStrm;
    u32 width, height;

    XVidC_VideoWindow Win_Logo;
    XVidC_VideoWindow Win_Layer1;
    u32 Stride_Layer1;

    width       = 1920;
    height  = 1080;

  //Stop TPG
    XVMix_Stop(&Mix);

    VidStrm.VmId           = XVIDC_VM_1080_60_P;
    VidStrm.ColorFormatId  = XVIDC_CSF_RGB;
    VidStrm.FrameRate      = XVIDC_FR_60HZ;
    VidStrm.IsInterlaced   = FALSE;
    VidStrm.ColorDepth     = XVIDC_BPC_8;
    VidStrm.PixPerClk      = XVIDC_PPC_1;
    VidStrm.Timing.HActive = width;
    VidStrm.Timing.VActive = height;
    XVMix_SetVidStream(&Mix, &VidStrm);

    u32 MemAddr = 0x10000000;
    Status = XVMix_SetLayerBufferAddr(&Mix, XVMIX_LAYER_1, MemAddr);
    if(Status != XST_SUCCESS) {
        xil_printf("MIXER ERR:: Unable to set layer %d buffer addr to 0x%X\r\n", XVMIX_LAYER_1, MemAddr);
    }

    Win_Layer1.StartX   = 0;
    Win_Layer1.StartY   = 0;
    Win_Layer1.Width    = width;
    Win_Layer1.Height   = height;
    Stride_Layer1       =   width*3;
    XVMix_SetLayerWindow(&Mix, XVMIX_LAYER_1, &Win_Layer1, Stride_Layer1);

    Win_Logo.StartX = 0;
    Win_Logo.StartY = 0;
    Win_Logo.Width      = 64;
    Win_Logo.Height = 64;
    if(XVMix_IsLogoEnabled(&Mix)) {
        Status = XVMix_LoadLogo(&Mix, &Win_Logo, Logo_R, Logo_G, Logo_B);
        if(Status != XST_SUCCESS) {
            xil_printf("MIXER ERR:: Unable to load Logo \r\n");
        }
        if(XVMix_IsLogoPixAlphaEnabled(&Mix)) {
            Status = XVMix_LoadLogoPixelAlpha(&Mix, &Win_Logo, Logo_A);
            if(Status != XST_SUCCESS) {
                xil_printf("MIXER ERR:: Unable to load Logo pixel alpha \r\n");
            }
        }
    } else {
        xil_printf("INFO: Logo Layer Disabled in HW \r\n");
    }


    XVMix_SetLayerAlpha(&Mix, XVMIX_LAYER_1, 255);
    XVMix_SetLayerAlpha(&Mix, XVMIX_LAYER_2, 255);
    XVMix_SetLayerAlpha(&Mix, XVMIX_LAYER_3, 255);
    XVMix_SetLayerAlpha(&Mix, XVMIX_LAYER_4, 255);
    XVMix_SetLayerAlpha(&Mix, XVMIX_LAYER_LOGO, 255);   //alpha layer should set to 255, then it will be enable to display
    XVMix_SetBackgndColor(&Mix, XVMIX_BKGND_RED, XVIDC_BPC_8);

    XVMix_LayerDisable(&Mix, XVMIX_LAYER_ALL);
    XVMix_LayerEnable(&Mix, XVMIX_LAYER_1);
    XVMix_LayerEnable(&Mix, XVMIX_LAYER_LOGO);

    //Start Mix
    XVMix_InterruptDisable(&Mix);
    XVMix_Start(&Mix);

    xil_printf("INFO: Mixer configured\r\n");

    usleep(1000);
    XVMix_DbgReportStatus(&Mix);
    XVMix_DbgLayerInfo(&Mix,XVMIX_LAYER_1);

    return (XST_SUCCESS);
}

void DemoPrintTest(u8 *frame, u32 width, u32 height, u32 stride, int pattern)
{
    u32 xcoi, ycoi;
    u32 iPixelAddr;
    u8 wRed, wBlue, wGreen;
    u32 wCurrentInt;
    double fRed, fBlue, fGreen, fColor;
    u32 xLeft, xMid, xRight, xInt;
    u32 yMid, yInt;
    double xInc, yInc;

    switch (pattern)
    {
    case DEMO_PATTERN_0:

        xInt = width / 4; //Four intervals, each with width/4 pixels
        xLeft = xInt * 3;
        xMid = xInt * 2 * 3;
        xRight = xInt * 3 * 3;
        xInc = 256.0 / ((double) xInt); //256 color intensities are cycled through per interval (overflow must be caught when color=256.0)

        yInt = height / 2; //Two intervals, each with width/2 lines
        yMid = yInt;
        yInc = 256.0 / ((double) yInt); //256 color intensities are cycled through per interval (overflow must be caught when color=256.0)

        fBlue = 0.0;
        fRed = 256.0;
        for(xcoi = 0; xcoi < (width*3); xcoi+=3)
        {
            /*
             * Convert color intensities to integers < 256, and trim values >=256
             */
            wRed = (fRed >= 256.0) ? 255 : ((u8) fRed);
            wBlue = (fBlue >= 256.0) ? 255 : ((u8) fBlue);
            iPixelAddr = xcoi;
            fGreen = 0.0;
            for(ycoi = 0; ycoi < height; ycoi++)
            {

                wGreen = (fGreen >= 256.0) ? 255 : ((u8) fGreen);
                frame[iPixelAddr] = wRed;
                frame[iPixelAddr + 1] = wBlue;
                frame[iPixelAddr + 2] = wGreen;
                if (ycoi < yMid)
                {
                    fGreen += yInc;
                }
                else
                {
                    fGreen -= yInc;
                }

                /*
                 * This pattern is printed one vertical line at a time, so the address must be incremented
                 * by the stride instead of just 1.
                 */
                iPixelAddr += stride;
            }

            if (xcoi < xLeft)
            {
                fBlue = 0.0;
                fRed -= xInc;
            }
            else if (xcoi < xMid)
            {
                fBlue += xInc;
                fRed += xInc;
            }
            else if (xcoi < xRight)
            {
                fBlue -= xInc;
                fRed -= xInc;
            }
            else
            {
                fBlue += xInc;
                fRed = 0;
            }
        }
        /*
         * Flush the framebuffer memory range to ensure changes are written to the
         * actual memory, and therefore accessible by the VDMA.
         */
        Xil_DCacheFlushRange((unsigned int) frame, width * stride);
        break;
    case DEMO_PATTERN_1:

        xInt = width / 7; //Seven intervals, each with width/7 pixels
        xInc = 256.0 / ((double) xInt); //256 color intensities per interval. Notice that overflow is handled for this pattern.

        fColor = 0.0;
        wCurrentInt = 1;
        for(xcoi = 0; xcoi < (width*3); xcoi+=3)
        {

            /*
             * Just draw white in the last partial interval (when width is not divisible by 7)
             */
            if (wCurrentInt > 7)
            {
                wRed = 255;
                wBlue = 255;
                wGreen = 255;
            }
            else
            {
                if (wCurrentInt & 0b001)
                    wRed = (u8) fColor;
                else
                    wRed = 0;

                if (wCurrentInt & 0b010)
                    wBlue = (u8) fColor;
                else
                    wBlue = 0;

                if (wCurrentInt & 0b100)
                    wGreen = (u8) fColor;
                else
                    wGreen = 0;
            }

            iPixelAddr = xcoi;

            for(ycoi = 0; ycoi < height; ycoi++)
            {
                frame[iPixelAddr] = wRed;
                frame[iPixelAddr + 1] = wBlue;
                frame[iPixelAddr + 2] = wGreen;
                /*
                 * This pattern is printed one vertical line at a time, so the address must be incremented
                 * by the stride instead of just 1.
                 */
                iPixelAddr += stride;
            }

            fColor += xInc;
            if (fColor >= 256.0)
            {
                fColor = 0.0;
                wCurrentInt++;
            }
        }
        /*
         * Flush the framebuffer memory range to ensure changes are written to the
         * actual memory, and therefore accessible by the VDMA.
         */
        Xil_DCacheFlushRange((unsigned int) frame, width * stride);
        break;
    default :
        xil_printf("Error: invalid pattern passed to DemoPrintTest");
    }
}

void DemoLogoTest(void)
{
    u16 logo_x=0;
    u16 logo_y=200;
    while(1)
    {
        usleep(10000);
        if(logo_x < 1920-64)
            logo_x++;
        else
            logo_x = 0;
        XVMix_MoveLayerWindow(&Mix, XVMIX_LAYER_LOGO,logo_x,logo_y);
        XVMix_MoveLayerWindow(&Mix, XVMIX_LAYER_LOGO,logo_x,logo_y);
    }
}
int main()
{
    init_platform();

    print("Hello World\n\r");

    //frame base, 1920*1080*3
    u8 *pixel_base;
    pixel_base=(u8 *)0x10000000;
    u32 f_width, f_height, f_stride;
    f_width = 1920;
    f_height    = 1080;
    f_stride    = 1920 * 3;

    print("step 1: test tpg output\n\r");
    init_gpio();
    init_tpg(TPG_PASSTHROUGH_DISABLE);
    usleep(6000000);

    print("step 2: enable tpg passthrough and init mix\n\r");
    init_gpio();
    init_mix();
    init_tpg(TPG_PASSTHROUGH_ENABLE);
    usleep(6000000);

    print("step 3: test display pattern 0\n\r");
    DemoPrintTest(pixel_base, f_width, f_height, f_stride, DEMO_PATTERN_0);
    usleep(6000000);

    print("step 4: test display pattern 1\n\r");
    DemoPrintTest(pixel_base, f_width, f_height, f_stride, DEMO_PATTERN_1);
    usleep(6000000);

    print("step 5: move mix logo\n\r");
    DemoLogoTest();

    print("\r\nTEST DONE!!!\n\r");

    cleanup_platform();
    return 0;
}

程式碼是在剛才TPG上的程式碼做的修改,將TPG的passthrough功能開啟,然後讓mix從基址0x10000000讀取RGB原始資料並進行輸出,還用程式在影象快取地址上軟體寫了pattern可以看到色彩變化,最後還測試了mix的logo功能,讓logo水平移動。logo的原始資料包含了4個通道:RGBA,各自為64*64位元組,通過mix的驅動函式可以將logo寫到pl內的bram內。logo的原始資料比較大,我就不貼了。類似下圖的定義
19.png
可以用python或者matlab生成這四個陣列。這個logo功能是支援透明度的,所以效果還是不錯的。
使用PS畫一個logo的png檔案,Matlab生成logo陣列的程式碼:

%clear
clc;
clear;
%read image
[img,map,alpha] = imread('logo.png');
[h,w,dim] = size(img);
%split r,g,b,alpha
for i=1:h
    for j=1:w
        r(i,j) = img(i,j,1);
        g(i,j) = img(i,j,2);
        b(i,j) = img(i,j,3);
    end
end

%bug fixed
%vivado 2017.1 video mixer logo bug, see details in video_mixer IP bug.txt
%%
r_d = reshape(r', 1 ,w*h);
g_d = reshape(g', 1 ,w*h);
b_d = reshape(b', 1 ,w*h);
alpha_d = reshape(alpha', 1 ,w*h);
for i=2:w*h
    r_d_s(i) = r_d(i-1);
    g_d_s(i) = g_d(i-1);
    b_d_s(i) = b_d(i-1);
    alpha_d_s(i) = alpha_d(i-1);
end
r_d_s(1) = r_d(w*h);
g_d_s(1) = g_d(w*h);
b_d_s(1) = b_d(w*h);
alpha_d_s(1) = alpha_d(w*h);
r_r = reshape(r_d_s, w, h);
g_r = reshape(g_d_s, w, h);
b_r = reshape(b_d_s, w, h);
alpha_r = reshape(alpha_d_s, w, h);
r = r_r';
g = g_r';
b = b_r';
alpha = alpha_r';
%%

%write logo.c
fid = fopen('logo.c','wt');
fprintf(fid,'%s\n','/*');
fprintf(fid,'%s\n',' * logo.c');
fprintf(fid,'%s\n',' *');
fprintf(fid,'%s%d * %d\n',' *  LOGO size : ',h,w);
fprintf(fid,'%s%s\n',' * Created on : ',datestr(now));
fprintf(fid,'%s%s\n',' *       Tool : Matlab ',version);
fprintf(fid,'%s\n',' *     Author : Vacajk');
fprintf(fid,'%s\n',' */');
fprintf(fid,'\n');
%fprint array data
%red
fprintf(fid,'%s\n','unsigned char Logo_R[] = {');
for i=1:h
    for j=1:w
        fprintf(fid,'%s%02x,','0x',r(i,j));
    end
    fprintf(fid,'\n');
end
fprintf(fid,'%s\n','};');
fprintf(fid,'\n');
%green
fprintf(fid,'%s\n','unsigned char Logo_G[] = {');
for i=1:h
    for j=1:w
        fprintf(fid,'%s%02x,','0x',g(i,j));
    end
    fprintf(fid,'\n');
end
fprintf(fid,'%s\n','};');
fprintf(fid,'\n');
%blue
fprintf(fid,'%s\n','unsigned char Logo_B[] = {');
for i=1:h
    for j=1:w
        fprintf(fid,'%s%02x,','0x',b(i,j));
    end
    fprintf(fid,'\n');
end
fprintf(fid,'%s\n','};');
fprintf(fid,'\n');
%alpha
fprintf(fid,'%s\n','unsigned char Logo_A[] = {');
for i=1:h
    for j=1:w
        fprintf(fid,'%s%02x,','0x',alpha(i,j));
    end
    fprintf(fid,'\n');
end
fprintf(fid,'%s\n','};');
%close fid handle
fclose(fid);

video_mixer IP bug.txt

1.vivado 2017.1中 video mixer IP核在使用1 sample per clock時,logo會有bug。(因為官網說1spc時候沒有驗證,感覺是這裡有問題)
logo的四個通道RGBA陣列的第1個位元組會跑到螢幕上的最後一個位元組,第2個位元組將作為首地址顯示。
2.XVMIX_SCALE_FACTOR_4X能完全顯示logo,但是1x和2x的時候前2列前1列資料會丟失,不知道什麼原因
為了解決這個問題,在m檔案中,將陣列的最後一個位元組和第一個位元組對調。。這樣就能確保顯示的是正確的,不會錯行顯示

3 FRMBUF_WR測試

mix和tpg都測試完了,下面來測試frmbuf_wr ip的功能。
frmbuf_wr是連線到hdmi rx上的,前面已經解決了hpd訊號的問題,我們電腦輸入到MIZ7035的HDMI訊號也已經被dvi2rgb IP的mmcms鎖定,說明輸入資料已經發過來了,只要把視訊輸入通路上的frmbuf_wr打通,影象資料就能夠寫到ddr中了。
為了方便,我直接將frmbuf_wr寫入的幀地址與mix讀取的幀地址寫成一樣的,這樣通過HDMI RX接收到的視訊經frmbuf_wr寫入記憶體,就將能夠直接通過mix、tpg最後到HDMI TX進行顯示了。
當然其中有個問題是HDMI輸入的視訊必須要保證是1080p的視訊,否則配置與實際不符的話會出問題。實際用的時候就需要根據hdmi_tx_ss中的vtc模組去檢測輸入影象了,然後去按照實際視訊格式進行frmbuf_wr的配置以及顯示輸出的配置。

測試程式碼:

#define XVFRMBUFWR_BUFFER_BASEADDR (0x10000000)

int init_frmbufwr(void)
{

    u32 width, height;
    u32 StrideInBytes;
    XVidC_VideoStream VidStrm;
    XVidC_VideoTiming const *TimingPtr;

    int Status;
    Status = XVFrmbufWr_Initialize(&FrmbufWr, XPAR_V_DMA_SS_V_FRMBUF_WR_DEVICE_ID);
    if(Status != XST_SUCCESS) {
        xil_printf("ERROR:: Frame Buffer Write initialization failed\r\n");
        return(XST_FAILURE);
    }

    /* Stop Frame Buffers */
    XVFrmbufWr_Stop(&FrmbufWr);

    width       = 1920;
    height  = 1080;
    StrideInBytes = width*3;

    VidStrm.VmId           = XVIDC_VM_1080_60_P;
    VidStrm.ColorFormatId  = XVIDC_CSF_RGB;
    VidStrm.FrameRate      = XVIDC_FR_60HZ;
    VidStrm.IsInterlaced   = FALSE;
    VidStrm.ColorDepth     = XVIDC_BPC_8;
    VidStrm.PixPerClk      = XVIDC_PPC_1;
    VidStrm.Timing.HActive = width;
    VidStrm.Timing.VActive = height;
    TimingPtr = XVidC_GetTimingInfo(VidStrm.VmId);
    VidStrm.Timing = *TimingPtr;

    /* Configure  Frame Buffers */
    Status = XVFrmbufWr_SetMemFormat(&FrmbufWr, StrideInBytes, XVIDC_CSF_MEM_RGB8, &VidStrm);
    if(Status != XST_SUCCESS) {
        xil_printf("ERROR:: Unable to configure Frame Buffer Write\r\n");
        return(XST_FAILURE);
    }

    Status = XVFrmbufWr_SetBufferAddr(&FrmbufWr, (UINTPTR)0x10000000);
    if(Status != XST_SUCCESS) {
        xil_printf("ERROR:: Unable to configure Frame Buffer Write buffer address\r\n");
        return(XST_FAILURE);
    }

    /* Enable Interrupt */
    XVFrmbufWr_InterruptDisable(&FrmbufWr);

    /* Start Frame Buffers */
    XVFrmbufWr_Start(&FrmbufWr);

    xil_printf("INFO: FRMBUF configured\r\n");

    usleep(1000);
    XVFrmbufWr_DbgReportStatus(&FrmbufWr);

    return (XST_SUCCESS);
}

結果呢,等我寫完程式碼進行測試時候,發現1080p的視訊輸入板子沒有任何反應。看了一下原理圖,HDMI RX的聯結器使用的是交流耦合,然後進入FPGA的,但是耦合的電容後面的端接電阻沒有焊接,那我手動焊接一下。
這裡為什麼要用交流耦合,是因為輸入的TMDS訊號與接入的FPGA HP BANK電壓不能匹配,我們需要將TMDS訊號轉為LVDS訊號才能輸入HP BANK。
可以看一下這篇文件:交流耦合的優點
https://www.maximintegrated.com/cn/app-notes/index.mvp/id/4085
下圖就是跟MIZ7035使用到的相同端接方法
20.png
但是這裡的VCC是3.3V,戴維寧等效電阻算出來是50歐。而我們的系統的VCC是1.8V,LVDS的共模電壓為1.2V,根據戴維寧端接方法計算:

1.2V/1.8V=2/3=2R/(1R+2R) ->上拉電阻為1R、下拉電阻為2R
1R*2R/(1R+2R)=50

得到R=75,2R=150。
運氣比較好,在電阻本里剛好找到了這兩個阻值的電阻,焊接上去。
果然能夠顯示了,但是解析度最高只能設定到1600*900。比這個值大的解析度卻不能正常顯示,出現的是藍色的條紋。還沒分析出具體的原因。我用ila線上抓過dvi2rgb的訊號,發現它的輸出訊號是有異常的,但是時鐘看起來沒有問題,看來問題就是出現在了這個ip中的某個地方。我試了將ac耦合電容換成0.2uf將lvds輸入訊號的擺浮稍微提高了一些,仍然沒有效果。實在是不知道該怎麼解決了。那就先用1600*900區測吧。

dvi2rgb中iodelay使用的是200MHz參考時鐘,在1080p的時候serdes只有大約17個間隔,不知道是不是data訊號的抖動過大導致不能鎖住?可以去改成300MHz,但是改了後對低解析度的可能又不支援了。
在這之中,還焊接過上拉10K下拉20K的方法,但是能夠正常輸入的解析度支援的更低了,看來阻抗還是對整個系統有影響的。沒學過高速電路,搞得人有點不太懂額。又問了老闆,老闆說他們AC耦合時候也能支援到1600*900,我當時沒有試,那看來我後面接的戴維寧電阻沒起到用處?

4 使用PL側的DDR作為緩衝

前面程式碼在0x10000000位置建立了一個幀緩衝,是位於PS側的DDR3。在SDK中使用搜索替換功能,將所有的0x10000000替換為0xC0000000,編譯並重新下載。
HDMI輸入輸出依然OK,這就說明我們的PL DDR也是能作為影象快取的,不錯不錯。

5 三重緩衝

輸入輸出打通了,但是隻用了一塊幀快取。可以想一想,同時對一片區域的讀寫,當輸入輸出時鐘頻率完全同步時,可能有機會得到一個正常的視訊顯示。但實際是頻率有略微差別的,所以輸入輸出會在每一時刻發生對一個位元組資料的同時讀寫,這時候就會發生影象撕裂,或是一些其他的情況。因此需要使用多個快取進行影象緩衝,實現讀寫不在同一個緩衝的目的。經常使用的緩衝數有2或3。影象處理時有可能CPU也去參與到其中,而CPU是帶cache的,資料更新到DDR是有時間要求的,所以使用3重緩衝更為合適。這也只是我的經驗,不同情況下可能有不同的結果。

這裡說一下思路,具體實現等有空了再來做。
Xilinx的VDMA IP是帶有多個緩衝自動切換功能的,但是畢竟沒有mix IP好用。
為了同步,有以下幾個方面需要進行處理:

  1. 視訊輸入是經過frmbuf_wr寫進記憶體的,所以需要使用它的中斷訊號來對輸入進行同步處理
  2. 視訊輸出是經過mix和tpg輸出到外部顯示器的,所以可以選擇,我一般會用mix的中斷來對輸出進行同步處理
  3. 在該系統中,輸出的高速時鐘是通過板內外部100m時鐘倍頻得到的,而輸出的高速時鐘是PC給板子的。若只是一個輸入輸出顯示的工程,這樣做就可以了。但若是一個嚴格要求的視訊處理卡(做到0丟幀),則需要將輸入的高速時鐘接入到HDMI TX通道中去,這樣才能夠保證兩個通道達到完全意義上的同步。

3 總結

在MIZ7035開發板上完成了HDMI輸入輸出的設計,分析了開發板設計上的一些問題(這些問題還真的很費時間)。
給出了整個工程的驅動程式碼,大家可以參考使用。