01signal.com

OV7670 camera sensor と Smart Zynqからのライブビューとビデオキャプチャ

この Web ページは、 Smart Zynq boardの機能を探求する小規模プロジェクトのグループに属しています。

このプロジェクトは HelloFPGAでも公開されており、中国の読者におすすめです。

序章

このチュートリアルでは、 OV7670 camera module を Smart Zynq に接続し、ボード自体の HDMI outputでライブ ビデオ信号を表示する方法を説明します。また、簡単な Linux コマンドを使用して、 raw video stream をファイルに簡単に保存する方法も示します。

このチュートリアルの情報は、他のタイプの data acquisition にも当てはまります。 以下に示す手法は、 image data の他のソースやデジタル データの他のソースでも使用できます。

camera module は、 Zynq chipの PL (FPGA) 部分に接続されています。したがって、 image data を ARM processorに送信する前に、 image processing を実装する logic を追加するのは簡単です。このチュートリアルは Xillinuxに基づいているため、システムの processor 部分は完全な Linux distributionで構成されます。

OV7670 module がこのデモ用に選択されたのは、このハードウェアが安価で人気があり、購入しやすいためです。さらに、このコンポーネントが生成するデジタル信号は理解しやすいものです。

ただし、 OV7670 には残念な欠陥があります。 デフォルトでは、ビデオ ストリームの色は正しくありません。これは、このカメラ センサーの既知の問題です。このカメラの registers を少数変更することで、この欠陥を修正することが可能です。したがって、このチュートリアルは 2 つの部分に分かれています。

このチュートリアルの大部分は、実装がどのように機能するかを説明していることに注意してください。カメラを使用するために、これらの説明を理解する必要はありません。

OV7670 module

このチュートリアルは、以下の図に示すカメラ モジュールに基づいています。

The OV7670 camera sensor module used in this project

このモジュールでは、 pins または pin header のほとんどが OV7670 コンポーネントに直接接続されています。 3.3V と GND のみが voltage regulatorsに接続されます。したがって、 FPGA とモジュール間のすべての接続は、 FPGA と OV7670 コンポーネント間の直接接続になります。

市場には、同じ機能を持つ他のモジュールがあります。これらの他のモジュールも使用してもおそらく問題ありません。たとえば、 PCBに「2017/3/15」と書かれた別のモジュールがあります。このモジュールも正常に動作します。一方、正常に動作しないモジュールがあります。動作しないモジュールは、 PCBに「QYF-OV7670 V3.0」と書かれています。

もう一つ注意すべき点は、 OV7670 コンポーネントにはさまざまなリビジョンがあることです。モジュールで正しいリビジョンが使用されているかどうかを確認できます。その方法については、このチュートリアルのパート 2で説明します。

OV7670に関する主な情報源は 2 つあります。これら 2 つのドキュメントはインターネット上で見つけることができます。

Vivado プロジェクトの準備

demo bundleの zip ファイル ( boot partition kit) から新しい Vivado プロジェクトを作成します。テキスト エディタで verilog/src/xillydemo.v を開きます。コードの「PART 2」というラベルの付いた部分を削除します。その部分の代わりに、次のコード スニペットを挿入します。

