FPGA 大神 Adam Taylor 使用 ALINX VD100(AMD Versal系列)开发平台实现图像处理

本篇文章来自 FPGA 大神、Ardiuvo & Hackster.IO 知名博主 Adam Taylor。在这里感谢 Adam Taylor 对 ALINX 产品的关注与测试。为了让文章更易阅读,我们在原文的基础上作了一些灵活的调整,包括对一些专业名词进行了补充解释,便于初学者快速理解。

最近,我在办公室里搞了一块 ALINX VD100。

ALINX VD100.png

这是一块基于 AMD Versal Edge AI 平台的开发板,功能特别强大,可以用来做图像处理、人工智能等各种高阶应用。

为了方便随时开发,我把它连到了公司局域网,远程就能连接,由此开始了我的折腾之旅。

这次,我最想探索的是:怎么在这块板子上跑图像处理的应用。

VD100不仅带了两个 MIPI 摄像头接口,还能直接连上 LCD 屏幕,基本满足了图像应用开发的需求。

第一步:让屏幕先亮起来!

搞摄像头太复杂,我决定先从屏幕入手——先通过测试图案生成器(Test Pattern Generator)验证 LCD 屏幕图像显示链路的有效性。

ALINX VD100开发板支持的 LCD 屏幕,分辨率是 1280×720(WXGA 标准)。

数据传输采用的是 VESA 标准的 LVDS 接口。

这里稍微解释一下:

LVDS(低压差分信号)是一种高速又抗干扰的数据传输方式,特别适合屏幕传输高速画面。

VESA 和 JEIDA 是常见的两种 LVDS 传输标准,咱们用的是 VESA。VESA 更国际通用,JEIDA 常见于日本厂商。

在 VESA 标准下使用 RG888 格式时,屏幕的每一帧图像数据和控制信号(如同步信号 hsync、vsync)会被打包进 4 条数据通道里,同时还有第 5 条通道专门传时钟(Clock),方便接收端正确还原数据。

还原数据.JPG

每次时钟跳动时,每条数据通道都会同步传 7 位数据。

听起来有点复杂?简单说就是:用 4+1 条小路,高速搬运屏幕画面。

开发流程:搭建系统设计

为了让板子顺利传屏幕数据,我们需要在 Vivado(AMD/Xilinx 的开发工具)里做一套设计,包括:

  1. CIPS
    → 配置 VD100 平台上的 V100 SoM。

  2. NOC
    → 配置 DDRMC,并开两条 MAXI 输出,提供数据存取支持。

  3. 视频测试图生成器 (Video Test Pattern Generator, TPG)
    → 通过 AXI Lite 总线连接到 NOC,生成标准图像(比如彩条、棋盘格)。

  4. 视频时序生成器 (Video Timing Controller, VTC)
    → 通过 AXI Lite 总线连接到 NOC,生成 LCD 显示需要的同步信号(如 hsync、vsync)。

  5. AXI4-Stream to Video Out
    → 与上述两个生成器相连,把测试图和时序信息组织成并行视频流(RGB888格式)。

  6. LCD_LVDS IP核
    → 把并行视频信号转成符合 LVDS 规范的数据流。

  7. Advanced IO Wizard
    → 负责真正的串行化操作,把数据以 LVDS 标准发出去(包括 4 路数据+ 1 路时钟)。

最终 LCD 屏幕接收到 LVDS 信号并显示图像。

这套设计(可以在我的 GitHub 上找到)如下所示:

ALINX VD100-3.png

通过这个系统,我们可以使用 CIPS 内置的处理器来控制测试图案,从而验证各颜色通道是否正常。

设置和驱动 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 ;
intmain(){    XVtc_SourceSelect SourceSelect;    XVtc_Timing vtcTiming;    u32 height,width,status;    init_platform();
    print("Setting up Timing\n\r");    vtc_config = XVtc_LookupConfig(XPAR_XVTC_0_BASEADDR);            XVtc_CfgInitialize(&VtcInst,vtc_config ,XPAR_XVTC_0_BASEADDR);
    print("Setting up Video\n\r");    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 TPG\n\r");
      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 Source\n\r");
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 Gen\n\r");    
  XVtc_SetGeneratorTiming(&VtcInst, &vtcTiming);  XVtc_SetSource(&VtcInst, &SourceSelect);  XVtc_EnableGenerator(&VtcInst);    XVtc_RegUpdateEnable(&VtcInst);  XVtc_Enable(&VtcInst);
while(1){
    };
    cleanup_platform();return0;}

LCD_LVDS 这个模块,我是直接从 ALINX 的 GitHub 仓库上下载的。

下载好后,把它加到 Vivado 里面,就能像拼积木一样拖进设计里。

LCD_LVDS 下载链接:https://github.com/alinxalinx/VD100_2023.2/tree/master/Demo/course_s1

写好所有程序后,我们让开发板运行,屏幕上果然显示出了测试图案,色彩鲜明,说明各个颜色通道都正常了。开发板和屏幕之间的沟通,算是正式打通了!

测试图案.png

测试图案2.png

不过,这里面有个很有意思的事情。

Advanced IO Wizard 这个模块,默认是按 8 位一组来打包数据发出去的。

而我们的 VESA LVDS 传输,要求 7 位一组。这咋办?

我用到了一个叫做 Gearbox 的小模块,把 7 位数据转换成 4 位数据输出。

然后,用 Advanced IO Wizard 把 4 位数据高速串行发出去。

这样就实现了 7 位序列化,虽然中间多了一步变换,但整体还是很高效的。

不过,其实只要手动配置一下,Advanced IO Wizard 也是可以支持直接 7 位打包发送的,只是这次参考了 AMD 的官方应用笔记(参考代码叫 tx_piso_7to1),所以先用了 Gearbox 的方式。

未来有机会的话,我想试着优化一下,直接用 7 位串行模式,把系统做得更简洁高效!

接下来要做的

现在图像输出环节已经搞定了,接下来就是更刺激的前端部分:

通过 MIPI 接口接入摄像头,把真实拍到的图像,实时显示到屏幕上。

真正的图像处理任务,马上就要开始啦!

原文链接:https://www.adiuvoengineering.com/post/microzed-chronicles-versal-ai-edge-vesa-lcd-display 

(未完待续)

本文转载自:ALINX

最新文章

最新文章