• 正文
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

FPGA大神Adam Taylor使用ALINX VD100(AMD Versal系列)實現(xiàn)圖像處理

7小時前
312
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

本篇文章來自 FPGA 大神、Ardiuvo & Hackster.IO?知名博主 Adam Taylor。在這里感謝 Adam Taylor 對 ALINX 產(chǎn)品的關(guān)注與測試。為了讓文章更易閱讀,我們在原文的基礎(chǔ)上作了一些靈活的調(diào)整,包括對一些專業(yè)名詞進(jìn)行了補充解釋,便于初學(xué)者快速理解。原文鏈接已貼在文章底部,歡迎大家在評論區(qū)友好互動。


最近,我在辦公室里搞了一塊?ALINX VD100。

這是一塊基于?AMD Versal Edge AI?平臺的開發(fā)板,功能特別強大,可以用來做圖像處理、人工智能等各種高階應(yīng)用。

為了方便隨時開發(fā),我把它連到了公司局域網(wǎng),遠(yuǎn)程就能連接,由此開始了我的折騰之旅。

這次,我最想探索的是:怎么在這塊板子上跑圖像處理的應(yīng)用。

VD100不僅帶了兩個 MIPI 攝像頭接口,還能直接連上?LCD?屏幕,基本滿足了圖像應(yīng)用開發(fā)的需求。

第一步:讓屏幕先亮起來!

搞攝像頭太復(fù)雜,我決定先從屏幕入手——先通過測試圖案生成器(Test Pattern Generator)驗證 LCD 屏幕圖像顯示鏈路的有效性。

ALINX VD100開發(fā)板支持的 LCD 屏幕,分辨率是?1280×720(WXGA 標(biāo)準(zhǔn))。

數(shù)據(jù)傳輸采用的是?VESA 標(biāo)準(zhǔn)的 LVDS 接口。

這里稍微解釋一下:

LVDS(低壓差分信號)是一種高速又抗干擾的數(shù)據(jù)傳輸方式,特別適合屏幕傳輸高速畫面。

VESA 和 JEIDA 是常見的兩種 LVDS 傳輸標(biāo)準(zhǔn),咱們用的是 VESA。VESA 更國際通用,JEIDA 常見于日本廠商

在 VESA 標(biāo)準(zhǔn)下使用 RG888 格式時,屏幕的每一幀圖像數(shù)據(jù)和控制信號(如同步信號 hsync、vsync)會被打包進(jìn) 4 條數(shù)據(jù)通道里,同時還有第 5 條通道專門傳時鐘(Clock),方便接收端正確還原數(shù)據(jù)。

每次時鐘跳動時,每條數(shù)據(jù)通道都會同步傳?7 位數(shù)據(jù)。

聽起來有點復(fù)雜?簡單說就是:用 4+1 條小路,高速搬運屏幕畫面。

開發(fā)流程:搭建系統(tǒng)設(shè)計

為了讓板子順利傳屏幕數(shù)據(jù),我們需要在 Vivado(AMD/Xilinx 的開發(fā)工具)里做一套設(shè)計,包括:

  1. CIPS
    → 配置 VD100 平臺上的 V100 SoM。
  2. NOC
    → 配置 DDRMC,并開兩條 MAXI 輸出,提供數(shù)據(jù)存取支持。
  3. 視頻測試圖生成器 (Video Test Pattern Generator, TPG)
    → 通過 AXI Lite 總線連接到 NOC,生成標(biāo)準(zhǔn)圖像(比如彩條、棋盤格)。
  4. 視頻時序生成器 (Video Timing Controller, VTC)
    → 通過 AXI Lite 總線連接到 NOC,生成 LCD 顯示需要的同步信號(如 hsync、vsync)。
  5. AXI4-Stream to Video Out
    → 與上述兩個生成器相連,把測試圖和時序信息組織成并行視頻流(RGB888格式)。
  6. LCD_LVDS IP核
    → 把并行視頻信號轉(zhuǎn)成符合 LVDS 規(guī)范的數(shù)據(jù)流。
  7. Advanced IO Wizard
    → 負(fù)責(zé)真正的串行化操作,把數(shù)據(jù)以 LVDS 標(biāo)準(zhǔn)發(fā)出去(包括 4 路數(shù)據(jù)+ 1 路時鐘)。

最終 LCD 屏幕接收到 LVDS 信號并顯示圖像。

這套設(shè)計(可以在我的 GitHub 上找到)如下所示:

通過這個系統(tǒng),我們可以使用 CIPS 內(nèi)置的處理器來控制測試圖案,從而驗證各顏色通道是否正常。

設(shè)置和驅(qū)動 LCD 顯示器的 CIPS 端代碼也非常簡單,如下所示:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xvtc.h"
#include "xparameters.h"
#include "xv_tpg.h"
#include "xvidc.h"
#include"vga.h"

XV_tpg      tpg;
XVtc	    VtcInst;
VideoMode   video;
XVtc_Config *vtc_config ;