/*
    * PART 2
    * ======
    *
    * This code demonstrates a frame grabber (data acquisition) from
    * an OV7670 camera module.
    *
    */

   reg [1:0]  clkdiv;

   always @(posedge bus_clk)
     clkdiv <= clkdiv + 1;

   assign J6[10] = clkdiv[1]; // MCLK / XCLK

   assign J6[0] = 0; // PWDN, the camera is always on
   assign J6[1] = !user_w_write_32_open; // RESET#, active low

   wire [7:0] D_in;
   wire       pclk_in, hsync_in, vsync_in;

   assign D_in = J6[9:2];
   assign pclk_in = J6[11];
   assign hsync_in = J6[12];
   assign vsync_in = J6[13];

   (* IOB = "TRUE" *) reg [7:0] D_guard;
   (* IOB = "TRUE" *) reg       pclk_guard, hsync_guard, vsync_guard;

   reg [7:0]  D;
   reg 	      pclk, hsync, vsync;

   always @(posedge bus_clk)
     begin
	// Metastability guards on asynchronous inputs
	D_guard <= D_in;
	pclk_guard <= pclk_in;
	hsync_guard <= hsync_in;
	vsync_guard <= vsync_in;

	D <= D_guard;
	pclk <= pclk_guard;
	hsync <= hsync_guard;
	vsync <= vsync_guard;
     end

   wire       sample_valid;
   reg 	      previous_pclk;

   always @(posedge bus_clk)
     previous_pclk <= pclk;

   assign sample_valid = pclk && !previous_pclk;

   // wait_for_frame's purpose is to start getting data from the camera
   // at the beginning of a frame.
   reg 	      wait_for_frame;

   always @(posedge bus_clk)
     if (!user_r_read_32_open)
       wait_for_frame <= 1;
     else if (sample_valid && vsync)
       wait_for_frame <= 0;

   // fifo_has_been_full changes to '1' when the FIFO becomes full, so
   // that the data acquisition stops and an EOF is sent to the host.
   // This ensures that the data that arrives to the host is contiguous.

   reg 	      fifo_has_been_nonfull, fifo_has_been_full;
   wire       fifo_full;

   always @(posedge bus_clk)
     begin
	if (!fifo_full)
	  fifo_has_been_nonfull <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_nonfull <= 0;

	if (fifo_full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_full <= 0;
     end

   assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;

   // This part writes pixels from the camera to the FIFO

   reg 	      fifo_wr_en;
   reg [1:0]  byte_position;
   reg [31:0] dataword;

   always @(posedge bus_clk)
     if (wait_for_frame)
       begin
	  byte_position <= 0;
	  fifo_wr_en <= 0;
       end
     else if (sample_valid && hsync)
       begin
	  case (byte_position)
	    0: dataword[7:0] <= D;
	    1: dataword[15:8] <= D;
	    2: dataword[23:16] <= D;
	    3: dataword[31:24] <= D;
	  endcase

	  if (byte_position == 3)
	    fifo_wr_en <= !fifo_has_been_full;
	  else
	    fifo_wr_en <= 0;

	  byte_position <= byte_position + 1;
       end
     else
       fifo_wr_en <= 0;

   fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      .srst(!user_r_read_32_open),

      .din(dataword),
      .wr_en(fifo_wr_en),
      .full(fifo_full),

      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty)
      );

あるいは、この変更後の xillydemo.v をここからダウンロードすることもできます。

この変更を加えた後、通常どおり bitstream ファイルを作成します。この Verilog コードがどのように機能するかについては、このページの以下で詳しく説明します。

カメラモジュールの接続

短い Dupont ジャンパー ワイヤを使用して、カメラ モジュールと Smart Zynq ボードを接続できます。ワイヤの長さは 10 cm 以下である必要があります。最適な長さは 5 cmです。ワイヤーが長い場合、 crosstalkによってデジタル信号の品質が損なわれる可能性があります。 horizontal sync で過度のノイズが発生すると、ビデオ画像に緑と紫の縞模様が飛び出す原因になります。

ワイヤの長さが 10 cmの場合、 OV7670の I/O driver currentを短くするには register を変更する必要がある場合があります。このチュートリアルの後半では、この変更を行う方法を示します。

これは、 Smart Zynq SP ボードに接続された OV7670 モジュールの写真です。

OV7670 module connected to the Smart Zynq board

下の写真は反対方向から撮った写真です。左上の小さい画像は、 pin header の最後の pin が何にも接続されていないことを強調しています。

OV7670 module connected to the Smart Zynq board

