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個
- 片上BUFx的輸出
- MMCM/PLL經BUFx的輸出
不管用哪種方式,其實都是必須經過BUFx進行緩衝的。
BUFx可用的組合方式可以看xapp585,
而XC7Z035-2的速率如下圖。
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,則另外一個通道只能有以下情況:
- MMCM+BUFG+BUFG,因為BUFIO不能跨時鐘域
- 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(高電平時)。
那麼當我們用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
rgb2div
輸入輸出各自的vtc、vid參考demo即可。demo中的vdma ip我們不去使用。
影象資料與DDR的互動使用了tpg、mix、frmbuf_wr,跟ZCU102中的配置相同,區別在於為了跨不同BANK,我們在兩個AXI4-Stream Register Slice與其相連的IP間插入了一個AXI4-Stream Data FIFO。
整體框圖如下
hdmi_rx_ss
hdmi_tx_ss
其中的clk_wiz配置如下
clk_wiz的fb輸入輸出直接相連線,沒有經過任何BUF,因為我們用到的pclk和sclk的相位關係在HDMI TX中並不重要,axi_dynclk的原始碼中有寫。
v_dma_ss
zynq_ss,v_dma_ss過來的HP訊號同時接入了PS和MIG,這樣就能夠我們自行選擇用哪一邊的DDR了。但是PL端的DDR在這個設計中PS是沒有定址的,所以訪問不了。
addree配置,pl ddr定位到了0xC000000,1GB空間。
約束
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]
編譯
資源佔用
時序錯誤,bufh不能達到742.5MHz
資源分佈
生成並輸出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。
能讀到顯示器的配置,證明DDC介面的EDID資訊能夠正常傳輸。這個顯示裝置的名稱,我們可以在dvi2rgb的IP目錄中修改對應的資料即可。
再觀察一下開發板上的LED,
- LED0亮了,表示pl_ddr_init_done有效,MIG模組初始化PL DDR完成
- 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的原始資料比較大,我就不貼了。類似下圖的定義
可以用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使用到的相同端接方法
但是這裡的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好用。
為了同步,有以下幾個方面需要進行處理:
- 視訊輸入是經過frmbuf_wr寫進記憶體的,所以需要使用它的中斷訊號來對輸入進行同步處理
- 視訊輸出是經過mix和tpg輸出到外部顯示器的,所以可以選擇,我一般會用mix的中斷來對輸出進行同步處理
- 在該系統中,輸出的高速時鐘是通過板內外部100m時鐘倍頻得到的,而輸出的高速時鐘是PC給板子的。若只是一個輸入輸出顯示的工程,這樣做就可以了。但若是一個嚴格要求的視訊處理卡(做到0丟幀),則需要將輸入的高速時鐘接入到HDMI TX通道中去,這樣才能夠保證兩個通道達到完全意義上的同步。
3 總結
在MIZ7035開發板上完成了HDMI輸入輸出的設計,分析了開發板設計上的一些問題(這些問題還真的很費時間)。
給出了整個工程的驅動程式碼,大家可以參考使用。