Jetsonのカメラドライバを書く[準備編]

# この連載について

次の記事: [Jetsonのカメラドライバを書く[I2C編]](https://qiita.com/uzuna/items/693cabb800173f2175f5)

この連載ではJetson orin-nano(L4T 35.3.1)のMIPI-CSI2カメラドライバを書いていきます。
記事にしようと思ったのは、欲しいカメラドライバが存在しなかったこと、そして開発しようと思うと必要な情報がまとまっていなくて困ったからです。
他にもカメラドライバ周りのトラブルを抱えている人の参考になるように情報を残しておきたいと思いました。

この記事ではカメラドライバを書くに至った経緯と、その時に必要となる情報がどこにあるのかについて書きます。
どうぞお付き合いください。

# JetsonとMIPI-CSIカメラ

JetsonはMIPI-CSI2のカメラと接続できます。
特に[Jetson Nano Developer Kit](https://developer.nvidia.com/embedded/jetson-nano-developer-kit)からはRaspberryPiシリーズと互換性のある15pinのコネクタがあり、最上位機種のAGXを除く開発者キット、そしてJetson開発者キットの互換ボードはたいていこのカメラコネクタを備えています。[Jetson Orin Nano Developer Kit](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/#advanced-features)もRaspberryPi Zero Wと互換性のあるカメラコネクタを備えています。
互換性あるなら早く繋ごうぜ、でもいいのですが、ここではもう一歩立ち入って[Jetson Orin Nanoのモジュールの仕様](https://developer.nvidia.com/embedded/downloads#?search=Jetson%20Orin%20Nano%20Series%20Module%20Data%20Sheet)を見てみましょう。

> Fifth-generation NVIDIA camera solution (NVCSI 2.0, VI 5.0, and ISP 6.x) provides a combination host that supports enhanced
MIPI D-PHY (with lane deskew support) physical layer options in two 4-lane or four 2-lane configurations; or combinations of
these. Orin Nano can support up to 16 virtual channels (VC) and supports data type interleaving.
> -- JETSON | ORIN NANO SERIES | DATA SHEET | DS-11105-001_v1.1 p.9 --

上記の通りJetsonはMIPI信号を処理するための領域とISPを備えています。
手早くカメラを使いたいのならUSBカメラがとても簡単です。しかしUSBカメラはセンサやレンズが不明なことが多く、高度(で煩雑)な制御は内部ISPが処理していて触れないことがほとんどです。もしも画像の素性を正確に把握したい、用途に合わせてセンサーの性能を引き出したいなら、MIPI-CSIのカメラは有力な選択肢になります。

## Jetson向けのMIPI-CSIカメラ

前述のとおりJetsonにはRaspberryPi向けの製品を繋ぐことも出来る他、[NVIDIAから対応製品について情報公開されています。](https://developer.nvidia.com/embedded/jetson-partner-supported-cameras?t1_supported-jetson-products=Orin)NVIDIAはJetson向けのカメラを作るメーカーを`Jetson Camera Partner`として認定しており、これらのベンダーがJetson向けの様々なセンサー、レンズを販売しています。

メーカーも多く製品も多種あるのですが...個人で買うには少し躊躇する点があります。
まずラズベリーパイ向けのカメラセンサーと比べると桁一つ高いです。安くても10000円を切ることはありません。また、購入できるサイトも限られています。一部のオンラインストアのあるメーカーを除くと国内代理店経由での購入となり、問い合わせからの取り寄せとなります。そしてドライバの入手が難しいです。メーカーサイトからのDLならいいほうで、メーカーによっては営業に問い合わせが必要で、手に入ってもL4Tのバージョンが古かったりします。
手が出しにくい...。


一方RaspberryPiのカメラは入手性、価格ともに個人での購入は容易です。
手元にはちょうど[Raspberry Pi Camera Module 3](https://www.raspberrypi.com/products/camera-module-3/)があります。
幸いなことに[Raspberry Pi Camera Module 2ことIMX219](https://www.raspberrypi.com/products/camera-module-v2/)と[Raspberry Pi High Quality CameraことIMX477](https://www.raspberrypi.com/products/raspberry-pi-high-quality-camera/)のラズパイカメラはL4Tにドライバが含まれているため最新のL4Tでも使えます。個人で使うには、やはりこれらが良いです。

ところで、問題は含まれて"いない"カメラモジュールをどうするかです。
ものはある、ドライバがない。ならば、書くしかないですね。

# Raspberry Pi Camera Module 3のドライバを書く

今回は[Raspberry Pi Camera Module 3ことIMX708](https://www.raspberrypi.com/products/camera-module-3/)のドライバを書きます。

## 開発を始める前に

Jetsonのカメラドライバを書くには、特にそれが販売可能なほどの品質のものを作る場合は、Hunter x Hunterの暗黒大陸よろしく「許可」「手段」「資格」「契約」の4つが必要となります。
- 契約: イメージセンサメーカーなどとのNDA。これがないとデータシートが得られず、レジスタ設定がわからないのでセンサーが動かせません
- 手段: 開発対象のJetsonやMIPI信号を確認できる機材。不具合時に調べる手段がないと手探りとなって辛い目に合います
- 資格: NVIDIAのカメラパートナーに関する契約。実はISP周りは非公開でなので触れないです。ISPが触れないと画作りができません
- 許可: ドライバ対象となるカメラモジュールメーカーの許可。ドライバで手広く販売するならモジュールメーカーに礼を通しましょう

私はどれも持っていません。そして多くの個人開発者でも同じでしょう。
しかしながら、NVIDIAもドライバ開発についてはある程度のガイドを出しているため、海を渡るほど大変では無く、ある程度地続きとは言えます。せっかくなので入口ぐらいは見て行きましょう。
そして何より、ドライバを作るとイメージセンサーがどのように制御され、画像データとして使えるまでに何が起こっているのかを知ることが出来ます。Jetsonにおけるトラブルの解決、カメラへの理解を深める絶好の機会です。

## 前提説明

この開発ガイドはラズパイや互換ボード、OSで動いているカメラを、Jetson関係の環境で動かしたい人向けです。画作りとかをして使えるキレイな絵を取るなどは対象外ですのでご注意ください。

改めて、使う機材は以下のとおりです。
- [Jetson Orin nano 8GB 開発者キット](https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/)
- [IMX708(Raspberry Pi Camera Module 3)](https://www.raspberrypi.com/products/camera-module-3/)
- [Raspberry Pi Zero用カメラケーブル(15-pin to 22-pin FFC cable)](https://www.arducam.com/raspberry-pi-camera-pinout/)

開発者キットはSpeedstudio J401などの互換ボードなどでも大丈夫ですし、カメラもソースコードが見つかるものなら同じように移植できます。

さて先程の4つの条件、一つも持っていないと言いましたが幾つかの抜け道があります。

まず**手段**ですが、カメラコネクタに互換性があるので、類似の環境で動作確認が取れているならオシロスコープなどいりません。**動くと信じる心**があれば大丈夫です。
そして**契約**ですが、要はレジスタ設定がわかればよいのです。RaspberryPiはカーネルのソースコードが公開されており、そこにカメラドライバも含まれます。動かない時に**根気強く調べる覚悟**を用意すれば解決したも同然です。
**資格**はISPによる画作りには必須ですが、今回は絵が撮れればOKなのでISPのチューニングなどは忘れていいです。**絵がイマイチでも許す寛大さ**を持ちましょう。
最後に**許可**ですがlinux kernelはGPLv2に基づいて公開されており、カメラドライバも基本的にこのライセンスの範疇です。個人用途なら公開は不要ですし、もしも出すのなら**GPLv2ライセンスに従いましょう**。

無事前提条件をクリア(?)したので先に進みましょう。

## 作業の段階

大まかな段階は以下の通りです。
必要な情報を集めた上で接続を確認し、信号が低速なところから順に動作を確認して、最後に映像を得るという流れになります。
今回は準備編なので**I2Cの疎通確認まで**を行います。

1. 必要なリファレンスを知る
1. カメラモジュールの接続を確認する(I2C) <--この記事ではここまで
1. ドライバでカメラを認識する
1. RAWを得る
1. ISPを通して絵を得る

## 必要なリファレンスを知る

カメラドライバを書くにあたって、知らなければならない情報は以下です。
イメージセンサの制御情報はドライバから読み解くので後回しにして、それ以外の資料について見ていきましょう。

1. MIPI-CSI2の情報
1. JetsonのMIPI-CSI2関係の情報
1. イメージセンサの制御情報

### MIPI-CSI2

そもそもMIPI-CSI2の仕様ってどんなのでしょう?
[MIPI Alliance - MIPI CSI-2](https://www.mipi.org/specifications/csi-2)に最新の規格書があります。が、個人での入手は難しいです。

仕方ないので情報をまとめているサイトや、センサーやMIPI-CSI2に対応しているデバイスのデータシートなどから情報をかき集めます。
まずは[MACNIKA社の【徹底解説】MIPI なんて難しくない！新人がゼロから学ぶ MIPI 規格まとめページ](https://www.macnica.co.jp/business/semiconductor/articles/lattice/141503/)です。概要をまとめて公開されておりとても参考になります。大変ありがたいです。
また[エンジニアの電気屋さんのMIPI CSI-2の規格を調べて波形を確認してみた](https://misoji-engineer.com/archives/mipi-csi-2-specification.html)ではRaspberryPIへの接続、オシロ波形など丁寧に解説を載せておられます。助かります。MIPIの信号はこんなふうになってるんですね。

他には[IMX708のレジスタ解析を試みている人](https://github.com/Hermann-SW/imx708_regs_annotated)がいます。何故かIMX219とIMX477のデータシートへのリンクが存在するため、イメージセンサの制御方法についても窺い知ることが出来ます。センサーの気持ちを知る上で重要な資料です。

詳細は各自で追っていただくとして、最低限知っておいてほしいのはビデオフレーム図です。
MACNICA社のひろみんさんの記事、[【徹底解説】MIPI なんて難しくない！新人がゼロから学ぶ MIPI 規格 基礎編](https://www.macnica.co.jp/business/semiconductor/articles/lattice/142604/)からビデオフレーム図を引用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/d6650688-1969-0188-bf03-f3d6ba76b91b.png)

MIPI-CSI2(Camera Serial Interface 2)ではイメージセンサーの各画素をシリアル(=時間的に直列方向)に読み出します。センサーの画素領域をスキャンするように、1Lineずつ読み出しており、1line単位でデータパケットを構成しています。MIPI-CSI2の規格はこのパケットのデータの構造や電気的な仕様の定義がされているようです。

今回のドライバ開発で知っておくことは以下です。

1. 1フレームのデータ開始を示す制御用のショートパケットFS(FrameStart)とデータの終わりを示すFE(FrameEnd)がある
1. 1ラインごとに1つのロングパケットが出力される
1. ロングパケットの中身はData IDで定義されており、パケットヘッダで処理の振り分けが出来る
1. 画素データ以外に埋め込みデータ(制御情報などのメタデータ)を持つ場合がある(IMX219などのデータシートを参照)
1. 各ラインとフレームの間にはデータを転送のないブランク期間がある

これらの仕様はカメラの制御実装とも繋がっています。

- 撮影画像の大きさ -> 画素の読み出し開始点、終了点を変えることができます。これによって有効画素のうち**任意の矩形**を切り取って画像として取得できます
- FPS -> センサーは画素、ラインをスキャンする制御周波数が決まってます。この制御周波数が1秒間に読める画素数の上限を決まり、この画素に対して読み出す1フレーム(縦*横の大きさ)が幾つ入るかでFPSが決まります
- 露光時間 -> 読み出しの1クロック前に露光を始めるのかで露光時間が決まります。これも制御周波数の周期で制御されます。

画面全体をスキャンする周期はセンサー内蔵の発振素子で制御されており、読み出し速度に合わせてデータがイメージセンサから出力されます。データを受ける側はそれに間に合う転送速度で信号を受ける必要があり、Jetson Orin nanoは最大で `2.5 Gbits/sec per pair`の速度で受けることができます。
ちなみにRaspberryPiカメラにあるような薄いFPCでは、たった15cmでも1Gbps程度を超えると信号が乱れて読めなかったりします。いいケーブルが必要ですね。

### JetsonのMIPI-CSI関係とドライバソースの場所

どのようなデータが来るかわかったので、Jetsonがどのように受けるのかを調べます。

これについて、NVIDIAは[Jetson camera driver development](https://www.brainshark.com/1/player/nvidia?pi=zGjz1LZr7zTpFOz0&r3f1=aa90eebdb1bce4f6a6fd97e8bcacbcf0bdeced9df6a6b1aaacf7e7ed91fea0a0e1bdb6e4b4&fb=0)という動画を公開しており、Jetson側の実装の概要を掴むのにはとても良いです。ただしちょっと古いので、最新の資料も見る必要があります。

この記事を書いたときの最新のバージョン、[L4T45.3.1 DeveloperGuide - Camera Development](https://docs.nvidia.com/jetson/archives/r35.3.1/DeveloperGuide/text/SD/CameraDevelopment.html)にもカメラドライバ開発についてページが設けられています。

上はL4T全般の情報であるため、実際に使う機材である[Jetson Orin nano開発キットのキャリアボードに関するリファレンス](https://developer.nvidia.com/embedded/downloads#?search=Developer%20Kit%20Carrier&tx=$product,jetson_orin_nano)と[Orin SoMのテクニカルリファレンスマニュアル(TRM)](https://developer.nvidia.com/embedded/downloads#?search=Technical%20Reference%20Manual&tx=$product,jetson_agx_orin)についても入手しておくことをおすすめします。

### 開発キットのカメラ接続

Jetsonとカメラの接続については __Jetson Orin Nano Developer Kit Carrier Board SP-11324-001_v1.0の p.18__ が詳しく、ピンアサインやピンの電圧の情報があります。
ピンの接続はデバイスツリーの設定に関わるので、迷ったときにはここに戻ってこれるように覚えておいてください。

### VI6

JetsonがMIPI-CSI2のシグナルを受けてメモリに乗せるまでのブロックはVI6という名前で呼ばれており、以下のような構成になっています。
これもまた、デバイスツリーの設定に関わるのでなんとなく把握しておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/d19671ee-62e0-7940-b24f-f2e86a57a131.png)
> ORIN | TRM | SUBJECT TO CHANGE | VI 5
Copyright © 2014-2022 NVIDIA Corporation. All rights reserved.
DP-10508-002_v1.0p | Page 1898

細かいところはTRMを参照してもらうとして、かいつまんで解説すると以下のような流れになっています。

- NVCSIがCSIのシグナルを受ける
- パケット解析、ピクセルフォーマットの変換を通ってMIPI-CSI2パケットから利用するデータに変換される
- ATOMPが適切に解釈し、最終的にはVIメモリ(上図のEMC)に期待する並びで書き出す
- メモリに書き出されたら、一連のデータをフレームとしてV4L2に渡す

### イメージセンサの制御情報

先述の通り本来はセンサのデータシートを入手するのですが今回はありません。
実際のところIMX7XX番台は型番すら公開されておらず、噂ベースの情報だとスマホなどの特定ベンダー向けのカスタム製品のように思われます。詳細は追いきれませんでした。
もしもご存知の方がいたら教えてください。

しかたがないので制御情報は[RaspberryPi linuxのimx708の実装](https://github.com/raspberrypi/linux/blob/rpi-6.1.y/drivers/media/i2c/imx708.c)を参照することにします。
一応外形などなら[camera-module-3-product-brief.pdf](https://datasheets.raspberrypi.com/camera/camera-module-3-product-brief.pdf)に情報があります

## カメラモジュールの接続を確認する(I2C)

情報が整ったので実際に触っていきます。
まずはI2Cで接続確認をしましょう。

カメラの接続ですが、意図せぬ電流でカメラを壊さないためJetsonの電源offに加え、電源ケーブルを外してから行います。
開発者ボードは電源をつないだ時点でカメラコネクタに3.3Vが来ています。こういうのがあるので本来は回路図を見たり、テスターを当てておくのが良いです。
L4T35.3.1の不具合を避けるために**CAM0は避けて、CAM1を使ってください**。
linuxが起動したら`sudo apt install i2c-tools`でi2c-toolsを入れて`i2cdetect`でデバイスを見つけます。
RaspberryPiのIMX708デバイスツリー`linux/arch/arm/boot/dts/overlays/imx708.dtsi`を見ると`0x1a`というスレーブアドレスが書いてあるので、これが見つかるはずです!!

```sh
$ i2cdetect -y -r 10
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    
```

...見つかりませんね。

### 電源を入れる、もしくはXCLRを上げる

この原因はXCLR(≒ソフトリセットピン)がLowだからです。IMX219のデータシート`8-1 Power on sequence, p77`の項目を見ると、電源とクロックを印加した後にXCLRをHighにすることでセンサーが動作します。この資料をさらに読み込むと、正常に動作させるために電源やクロックをどのタイミングで入れるべきかが細かく書かれています。ところがRaspberryPiのモジュールは3.3Vしか入りません。これはモジュール上にレギュレーターやオシレータが実装されており、それらがデータシートのタイミングに従ってイメージセンサーに供給するようになっているからです。
なので、3.3Vを入れた時点で電源を入れる前提条件は整っていて、後はXCLRのピンを上げてやれば機能をするはずです。
そしてカメラコネクタの15pinを見てもらうとわかるのですが`CAM1_PWDN`という名前のピンが繋がっています。これが最終的にはXCLRに繋がっているはずなので上げましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92809/9eeeadeb-3c72-1a4e-1400-645bf4c80065.png)

#### GPIOをexport

`CAM1_PWDN` を制御する方法を探します。
pinmuxやTRMを追うべきですが、ここでは横着をしてIMX219のデバイスツリーを見てみましょう。
互換性があるっていいですね。

```hardware/nvidia/platform/t23x/p3768/kernel-dts/cvb/tegra234-p3768-camera-rbpcv2-imx219.dts
#define CAM1_PWDN	TEGRA234_MAIN_GPIO(AC, 0)
```

CAM1_PWDNはPort`AC`のPinOffset`0`のようですね。
これはtegra234でのポートの呼び方なので実際のGPIO numberにします。
[L4T35.3.1 DocumentsのIdentifying the GPIO Number](https://docs.nvidia.com/jetson/archives/r35.3.1/DeveloperGuide/text/HR/JetsonModuleAdaptationAndBringUp/JetsonOrinNxNanoSeries.html#identifying-the-gpio-number)に変換用のテーブルと計算式`base + port_offset + pin_offset`が書いてあります。

ベースの番号はJetsonで実際に見てみます。

```
# ls /sys/class/gpio
export  gpiochip316  gpiochip348  unexport
```

316はAON(AlwaysON)のものなので348がベースですね。`346 + 138 = 486`とわかります。
実際にexportすると`PAC.00`が見つかります。この名称ルールは`P<PortName>.<PinOffset>`なので、期待するピンがexportされたとわかります。

```
$ sudo -i
# echo 486 > /sys/class/gpio/export
# ls /sys/class/gpio
export  gpiochip316  gpiochip348  PAC.00  unexport
# echo 1 > /sys/class/gpio/PAC.00/value
```

`1`を書き込んでHighにすると

```
$ i2cdetect -y -r 10
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- 0c -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  
```

`0x1a`が見つかりました :tada: 
接続は問題なさそうですね。
他にいくつか反応があるのはオートフォーカスのものでしょうか。今回はカメラだけなので気にしないことにします。

## まとめ

今回は開発に必要な情報と参照先についてをまとめ、デバイスの接続を確認出来るところまで進めました。
ビデオフレーム図がわかればカメラについて出来ること出来ないことが考えられるようになるので、なぜ特定のフォーマットでしか撮らないのか、高解像度でFPSが下がるのかを理解できます。

Jetsonの実装についても少しだけ触れました。
ドライバがI2Cを握っていなければ今回のようにi2c-toolsを使って直接レジスタにアクセス出来ます。
もしも動かない(と思われる)デバイスや不明なデバイスがある場合は一度I2Cからアクセスしてみるといいかもしれません。

次回、ドライバでGPIOを操作してI2Cを触るところをやります。

次の記事: [Jetsonのカメラドライバを書く[I2C編]](https://qiita.com/uzuna/items/693cabb800173f2175f5)