上の画像は、ワイヤーの接続方法を示しています。 まず、 Smart Zynq ボードの裏側にある「Bank 33 VCCIO Vadj」と書かれている場所を探します。このマークに近い pins の行が、これから扱う pin header です。 HDMI コネクタに近い pin header です。

カメラセンサーと Smart Zynq ボードの間には16本のワイヤーが並列接続されています。 3.3V と GND のみ pin header上の別の場所に接続されています。これら 2 本のワイヤは短い必要はありません。

pin headerの最後の pin は 5Vであるため、この pinにはワイヤを接続しないでください。

カメラモジュールと Smart Zynq、 pin header間の配線仕様です。この情報は上の画像からも推測できます。

Pin header 1 3 5 7 9 11 13 15 35
Module pin PWDN D0 D2 D4 D6 MCLK HS SDA GND
Module pin RST D1 D3 D5 D7 PCLK VS SCL 3.3V
Pin header 2 4 6 8 10 12 14 16 37

繰り返しになりますが、 3.3V と GNDの接続には十分注意してください。これら 2 本のワイヤを正しく接続しないと、カメラ モジュールが破損する可能性があります。

Frame grabbing

更新された xillydemo.v に基づく bitstream ファイルを使用して Xillinux を起動します (上記を参照)。

このコマンド ( shell prompt) は、カメラの出力から短いビデオ クリップを作成します。

# cat /dev/xillybus_read_32 > clip.raw

このコマンドは数秒間実行されてから停止します。これは、ビデオ ストリームの data rate が SD cardのデータ書き込み速度よりも高速であるためです。これにより、 overflow が発生し、データ フローが停止します。この動作の背後にあるメカニズムを以下で説明します。

次のコマンドを使用して、このビデオ クリップを再生できます。

# mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400:fps=31.25 clip.raw

このコマンドが Xillinuxのグラフィカル デスクトップ内にある terminal window で使用される場合、ビデオは Xillinuxのグラフィカル インターフェイスで再生されます。

別のページで説明されている手法を使用して、別のコンピュータの画面でビデオを再生することもできます。たとえば、他のコンピュータの IP address が 192.168.1.11の場合、次のようにコマンドを変更します。

# DISPLAY=192.168.1.11:0 mplayer -demuxer rawvideo ...

すでに述べたように、ビデオ クリップの色が正しくありません。次のページでは、この問題を修正する方法について説明します。

このコマンドは、1 つの video frame を frame.rawという名前のファイルに読み取ります。

# dd if=/dev/xillybus_read_32 of=frame.raw bs=614400 iflag=fullblock count=1

raw frame のフォーマットは UYVY 4:2:2です。つまり、各画素は 16 bitsで構成されています。最初のバイトは、最初のピクセルの U コンポーネント (または Cb) です。次のバイトは、同じピクセルの Y コンポーネントです。 3 番目と 4 番目のバイトは、2 番目のピクセル (Cr および Y) の V および Y コンポーネントです。

このファイルは、次のコマンドを使用して PNG ファイルに変換できます。

# convert -size 640x480 pal:frame.raw frame.png

画像を表示するための簡単なツールがあります。

# display frame.png &

Live view

カメラの live view を取得するには、以下を含む liveview.sh という名前のファイルを作成します。

#!/bin/bash

while [ 1 ] ; do
  dd if=/dev/xillybus_read_32 bs=614400 iflag=fullblock count=1 2>/dev/null
done | mplayer -demuxer rawvideo -rawvideo w=640:h=480:format=uyvy:size=614400 -

この script を次のように実行します

# bash liveview.sh

なぜこの script が必要なのでしょうか? mplayer は遅すぎるため、 device file からデータを直接読み取ることは不可能です。言い換えれば、 Zynqの ARM processor は、ビデオを正しいフレーム レート (31.25 fps) で再生できるほど強力ではありません。それを試してみると、ビデオは短時間しか再生されません。 FPGA内に overflow があるとデータ フローが停止します。

