NSLで回路を作ろう#08: カメラキャプチャ画像をディスプレイに表示 (3)

前回の続き
- 1 回路について、SCCBコントローラ回路([リンク](https://qiita.com/mk_masa/items/2c1415e10d6bd69263d6))
- 2 カメラキャプチャ回路([リンク](https://qiita.com/mk_masa/items/f61b7d4864cd7f2b228b))
- 3 ソフトウェアプログラム&動作確認(今回)

これまでに、SCCBコントローラ回路とカメラキャプチャ回路の作成を行ってきた。
- 今回は最終ステップとして、NiosIIシステムの構築、プログラムの作成、そして動作チェックを取り組む

# NiosIIシステムの構築

今回のNiosIIシステムは[この記事][prev]のものから拡張したようなものなので、
その状態からコピーして作っていく。
- プロジェクトの場所からFIFO,disp_ipフォルダと「～.qsys」ファイルをコピーして新たなプロジェクトフォルダへもってくる
  - FIFOフォルダはbuf_fifoと混ざっても問題なし
  - qsysファイルは、今回のプロジェクト名前に合わせてリネームしておく
  - うまく設定できない場合は新規から作った方がいいかも
- disp_fifo.qipをプロジェクトに追加しておく


前記事の設定から新たに追加するコンポーネントの設定を行う
- PIO 3つ (PIO_3, PIO_4, PIO_5)
- SCCBコントローラ(sccb)
- キャプチャ回路(cap_ip)


## PIOの設定

新たに追加したPIO 3つ (PIO_3, PIO_4, PIO_5) に関しては以下のように設定を行う。
![PIO_setting01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/88421ba0-dfec-98b3-08fa-b0a3dd39f6c6.png)

## SCCBコントローラの追加

sccbフォルダを作成し、以下の`sccb_hw.tcl`ファイルと作成したNSLをVerilogにコンパイルしたファイルと一緒に入れておく。

<details>
<summary>sccb_hw.tcl</summary>

```tcl
package require -exact qsys 16.1


# 
# module sccb
# 
set_module_property DESCRIPTION ""
set_module_property NAME sccb
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME sccb
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false


# 
# file sets
# 
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL sccb
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file sccb.v VERILOG PATH sccb.v TOP_LEVEL_FILE


# 
# parameters
# 


# 
# display items
# 


# 
# connection point clock
# 
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""

add_interface_port clock m_clock clk Input 1


# 
# connection point reset
# 
add_interface reset reset end
set_interface_property reset associatedClock clock
set_interface_property reset synchronousEdges DEASSERT
set_interface_property reset ENABLED true
set_interface_property reset EXPORT_OF ""
set_interface_property reset PORT_NAME_MAP ""
set_interface_property reset CMSIS_SVD_VARIABLES ""
set_interface_property reset SVD_ADDRESS_GROUP ""

add_interface_port reset p_reset reset Input 1


# 
# connection point avalon_slave_0
# 
add_interface avalon_slave_0 avalon end
set_interface_property avalon_slave_0 addressUnits WORDS
set_interface_property avalon_slave_0 associatedClock Clock
set_interface_property avalon_slave_0 associatedReset Reset
set_interface_property avalon_slave_0 bitsPerSymbol 8
set_interface_property avalon_slave_0 burstOnBurstBoundariesOnly false
set_interface_property avalon_slave_0 burstcountUnits WORDS
set_interface_property avalon_slave_0 explicitAddressSpan 0
set_interface_property avalon_slave_0 holdTime 0
set_interface_property avalon_slave_0 linewrapBursts false
set_interface_property avalon_slave_0 maximumPendingReadTransactions 0
set_interface_property avalon_slave_0 maximumPendingWriteTransactions 0
set_interface_property avalon_slave_0 readLatency 0
set_interface_property avalon_slave_0 readWaitTime 1
set_interface_property avalon_slave_0 setupTime 0
set_interface_property avalon_slave_0 timingUnits Cycles
set_interface_property avalon_slave_0 writeWaitTime 0
set_interface_property avalon_slave_0 ENABLED true
set_interface_property avalon_slave_0 EXPORT_OF ""
set_interface_property avalon_slave_0 PORT_NAME_MAP ""
set_interface_property avalon_slave_0 CMSIS_SVD_VARIABLES ""
set_interface_property avalon_slave_0 SVD_ADDRESS_GROUP ""

add_interface_port avalon_slave_0 avs_address address Input 1
add_interface_port avalon_slave_0 avs_readdata readdata Output 16
add_interface_port avalon_slave_0 avs_writedata writedata Input 16
add_interface_port avalon_slave_0 avs_read read Input 1
add_interface_port avalon_slave_0 avs_write write Input 1
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isFlash 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isMemoryDevice 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isNonVolatileStorage 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isPrintableDevice 0


# 
# connection point conduit_end
# 
add_interface conduit_end conduit end
set_interface_property conduit_end associatedClock ""
set_interface_property conduit_end associatedReset ""
set_interface_property conduit_end ENABLED true
set_interface_property conduit_end EXPORT_OF ""
set_interface_property conduit_end PORT_NAME_MAP ""
set_interface_property conduit_end CMSIS_SVD_VARIABLES ""
set_interface_property conduit_end SVD_ADDRESS_GROUP ""

add_interface_port conduit_end SCL scl Output 1
add_interface_port conduit_end SDA sda Output 1


```

</details>


Platform Designerの画面左上IP Catalogでsccbを右クリック>Editを選択
- 追加ファイル・信号の設定が以下になっていることを確認する。

問題なければコンポーネントを追加する。

![](figure/sccb_setting00.png)

## キャプチャ回路の追加

cap_ipフォルダを作成し、以下の`cap_ip_hw.tcl`ファイルと作成したNSLをVerilogにコンパイルしたファイルと一緒に入れておく。

<details>
<summary>cap_ip_hw.tcl</summary>

```tcl
package require -exact qsys 16.1


# 
# module cap_ip
# 
set_module_property DESCRIPTION ""
set_module_property NAME cap_ip
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME cap_ip
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false


# 
# file sets
# 
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL cap_ip
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file blank_flag.v VERILOG PATH blank_flag.v
add_fileset_file cap_ip.v VERILOG PATH cap_ip.v TOP_LEVEL_FILE
add_fileset_file conf_reg.v VERILOG PATH conf_reg.v
add_fileset_file input_ov7670.v VERILOG PATH input_ov7670.v
add_fileset_file sdram_transfer.v VERILOG PATH sdram_transfer.v
add_fileset_file xclk_gen.v VERILOG PATH xclk_gen.v


# 
# parameters
# 


# 
# display items
# 


# 
# connection point clock
# 
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""

add_interface_port clock m_clock clk Input 1


# 
# connection point reset
# 
add_interface reset reset end
set_interface_property reset associatedClock clock
set_interface_property reset synchronousEdges DEASSERT
set_interface_property reset ENABLED true
set_interface_property reset EXPORT_OF ""
set_interface_property reset PORT_NAME_MAP ""
set_interface_property reset CMSIS_SVD_VARIABLES ""
set_interface_property reset SVD_ADDRESS_GROUP ""

add_interface_port reset p_reset reset Input 1


# 
# connection point avalon_master_0
# 
add_interface avalon_master_0 avalon start
set_interface_property avalon_master_0 addressUnits SYMBOLS
set_interface_property avalon_master_0 associatedClock clock
set_interface_property avalon_master_0 associatedReset reset
set_interface_property avalon_master_0 bitsPerSymbol 8
set_interface_property avalon_master_0 burstOnBurstBoundariesOnly false
set_interface_property avalon_master_0 burstcountUnits WORDS
set_interface_property avalon_master_0 doStreamReads false
set_interface_property avalon_master_0 doStreamWrites false
set_interface_property avalon_master_0 holdTime 0
set_interface_property avalon_master_0 linewrapBursts false
set_interface_property avalon_master_0 maximumPendingReadTransactions 0
set_interface_property avalon_master_0 maximumPendingWriteTransactions 0
set_interface_property avalon_master_0 readLatency 0
set_interface_property avalon_master_0 readWaitTime 1
set_interface_property avalon_master_0 setupTime 0
set_interface_property avalon_master_0 timingUnits Cycles
set_interface_property avalon_master_0 writeWaitTime 0
set_interface_property avalon_master_0 ENABLED true
set_interface_property avalon_master_0 EXPORT_OF ""
set_interface_property avalon_master_0 PORT_NAME_MAP ""
set_interface_property avalon_master_0 CMSIS_SVD_VARIABLES ""
set_interface_property avalon_master_0 SVD_ADDRESS_GROUP ""

add_interface_port avalon_master_0 avm_waitrequest waitrequest Input 1
add_interface_port avalon_master_0 avm_address address Output 26
add_interface_port avalon_master_0 avm_write write Output 1
add_interface_port avalon_master_0 avm_writedata writedata Output 32
add_interface_port avalon_master_0 avm_burstcount burstcount Output 5


# 
# connection point conduit_end_0
# 
add_interface conduit_end_0 conduit end
set_interface_property conduit_end_0 associatedClock clock
set_interface_property conduit_end_0 associatedReset reset
set_interface_property conduit_end_0 ENABLED true
set_interface_property conduit_end_0 EXPORT_OF ""
set_interface_property conduit_end_0 PORT_NAME_MAP ""
set_interface_property conduit_end_0 CMSIS_SVD_VARIABLES ""
set_interface_property conduit_end_0 SVD_ADDRESS_GROUP ""

add_interface_port conduit_end_0 coe_cc_PCLK pclk Input 1
add_interface_port conduit_end_0 coe_cc_VSYNC vsync Input 1
add_interface_port conduit_end_0 coe_cc_HREF href Input 1
add_interface_port conduit_end_0 coe_cc_CAMDATA camdata Input 8
add_interface_port conduit_end_0 coe_cc_XCLK xclk Output 1
add_interface_port conduit_end_0 coe_cm_CAPADDR capaddr Input 26
add_interface_port conduit_end_0 coe_cm_CAPON capon Input 1
add_interface_port conduit_end_0 coe_cm_CLRCBLNK clrcblnk Input 1
add_interface_port conduit_end_0 coe_cm_CBLANK cblank Output 1


```

</details>


Platform Designerの画面左上IP Catalogでcap_ipを右クリック>Editを選択
- 追加ファイル・信号の設定が以下になっていることを確認する。

![cap_ip_setting00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/551adeea-a465-473a-1d71-49c9a9215c3f.png)

## 配線

下図を参考にクロックとリセット、外部端子、IRQ等の配線をする。詳しい手順は[この記事][prev3]や[テキスト][text]参照
![nios2_components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/87f3913c-12f6-c13c-ee7d-d6c17c6e6314.png)

# 最上位階層の作成

最上位階層は[テキスト][text]でダウンロードしたものを使用する
- 詳しくは[グラフィック表示回路][prev]の回を参照
- 端子名を変更する
  - CLK -> m_clock
  - RST -> p_reset
- カメラの接続の都合上、GPIOとの接続は以下の通りにした。 

```verilog
wire PCLK  = GPIO_1[24];
wire VSYNC = GPIO_1[22];
wire HREF  = GPIO_1[23];
wire [7:0] CAMDATA = { GPIO_1[26], GPIO_1[27], GPIO_1[28], GPIO_1[29],
	GPIO_1[30], GPIO_1[31], GPIO_1[32], GPIO_1[33] };
wire    XCLK, SCL, SDA;
assign GPIO_1[25] = XCLK;
assign GPIO_1[20] = SCL;
assign GPIO_1[21] = SDA;
assign GPIO_1[34] = 1'b1;
assign GPIO_1[35] = 1'b0;
```
この部分は環境によって異なるので適宜調整。


# ソフトウェアプログラムの作成

[前々回](https://qiita.com/mk_masa/items/2c1415e10d6bd69263d6)で示した以下のCプログラムを作成する
- キャプチャした画像をVGAに表示する
  - 準備 
  - OV7670の設定・初期化
  - 各ブランクフラグを読み取る
  - キャプチャ回路とディスプレイ回路の設定を行う


## 準備

SCCB通信で書込むデータをヘッダファイルで別途定義する。
- 以下のような内容であるが非常に長いので、省略する
  - [テキストのサイト][text]からダウンロードできる`ov7670.h`を使用する

```c
unsigned short init_data[] = {
    0x1280,
    0x1713,
    0x1801,
// ... 
// 設定値が大量に書かれている(中略)
// ...
    0x3dc0,
    0x3a0d,
    0xffff
};

```

定数の定義
PIOやSCCBコントローラのレジスタをやり取りするために、
アドレスなどをわかりやすく`#define`しておく
```c
#define DISPADDR PIO_0_BASE
#define CLRV_DON PIO_1_BASE
#define VBLANK   PIO_2_BASE

#define CAPADDR  PIO_3_BASE
#define CLRC_CON PIO_4_BASE
#define CBLANK   PIO_5_BASE

#define ON_BIT  0x01
#define CLR_BIT 0x02

#define SCCBREG  SCCB_0_BASE
#define SCCBSTAT (SCCB_0_BASE + 0x02)
#define SCCBBUSY 1
```

### OV7670の設定・初期化
- SCCBコントローラに値を書込んで、SCCB通信を行わせる関数を定義する
```c
void sccb_write( int data )
{
	while ((IORD_16DIRECT(SCCBSTAT, 0) & SCCBBUSY) != 0);
	IOWR_16DIRECT(SCCBREG, 0, data);
}
```
- 準備で用意した配列と先ほど定義した関数を使用してレジスタを初期化する処理を行う
```c
void ov7670_init( void )
{
	unsigned short * data=init_data;

	while ( *data!=0xffff ) {
		sccb_write( *data++ );
	}
	while ((IORD_ALTERA_AVALON_PIO_DATA(SCCBSTAT) & SCCBBUSY) != 0);
}
```

## 各ブランクフラグを読み取る関数
[グラフィック表示回路][prev]とカメラキャプチャ回路のブランク状態を示す信号を受け取るまで待機する関数を
それぞれの回路用に定義する。
```c
// VGAのブランク状態(VBLANK)待ち
void wait_vblank(void) {
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRV_DON, CLR_BIT);
	IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRV_DON, CLR_BIT);
	while (IORD_ALTERA_AVALON_PIO_DATA(VBLANK)==0);
}

// キャプチャ回路のブランク状態(CBLANK)待ち
void wait_cblank(void) {
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRC_CON, CLR_BIT);
	IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRC_CON, CLR_BIT);
	while (IORD_ALTERA_AVALON_PIO_DATA(CBLANK)==0);
}
```


メイン関数では、CMOSカメラの初期化を行った後、
VGAの表示とキャプチャをオンにする。
```c
int main()
{
	ov7670_init();

	// 表示ON
	wait_vblank();
	IOWR_ALTERA_AVALON_PIO_DATA(DISPADDR, 0x03000000);
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRV_DON, ON_BIT);

	while (1)
	{
		// キャプチャON 
		wait_cblank();
		IOWR_ALTERA_AVALON_PIO_DATA(CAPADDR, 0x03000000);
		IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRC_CON, ON_BIT);
		wait_cblank();
		IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRC_CON, ON_BIT);
	}

	return 0;
}
```

<!-- #region program -->

<details>
<summary>プログラム全体</summary>

```c

#include "system.h"
#include "altera_avalon_pio_regs.h"

#include "ov7670.h"

#define DISPADDR PIO_0_BASE
#define CLRV_DON PIO_1_BASE
#define VBLANK PIO_2_BASE

#define CAPADDR PIO_3_BASE
#define CLRC_CON PIO_4_BASE
#define CBLANK PIO_5_BASE

#define ON_BIT 0x01
#define CLR_BIT 0x02

#define SCCBREG SCCB_0_BASE
#define SCCBSTAT (SCCB_0_BASE + 0x02)
#define SCCBBUSY 1

void sccb_write( int data )
{
	while ((IORD_16DIRECT(SCCBSTAT, 0) & SCCBBUSY) != 0);
	IOWR_16DIRECT(SCCBREG, 0, data);
}

void ov7670_init( void )
{
	unsigned short * data=init_data;

	while ( *data!=0xffff ) {
		sccb_write( *data++ );
	}
	while ((IORD_ALTERA_AVALON_PIO_DATA(SCCBSTAT) & SCCBBUSY) != 0);
}

// VGAのブランク状態(VBLANK)待ち
void wait_vblank(void) {
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRV_DON, CLR_BIT);
	IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRV_DON, CLR_BIT);
	while (IORD_ALTERA_AVALON_PIO_DATA(VBLANK)==0);
}

// キャプチャ回路のブランク状態(CBLANK)待ち
void wait_cblank(void) {
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRC_CON, CLR_BIT);
	IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRC_CON, CLR_BIT);
	while (IORD_ALTERA_AVALON_PIO_DATA(CBLANK)==0);
}

int main()
{
	ov7670_init();

	// 表示ON
	wait_vblank();
	IOWR_ALTERA_AVALON_PIO_DATA(DISPADDR, 0x03000000);
	IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRV_DON, ON_BIT);

	while (1)
	{
		// キャプチャON 
		wait_cblank();
		IOWR_ALTERA_AVALON_PIO_DATA(CAPADDR, 0x03000000);
		IOWR_ALTERA_AVALON_PIO_SET_BITS(CLRC_CON, ON_BIT);
		wait_cblank();
		IOWR_ALTERA_AVALON_PIO_CLEAR_BITS(CLRC_CON, ON_BIT);
	}

	return 0;
}
```

</details>

<!-- #endregion -->


# 3. 動作チェック

ModelSim使って波形を見てみる。

SCCBコントローラ回路の通信チェック
- `0x1280`を書込み、SDAに書込んだ値が送信されていることを確認

![sccb_wave10.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/5beeff2a-4327-74f4-f693-6e9ee9e89903.png)


cap_ipのデータ書込みチェック
- 入力したデータ(`06` `6d`)が想定されたフォーマット(`d06060`)に変換されて出力されていることを確認

![cap_ip_wave00.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/bcbfb652-183d-1917-ad0d-b33ea61aca11.png)



DE1-SoCにインストールしてみて実際にキャプチャ出来ているかチェック。

![camcap_check00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/7af09aee-21da-6db1-9ea5-760eb958f2f2.png)
映っているのは予備のCMOSカメラ(ジャンパー線付き)

![camcap_check01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588818/d1c30cf0-b3dc-265b-8f9b-fc6e45e8876d.png)

映っているのはキーボード(HP製)

問題なくキャプチャ出来ていると思われる

# まとめ

CMOSカメラでキャプチャしたものをVGAグラフィック回路で表示させるシステムを作った。
- SCCB通信でカメラの設定を行う
- カメラから送られてきた画像をFIFOに格納する
- FIFOの値をAvalon-MMのマスター回路でSDRAMに書込む
- キャプチャした画像を[VGAグラフィック回路][prev]に表示させた

ここまででテキストで紹介されていた回路は一通り作成した。

# 参考文献

- [【改訂2版】FPGAボードで学ぶ 組込みシステム開発入門［Intel FPGA編］][text]

[text]: https://gihyo.jp/book/2018/978-4-7741-9388-5
[prev5]: https://qiita.com/mk_masa/items/2c4eb17dde97695cee4c
[prev4]: https://qiita.com/mk_masa/items/85d6e47b081195c277db
[prev3]: https://qiita.com/mk_masa/items/2088aac1ca16b49df952
[prev2]: https://qiita.com/mk_masa/items/fc846bf6e4472593900e
[prev]: https://qiita.com/mk_masa/items/b725323a9844bf882975