0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[組み込み] Zynqでカメラ制御

Last updated at Posted at 2025-03-30

はじめに

今回は、FPGAとソフトの両方を開発しようという内容になります。
XilinxにはZynqというCPUとFPGAが1つのパッケージ化されたSoCがあります。
今回はそれを使用します。

作るモノは前からカメラ関連をやりたいと思っていたので、それを題材とします。

さすがに1からシステムを作るのは骨が折れるので、ベースを使用してそこから簡単な機能を追加という形にしていきます。

ベースはDigilentのpcam-5cというプロジェクトを使用します。

動作環境

PC:M4 Macbook Pro(Sequoia 15.1)
SW IDE:Vitis 2023.1
FPGA IDE:Vivado 2023.1
ディスプレイ:HDMI接続できるやつ
評価ボード:Zybo Z-10(今買うと5万超えるんですね…めっちゃ値上がりしてる)
カメラモジュール:Pcam 5C(OV5640)
カメラモジュール仕様↓

実施する場合の注意点
・IDEはWindows版かLinux版しかないためどちらかで実施してください。
 また、そこそこのPCスペック(特にメモリ)を要求してきますのでご注意を。
 (今回に関して言うと、16GBあれば動くと思います。)

私みたいにMac(Appleシリコン)でどうしてもやりたいんだ!という方は以下を実施すればできます。
ただし全然おすすめはしないです

既存システムの概要

ざっくりとしたシステム構成を書きます。

システム構成

image.png

処理の流れとしては以下ですね。
・OV5640からMIPIでカメラデータを受け取る
・カメラデータをPL部で画像処理する
・DMAを使用しカメラデータをDDRへ保存する
・出力タイミングにあわせ、DDRからデータを抜き出し、TMDSでディスプレイに送信する

クロック&リセット系統

image.png

バス構成

image.png

モジュール詳細(PL)

MIPI_D_PHY_RX/MIPI_CSI_2_RX

カメラからのMIPI信号を受信するモジュール。

D_PHYの仕様
・AXI4-Liteよりレジスタアクセス可能
・リファレンスクロックとして200MHzを入力

CSI_2の仕様
・AXI4-Liteよりレジスタアクセス可能
・出力のデータタイプはRAW10(データの並びはBGBG/GRGR)
・MIPI信号をAXI4-Stream(150MHz)へ変換し出力
・AXI4-Streamのフォーマット

AXI_BayreToRGB

ベイヤー配列をRGBに変換する(デモザイク)モジュール。

名前がBayerToRGBなので、てっきり出力がRGBの順で出てくると
思ったが違ったっぽい。ソースコードを見るとRBGの順。トラップですね…

RBG順.png

ではなんでこの順番かと言うと、後段のVideo OutモジュールがRBGの並びなのでこの順番にしているのだと思います。

VideoOut入力.png

モジュールの仕様
・入力データサイズ:40bit(RBG10bit)
・入力データサンプル数:4/clk
・出力データサイズ:32bit(RBG10bit + 2bitsのパディング)
・出力データサンプル数:1/clk
・最大画素数:2048pix
・ラインバッファを持つ(1ライン分)
・TUSERはフレームスタートの意味を付与する
・TLASTはライン終了の意味を付与する

入力データのイメージ

データ転送順.png

デモザイクの処理のイメージ

デモザイク処理イメージ.png

GammaCorrection

ガンマ補正を行うモジュール。

モジュールの仕様
・入出力IF:AXI4-Stream
・入力データサイズ:32bit(RBG10bits + 2bitsのパディング)
・入力データサンプル数:1/clk
・出力データサイズ:24bit(RGB8bits)
・出力データサンプル数:1/clk
・AXI4-Liteよりレジスタアクセス可能
 レジスタでガンマ補正値を変更できる(補正値はROM持ち)
 設定可能ガンマ補正値:1.0、1.5、1.8、2.2

axi_vdma(VDMA)

PLからのデータをPS制御でメモリへ送る(逆も然り)モジュール。

モジュールの仕様
・S2MMのデータ幅:24bit
 バーストサイズ:8
 ラインバッファサイズ:1024
 メモリマップのデータ幅:64
・MM2Sのデータ幅:24bit
 バーストサイズ:8
 ラインバッファサイズ:1024
 メモリマップのデータ幅:64
・フレームバッファを持つ(3つ)
・アドレス幅は32bit
・AXI4-Liteよりレジスタアクセス可能

v_tc(Video Timing Controller)

ビデオタイミングの生成/検出を行うモジュール。

モジュールの仕様
・ビデオタイミングの検出モードは使用せず、生成モードのみ使用
・ビデオフォーマットは720p
・AXI4-Liteよりレジスタアクセス可能

v_axi4s_vid_out(Video Out)