この script は、毎回 /dev/xillybus_read_32 から raw frame を読み取る無限ループに基づいています。これは、以前に 1 つのフレームを frame.rawに読み込むために使用されたコマンドと同じです。ただし、今回は、 dd用の出力ファイルは定義されていません。したがって、 dd は代わりにデータを standard output に書き込みます。

この無限ループの結果は、 pipe によって mplayerの standard input にリダイレクトされます (ループの終わりの「|」に注意してください)。 mplayer は、 standard inputから届いた映像データを再生します。

dd は常に完全なビデオ フレームを読み取るため、 script は overflow の問題を解決します。 mplayer がさらにデータを受け入れる準備ができていない場合、 pipe のデータ フローは一時的に停止します。その結果、 dd はカメラセンサーから video frames をスキップします。したがって、画面に表示されるフレーム レートはカメラのフレーム レートよりも低くなります。より正確には、表示されているフレーム レートは、 mplayer が表示できる最大フレーム レートです。

mplayer自体の bufferingのせいで、画面に表示されるビデオ画像がわずかに遅れます。低い latencyを実現するには、 bufferを追加せずに画面に画像を表示する簡単なプログラムを作成する必要があります。

mplayer は強力なメディア プレーヤーです。たとえば、間違った色が煩わしい場合は、ビデオ クリップを白黒ビデオとして再生することができます。 saturation をゼロにするには、次の部分をコマンドに追加します。

# mplayer -saturation -100 -demuxer rawvideo ...

このページの実践的な部分は終わりです

このページの残りの部分では、 data acquisitionを実行する logic の実装について説明します。実践的なトピックのみに興味がある場合は、このチュートリアルの次の部分を読み続けてください。

FPGA と host間の通信

この例の logic は Xillybus IP coreに基づいています。この IP core は hostとの通信を担当します。

上記の、 Verilog コードで置き換えられた部分が次で終わっていることを思い出してください。

fifo_32x512 fifo_32
     (
      .clk(bus_clk),
      .srst(!user_r_read_32_open),

      .din(dataword),
      .wr_en(fifo_wr_en),
      .full(fifo_full),

      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty)
      );

これは標準の FIFOの instantiation です。この FIFO には、 FIFOからのデータの読み取りを目的とした 3 つの ports があります。 rd_en、 dout 、 empty。これらの ports は Xillybus IP coreに接続されます。これにより、 IP core が FIFO からデータを読み取り、このデータを hostに送信できるようになります。その結果、 FIFO に書き込まれたすべての内容が、 /dev/xillybus_read_32という名前の device file に到達します。つまり、 host 上の通常のコンピュータ プログラムは、 /dev/xillybus_read_32 を通常のファイルとして開くことができます。プログラムがこのファイルから読み取るとき、 FPGA内の application logic によって FIFO に書き込まれたデータを受け取ります。

FIFO には、データの書き込みを目的とした 3 つの ports があります。 wr_en、 din 、 full。これらの ports は、カメラ センサーからピクセル データを収集する logic に接続されています。このページの次のセクションでは、この logic がどのように機能するかを説明します。現時点では、 logic が @dataword と @fifo_wr_enの助けを借りてピクセル データを FIFO に書き込むことだけを指摘します。この時点から、このデータを host上で実行されるコンピュータ プログラムに取り込むのが Xillybus IP coreの役割になります。これが、このコマンド (前述) がこのデータをファイルに書き込む理由です。

# cat /dev/xillybus_read_32 > clip.raw

FIFO の仕組みに関する一般的な説明については、このページを参照してください。

データ フローは次の図で要約できます。

Simplified diagram of data acquisition with Xillybus

この Web サイトには Xillybusに関するセクションがあり、このセクションには data acquisitionについて説明するページがあります。そのページを読んでみると役に立つかもしれません。

