はじめに
今回はカメラから画像を取得し、HDMI経由でディスプレイに映したいと思います。
なぜカメラ制御をやりたいかというと、、、流行りだから。(笑)
というのもありますが、やっぱりFPGA×画像処理(映像処理)は切っても切り離せない存在なのでぜひやりたいなと思い。あとそれに、Tang Nanoに公式のサンプル(カメラ制御は4kしかないが)が落ちているのでまだやりやすいかなと思いまして。なのでサンプルベースで今回は進めていきます。
https://github.com/sipeed/TangNano-4K-example
開発環境
評価ボード : Tang Nano 9k
カメラモジュール : OV7670
ディスプレイ :
IDE : Gowin EDA
言語 : SystemVerilog,Verilog
シミュレーションツール : Icarus Verilog(iverilog)
波形表示ツール : gtkwave v3.3.104
書き込みツール : openFPGALoader v0.10.0
ホストマシン : macOS(Ventura 13.3.1)
仮想マシン : VirtualBox7.0(Ubuntu 22.04.2)
OV7670とは
オムニビジョン・テクノロジーズ社のカメラモジュール。
ほんとはサンプルと同じくOV2640を購入しようと思ったが、
OV7670の方が安かった(500円ほど)のでこちらにしました。
安価なためぜんぜん高画質とかではないです。が、今回はカメラ性能云々はどっちでもいい(画像/映像処理ができればいい)のでこのモジュールを選定。
ということで、まずはこのカメラの仕組みを見ていこう。
データシートは以下。
http://web.mit.edu/6.111/www/f2016/tools/OV7670_2006.pdf
カメラ画像はHREFとVSYNCがアサートされている時にPCLKに合わせて画像データが送られてくる。
レジスタ制御はSCCB(I2C互換)インターフェースを使用して設定する。
ふむふむ。制御自体は特に難しくはないさそうだが、
レジスタ設定がいっぱいあって気が狂いそう、、、
まぁ、OV7670の情報はいっぱいあるので大丈夫か。
仕様
とりあえずカメラの使い方はわかったからまずは仕様を決めよう。
と言っても今回はサンプルベースなので大方既に決まっている。
まずはサンプルの構成を確認しよう。
サンプルの画像データ経路は
カメラ(OV2640)→VideoFrameBuffer(HyperRAM)→syn_gen→DVI_TX→ディスプレイ
となっている。なので基本はこの構成を使用する。
しかしそのままでは動かせないので、以下に変化点を記載する。
・画像データ
画像サイズ:640×480
フォーマット:RGB565(1画素16bit)30fps
システムクロック:25MHz、ピクセルクロック:25MHz
・カメラレジスタへの設定はSCCBインターフェースでアクセス
クロック:200KHz
・内部動作周波数はカメラクロックと同じく25MHz
・Tang Nano 9kにはHyperRAMが搭載されていないため、代わりにPSRAMを使用
設計
1.基本設計
ピン配置
ピン番号 | 信号名 | in/out | IO_TYPE |
---|---|---|---|
4 | RST_N | in | LVCMOS18 |
52 | CLK_27M | in | LVCMOS33 |
10 | LED[0] | out | - |
11 | LED[1] | out | - |
13 | LED[2] | out | - |
14 | LED[3] | out | - |
15 | LED[4] | out | - |
16 | LED[5] | out | - |
35 | XCLK | out | LVCMOS33 |
36 | PCLK | in | LVCMOS33 |
34 | SCL | out | LVCMOS33 |
33 | SDA | out | LVCMOS33 |
37 | VSYNC | in | LVCMOS33 |
38 | HREF | in | LVCMOS33 |
31 | PIXDATA[0] | in | LVCMOS33 |
30 | PIXDATA[1] | in | LVCMOS33 |
29 | PIXDATA[2] | in | LVCMOS33 |
28 | PIXDATA[3] | in | LVCMOS33 |
27 | PIXDATA[4] | in | LVCMOS33 |
26 | PIXDATA[5] | in | LVCMOS33 |
25 | PIXDATA[6] | in | LVCMOS33 |
32 | PIXDATA[7] | in | LVCMOS33 |
69,68 | O_tmds_clk_p/n | out | - |
71,70 | O_tmds_data_p/n[0] | out | - |
73,72 | O_tmds_data_p/n[1] | out | - |
75,74 | O_tmds_data_p/n[2] | out | - |
2.詳細設計
クロック系統
■rPLL1
・27MHzを入力とし、126MHzを出力する。※125MHzが生成できなかったため126MHzにしました
・pllの出力が安定後に動作して欲しいため、lock信号解除を全体ののリセット信号として使用する。
■rPLL2
・27MHzを入力とし、168MHzを出力する。
・pllの出力が安定後に動作して欲しいため、lock信号解除を全体ののリセット信号として使用する。
■clkdiv1
・126MHzを入力とし、5分周した25MHzを出力する。※正確には25.2MHzです
SCCB I/F
■sccb_top
・sccbのtopモジュール。
■sccb_if
・今回は映像出力をメインでやりたかったので、レジスタ制御部分は他の方のモジュール
を使用しました。ということで、ベースは@kkumt9993さんのモジュールで作成。
詳細はそちらの記事を参照してください。
https://qiita.com/kkumt9993/items/096a497419db4efe2784
そのため変更点のみを記載する。
※ゆくゆくはカメラモジュールからのレジスタリードもしたいなぁ、、、
★変更点
・init_doneを追加。レジスタ初期化が完了したことを通知する信号。
・リセットがかかると再度レジスタ設定を実施するように修正。
・ステートマシンをcase文に変更。(可読性向上のため)
■sccb_rom
・OV7670のレジスタ設定値。
ここの設定値が難しい、、、
カメラ系統
■camera_top
・カメラモジュールのtop。
・カメラからの画像データを使用するか、パターンジェネレーターからの
データを使用するかのセレクタ回路を保持する。
■camera_if
・カメラモジュールから25MHzごとに画像データを受け取る。
※RGB565の場合2クロックで1画素となる。
■camera_pg
・ダミー画像データ生成モジュール。サンプルをそのまま使用しているため説明を省く。
■Video_Frame_Buffer_Top
・GowinのIPで画像データをメモリへバッファリングするためのアービターモジュール。
・サンプルではメモリにHyperRAMを使用していたが、Tang Nano9kにはHyperSRAMは
ないため代わりにPSRAMを使用する。
・設定はgitのipcファイルを確認してください
■PSRAM_Memory_Interface_HS_Top
・PSRAMのインターフェースモジュール
・設定はgitのipcファイルを確認してください
■syn_gen
・ビデオ出力の同期信号生成モジュール
・640×480の同期信号のタイミングは以下のサイトを参照
http://tinyvga.com/vga-timing
■DVI_TX_Top
・DVIのインターフェースモジュール
・設定はgitのipcファイルを確認してください
・IOタイプはELVDSを設定する。TLVDSだと以下のエラーが出てくる。なにが違うんだろう?
Conflicting multiple constraints specified for location of Instance 'TMDS_CLK_P_O'(type: TLVDS_OBUF); Or constrained location for the Instance is not available; Or constrained location type is not matched with the instance
■デバッグ用
・LEDをデバッグ用に用意
・不論理動作
・用途
0:レジスタ設定完了、1〜5:未使用
3.実装
ソースコードは以下を参照。
https://github.com/kiiisy/camera
注意点
・PSRAM_Memory_Interface_HS_Topについて
- 外端子(O_psram_***)どこに繋ぐのか問題
初見だとどこに繋ぐのだろうか?って思った。PSRAMは内蔵って載ってあるしなと思い。
で、サンプル見ると外部端子で出しているだけでどこにも繋いでないっぽい。そ、そうなんだ、、、
※外部端子というのはtop階層のin/outのこと - 外端子(O_psram_***)の信号名が決められている問題
なんとですね、外端子の信号名が決められているのですよね。(個人的に外部端子は大文字にしたいのだが、、、)
正:モジュール端子名と同じ名前
例)O_psram_ck,IO_psram_dqなど
誤:外端子と異なる名前
例)O_PSRAM_CK,IO_PSRAM_DQなど
信号名が異なると以下のエラーが出てきます。
Running placement......
ERROR (PR2067) : Instance 'U_PSRAM_Memory_Interface_HS_Top/u_psram_top/u_psram_wd/rwds_iobuf_gen[0].rwds_iobuf' in hclk tree must have constraint
ERROR (PR2067) : Instance 'U_PSRAM_Memory_Interface_HS_Top/u_psram_top/u_psram_wd/cs_iobuf_gen[0].cs_obuf' in hclk tree must have constraint
4.Synthesis/Implementation
じゃあ実装も終わったので、次に進みましょう!
ということで、次はcst(物理制約)ファイルと、sdc(タイミング制約)ファイルを作成しましょう。
中身は、上記のgithubに登録しているのでそちらを確認ください。
じゃあまずはcstファイルから
といってもcstファイルは特に説明することがない(好きな場所にピン配すればよき)ので次で。
ただし、DVIは端子が固定なのでそこだけ注意が必要です。
次にsdcファイル
基本的にはcreate_clockで定義していくですが、今回は新たに定義を追加しています。
記載している定義について説明しますね。※間違っていたらすみません
create_clock:PLLから生成又は外部クロックが対象。
create_generated_clock:派生クロックで主にclkdivなどで生成したクロックが対象。
set_false_path:タイミング解析から指定したクロックなどを対象外にする定義。
ということで、sdc/cstファイルが用意できたらPlace &Routeしていきましょう!
実施すると以下のWARNがでてきた、、、え
WARN (TA1117) : Can't calculate clocks' relationship between: "U_sccb_top/clk_200k" and "U_clkdiv1/clkdiv_inst/CLKOUT.default_gen_clk"
WARN (TA1117) : Can't calculate clocks' relationship between: "CLK_27M" and "clk_84m"
WARN (TA1117) : Can't calculate clocks' relationship between: "CLK_27M" and "clk_168m"
WARN (PR1014) : Generic routing resource will be used to clock signal 'PCLK_d' by the specified constraint. And then it may lead to the excessive delay or skew
クロックの関係が計算できないやつと、遅延とかスキューが発生するかもよのやつ。
サンプルベースでやったのになぁ。じゃあサンプルはどうなってるんやと思い見てみるとサンプルでもこれが出ているままだった、、、
特に調べても出てこなかったのでPR1014については無視で、TA1117の「"CLK_27M" and...」のやつはクロック同士関係がないのでfalse_pathで回避。残りのTA1117は解決策がわからなかったのでこれも無視した。
(まぁ趣味だし多少ゆるくてもいいかと)
無事、合成も通ったのでOKと言いたいところだが、嫌なものを見てしまった。
タイミング解析がめっちゃ赤い、、、
内容確認してみると、言われている箇所はVideoFrameBufferやPSRAM
の内部のことを言われている。
ここはGowinによって中身が暗号化されているのでユーザーからは見えない仕組み
になっているからここで言われても知らんしって言いたい。だからこれも無視する。
シミュレーション
シミュレーションをやろうと思ったのですが、一部のモジュールしかやってません。
というのもですね、Video_Frame_BufferやPSRAMなどはIPが暗号化されていて実施できないんですよね、、、なので波形見れるものだけ見ました。
実施モジュール:camera_top,sccb_top,syn_gen
未実施モジュール:Video_Frame_Buffer_Top,PSRAM_Memory_Interface_HS_Top,DVI_TX_Top
※gitのsimフォルダに入れています
実機確認
色味はなんか微妙です、、、
レジスタ設定をうまいことやらないと綺麗な絵にはなりませんね。
最後に
とりあえずこれで最低限のカメラからの映像出力はできましたのでよかったです。
今回はすごい楽をしたのでまだまだやりたいことがあります。
・RAWデータで受け取り、デモザイク処理や補正処理などの画像処理をやりたい
・PCからシリアルでレジスタ設定したい
・VideoFrameBufferを使用せずに自分のモジュールで制御したい
などなど色々あります。
なので、この辺はおいおいやっていこうかと思います。