AXI4-Streamデータをビデオデータ(RGB)に変換し、出力するモジュール。
上記のvtcとセットで使用する方がいい。

モジュールの仕様
・AXI4-Streamのデータフォーマットは「RBG」
・FIFOの深さは1024

rgb2dvi

ビデオデータをTMDSに変換し、出力するモジュール。

モジュールの仕様
・リセット信号(aRst_n)は前段のv_axi4s_vid_outモジュールのロック信号を入力とする
・PixelClkとSerialClkを入力する必要があり、以下の関係であること
 SerialClk = PixelClk × 5
 ※SerialClkは内部で作成も可

モジュール詳細(PS)

ファイル構成

ファイル構成.png

フローチャート

image.png

シーケンス図(画像フォーマット変更時)

image.png

追加機能

ここからが本題になります。
簡単な機能を1つ追加したいと思います。

既存のシステムがカラー画像なので、これをグレースケール化する機能を追加します。

仕様

・入出力のデータフォーマットはAXI4-Streamとする。
 中のデータ構成は前後のモジュールと同じ。
・クロックリセット構成は前後のモジュールと同じ。
・AXI4-Liteよりレジスタアクセス可能。
・グレースケール化の方法は以下の通り。とりあえず楽な最大値/最小値方法で実装する。

グレースケール化
方法 内容
最大値/最小値 RGBの最大値・最小値を取得し設定する方法
平均値 RGBを単純に平均し設定する方法
NTSC重み付け NTSCという規格に準拠し設定する方法
チャネル選択 RGBのどれか1つのチャネルを取得し設定する方法

ブロック図

image.png

上記が機能追加後のブロック図になります。
BayerToRGBモジュールとGammmaCorrectionモジュールの間に入れました。

クロック&リセット系統

image.png

バス接続

image.png

フローチャート

image.png

シーケンス図(画像フォーマット変更時)

image.png

実装

ソースコード

PL部

