1500円ZYNQ基板とPiCamV2でZyboのPcam5Cデモを動かす(後編)

#1. はじめに
1500円 ZYNQ 基板( EBAZ4205 )で、Digilent Zybo Z7-20 の Pcam 5C デモを動かしてみます。
このデモでは、CMOS カメラで撮影した映像を、HDMI 出力してモニタに映すことができます。

https://reference.digilentinc.com/learn/programmable-logic/tutorials/zybo-z7-pcam-5c-demo/start

カメラは、手持ちの __Raspberry Pi Camera Module V2__ (SONY IMX219) を使用します。

記述が長くなりましたので、記事を前編と後編に分けています。

[前編: Vivado での作業](https://qiita.com/kan573/items/b095f9e93eb48bcf3874)
後編: 接続ケーブルの作成と、Vitis での作業

後編の今回は、CSI-2 カメラ入力 + HDMI 出力ケーブルの作成と、Vitis での動作確認を行います。


#2. 使用環境

1. Windows10 Pro (20H2)
2. Vivado 2020.1 (Windows 版)
3. Vitis 2020.1 (同上)
4. EBAZ4205
5. Raspberry Pi Camera Module V2 (SONY IMX219 搭載)
6. HDMI Monitor (Leader LV5382)


#3. 参考資料
[Zybo Z7 Pcam 5C Demo](https://reference.digilentinc.com/learn/programmable-logic/tutorials/zybo-z7-pcam-5c-demo/start)
[OV5640 datasheet](https://cdn.sparkfun.com/datasheets/Sensors/LightImaging/OV5640_datasheet.pdf)
[IMX219 datasheet](http://www.opensourceinstruments.com/Electronics/Data/IMX219PQ.pdf)
[XAPP894 D-PHY Solutions](https://japan.xilinx.com/support/documentation/application_notes/xapp894-d-phy-solutions.pdf)


#4. ハードウェアの準備

前回の記事で作成した、ピン配置表を示します。
![H1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/f137b1e3-9caa-bf82-0448-a2ef72e38cba.png)


##4-1. HDMI 出力＋CSI-2 入力ケーブルの作成

ピン配置表を元に、変換ケーブルの接続図を作成しました。
![H2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/24ecdbfc-db39-4884-4fec-ed640c1d2bcc.png)



これを、

aitendo の [HDMI コネクタ with 基板](https://www.aitendo.com/product/6363) と、
![H3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/71acda40-f97c-56e3-4040-bca4546ebdbe.png)


[FFCコネクタwith基板★1.0/15P](https://www.aitendo.com/product/19320) と、
![H4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/cc73b6de-e5b1-9fd2-b4b9-5221b30026a4.png)


[★2.0★2列ピンソケット [PS20DV] 2x10](https://www.aitendo.com/product/17637) を使って作成します。
![H5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/dac44dd9-fbcd-f8c0-1aaa-d73a2055a25b.png)


完成したケーブルは下記のようになりました。

HDMI と CSI-2 の高速差動信号が通る線は、波形改善のためツイストしてあります。
[Zybo HDMIデモ](https://qiita.com/kan573/items/aaacd53027471f36974d)では、ツイストで顕著に性能向上したので、撚り数を更に増やしてみました。
![H6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/ea2200e1-0ebe-78ba-34f5-dbe3cdf33bcc.jpeg)


( __FFC ケーブルについて注意__ )
Raspberry Pi Camera Module V2 に付属の FFC ケーブルは、端子が逆面タイプです。
![H5_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/c4debb30-1115-44a3-1b57-e7c72f2defb2.png)

FFCコネクタwith基板では、端子が同一面の FFC ケーブルを使用しないと、1-15 番ピンが、裏返しの 15-1 番ピンに接続されてしまいます。
そのため、[FFCケーブル★1.0-15P★ [FFC1015P] 150S](https://www.aitendo.com/product/9270) も一緒に調達してください。
![H5_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/054d2092-86f0-b4f9-5ef9-f2121e2a2c0a.png)


##4-2. ダンピング抵抗の 22Ω → 0Ω交換
CSI-2 受信、HDMI 送信どちらも、PL の I/O ピンから直結する回路にする必要があります。
HDMIデモの時と同様に、22Ω のダンピング抵抗を、0Ω に交換します。

※HDMI デモで使った EBAZ4205 基板は、一部に 3.3V プルアップ抵抗を付けたので、また別の EBAZ4205 基板を使用しています。いっぱい買ってあるので全然 OK。

DATA1～DATA3 コネクタへの途中にある 22Ω のダンピング抵抗を、すべて 0Ωに交換します。
![H7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/65dde06f-23cb-763d-c937-513845323714.png)


基板上のダンピング抵抗を、全て交換しました。
(写真は使いまわし)
![H8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/d0888177-1b73-1ed3-916d-66af0b09b5ba.jpeg)


あと、DATA1 コネクタから接続する HDMI OUT の +5V Power 端子に電源を出力するため、逆流防止ダイオード D21 にジャンパを追加します。
(EBAZ4205 の電源供給は +5V で行っているので、V IN に直結でよい)

同様に、DATA2 コネクタから +3.3V を出力するため、逆流防止ダイオード D22-K に、基板上の +3.3V 電源ラインからジャンパを追加します。
![H9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/d900e448-3325-eda4-9e49-416eb882a23b.png)


実基板の配線は、下記のようになりました。
+3.3V は C2321/C2319 から引き出し、+5V は D21 をバイパスしています。
![H10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/38545a72-55f8-2be4-b8c1-0ae2792a2a17.jpeg)



#5. Vitis プロジェクトの作成

##5-1. プログラムの読み込み
設計ファイルから sdk_appsrc フォルダを展開して、適当なフォルダに置いておきます。
このフォルダに、前回の記事でエクスポートした system_wrapper.xsa をコピーします。
例では \デスクトップ\EBAZ です。
![H11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/1881147d-d52f-bc18-c3bd-bc878832b271.png)


Vitis を起動したら、File - Switch Workspace... - Other... で \デスクトップ\EBAZ をワークスペースに指定して、Launch します。

Create Platform Project で、EBAZ-PiCam としてプロジェクトを作成し、system_wrapper.xsa を指定して Finish。
![H12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/a2de7742-8445-b6d1-28d3-4caf77ed3120.png)
![H13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/743fb3d2-ee60-8c04-ce41-9920b16bafc9.png)


プラットフォームプロジェクトを、先にビルドしておきます。ビルドで Out-of-date が解消します。
![H14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/650ded78-33a9-49df-e804-49993b488b7b.png)


次に File - New - Application Project... で EBAZ-PiCam を platform に選択し、EBAZ-PiCam_app という名前で Next。
![H15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/ed921a69-910f-c4a9-eef8-52cf4425af44.png)


standalone on ps7_cortexa9_0 で Next した後、Empty Applicaion(C++) を選び Finish。
![H16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/e6d3049b-f7f7-559c-10fe-cd5d3acffdc1.png)


EBAZ-PiCam_app の src フォルダで右クリックして、Inport Sources... を選びます。
![H17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/0e85a939-06b3-94ed-fdd8-41aca252e9d1.png)


From directory: に \sdk_appsrc を指定します。
Import Sources で、sdk_appsrc チェック、Overwrite existing resources without warning にチェックを入れて Finish。
![H18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/7a711836-221b-a129-d154-749cc574197a.png)


##5-2. カメラ設定の変更
OV5640 用の設定ファイルを、IMX219 用に差し替えます。
![H19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/69f06dd0-9782-3923-8b83-8c4fb7facf14.png)


OV5640.h を、下記のファイルで上書きします。
同ファイル名で、中身だけ IMX219 用に変更してあります。

namespace に digilent が残っていますが、ご愛敬ということで...

```OV5640.h
/*
 * OV5640.h
 *
 *  Created on: May 15, 2021
 *      Author: kan573
 *   Retarget : IMX219 (Raspberry Pi Camera Module V2.1)
 */

#ifndef OV5640_H_
#define OV5640_H_

#include <sstream>
#include <iostream>
#include <cstdio>
#include <climits>

#include "I2C_Client.h"
#include "GPIO_Client.h"
#include "../hdmi/VideoOutput.h"

#define SIZEOF_ARRAY(x) sizeof(x)/sizeof(x[0])
#define MAP_ENUM_TO_CFG(en, cfg) en, cfg, SIZEOF_ARRAY(cfg)

namespace digilent {

typedef enum {OK=0, ERR_LOGICAL, ERR_GENERAL} Errc;

namespace OV5640_cfg {
	using config_word_t = struct { uint16_t addr; uint8_t data; } ;
	using mode_t = enum { MODE_720P_1280_720_60fps = 0, MODE_1080P_1920_1080_15fps,
		MODE_1080P_1920_1080_30fps, MODE_END } ;
	using config_modes_t = struct { mode_t mode; config_word_t const* cfg; size_t cfg_size; };
	using test_t = enum { TEST_DISABLED = 0, TEST_EIGHT_COLOR_BAR, TEST_END };
	using awb_t = enum { AWB_DISABLED = 0, AWB_SIMPLE, AWB_ADVANCED, AWB_END };
	using config_awb_t = struct { awb_t awb; config_word_t const* cfg; size_t cfg_size; };
	using isp_format_t = enum { ISP_RAW = 0, ISP_RGB, ISP_END };
	uint16_t const OV5640_REG_PRE_ISP_TEST_SET1 = 0x503D;
	uint16_t const OV5640_FORMAT_MUX_CONTROL = 0x501f;

	config_word_t const cfg_advanced_awb_[] =
	{
			{0x0100 ,0x01}		// Start Stream
	};

	config_word_t const cfg_simple_awb_[] =
	{	// No function assignment
	};

	config_word_t const cfg_disable_awb_[] =
	{	// No function assignment
	};

	config_word_t const cfg_720p_60fps_[] =
	{		//1280 x 720 binned, RAW10, MIPISCLK=456M, SCLK=91.2Mz, PCLK=91.2M, 2lane_mipi
			{0x6620, 0x01},
			{0x6621, 0x01},
			{0x6622, 0x01},
			{0x6623, 0x01},

			{0x30EB, 0x05},
			{0x30EB, 0x0C},
			{0x300A, 0xFF},
			{0x300B, 0xFF},
			{0x30EB, 0x05},
			{0x30EB, 0x09},

			{0x0114, 0x01},
			{0x0128, 0x00},
			{0x012A, 0x18},
			{0x012B, 0x00},
			{0x0157, 0x50},
			{0x0158, 0x01},
			{0x0159, 0x00},
			{0x015A, 0x03},
			{0x015B, 0x68},
			{0x0160, 0x03},
			{0x0161, 0x6E},
			{0x0162, 0x0D},
			{0x0163, 0x80},
			{0x0164, 0x01},
			{0x0165, 0x68},
			{0x0166, 0x0B},
			{0x0167, 0x68},
			{0x0168, 0x02},
			{0x0169, 0x00},
			{0x016A, 0x07},
			{0x016B, 0xA0},
			{0x016C, 0x05},
			{0x016D, 0x00},
			{0x016E, 0x02},
			{0x016F, 0xD0},
			{0x0170, 0x01},
			{0x0171, 0x01},
			{0x0172, 0x00},
			{0x0174, 0x01},
			{0x0175, 0x01},
			{0x0176, 0x01},
			{0x0177, 0x01},
			{0x018C, 0x0A},
			{0x018D, 0x0A},
			{0xD1EA, 0x00},
			{0xD1EB, 0x20},
			{0x0301, 0x05},
			{0x0303, 0x01},
			{0x0304, 0x03},
			{0x0305, 0x03},
			{0x0306, 0x00},
			{0x0307, 0x39},
			{0x0309, 0x0A},
			{0x030B, 0x01},
			{0x030C, 0x00},
			{0x030D, 0x72},
			{0x455E, 0x00},
			{0x471E, 0x4B},
			{0x4767, 0x0F},
			{0x4750, 0x14},
			{0x4540, 0x00},
			{0x47B4, 0x14},
			{0x4713, 0x30},
			{0x478B, 0x10},
			{0x478F, 0x10},
			{0x4793, 0x10},
			{0x4797, 0x0E},
			{0x479B, 0x0E},

			{0x0601, 0x00},
			{0x0620, 0x00},
			{0x0621, 0x00},
			{0x0621, 0x00},
			{0x0623, 0x00},
			{0x0624, 0x05},
			{0x0625, 0x00},
			{0x0626, 0x02},
			{0x0627, 0xD0},
	};

	config_word_t const cfg_1080p_15fps_[] =
	{		//1920 x 1080 @ 30 fps, RAW10, MIPISCLK=288, SCLK=57.6MHz, PCLK=57.6M, 2lane_mipi
			{0x6620, 0x01},
			{0x6621, 0x01},
			{0x6622, 0x01},
			{0x6623, 0x01},

			{0x30EB, 0x05},
			{0x30EB, 0x0C},
			{0x300A, 0xFF},
			{0x300B, 0xFF},
			{0x30EB, 0x05},
			{0x30EB, 0x09},
			{0x0114, 0x01},
			{0x0128, 0x00},
			{0x012A, 0x18},
			{0x012B, 0x00},

			{0x0157, 0x80},
			{0x0158, 0x01},
			{0x0159, 0x00},

			{0x015A, 0x04},
			{0x015B, 0x52},
			{0x0160, 0x04},
			{0x0161, 0x58},
			{0x0162, 0x0D},
			{0x0163, 0x78},
			{0x0164, 0x02},
			{0x0165, 0xA8},
			{0x0166, 0x0A},
			{0x0167, 0x28},
			{0x0168, 0x02},
			{0x0169, 0xB4},
			{0x016A, 0x06},
			{0x016B, 0xEC},
			{0x016C, 0x07},
			{0x016D, 0x80},
			{0x016E, 0x04},
			{0x016F, 0x38},

			{0x0170, 0x01},
			{0x0171, 0x01},
			{0x0172, 0x00},
			{0x0174, 0x00},
			{0x0175, 0x00},
			{0x0176, 0x00},
			{0x0177, 0x00},

			{0x018C, 0x0A},
			{0x018D, 0x0A},
			{0xD1EA, 0x00},
			{0xD1EB, 0x20},
			{0x0301, 0x05},
			{0x0303, 0x01},
			{0x0304, 0x02},
			{0x0305, 0x02},
			{0x0306, 0x00},
			{0x0307, 0x24},
			{0x0309, 0x0A},
			{0x030B, 0x01},
			{0x030C, 0x00},
			{0x030D, 0x48},
			{0x455E, 0x00},
			{0x471E, 0x4B},
			{0x4767, 0x0F},
			{0x4750, 0x14},
			{0x4540, 0x00},
			{0x47B4, 0x14},
			{0x4713, 0x30},
			{0x478B, 0x10},
			{0x478F, 0x10},
			{0x4793, 0x10},
			{0x4797, 0x0E},
			{0x479B, 0x0E},
			{0x0601, 0x00},
			{0x0620, 0x00},
			{0x0621, 0x00},
			{0x0621, 0x00},
			{0x0623, 0x00},
			{0x0624, 0x07},
			{0x0625, 0x80},
			{0x0626, 0x04},
			{0x0627, 0x38},
	};

	config_word_t const cfg_1080p_30fps_[] =
	{		//1920 x 1080 @ 60fps, RAW10, MIPISCLK=520, SCLK=130MHz, PCLK=130M, 2lane_mipi
			{0x6620, 0x01},
			{0x6621, 0x01},
			{0x6622, 0x01},
			{0x6623, 0x01},

			{0x30EB, 0x05},
			{0x30EB, 0x0C},
			{0x300A, 0xFF},
			{0x300B, 0xFF},
			{0x30EB, 0x05},
			{0x30EB, 0x09},
			{0x0114, 0x01},
			{0x0128, 0x00},
			{0x012A, 0x18},
			{0x012B, 0x00},

			{0x0157, 0xB0},
			{0x0158, 0x01},
			{0x0159, 0x00},

			{0x015A, 0x04},
			{0x015B, 0xE0},
			{0x0160, 0x04},
			{0x0161, 0xE8},
			{0x0162, 0x0D},
			{0x0163, 0x78},
			{0x0164, 0x02},
			{0x0165, 0xA8},
			{0x0166, 0x0A},
			{0x0167, 0x28},
			{0x0168, 0x02},
			{0x0169, 0xB4},
			{0x016A, 0x06},
			{0x016B, 0xEC},
			{0x016C, 0x07},
			{0x016D, 0x80},
			{0x016E, 0x04},
			{0x016F, 0x38},

			{0x0170, 0x01},
			{0x0171, 0x01},
			{0x0172, 0x00},
			{0x0174, 0x00},
			{0x0175, 0x00},
			{0x0176, 0x00},
			{0x0177, 0x00},

			{0x018C, 0x0A},
			{0x018D, 0x0A},
			{0xD1EA, 0x00},
			{0xD1EB, 0x20},
			{0x0301, 0x04},
			{0x0303, 0x01},
			{0x0304, 0x02},
			{0x0305, 0x02},
			{0x0306, 0x00},
			{0x0307, 0x41},
			{0x0309, 0x0A},
			{0x030B, 0x01},
			{0x030C, 0x00},
			{0x030D, 0x82},
			{0x455E, 0x00},
			{0x471E, 0x4B},
			{0x4767, 0x0F},
			{0x4750, 0x14},
			{0x4540, 0x00},
			{0x47B4, 0x14},
			{0x4713, 0x30},
			{0x478B, 0x10},
			{0x478F, 0x10},
			{0x4793, 0x10},
			{0x4797, 0x0E},
			{0x479B, 0x0E},
			{0x0601, 0x00},
			{0x0620, 0x00},
			{0x0621, 0x00},
			{0x0621, 0x00},
			{0x0623, 0x00},
			{0x0624, 0x07},
			{0x0625, 0x80},
			{0x0626, 0x04},
			{0x0627, 0x38},
	};


	config_word_t const cfg_init_[] =
	{
		{0x0100, 0x00},			// Stream off
		{0x0103, 0x01}			// Software reset
	};
	config_modes_t const modes[] =
	{
			{ MAP_ENUM_TO_CFG(MODE_720P_1280_720_60fps, cfg_720p_60fps_) },
			{ MAP_ENUM_TO_CFG(MODE_1080P_1920_1080_15fps, cfg_1080p_15fps_) },
			{ MAP_ENUM_TO_CFG(MODE_1080P_1920_1080_30fps, cfg_1080p_30fps_), },
	};
	config_awb_t const awbs[] =
	{
			{ MAP_ENUM_TO_CFG(AWB_DISABLED, cfg_disable_awb_) },
			{ MAP_ENUM_TO_CFG(AWB_SIMPLE, cfg_simple_awb_) },
			{ MAP_ENUM_TO_CFG(AWB_ADVANCED, cfg_advanced_awb_) }
	};
}

class OV5640 {
public:
	class HardwareError;

	OV5640(I2C_Client& iic, GPIO_Client& gpio) :
		iic_(iic), gpio_(gpio)
	{
		reset();
		init();
	}

	void init()
	{
		usleep(1000000);

		size_t i;
		for (i=0;i<sizeof(OV5640_cfg::cfg_init_)/sizeof(OV5640_cfg::cfg_init_[0]); ++i)
		{
			writeReg(OV5640_cfg::cfg_init_[i].addr, OV5640_cfg::cfg_init_[i].data);
		}

		usleep(2000000);	// wait 24ms after reset

	}

	Errc reset()
	{
		//Power cycle
		gpio_.clearBit(gpio_.Bits::CAM_GPIO0);
		usleep(1000000);
		gpio_.setBit(gpio_.Bits::CAM_GPIO0);
		usleep(1000000);

		return OK;
	}

	Errc set_mode(OV5640_cfg::mode_t mode)
	{
		if (mode >= OV5640_cfg::mode_t::MODE_END)
			return ERR_LOGICAL;

		auto cfg_mode = &OV5640_cfg::modes[mode];
		writeConfig(cfg_mode->cfg, cfg_mode->cfg_size);

		return OK;
	}

	Errc set_awb(OV5640_cfg::awb_t awb)
	{
		if (awb >= OV5640_cfg::awb_t::AWB_END)
			return ERR_LOGICAL;

		auto cfg_mode = &OV5640_cfg::awbs[awb];
		writeConfig(cfg_mode->cfg, cfg_mode->cfg_size);

		return OK;

	}

	Errc set_isp_format(OV5640_cfg::isp_format_t isp)
	{
		if (isp >= OV5640_cfg::isp_format_t::ISP_END)
			return ERR_LOGICAL;

		switch (isp)
		{
			case OV5640_cfg::isp_format_t::ISP_RGB:
//				writeReg(OV5640_cfg::OV5640_FORMAT_MUX_CONTROL, 0x01);
				break;
			case OV5640_cfg::isp_format_t::ISP_RAW:
//				writeReg(OV5640_cfg::OV5640_FORMAT_MUX_CONTROL, 0x03);
				break;
			default:
				break;
				}

		return OK;
	}

	~OV5640() { }
	void set_test(OV5640_cfg::test_t test)
	{
		switch(test)
		{
			case OV5640_cfg::test_t::TEST_DISABLED:
//				writeReg(OV5640_cfg::OV5640_REG_PRE_ISP_TEST_SET1, 0x00);
				break;
			case OV5640_cfg::test_t::TEST_EIGHT_COLOR_BAR:
//				writeReg(OV5640_cfg::OV5640_REG_PRE_ISP_TEST_SET1, 0x80);
				break;
			default:
				break;
		}
	}
	void readReg(uint16_t reg_addr, uint8_t& buf)
	{
		for(auto retry_count = retry_count_; retry_count > 0; --retry_count)
		{
			try
			{
				auto buf_addr = std::vector<uint8_t>{(uint8_t)(reg_addr>>8), (uint8_t)reg_addr};
				iic_.write(dev_address_, buf_addr.data(), buf_addr.size());
				iic_.read(dev_address_, &buf, 1);
				break; //If no exceptions, no mo retries
			}
			catch (I2C_Client::TransmitError const& e)
			{
				if (retry_count > 0)
				{
					continue;
				}
				else
				{
					throw HardwareError(HardwareError::IIC_NACK, e.what());
				}
			}
		}
	}
	void writeReg(uint16_t reg_addr, uint8_t const reg_data)
	{
		for(auto retry_count = retry_count_; retry_count > 0; --retry_count)
		{
			try
			{
				auto buf = std::vector<uint8_t>{(uint8_t)(reg_addr>>8), (uint8_t)reg_addr, reg_data};
				iic_.write(dev_address_, buf.data(), buf.size());
				break; //If no exceptions, no mo retries
			}
			catch (I2C_Client::TransmitError const& e)
			{
				if (retry_count > 0) continue;
				else throw HardwareError(HardwareError::IIC_NACK, e.what());
			}
		}
	}

	void writeRegLiquid(uint8_t const reg_data)
		{
			for(auto retry_count = retry_count_; retry_count > 0; --retry_count)
			{
				try
				{
					auto buf = std::vector<uint8_t>{0x06, 0x01, reg_data};		// 0x0601 = test_pattern_mode
					iic_.write(dev_address2_, buf.data(), buf.size());
					break; //If no exceptions, no mo retries
				}
				catch (I2C_Client::TransmitError const& e)
				{
					if (retry_count > 0) continue;
					else throw HardwareError(HardwareError::IIC_NACK, e.what());
				}
			}
		}
	class HardwareError : public std::runtime_error
	{
	public:
		using Errc = enum {WRONG_ID = 1, IIC_NACK};
		HardwareError(Errc errc, char const* msg) : std::runtime_error(msg), errc_(errc) {}
		Errc errc() const { return errc_; }
	private:
		Errc errc_;
	};
private:
	void usleep(uint32_t time)
	{//TODO couldn't think of anything better
		for (uint32_t i=0; i<time; i++) ;
	}
	void writeConfig(OV5640_cfg::config_word_t const* cfg, size_t cfg_size)
	{
		for (size_t i=0; i<cfg_size; ++i)
		{
			writeReg(cfg[i].addr, cfg[i].data);
		}
	}
private:
	I2C_Client& iic_;
	GPIO_Client& gpio_;
	uint8_t dev_address_ = (0x20 >> 1);
	uint8_t dev_address2_ = (0x20 >> 1);
	unsigned int const retry_count_ = 10;
};

} /* namespace digilent */

#endif /* OV5640_H_ */

```

##5-3. コンパイル
これで準備はできましたので、アプリケーションプロジェクトをビルドします。
![H20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/d7a97e77-9524-5326-82f9-25c854242ea8.png)


ビルドが終わったら準備完了です。


#6. 動作確認

EBAZ4205 基板と HDMI出力/CSI-2 入力ケーブル、HDMI モニタ、他一式を接続しておきます。
![H28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/5d42a370-64e5-d642-fd40-db0ed9f8a273.jpeg)


EBAZ4205 基板と HDMI モニタの電源を入れ、Vitis から認識できるようにしておきます。

TeraTerm を起動して、設定で Local Echo にチェックを入れて OK します。
![H21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/f508e57d-9da6-01e8-dfa8-1c584c0d35d8.png)


##6-1. プログラム起動

Run ボタンを押して、アプリケーションを起動します。
![H22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/69ee9e30-09f9-0e0d-a09e-b481e128b187.png)


TeraTerm にアプリケーションの操作画面が表示され、HDMI モニタにカメラの映像が映ります。
![H23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/ec8adae6-a639-3987-6d63-86e86d38647d.png)
![H24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/f5f60938-8fbb-1272-fb1c-f85aad144f35.jpeg)


##6-2. プログラムの操作方法
HDMI Demo の時とは、操作が違うので注意してください。
指定のアルファベットや数値を入力した後、ENTER する必要があります。

main.cc はそのまま流用していますので、操作内容が画面と合っていません。
使える操作は、下記の通りです。

>a: カメラと HDMI 出力のモード切り替え
> 1, CAMERA: 1280x 720/60p, HDMI OUT: 1280x720/60p
> 2, CAMERA: 1920x1080/30p, HDMI OUT: 1920x1080/60p
> 3, CAMERA: 1920x1080/60p, HDMI OUT: 1920x1080/60p
>
>b: IMX219 内蔵テストパターン出力
> 00, テストオフ(通常出力)
> 01, 全黒
> 02, 100% カラーバー
> 03, グレーフェード付 100% カラーバー
> 04, PN9(9ビット疑似ノイズ)
> 05, 16分割カラーバー
> 06, 16分割逆カラーバー
> 07, カラムカウンタ
> 08, 逆カラムカウンタ
> 09, PN31(31ビット疑似ノイズ)
>
>d: 機能の割り当て無し
>
>e: 指定レジスタに書き込み
>
>f: 指定レジスタから読み出し
>
>g: ガンマカーブ変更(Bayer2RGB IP)
> 1, Gamma Factor = 1
> 2, Gamma Factor = 1/1.2
> 3, Gamma Factor = 1/1.5
> 4, Gamma Factor = 1/1.8 (Default)
> 5, Gamma Factor = 1/2.2
>
>h: 機能の割り当て無し

メニューの場所により、アルファベットまたは数字 1 文字を入力して ENTER のところと、
2桁 or 4桁の HEX 値を入力して ENTER のところがあります。

メニューのアルファベット 1 文字を入力する所で返答がおかしくなったら、
aa とか bb というように 2 文字を入力して ENTER すると戻ります。
(コマンド解釈に癖が多い)

##6-3. テストパターンの表示
メニューの b で、IMX219 に内蔵されたテストパターンを表示することができます。
全部で 10 種類のテストパターンがあり、CSI-2 信号経路の評価に使えます。
パターンの詳細は、データシートを参照してください。

例は 03, グレーフェード付 100% カラーバー です。
![H27.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/06975df6-6a18-3824-d87d-1b100b5cf467.jpeg)

00, テストオフ(通常出力) で、元のカメラ画像に戻ります。


##6-4. レジスタの Read/Write
メニューの e と f で、カメラのレジスタを直接読み書きできるのが、非常に便利です。

カメラの露出が、0x0157 で操作できますので、映像が明るすぎる/暗すぎるときは、値を変更してみてください。

大きい値を書くと露出量を増やす(明るく映る)、小さい値では露出量を減らす(暗く映る)
という操作ができます。
![H26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/c4812a8b-3678-cdc1-eac0-fac78afabc38.png)


全てのレジスタを読み書きできますので、IMX219 データシートを見ながら触ってみてください。

#7. IMX219 の設定と、ケーブルの調整について
データシートで設定用レジスタの詳細を読めますが、パラメータ値を実際に書いてみると、いろいろと制約があることがわかりました。
raspivid でカメラをいろいろ操作して、I2C のデータを全ダンプして確認し、IMX219 の設定方法を理解しました。

今回の実習では、ケーブルと Zynq の実力値で調整した設定を ov5640.h に記載しています。

Zynq の LVDS_25 な I/O ピンで受信できる信号周波数は、スピードグレード -1 の場合、最大 950 Mb/s となっています(DS187)
1080/60p の設定では、1040 Mbps で受信している計算になりますので、受信エラーで画面にノイズが見えます。

03 グレーフェード付 100% カラーバーで、特定の明るさの所にノイズが見えやすいです。
FFC ケーブルや、CSI-2 - DATA2 ケーブルを指でつまむと、ノイズの見え方が変わります。

ピンセットで CSI-2 の信号線を触れて、良くなる(⇒ ディレイさせると受信状態が改善する)ところを探し、配線を適度に延長すると、ノイズを減らすことができます。

手元のケーブルでは、1080/60p でも、ほぼノイズレスが実現できています。
![H25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955220/947e1a07-93f8-ebf4-6d72-5c8a35416016.jpeg)

上にあるグレーフェード付 100% カラーバーの画像を拡大してみると、右側のブルーの帯とグレーの帯の中間あたりに、それぞれ少しノイズが出ているのが見えると思います。


#8. 全体のまとめ
EBAZ4205 で、Zybo Z7 Pcam 5C Demo を実行することができました。

RasPi にカメラを接続してHDMI出力するのと違い、スケーラが噛まない素のカメラ画像を確認することができます。
汎用の CSI-2 カメラテストベッドとして利用してもよさそうです。