user_r_read_32_open は FIFOの srst portに接続されていることに注意してください。 /dev/xillybus_read_32 を host上で開くと、この signal が High に変わります。 signal は NOTに接続されているため、 device file が閉じているとき、 FIFO は reset 状態に保持されます。これにより、ファイルを閉じるたびに、 FIFO 内のすべてのデータが確実に削除されます。

カメラセンサーとのインターフェース

次に、上記の Verilog コードの先頭を見てみましょう。

reg [1:0]  clkdiv;

   always @(posedge bus_clk)
     clkdiv <= clkdiv + 1;

   assign J6[10] = clkdiv[1]; // MCLK / XCLK

@bus_clkの周波数は 100 MHzです。この clock は、 @clkdivの助けを借りて 4 で割られます。したがって、カメラ モジュールは 25 MHz reference clockを受け取ります。カメラの datasheetによると、これは許容される周波数です。ただし、カメラは、 reference clockの周波数が 24 MHzのときに 30 fps ビデオ ストリームを生成するように設計されています。したがって、実際のビデオ フレーム レートはわずかに高くなります。 31.25 fps。

これは通常、 clockを作成するための間違った方法であることに注意してください。正しい方法は、 PLL または同様のリソースを使用することです。 @clkdiv は output signalを作成するためにのみ使用されるため、この特定のケースではこの方法に問題はありません。 FPGA自体の logic はこの信号を使用しません。

Verilog コードの次の部分は次のとおりです。

assign J6[0] = 0; // PWDN, the camera is always on
   assign J6[1] = !user_w_write_32_open; // RESET#, active low

J6[0] はカメラモジュールの PWDN pinに接続されています。カメラの電源がオフになることはありません。

J6[1] はカメラの RESET#に接続されています。この pin が Low になると、カメラがリセットされます。 host上のプログラムによって /dev/xillybus_write_32 が開かれると、 @user_w_write_32_open は High になります。通常、 @user_w_write_32_open がローであり、その結果 J6[1] がハイになるため、カメラはリセットされません。この配置により、次のコマンドでカメラをリセットできます。

# echo 1 > /dev/xillybus_write_32

このコマンドは device file を短時間開きます。これにより、望ましい結果が得られます。

ここまで、 FPGA からカメラセンサーへの信号がどのように生成されるかを示しました。次に、カメラセンサーから FPGAへの信号について説明します。

OV7670 は、 FPGAから reference clock と同じ周波数を持つ pixel clock を生成します。つまり、 PCLKの周波数は 25 MHzになります。この信号は Verilog コードで @pclk_in に接続されます。

カメラ センサーは、ビデオ データを含む 3 つの信号も生成します。 Verilog コード内のこれらの信号の名前は、 @D_in 、 @hsync_in 、および @vsync_inです。カメラ センサーは、 @pclk_in が High から Low (falling edge) に変化すると同時に、これらの信号の値を変化させます。より正確には、 @D_in 、 @hsync_in 、および @vsync_in の変更は、 @pclk_inの falling edge に合わせて行われます。 FPGAの観点からは、これは source synchronous inputと呼ばれます。

次に、 Verilog コードの関連部分を見てみましょう。

wire [7:0] D_in;
   wire       pclk_in, hsync_in, vsync_in;

   assign D_in = J6[9:2];
   assign pclk_in = J6[11];
   assign hsync_in = J6[12];
   assign vsync_in = J6[13];

   (* IOB = "TRUE" *) reg [7:0] D_guard;
   (* IOB = "TRUE" *) reg       pclk_guard, hsync_guard, vsync_guard;

   reg [7:0]  D;
   reg 	      pclk, hsync, vsync;

   always @(posedge bus_clk)
     begin
	// Metastability guards on asynchronous inputs
	D_guard <= D_in;
	pclk_guard <= pclk_in;
	hsync_guard <= hsync_in;
	vsync_guard <= vsync_in;

	D <= D_guard;
	pclk <= pclk_guard;
	hsync <= hsync_guard;
	vsync <= vsync_guard;
     end