module grayscale_top #(
    parameter integer C_S_AXIS_DATA_WIDTH = 32
) (
    input  wire        CLK,
    input  wire        RESET,
    // S AXI-Stream interface
    output wire        S_AXIS_VIDEO_TREADY,
    input  wire [C_S_AXIS_DATA_WIDTH-1:0] S_AXIS_VIDEO_TDATA,
    input  wire        S_AXIS_VIDEO_TVALID,
    input  wire        S_AXIS_VIDEO_TUSER,
    input  wire        S_AXIS_VIDEO_TLAST,

    // M AXI-Stream interface
    input  wire        M_AXIS_VIDEO_TREADY,
    output wire [C_S_AXIS_DATA_WIDTH-1:0] M_AXIS_VIDEO_TDATA,
    output wire        M_AXIS_VIDEO_TVALID,
    output wire        M_AXIS_VIDEO_TUSER,
    output wire        M_AXIS_VIDEO_TLAST,

    // Grayscale
    input wire         GRAYSCALE_EN,
    input wire         GRAYSCALE_PTN
);

    wire [ 9: 0] w_red;
    wire [ 9: 0] w_green;
    wire [ 9: 0] w_blue;
    reg  [ 9: 0] r_max;
    reg          r_axis_video_tready;
    reg          r_axis_video_tvalid;
    reg          r_axis_video_tuser;
    reg          r_axis_video_tlast;

    assign w_green = S_AXIS_VIDEO_TDATA[ 9: 0];
    assign w_blue  = S_AXIS_VIDEO_TDATA[19:10];
    assign w_red   = S_AXIS_VIDEO_TDATA[29:20];

    always @(posedge CLK or posedge RESET) begin
        if (RESET) begin
            r_max <= 10'd0;
        end else begin
            if (S_AXIS_VIDEO_TVALID) begin
                r_max <= (w_red > w_green && w_red > w_blue) ? w_red : (w_green > w_red && w_green > w_blue) ? w_green : w_blue;
            end else begin
                r_max <= 10'd0;
            end
        end
    end

    always @(posedge CLK or posedge RESET) begin
        if (RESET) begin
            r_axis_video_tready <= 1'b0;
            r_axis_video_tvalid <= 1'b0;
            r_axis_video_tuser  <= 1'b0;
            r_axis_video_tlast  <= 1'b0;
        end else begin
            r_axis_video_tready <= M_AXIS_VIDEO_TREADY;
            r_axis_video_tvalid <= S_AXIS_VIDEO_TVALID;
            r_axis_video_tuser  <= S_AXIS_VIDEO_TUSER;
            r_axis_video_tlast  <= S_AXIS_VIDEO_TLAST;
        end
    end

    assign S_AXIS_VIDEO_TREADY = (GRAYSCALE_EN) ? r_axis_video_tready : M_AXIS_VIDEO_TREADY;
    assign M_AXIS_VIDEO_TDATA  = (GRAYSCALE_EN) ? {2'b00, r_max[9:0], r_max[9:0], r_max[9:0]} : S_AXIS_VIDEO_TDATA;
    assign M_AXIS_VIDEO_TVALID = (GRAYSCALE_EN) ? r_axis_video_tvalid : S_AXIS_VIDEO_TVALID;
    assign M_AXIS_VIDEO_TUSER  = (GRAYSCALE_EN) ? r_axis_video_tuser : S_AXIS_VIDEO_TUSER;
    assign M_AXIS_VIDEO_TLAST  = (GRAYSCALE_EN) ? r_axis_video_tlast : S_AXIS_VIDEO_TLAST;

endmodule

解説(PL部)

まず、レジスタは以下になります。

GRAYSCALE_EN:0:OFF、1:ON
GRAYSCALE_PTN:使用していない

PTNはグレースケール化のパターンを割り振りたいと思って一応持っておきました。
実際は使ってないです。

処理としてはマスターからのTVALID信号で最大値を取得し、他の信号も一段ずらしてスレーブに投げているだけになります。

波形

以下が、シミュレーションの波形になります。

grayscaleの波形.png

最大値が選ばれて出ていますね。

PS部

void pipeline_mode_change(AXI_VDMA<ScuGicInterruptController> &vdma_driver,
                          OV5640 &cam, VideoOutput &vid, Resolution res,
                          OV5640_cfg::mode_t mode)
{
    // Bring up input pipeline back-to-front
    {
        vdma_driver.resetWrite();
        MIPI_CSI_2_RX_mWriteReg(XPAR_MIPI_CSI_2_RX_0_S_AXI_LITE_BASEADDR,
                                CR_OFFSET, (CR_RESET_MASK & ~CR_ENABLE_MASK));
        MIPI_D_PHY_RX_mWriteReg(XPAR_MIPI_D_PHY_RX_0_S_AXI_LITE_BASEADDR,
                                CR_OFFSET, (CR_RESET_MASK & ~CR_ENABLE_MASK));
        cam.reset();
    }

    {
        vdma_driver.configureWrite(timing[static_cast<int>(res)].h_active,
                                   timing[static_cast<int>(res)].v_active);
        Xil_Out32(GAMMA_BASE_ADDR, 3);     // Set Gamma correction factor to 1/1.8
        Xil_Out32(GRAYSCALE_BASE_ADDR, 0); // Set GrayScale mode to disabled
        // TODO CSI-2, D-PHY config here
        cam.init();
    }

    {
        vdma_driver.enableWrite();
        MIPI_CSI_2_RX_mWriteReg(XPAR_MIPI_CSI_2_RX_0_S_AXI_LITE_BASEADDR,
                                CR_OFFSET, CR_ENABLE_MASK);
        MIPI_D_PHY_RX_mWriteReg(XPAR_MIPI_D_PHY_RX_0_S_AXI_LITE_BASEADDR,
                                CR_OFFSET, CR_ENABLE_MASK);
        cam.set_mode(mode);
        cam.set_awb(OV5640_cfg::awb_t::AWB_ADVANCED);
    }

    // Bring up output pipeline back-to-front
    {
        vid.reset();
        vdma_driver.resetRead();
    }

    {
        vid.configure(res);
        vdma_driver.configureRead(timing[static_cast<int>(res)].h_active,
                                  timing[static_cast<int>(res)].v_active);
    }

    {
        vid.enable();
        vdma_driver.enableRead();
    }
}

解説1(PS部)

pipeline_mode_change()関数にグレースケールレジスタ制御を追加しただけになります。

        case 'i':
            xil_printf(
                "  Please press the key corresponding to the desired GrayScale "
                "change:\r\n");
            xil_printf("    1. Enable GrayScale Mode\r\n");
            xil_printf("    2. Disable GrayScale Mode\r\n");
            read_char1 = getchar();
            getchar();
            xil_printf("Read: %d\r\n", read_char1);
            switch (read_char1)
            {
            case '1':
                Xil_Out32(GRAYSCALE_BASE_ADDR, 1);
                xil_printf("Enabled GrayScale Mode\r\n");
                break;
            case '2':
                Xil_Out32(GRAYSCALE_BASE_ADDR, 0);
                xil_printf("Disabled GrayScale Mode\r\n");
                break;

            default:
                xil_printf(
                    "  Selection is outside the available options! Please "
                    "retry...\r\n");
            }
        default:
            xil_printf(
                "\r\n  Selection is outside the available options! Please "
                "retry...");

解説2(PS部)

main関数のswitch文に”i”条件を追加しただけになります。

実機動作

pcam5c_grayscale.gif

最後に

今回は以上になります。

グレースケール化のみの追加でしたので、然程難しくはなかったと思います。
次はHLSを絡ませたり、ソフト側でなにかをやりたいと思っています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?