int main()
{
    XVtc_SourceSelect SourceSelect;
    XVtc_Timing vtcTiming;
    u32 height,width,status;
    init_platform();

    print("Setting up Timingnr");
    vtc_config = XVtc_LookupConfig(XPAR_XVTC_0_BASEADDR);        
    XVtc_CfgInitialize(&VtcInst,vtc_config ,XPAR_XVTC_0_BASEADDR);
    
    print("Setting up Videonr");
    video = VMODE_1280x720 ;
	vtcTiming.HActiveVideo = video.width;	
	vtcTiming.HFrontPorch = video.hps - video.width;	
	vtcTiming.HSyncWidth = video.hpe - video.hps;		
	vtcTiming.HBackPorch = video.hmax - video.hpe + 1;	
	vtcTiming.HSyncPolarity = video.hpol;	
	vtcTiming.VActiveVideo = video.height;	
	vtcTiming.V0FrontPorch = video.vps - video.height;	
	vtcTiming.V0SyncWidth = video.vpe - video.vps;	
	vtcTiming.V0BackPorch = video.vmax - video.vpe + 1;;	
	vtcTiming.V1FrontPorch = video.vps - video.height;	
	vtcTiming.V1SyncWidth = video.vpe - video.vps;	
	vtcTiming.V1BackPorch = video.vmax - video.vpe + 1;
	vtcTiming.VSyncPolarity = video.vpol;	
	vtcTiming.Interlaced = 0;

    print("Setting up TPGnr");
        
    	XV_tpg_Initialize(&tpg,XPAR_XV_TPG_0_BASEADDR );
    	status = XV_tpg_IsIdle(&tpg);
    	XV_tpg_Set_height(&tpg, (u32) video.height);
	XV_tpg_Set_width(&tpg, (u32) video.width);
	height = XV_tpg_Get_height(&tpg);
	width = XV_tpg_Get_width(&tpg);
	XV_tpg_Set_colorFormat(&tpg,XVIDC_CSF_RGB);
    	XV_tpg_Set_bckgndId(&tpg,XTPG_BKGND_TARTAN_COLOR_BARS);
	XV_tpg_Set_maskId(&tpg, 0x0);
	XV_tpg_Set_motionSpeed(&tpg, 0x4);
    	XV_tpg_EnableAutoRestart(&tpg);
	XV_tpg_Start(&tpg);
    
    print("Setting up Sourcenr");

    memset((void *)&SourceSelect, 0, sizeof(XVtc_SourceSelect));
	SourceSelect.VBlankPolSrc = 1;
	SourceSelect.VSyncPolSrc = 1;
	SourceSelect.HBlankPolSrc = 1;
	SourceSelect.HSyncPolSrc = 1;
	SourceSelect.ActiveVideoPolSrc = 1;
	SourceSelect.ActiveChromaPolSrc= 1;
	SourceSelect.VChromaSrc = 1;
	SourceSelect.VActiveSrc = 1;
	SourceSelect.VBackPorchSrc = 1;
	SourceSelect.VSyncSrc = 1;
	SourceSelect.VFrontPorchSrc = 1;
	SourceSelect.VTotalSrc = 1;
	SourceSelect.HActiveSrc = 1;
	SourceSelect.HBackPorchSrc = 1;
	SourceSelect.HSyncSrc = 1;
	SourceSelect.HFrontPorchSrc = 1;
	SourceSelect.HTotalSrc = 1;

    print("Run Timing Gennr");    
	
	XVtc_SetGeneratorTiming(&VtcInst, &vtcTiming);
	XVtc_SetSource(&VtcInst, &SourceSelect);
	XVtc_EnableGenerator(&VtcInst);
    XVtc_RegUpdateEnable(&VtcInst);
	XVtc_Enable(&VtcInst);

    while(1){

    };

    cleanup_platform();
    return 0;
}

LCD_LVDS 這個模塊,我是直接從 ALINX 的 GitHub 倉庫上下載的。

下載好后,把它加到 Vivado 里面,就能像拼積木一樣拖進(jìn)設(shè)計里。

LCD_LVDS 下載鏈接:github.com/alinxalinx/V

寫好所有程序后,我們讓開發(fā)板運行,屏幕上果然顯示出了測試圖案,色彩鮮明,說明各個顏色通道都正常了。開發(fā)板和屏幕之間的溝通,算是正式打通了!

不過,這里面有個很有意思的事情。

Advanced IO Wizard 這個模塊,默認(rèn)是按 8 位一組來打包數(shù)據(jù)發(fā)出去的。

而我們的 VESA LVDS 傳輸,要求 7 位一組。這咋辦?

我用到了一個叫做 Gearbox 的小模塊,把 7 位數(shù)據(jù)轉(zhuǎn)換成 4 位數(shù)據(jù)輸出。

然后,用 Advanced IO Wizard 把 4 位數(shù)據(jù)高速串行發(fā)出去。

這樣就實現(xiàn)了 7 位序列化,雖然中間多了一步變換,但整體還是很高效的。

不過,其實只要手動配置一下,Advanced IO Wizard 也是可以支持直接 7 位打包發(fā)送的,只是這次參考了 AMD 的官方應(yīng)用筆記(參考代碼叫 tx_piso_7to1),所以先用了 Gearbox 的方式。

未來有機會的話,我想試著優(yōu)化一下,直接用 7 位串行模式,把系統(tǒng)做得更簡潔高效!

接下來要做的

現(xiàn)在圖像輸出環(huán)節(jié)已經(jīng)搞定了,接下來就是更刺激的前端部分:

通過 MIPI 接口接入攝像頭,把真實拍到的圖像,實時顯示到屏幕上。

真正的圖像處理任務(wù),馬上就要開始啦!

(未完待續(xù))

原文鏈接:MicroZed Chronicles: Versal AI Edge, VESA LCD Display.

相關(guān)推薦