カメラ センサーからの信号はすべて @bus_clkを利用してサンプリングされることに注意してください。カメラの PCLK も他の信号と同じ方法でサンプリングされます。つまり、 PCLK は clockとしてではなく、 data signalとして扱われます。このテクニックについては以下で簡単に説明します。

また、 @bus_clk とカメラ センサーの信号間のタイミング関係が不明であることにも注意してください。したがって、これらの信号を受信する flip-flops の出力は信頼できません。 これらの flip-flopsの timing requirements を保証することは不可能であるため、短期間不安定になる可能性があります。これは、 clock domain crossingに関連する既知の問題です。

この問題の解決策については、別のページで説明します。 Metastability guards。これは、互いに直列に接続された 2 つの flip-flops があることを意味します。最初の flip-flop (@pclk_guardなど) は外部信号に接続されます。 2 番目の flip-flop は 1 番目の flip-flopに接続されています。したがって、最初の flip-flop が一時的に不安定になったとしても、2 番目の flip-flopの timing requirements は保証されます。したがって、2 番目の flip-flopの出力は信頼性があります。

結論は: @D、 @pclk、 @hsync 、および @vsync は、信頼性の高い registers ( @bus_clkと同期) です。

@bus_clkの周波数は 100 MHzであることを思い出してください。一方、 PCLKの周波数は 25 MHzです。したがって、 @D、 @hsync 、および @vsync の値は、4 つの clock cyclesごとに 1 回だけ消費される必要があります。しかし、4 つの clock cycle のうちどれでしょうか?

答えは、 Verilog コードの次の行にあります。

wire       sample_valid;
   reg 	      previous_pclk;

   always @(posedge bus_clk)
     previous_pclk <= pclk;

   assign sample_valid = pclk && !previous_pclk;

このコード スニペットは単純に次のことを意味します。 現在 @pclk が高く、以前の clock cycleでは低かった場合は、 @D、 @hsync 、および @vsyncの値を使用します。カメラの PCLK が高から低に変化すると、カメラ センサーの信号の値が変化することを思い出してください。したがって、 PCLK がローからハイに変化するとき、他の信号は安定します。

ただし、 @pclk、 @D、 @hsync 、および @vsync は registersです。これらの registers は @bus_clkと同期しており、特定の時間におけるカメラ センサー信号のスナップショットを表します。 PCLK 自体で rising edge を検出する代わりに、 logic は @pclkと同様のことを行います。 @pclk の値が低値から高値に変化したときが、他の registersの値を使用する適切なタイミングです。

この技術は 01-signal samplingと呼ばれます。この手法の背後にある考え方は、 01-signal samplingに関する別のページで詳しく説明されています。このページでは、この方法がどのように FPGAの timing requirementsを保証するかについても説明しています。これにより、 logic は @D、 @hsync 、および @vsync の値が正しいことを保証します。

データフローの開始と停止

ここでは、 hostへのデータ送信を防ぐことを目的とした 2 つの registers を見ていきます。

ここでは、これら 2 つの registers についてそれぞれ詳しく説明します。まず、 @wait_for_frame:

reg 	      wait_for_frame;

   always @(posedge bus_clk)
     if (!user_r_read_32_open)
       wait_for_frame <= 1;
     else if (sample_valid && vsync)
       wait_for_frame <= 0;

device file が開いていない場合、@wait_for_frameの値は高くなります。この register の値は、カメラセンサーの vsync 信号に応答して Low に変化します。この信号は、 frames間の期間中 High になります。つまり、 @vsync が High の場合、カメラからピクセル データは送信されません。

結論として、カメラからのピクセル データを無視する必要がある場合、 @wait_for_frame はハイになります。 device file が閉じている場合、または device file が最近開かれたが、カメラがまだ frameの中央にある場合。

