【HDMI出力編(3)】静止画をZynqからHDMI出力するVitisアプリケーション

Vitis
スポンサーリンク

本記事の概要

概要

Digilent社提供の画像をHDMI形式で出力するデモサンプルのソースコードを変更し、所定の静止画をディスプレイ上にHDMI出力するアプリケーション・プロジェクトを作成。

  • 各種IPを動作させるドライバ関数を用い、制御パラメータを設定。
  • フレームバッファに直接値を代入した後、キャッシュコヒーレンシを保つことによって、所定の静止画を出力することができることを確認。

※あえてキャッシュコヒーレンシを保たなくしたところ、黒飛びや白飛びなどノイズが大きく乗った画像が出力された。キャッシュコヒーレンシの整合性確認が必須であることを確認。

Vitisでは画像処理アプリケーションを開発する上で有用なライブラリ(Vitis ビジョンライブラリ)が用意されています。Vitisビジョンライブラリを試すための前準備として、前回の記事ではテスト画像をZynqからHDMIを通じて外部ディスプレイに出力できるようにしました。

Digilentのデモサンプルには、Zyboから外部ディスプレイへ出力するライブラリが用意されているので、デモサンプルをVitis 2020.2上で動作させてみました。今回は、デモサンプルのコードの学習も兼ねて、コードを改造してHDMI出力する画像を変更してみました。

この記事の対象読者
  • FPGAプログラミングの初学者
  • FPGAの開発に興味のある学生
  • Vitisを用いたアプリケーション開発は未経験のエンジニア

それでは、興味のある方はぜひ最後までご覧ください!

目標と工程

開発目標

本記事の目標は次の通りです:

統合ソフトウェア開発環境VitisとFPGA設計環境Vivadoを用いて
Zybo経由で外部ディスプレイに静止画を出力するアプリケーションを作成し、
表示する静止画を変更する

開発工程

開発工程

① Digilent社のHDMI出力デモサンプルのソースコードを改造し、指定の静止画像をHDMI出力するアプリケーション・プロジェクトを作成する
② ①を実装し、動作確認

システムの構成

開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vivado Design Suite – HLx Edition – 2020.2
    • Vitis コア開発キット – 2020.2
  • 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
    • Zynq XC7Z010-1CLG400C

開発ボード Zybo Zynq-7010評価ボード

動作原理

① DDRメモリから”HDMI出力”プログラムを読み込む
② CPUで処理
③ 外部ディスプレイにプログラムの出力結果を表示・確認

本記事では、DDRの内部に含まれている、外部ディスプレイ表示用のテストパターンを変更できるようにします。

プロジェクトの作成

ハードウェア設計(CPUと周辺回路の構成)の読み込み

Vivado上のプログラムの設定方法は下記記事で記載した方法と変更ありません。Digilent社のHPからダウンロードしたデモサンプルをそのままプロジェクトとして活用しています。

Vitisアプリケーションプロジェクトのコード

まず、最初にコード例を書いておきます。

#include <stdio.h>
#include "math.h"
#include <ctype.h>
#include <stdlib.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "vga_modes.h"
#include "xaxivdma.h"
#include "xvtc.h"
#include "./dynclk/dynclk.h"

#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

#define DEMO_MAX_FRAME (1920*1080*4)
#define DEMO_STRIDE (1920 * 4)
int dynclk_setting(u32 dynClkAddr, VideoMode vMode);
int VTC_setting(u16 vtcId, XVtc *vtc, VideoMode vMode);
int VDMA_setting(u16 VDMA_Id, XAxiVdma *vdma, XAxiVdma_DmaSetup *vdmasetup, u8 *framePtr, u32 stride, VideoMode vMode);

XAxiVdma vdma;
XAxiVdma_DmaSetup   vdmasetup;
XVtc vtc;
u8  frameBuf[DEMO_MAX_FRAME] __attribute__((aligned(0x20)));

