Jetson Orin Nano DevKitとCSIカメラを繋ぐ

## 今回の機材

- [Jetson Orin Nano 開発者キット](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/)
- [Raspberry Pi Camera Module 2](https://www.raspberrypi.com/products/camera-module-v2/)
- [Raspberry Pi Zero用カメラケーブル(15-pin to 22-pin FFC cable)](https://www.arducam.com/raspberry-pi-camera-pinout/)

## Camera Module 2 接続

XavierNXまではraspberrypiと同じ**15ピン 1.0mmピッチ**のコネクタでしたが、Orin Nanoから**22ピン 0.5mmピッチ**に変わっており、XavierNXまでで使えていたCSIカメラをそのまま繋ぐことは出来ません。
ですが、22ピン 0.5mmピッチはRaspberry Pi Zero Wと同じタイプです。
おおよそ同じピン配置なのでPi cameraの15-pin to 22-pin FFC cableが使えます。

RaspberryPiのコネクタのpin配置は[arducamのサイト](https://www.arducam.com/raspberry-pi-camera-pinout/)にわかりやすく図も乗っています。

画像を引用しました。
Pin#15から3V3、I2Cとなっています。

<img width="300" alt="RaspberryPiCamera配線図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/10fe4095-5385-3d79-cbb9-1d195755881a.png">

15-22pinの場合は以下の図の通り、間にある2レーン分を未接続にしてつないでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/7a786786-6608-7df6-6faf-e307d656c861.png)

Orin Nanoについては[Jetson Download CenterのCarrier Board Specification](https://developer.nvidia.com/embedded/downloads#?search=Carrier%20Board%20Specification&tx=$product,jetson_orin_nano)を参照しましょう。

OrinNanoの場合はButtomHeaderがある方がPin#1で、こちらから3V3, I2Cとなっています。
PinNoはRaspberryPiとは逆ですが、並びは同じなのでこの図にある通り、基板側に電極が来るようにつなげればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/9e7bd12f-9321-67f4-b0ef-d7e98697dfa8.png)

スライドを引き、少し上に持ち上げ、FFC電極面がキャリアボード側を向くように差して止めます。
差し込み感がわかりにくいですが[JetsonHacks Using the Jetson Orin Nano with CSI Cameras
](https://jetsonhacks.com/2023/04/05/using-the-jetson-orin-nano-with-csi-cameras/)に動画があるのでそちらも参考にしてください。

とりあえずCAM0につないでRebootをすると`dmesg`に次のようなログがあるはずです

これでドライバのロードもされてIMX219が認識されているはずです

```dmesgのログ
imx219 9-0010: tegracam sensor driver:imx219_v2.0.6
tegra-camrtc-capture-vi tegra-capture-vi: subdev imx219 9-0010 bound
```

```sh
v4l2-ctl --list-devices
NVIDIA Tegra Video Input Device (platform:tegra-camrtc-ca):
        /dev/media0

vi-output, imx219 9-0010 (platform:tegra-capture-vi:1):
        /dev/video0
```

## 撮影

### 失敗編(CAM0)

撮影してみましょう。
RAW画像をそのまま保存してみます。

```sh
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=RG10 --set-ctrl bypass_mode=0,sensor_mode=0 --stream-mmap --stream-count=1 --stream-to=test.raw
```

結果が出てこない場合はdmesgを見ましょう。
エラーに `uncorr_err: request timed out after 2500 ms` とある場合、ビデオ入力を受け取る`tegra-capture-vi`が入力のシグナルを受け付けなかったというエラーです。
実はこれ35.3.1ではデバイスツリーの設定不具合があって基本的に成功しません。
諦めてCAM1を使いましょう。

```dmesg
tegra-camrtc-capture-vi tegra-capture-vi: uncorr_err: request timed out after 2500 ms
tegra-camrtc-capture-vi tegra-capture-vi: err_rec: attempting to reset the capture channel
(NULL device *): vi_capture_control_message: NULL VI channel received
t194-nvcsi 13e40000.host1x:nvcsi@15a00000: csi5_stream_close: Error in closing stream_id=0, csi_port=0
(NULL device *): vi_capture_control_message: NULL VI channel received
t194-nvcsi 13e40000.host1x:nvcsi@15a00000: csi5_stream_open: VI channel not found for stream- 0 vc- 0
tegra-camrtc-capture-vi tegra-capture-vi: err_rec: successfully reset the capture channel
```

### 成功編(CAM1)

CAM1につなぐとカメラ `imx219 9-0010` -> `imx219 10-0010`となって認識できます。

```dmesg
imx219 10-0010: tegracam sensor driver:imx219_v2.0.6
tegra-camrtc-capture-vi tegra-capture-vi: subdev imx219 10-0010 bound
```

```sh
$ v4l2-ctl --list-devices
NVIDIA Tegra Video Input Device (platform:tegra-camrtc-ca):
        /dev/media0

vi-output, imx219 10-0010 (platform:tegra-capture-vi:2):
        /dev/video0
```

RAW取得コマンドも以前とことなり正常に終了してRAWデータが得られています。

```sh
$ v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=RG10 --set-ctrl bypass_mode=0,sensor_mode=0 --stream-mmap --stream-count=1 --stream-to=test.raw
<
```

### Argus

実際の映像も確認しましょう。
Jetson Orin NanoにもISPが乗っているのでArgusAPI経由でデベイヤーした画像が得られます。

#### argus_camera(実機)

argus_cameraで動作確認します。
以下のようなMakefileで`make setup`を実行したら、argus_cameraのバイナリが実行したディレクトリにできます。
実行したらカメラの画像が見れると思います。

なお、SSHでX11 Forwardしている場合はArgusAPIを叩くときにEGLの周りでエラーで動かないことがあります。
実機で見るか、`unset DISPLAY` してGStreamerで動画記録の方法をおすすめします。

```Makefile
.PHONY: setup
setup:
	sudo apt update
	sudo apt install -y nvidia-l4t-jetson-multimedia-api \
	cmake build-essential pkg-config libx11-dev libgtk-3-dev \
	libexpat1-dev libjpeg-dev libgstreamer1.0-dev v4l-utils tree
	cp -r /usr/src/jetson_multimedia_api .
	cd jetson_multimedia_api/argus/ && \
		mkdir -p build && \
		cd build && \
		cmake ..  && \
		make
	cp jetson_multimedia_api/argus/build/apps/camera/ui/camera/argus_camera .
```

#### Record Video by GStreamer

リモートでするならgstreamerからファイル保存をするのも楽です。
残念ながらOrinNanoにHW Encoderは載っていないので`x264enc`を使います。
出来たファイルをLocalMachineに持ってきて撮影できているか確認してください。

```sh
gst-launch-1.0 nvarguscamerasrc num-buffers=30 ! nvvidconv ! x264enc ! mp4mux ! filesink location=test.mp4
```

## まとめ

1. **15-pin to 22-pin FFC cable**があればRaspberryPiのカメラが使える
    1. switch-scienceで買うなら[RPI-A-003](https://www.switch-science.com/collections/%E3%82%AB%E3%83%A1%E3%83%A9/products/3195)
1. CAM0ポートでは動作しない
    1. L4T35.3.1のデフォルトのデバイスツリーでは動かない設定でした
    1. 下記原因究明編を参照してください


## 余談

XavierNXのキャリアボードからピンのアサインと関わっていました。
今回のピン配置ですが以下のとおりです。
左からIMX219カメラの15pinを22pin変換したもの、OrinのCAM0,1とXavierNXのJ1(CAM1とスクリーン印刷されていますが実際にはCAM0)
XavierNXのときには**CSI0**がつながっていましたがOrinNanoでは**CSI1**になっています。


| Pin # | IMX219 Camera with Cable | Orin Nano CAM0 | Orin Nano CAM1 | XavierNX J1 |
| --- | --- | --- | --- | --- |
| 1 | 3V3 | 3V3 | 3V3 | 3V3 |
| 2 | CAM_SDA | CAM_I2C_SDA | CAM_I2C_SDA | CAM_I2C_SDA |
| 3 | CAM_SCL | CAM_I2C_SCL | CAM_I2C_SCL | CAM_I2C_SCL |
| 4 |  | GND | GND | GND |
| 5 | CAM_IO1 | CAM0_MCLK | CAM0_MCLK | CAM0_MCLK |
| 6 | CAM_IO0 | CAM0_PWDN | CAM0_PWDN | CAM0_PWDN |
| 7 |  | GND | GND |  |
| 8 |  | CSI0_D1_P | CSI3_D1_P |  |
| 9 |  | CSI0_D1_N | CSI3_D1_N |  |
| 10 |  | GND | GND |  |
| 11 |  | CSI0_D0_P | CSI3_D0_P |  |
| 12 |  | CSI0_D0_N | CSI3_D0_N |  |
| 13 | GND | GND | GND | GND |
| 14 | CAM_CK_P | **CSI1_CLK_P** | CSI2_CLK_P | CSI0_D1_P |
| 15 | CAM_CK_N | **CSI1_CLK_N** | CSI2_CLK_N | CSI0_D1_N |
| 16 | GND | GND | GND | GND |
| 17 | CAM_D1_P | **CSI1_D1_P** | CSI2_D1_P | CSI0_D0_P |
| 18 | CAM_D1_N | **CSI1_D1_N** | CSI2_D1_N | CSI0_D0_N |
| 19 | GND | GND | GND | GND |
| 20 | CAM_D0_P | **CSI1_D0_P** | CSI2_D0_P | CSI0_CLK_P |
| 21 | CAM_D0_N | **CSI1_D0_N** | CSI2_D0_N | CSI0_CLK_N |
| 22 | GND | GND | GND | GND |


またOrinシリーズのCSI0,1はデータレーンの極性がswapしているので特別な指定が必要となってます。
L4Tソースの`tegra234-p3768-0000-a0.dtsi`にその記述があるので、カメラドライバを移植する時は変更をお忘れなく。

```hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-p3768-0000-a0.dtsi
tegra-capture-vi  {
	ports {
		port@0 {
			endpoint {
				port-index = <1>;
			};
		};
	};
};

host1x@13e00000 {
	nvcsi@15a00000 {
		csi_chan0 {
			ports {
				port@0 {
					endpoint@0 {
						port-index = <1>;
					};
				};
			};
		};
	};
};

cam_i2cmux{
		i2c@0 {
			rbpcv2_imx219_a@10 {
				mode0 {
                    # CSI1(CSI-B)を指定
					tegra_sinterface = "serial_b";
                    # CSIx_D1とCSIx_D0のスワップ
					lane_polarity = "6";
				};
				mode1 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				mode2 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				mode3 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				mode4 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				ports {
					port@0 {
						endpoint {
							port-index = <1>;
						};
					};
				};
			};

			rbpcv3_imx477_a@1a {
				mode0 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				mode1 {
					tegra_sinterface = "serial_b";
					lane_polarity = "6";
				};
				ports {
					port@0 {
						endpoint {
							port-index = <1>;
						};
					};
				};
			};
		};
	};
};
```


## CAM0原因究明編

なぜCAM0だけ機能しないのでしょう。
それはOrinNanoDevKitのピン設定と上のデバイスツリー設定に原因があります。

CAM0ポートは従来と違ってCSI1(別名CSI-B)が繋がっています。
TRMの接続経路を見てみると以下のようにCSI-AB Pad -> RX SCIL0 -> PP1 -> VIへと入力されます。

> DP-10508-002_v1.0p | Page 538より引用
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/573e8240-39cb-eeea-365b-c25490d423af.png)

従来とはroutingが異なるわけですね。
それを反映するのが上のデバイスツリーです。
大本のデバイスツリーでは`port_index=<0>`だったものを`<1>`に書き換えることで、OrinNXとOrinNanoの両方に対応しているわけですね。

で、上の設定には一箇所だけ設定不具合があり、以下が正しい設定となります。

```diff
host1x@13e00000 {
	nvcsi@15a00000 {
- 		csi_chan0 {
+		channel@0 {
			ports {
				port@0 {
					endpoint@0 {
						port-index = <1>;
					};
				};
			};
		};
	};
};
```

`csi_chan0`はシンボルなので実ツリーの指定が必要というわけでした。
ブロック単位でのroutingは正しくともPAD設定が繋がっていなかったのでデータ無しのエラーだったということです。