ここからは @fifo_has_been_fullの説明に移ります。 host に到着するデータがカメラ センサーが生成するデータと同じであることを確認することが重要です。ただし、コンピューター プログラムが device file からデータを十分に速く読み込まない場合、 overflow が FIFO で発生する可能性があります。 DMA buffers は最終的にいっぱいになるため、 FIFO のコンテンツをコピーする場所がなくなります。その結果、 Xillybus IP core は FIFOからデータを読み取ることができなくなります。そうなると、 FIFO の容量がいっぱいになってしまい、新しいデータを書き込むことができなくなります。

logic はこの状況を防ぐために何もできません。ただし、 logic は、 host に到着するデータが連続していることを保証できます。 FIFO がいっぱいになると、 logic は FIFOへのデータの書き込みを停止します。さらに、 FIFO がいっぱいになった後に FIFO が空になると、 logic は EOF を hostに送信するように要求します。その結果、コンピューター プログラムは、 FIFO がいっぱいになる前に FIFO に書き込まれたすべてのデータを受信します。そのデータの後、コンピュータは EOFを受信します。これは、通常のファイルの終わりに達したときに起こることと同じです。

このメカニズムにより、コンピューター プログラムは、到着したデータが正しく連続していることを信頼できるようになります。連続性が失われると、 EOF はコンピュータ プログラムに device fileを強制的に閉じます。プログラムが device file を再度開くと、 @wait_for_frameのおかげでデータは新しいフレームから開始されます。

これは、 Verilog コードの関連部分です。