int main(void)
{
    int Status;
    /* dynclk の設定*/
    Status = dynclk_setting(DYNCLK_BASEADDR, VMODE_640x480);

    /* VTCの設定 */
    Status = VTC_setting(DISP_VTC_ID, &vtc, VMODE_640x480);

    /* VDMAの設定 */
    Status = VDMA_setting(VGA_VDMA_ID, &vdma, &vdmasetup, frameBuf, DEMO_STRIDE, VMODE_640x480);

    /* frameBufへの書き込み */
    u32 iPixelAddr;
    u32 xcoi, ycoi;
    for(xcoi = 0; xcoi < (VMODE_640x480.width*4); xcoi+=4)
    {
        iPixelAddr = xcoi;
        for(ycoi = 0; ycoi < VMODE_640x480.height; ycoi++)
        {
            frameBuf[iPixelAddr]       = 0;   //blue
            frameBuf[iPixelAddr + 1]   = 0; //green
            frameBuf[iPixelAddr + 2]   = 255;   //red

            iPixelAddr += DEMO_STRIDE;
        }
    }
    Xil_DCacheFlushRange((unsigned int) frameBuf, DEMO_MAX_FRAME);

    return 0;
}

int dynclk_setting(u32 dynClkAddr, VideoMode vMode)
{
    ClkConfig clkReg;
    ClkMode clkMode;

    ClkFindParams(vMode.freq, &clkMode);

    if (!ClkFindReg(&clkReg, &clkMode))
    {
        xdbg_printf(XDBG_DEBUG_GENERAL, "Error calculating CLK register values\n\r");
        return XST_FAILURE;
    }
    
    ClkWriteReg(&clkReg, dynClkAddr);
    ClkStart(dynClkAddr);

    return XST_SUCCESS;
}

int VTC_setting(u16 vtcId, XVtc *vtc, VideoMode vMode){
    /* 略 */
}

int VDMA_setting(u16 VDMA_Id, XAxiVdma *vdma, XAxiVdma_DmaSetup *vdmasetup, u8 *framePtr, u32 stride, VideoMode vMode){
    /* 略 */
}

Vitisアプリケーションプロジェクトの構成

Vitis 2020.2のワークスペースは前回の記事と同様に作成しておきます。

HDMI出力プログラムのソースコードは本記事で示したコード例(display_demo_mod.c)を用いました。

display_demo_mod.cを[worlkspace]>[hdmi_out_application]>[src]に移動します。

[src]フォルダ内の他のファイルの構成は以下のようにしました:

“src”フォルダ内のファイルの構成
  • srcフォルダ
    • display_demo_mod.c:本記事で作成したソースコード
    • vga_modes.h:Digilent社からダウンロードしたファイルをそのまま活用
    • dynclkフォルダ:Digilent社からダウンロードしたファイルをそのまま活用
      • dynclk.c
      • dynclk.h

このファイルを移動させたらビルドし、実装まで行います。
実装までの手順の詳細を改めて[+]マーク以下に記載しておきます。

①プロジェクトのビルド
まず、プラットフォームプロジェクトをビルドしていきます。
エクスプローラ内のプラットフォームプロジェクト(hdmi_out_system_wrapper)を選択し、右クリックします。[Build Project]を選択すると、ビルドが始まります。

今回、ビルドにかかった時間は11.5秒でした。

次に、システムプロジェクトをビルドしていきます。
エクスプローラ内のシステムプロジェクト(hdmi_out_application_system)を選択し、右クリックします。同様に[Build Project]を選択すると、ビルドが始まります。

②コンフィグレーション
回路基板をホストPCにつなぎます。
つないで電源を入れたら、PC側からFPGAへのbitstreamファイルの書き込み(コンフィグレーション)とアプリケーションソフトウェアのDDRへの書き込みを実行します。

“hdmi_out_application_system”を右クリックし、[Debug as]>[Launch Hardware]を選択すると、コンフィグレーションとソフトウェアの書き込みが実施されます。
※今回の記事ではCOMポートの接続は不要です。

ソースコードの説明

IPの設定

    int Status;
    /* dynclk の設定*/
    Status = dynclk_setting(DYNCLK_BASEADDR, VMODE_640x480);

    /* VTCの設定 */
    Status = VTC_setting(DISP_VTC_ID, &vtc, VMODE_640x480);

    /* VDMAの設定 */
    Status = VDMA_setting(VGA_VDMA_ID, &vdma, &vdmasetup, frameBuf, DEMO_STRIDE, VMODE_640x480);

まず、上記のコードで、Vivadoプロジェクト上に配置したIPに制御パラメータを設定します。
プロジェクト上にあるIPのうち、プロセッサ上で設定が必要なものは以下の3つです。

プロジェクトを構成する主要IP
  • AXI Video Direct Memory Access (VDMA):データの読み出し。DDR3のSDRAMからデータを取ってきて、AXI4形式(設定では4byte)で出力させる。
  • Video Timing Controller (VTC):タイミング信号の生成。RGB出力のタイミングの同期信号を生成。
  • axi_dynclk : ダイナミッククロック・コントローラ。様々な解像度のビデオ出力に対応するため、クロック周波数を動的に変化させることが可能。

フレームバッファへの静止画の入力

AXI Video Direct Memory Access (VDMA)コアは、メモリとビデオインターフェースとの間に配置して、メモリとの画像の送受信をDMA方式で実行するためのIPコアです。DMA方式は、周辺機器(ペリフェラル)とメモリとを直接接続し、CPUを介さずにデータの書き込み・読み出しをできるようにする方式で、ビデオや音声などの大量かつ継続的にデータをメモリに入出力したい場合には、DMA方式を採用するほうが、結果的に処理性能を向上することが可能です。

メモリ上の特定の領域を読み出し続けるので、VDMAコアで指定しているフレームバッファにHDMI出力したい画像を格納すればよいわけです。

今回、VDMAで指定しているメモリの領域はframeBufという配列です。そこで、frameBufに適切な値を入れて実験してみました。次のソースコードでは、RGBのうち、赤色の画素値に最大の255を入力し、他の画素値をゼロとしています。つまり、赤色一色の画像が出力されれば、実験成功となります。

    /* frameBufへの書き込み */
    u32 iPixelAddr;
    u32 xcoi, ycoi;
    for(xcoi = 0; xcoi < (VMODE_640x480.width*4); xcoi+=4)
    {
        iPixelAddr = xcoi;
        for(ycoi = 0; ycoi < VMODE_640x480.height; ycoi++)
        {
            frameBuf[iPixelAddr]       = 0;   //blue
            frameBuf[iPixelAddr + 1]   = 0; //green
            frameBuf[iPixelAddr + 2]   = 255;   //red

            iPixelAddr += DEMO_STRIDE;
        }
    }
    Xil_DCacheFlushRange((unsigned int) frameBuf, DEMO_MAX_FRAME);

最後に、Xil_DCacheFlushRangeという関数を加えています。この関数は、フレームバッファのキャッシュをメモリに書き出し、キャッシュコヒーレンシを保つ役割を果たしています。

キャッシュコヒーレンシとは

CPUはメモリと読み出し・書き込みを行う際に、一時的にキャッシュと呼ばれるデータアクセスが早い領域に一旦データを溜め込んでおき、処理を行います。このとき、メモリの内容とキャッシュの内容の整合性(コヒーレンシ)をとることを、キャッシュコヒーレンシと呼びます。

今回は、Xil_DCacheFlushRangeをあえてコメントアウトして、キャッシュコヒーレンシが保たれなかった場合に、どんな画像になるかも試してみました。

実装と動作確認

では、実際にコンフィグレーションを行って、ディスプレイ上でHDMI出力を確認しましょう。

無事に写真の通り、ディスプレイに想定通りの赤色一色、解像度640×480の画像を出力することができました。

赤色一色ではなく好きな静止画を描画する場合には、以下のソースコードのframeBufへ値を代入する箇所を変更すれば良いでしょう。静止画の各画素値の値は、ヘッダファイルなどにまとめておき、それを読み出すようにすると楽です。ビットマップ画像ファイルをヘッダファイルに変換する方法については、以下の記事をご覧ください。

    for(xcoi = 0; xcoi < (VMODE_640x480.width*4); xcoi+=4)
    {
        iPixelAddr = xcoi;
        for(ycoi = 0; ycoi < VMODE_640x480.height; ycoi++)
        {
            frameBuf[iPixelAddr]       = 0;   //blue
            frameBuf[iPixelAddr + 1]   = 0; //green
            frameBuf[iPixelAddr + 2]   = 255;   //red

            iPixelAddr += DEMO_STRIDE;
        }
    }

失敗例

失敗例としてキャッシュを更新しなかった場合のディスプレイ表示も見せておきます。

    //Xil_DCacheFlushRange((unsigned int) frameBuf, DEMO_MAX_FRAME);

ソースコードにおいて、上記の記述をコメントアウトした場合のディスプレイ表示です。

写真のように所々、データが黒になっていたり、白飛びしているところが見られますね。キャッシュコヒーレンシが保たれないと、メモリに値を書き込む前のよくわからない画像が出力されます。Xil_DCacheFlushRangeが必要であることがよくわかります。

ひがし
ひがし

静止画を自在に変えることができれば、プログラムの幅が広がっていきますね。
ここまで読んでいただき、ありがとうございました!

コメント