reg 	      fifo_has_been_nonfull, fifo_has_been_full;
   wire       fifo_full;

   always @(posedge bus_clk)
     begin
	if (!fifo_full)
	  fifo_has_been_nonfull <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_nonfull <= 0;

	if (fifo_full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (!user_r_read_32_open)
	  fifo_has_been_full <= 0;
     end

   assign user_r_read_32_eof = fifo_has_been_full && user_r_read_32_empty;

FIFO がいっぱいになると、@fifo_has_been_full は High になります。 device file が開いていない場合、この register は Low に変化します。 @fifo_full と @fifo_has_been_nonfull の両方が High の場合、 @fifo_has_been_full は High に変化します。

@fifo_full は FIFOの "full" portに接続されています。しかし、なぜ @fifo_has_been_nonfull が必要なのでしょうか?その理由は、 FIFO が reset 状態に保たれる限り、 FIFO は「full」をハイに維持することが多いためです。この機能の目的は、 FIFO がまだデータを受信する準備ができていないことを application logic に伝えることです。 @fifo_has_been_nonfull の目的は、このシナリオで @fifo_has_been_full が誤ってハイになるのを防ぐことです。

@fifo_has_been_full と @user_r_read_32_empty の両方が High の場合、@user_r_read_32_eof は High になります。言い換えれば、 FIFO が過去にいっぱいで、現在は空の場合、 EOF が host に送信されます。この状況では、いずれにしても新しいデータは FIFO に書き込まれないことに注意してください。

データの連続性を確保するための同様のソリューションについては、別のページで説明しています。このページに記載されている解決策は、 FIFO の両側が異なる clock domainsに属している場合に必要です。このページに示されているコードでは、 FIFO は 1 台の clockとのみ同期しています。したがって、このページでは @fifo_has_been_full の実装がより簡単になります。

FIFOへのデータの書き込み

Verilog コードの次の部分では、ピクセル データを FIFOに書き込みます。

reg 	      fifo_wr_en;
   reg [1:0]  byte_position;
   reg [31:0] dataword;

   always @(posedge bus_clk)
     if (wait_for_frame)
       begin
	  byte_position <= 0;
	  fifo_wr_en <= 0;
       end
     else if (sample_valid && hsync)
       begin
	  case (byte_position)
	    0: dataword[7:0] <= D;
	    1: dataword[15:8] <= D;
	    2: dataword[23:16] <= D;
	    3: dataword[31:24] <= D;
	  endcase

	  if (byte_position == 3)
	    fifo_wr_en <= !fifo_has_been_full;
	  else
	    fifo_wr_en <= 0;

	  byte_position <= byte_position + 1;
       end
     else
       fifo_wr_en <= 0;

カメラ センサーからのピクセル データは、8 ビット幅のデータ要素として到着します。 logic のこの部分は、データを FIFOに書き込むことができるように、これらのデータ要素を 32 ビットに再編成します。 8-bit Xillybus stream (/dev/xillybus_write_8) は、次の 2 つの理由により、この目的には使用されません。

@wait_for_frame が High の場合、次の 2 つの可能性のいずれかにより、 FIFO には何も書き込まれません。 device file はオープンしていないか、 device file はオープンしているが、新しい frame の始まりにはまだ達していません。

カメラ センサーの HSYNC が高い場合、 data signals に有効なピクセルが含まれていることを意味します。式「sample_valid && hsync」の値は、次の 2 つの基準を組み合わせたものです。 @sample_valid が High の場合、 @hsync および @D には有効な値が含まれます。したがって、 @hsync が High の場合、 @D の値が @datawordの一部にコピーされます。また、 @D が @dataword の最後の部分にコピーされると (つまり、 @byte_position が 3に等しい)、 @fifo_wr_en は High になります。その結果、 @dataword が FIFOに書き込まれます。より正確には、 @fifo_wr_en の式は次のようになります。

fifo_wr_en <= !fifo_has_been_full;

したがって、 @fifo_has_been_full が High の場合、前述したように FIFOには何も書き込まれません。

Verilog コードと実際の pinsの関係

上記の Verilog コードでは J6という名前の inout port を使用していますが、この port との接続はどのようにして pin headerに到達するのでしょうか?答えは xillydemo.xdcにあります。このファイルは、 bitstream を作成する Vivado プロジェクトの一部です (「vivado-essentials」ディレクトリ内)。

xillydemo.xdc には、 FPGA が電子部品として正常に動作するために必要なさまざまな情報が含まれています。特に、このファイルには次の行が含まれています。

[ ... ]

## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22  [get_ports {J6[0]}];   #J6/1  = IO_B33_LN2
set_property PACKAGE_PIN T22  [get_ports {J6[1]}];   #J6/2  = IO_B33_LP2
set_property PACKAGE_PIN W22  [get_ports {J6[2]}];   #J6/3  = IO_B33_LN3
set_property PACKAGE_PIN V22  [get_ports {J6[3]}];   #J6/4  = IO_B33_LP3
set_property PACKAGE_PIN Y21  [get_ports {J6[4]}];   #J6/5  = IO_B33_LN9
set_property PACKAGE_PIN Y20  [get_ports {J6[5]}];   #J6/6  = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}];   #J6/7  = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}];   #J6/8  = IO_B33_LP7

[ ... ]

最初の行は、信号 J6[0] を U22に接続する必要があることを示しています。これは、 FPGAの物理パッケージ上の位置です。 Smart Zynqの schematicsによると、この FPGA pin は pin headerの最初の pin に接続されています。もう一方の ports の位置も同様に定義されます。

結論

このページでは、 Xillybus IP coreを使用して OV7670 からピクセル データを取得し、このデータを host に送信する方法を説明しました。

このチュートリアルの次の部分では、 Xillybus IP core を使用して SCCB (つまり I2C) を利用してカメラ センサーの registers を変更する方法を説明します。これは、カメラのパラメータを変更する場合に便利です。特に、これは正しい色の画像を取得するために必要です。

このページは英語から自動翻訳されています。 不明な点は元のページを参照してください。
Copyright © 2021-2024. All rights reserved. (b4b9813f)