この Web ページは、 Smart Zynq boardの機能を探求する小規模プロジェクトのグループに属しています。
このプロジェクトは HelloFPGAでも公開されており、中国語の読者におすすめです。
序章
このチュートリアルでは、 Smart Zynq ボードを使用して OV7670 camera sensorの registers にアクセスする方法を説明します。これは、カメラ センサーからビデオ データを受信する方法を説明した前のページの続編です。
OV7670 には、カメラ センサーのパラメーターを構成するための Serial Camera Control Bus インターフェイス (SCCB) があります。 SCCB protocol は、「OmniVision Serial Camera Control Bus (SCCB) Functional Specification」という名前の Omnivisionのドキュメントで定義されています。この protocol は、よく知られている I2C protocolと互換性があります。
カメラ センサーの registers にアクセスする主な目的は、カメラに正しい色の画像を生成させることです。ただし、 registersを通じてカメラを制御することには他にも利点があります。 デジタル信号の電流を制御し、カメラの明るさと色の自動調整を制御し、場合によっては停止し、 test pattern をリクエストします。
Zynqの processor には、それぞれ I2C bus master を実装する 2 つの内蔵ユニットがあります。これらのユニットの 1 つを使用して、カメラ センサーと通信することができます。残念ながら、これらの内蔵 I2C ユニットを使用しようとすると、 OV7670ではうまく動作しないことが判明しました。原因はおそらく、ワイヤー上の大量のノイズです。専用の pull-up resistors がないことも考えられる理由です ( FPGAの内部 pull-ups がこの目的に使用されました)。
内蔵の I2C ユニットは使用できないため、代わりに Verilog module がプロジェクトに追加されます。この logic は、ノイズの多い信号にうまく対処できるように設計されています。
Vivado プロジェクトへの変更
以下の手順は、前のページに従ってすでに作成されている Vivado プロジェクトに基づいています。
このリンクから I2C bus master の Verilog 実装をダウンロードします。このファイルを verilog/src/ ディレクトリにコピーします。次に、ファイルを Vivado プロジェクトに追加します。 File > Add Sources… をクリックし、「Add or create design sources」を選択します。次に、「 Next」をクリックします。 「Add Files」ボタンをクリックし、 verilog/src/ ディレクトリ内の「i2c_if.v」という名前のファイルを選択します。次に「Finish」ボタンをクリックします。
processor は 2 つの Xillybus streamsを利用してこの module を制御します。テキスト エディタで verilog/src/xillydemo.v を開きます。コードの「PART 3」というラベルの付いた部分を削除します。その部分の代わりに、次のコード スニペットを挿入します。
/*
* PART 3
* ======
*
* The instantiation of i2c_if demonstrates how to use two Xillybus
* streams to implement an I2C interface with the camera sensor module.
*
*/
i2c_if i2c_if_ins
(
.bus_clk(bus_clk),
.quiesce(quiesce),
.i2c_clk(J6[15]),
.i2c_data(J6[14]),
.user_w_write_8_open(user_w_write_8_open),
.user_w_write_8_wren(user_w_write_8_wren),
.user_w_write_8_data(user_w_write_8_data),
.user_w_write_8_full(user_w_write_8_full),
.user_r_read_8_open(user_r_read_8_open),
.user_r_read_8_rden(user_r_read_8_rden),
.user_r_read_8_data(user_r_read_8_data),
.user_r_read_8_empty(user_r_read_8_empty),
.user_r_read_8_eof(user_r_read_8_eof)
);
あるいは、この変更後の xillydemo.v をここからダウンロードすることもできます。
この変更を加えた後、通常どおり bitstream ファイルを作成します。
i2c_if module は J6[15] および J6[14]に接続することに注意してください。これらの ports は、 pin header を介してカメラ モジュールの SCL および SDAに接続されます。
カメラセンサーの registersを変更する
I2C コマンドをカメラに送信するコンピューター プログラムは、 このリンクからダウンロードできます。このプログラムを Xillinuxの file system にコピーします (たとえば、 scp を使用するか、ファイルを TF cardに直接コピーします)。
ファイルがある場所にディレクトリを変更します。 compilationを実行するには、 shell prompt で次のコマンドを入力します。
# gcc -Wall -O3 -o i2c i2c.c
このコマンドはサイレントに完了するはずです。
これがプログラムの実行方法です。プログラムが通常生成する出力も示します。
# ./i2c
Camera sensor's product ID is 0x7673
Reg 0x3d = 0x88 (to be altered)
Reg 0xb0 = 0x00 (to be altered)
Reg 0x6f = 0x9a (to be altered)
Wrote 0x3d = 0x81
Wrote 0xb0 = 0x84
Wrote 0x6f = 0x9f
まず、プログラムはカメラ センサーの product IDを含む registers を読み取ります。 OV7670 カメラ センサーにはさまざまなバージョンがあります。このチュートリアルは、番号 0x7673 ( product IDとして) で自身を識別するカメラ センサーに基づいています。 OV7670 カメラ センサーが別の product IDで使用される可能性はほとんどありません。 product ID が異なる場合は、別のモデルのカメラ センサーがカメラ モジュールに取り付けられている可能性があります。
プログラムは、カメラ センサーの画像の色が正しくなるように、必要最小限の変更を加えます。この目的のために 3 つの registers が変更されます。
変更を加える前に、プログラムは registersの既存の値を読み取ります。これらは、プログラムの出力の次の 3 行です。次に、プログラムはこれらの registersに正しい値を書き込みます。
この3つの registersの意味
残念ながら、カメラセンサーの registers の意味は部分的にしか文書化されていません。 OV7670の registers の多くは「reserved」として定義されます。したがって、 registers の一部の変更がなぜ必要なのかについては説明がありません。 OV7670や registersに関する情報源は数多くあります。 registers に関するヒントを探すのに最適な場所は、カメラ センサーの Linux driverです。 ov7670.c。特に、 ov7670_default_regs[] ( driverで定義された variable ) には、多くの貴重なヒントが含まれています。
これらは、 i2c.c プログラムが 3 つの registersを変更する理由について利用できる説明です。残念ながら、これらの変更が必要であることは明らかですが、一部の変更の理由は不明です。
- COM13 (0x3d): まず、この register の bit 0 が '1'になります。その結果、カメラセンサーの出力において、 U と V の位置が入れ替わります。これは、出力形式を UYVYにするために必要です。これは、 mplayer およびその他のソフトウェアが期待する形式です。なお、この registerの bit 3 は '0'に変更されています。この bit の意味はカメラセンサーの説明書には書かれていません。
- Reserved register (0xb0): この registerに関するドキュメントはありません。
- AWBCTR0 (0x6f): この register はカメラセンサーの white balanceに関連しています。 OV7670の Implementation Guideに従って、 0x9f をこの register に書き込むと、次の 2 つの変更が発生します。 Advanced AWB mode が有効になり、 Maximum color gain が 2x から 4xに変更されます。この register を変えないと white balance はうまく動作しません。
他の registersへの書き込み
これは @writelistの定義であり、 i2c.c プログラムの冒頭近くにあります。
static const struct {
int addr;
int value;
} writelist [] = {
{ 0x3d, 0x81 }, // COM13, swap UV, turn off reserved bit 3
{ 0xb0, 0x84 },
{ 0x6f, 0x9f }, // AWBCTR0, crucial for white balance
{ -1, -1 }, // Terminate
};
elements と @writelist array は 2 つの番号で構成されます。 最初の数字は registerのアドレスです。 2 番目の数字は、この registerに書き込まれる値です。
たとえば、最初の element は { 0x3d, 0x81 }です。これは、値 0x81 が COM13に書き込まれることを意味します。この registerの address は 0x3dです。
配列の最後の要素は { -1, -1 }である必要があります。
カメラセンサーの drive currentを縮小する
カメラ センサーと Smart Zynq ボード間のワイヤが長すぎる場合、ビデオ画像が不安定になる可能性があります。 ビデオ フレームがジャンプし、画像全体に緑と紫の縞模様が表示されます。これはワイヤー間に発生する crosstalk が原因でよく発生します。
カメラセンサーがワイヤーに流す電流を減らすことで、この問題を解決できる可能性があります。これを行うには、値 0x00 を COM2に書き込みます。この registerの address は 0x09です。
つまり、 @writelistの定義を次のように変更します。
static const struct {
int addr;
int value;
} writelist [] = {
{ 0x09, 0x00 }, // Drive current to 1x level
{ -1, -1 }, // Terminate
};
次に、このプログラムの compilation を実行し、前と同じようにプログラムを実行します。
他の可能性
カメラ センサーのドキュメント (特にOV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide ) には、他のいくつかの registersに関する情報が記載されています。前述したように、 Linux kernelのdriver も重要なヒントを提供する可能性があります。
このチュートリアルの最初の部分で、次のコマンドを使用してカメラ センサーをリセットできることを思い出してください。
# echo 1 > /dev/xillybus_write_32
このコマンドの結果として、すべての registers がデフォルト値に戻ります。
すべての registersの値を出力する
これは、 i2c.c プログラムの main() 関数の一部です。
if (0) { // Change this in order to print out registers instead
for (i=0; i<=0xc9; i++) {
i2c_read(i, &value);
printf("Reg 0x%02x = 0x%02x\n", i, value);
}
return 0;
}
この部分の目的は、すべての registersの値を示すことです。 「if (0)」状態のため、通常はこの部分に到達することはありません。すべての registersの値を出力するには、これを「if (1)」に変更します。
すべての registers を印刷するには 1 秒もかかりません。プログラムの実行が一時的に停止したり、プログラムが停止したりする場合、その原因は I2C busの通信エラーです。この問題が発生すると、プログラムの出力が不正確または不完全になる可能性があります。プログラムが迅速かつスムーズに実行されるまで再実行します。
すべての registers のプリントアウトは、 このリンクからダウンロードできます。このプリントアウトは、カメラ センサーが正しい色の画像を生成した場合の registers を反映しています。デフォルト値(カメラセンサーがリセットされた直後)の印刷物は、ここからダウンロードできます。カメラは自動輝度制御、ホワイトバランスなどの結果として、 registers の一部を継続的に変更することに注意してください。
I2C 書き込み操作の実行方法
このセクションでは、 I2C protocolの基本を理解している必要があります。
i2c.c プログラムは、2 つの Xillybus streamsを介して FPGA 内の i2c_if.v module と通信します。 /dev/xillybus_write_8 と /dev/xillybus_read_8。
I2C write operation は次のように行われます。
- host が /dev/xillybus_write_8を開くと、 FPGA は I2C start conditionを生成します。
- この device file に書き込まれるバイトは、 I2C ワイヤ上に (変更されずに) 表示されます。
- host が /dev/xillybus_write_8を閉じると、 FPGA は I2C stop conditionを生成します。
これらの手順は、 i2c_write() 関数によって実装されます。
static void i2c_write(int addr, unsigned char data) {
unsigned char sendbuf[3] = { i2c_addr << 1, addr, data };
allwrite(sendbuf, sizeof(sendbuf));
}
この関数は、3 バイトで構成される buffer を準備します。
- I2C addressです。これは書き込み操作の 0x42 です。
- register address。
- registerに書き込まれる値。
FPGA は、これらの 3 バイトを I2C bus経由でカメラ センサーに送信します。 i2c_write() 関数は、 /dev/xillybus_write_8を開き、 bufferからデータを書き込み、ファイルを閉じます。
I2C protocolによれば、受信者は busで送信される各バイトを確認する必要があります。 各バイト (8 ビットで構成) には、この目的のために 9 番目のビットがあります。この 9 番目のビットには、送信中に特別なタイムスロットがあります。バイトを受信した側は、バイトが受信されたことを確認するために、このタイムスロット中に SDA ワイヤを '0' に引き込む必要があります。
カメラ センサーがこのように応答しない場合、 FPGA 内の i2c_if module は device fileを介してそれ以上のバイトを受け入れることを拒否します。これによってエラーは発生しませんが、 close() への関数呼び出しは 1000 msの遅延後にのみ返されます。その理由は、 Xillybusの driver は、残りのすべてのデータが FPGA に到達するまでファイルを閉じる前に待機するためです。ただし、 I2C slave がバイトを認識していない場合、 FPGA は次のバイトの受け入れを拒否します。この状況では、 driver は 1000 msを待機し、とにかくファイルを閉じて、次のメッセージを kernel logに追加します。
Timed out while flushing. Output data may be lost.
kernel log のメッセージは「dmesg」コマンドで表示できます。
結論として、 i2c_write() への関数呼び出しが完了するまでに 1 秒かかる場合、その理由はおそらくカメラ センサーが I2C bus の操作に正しく応答しなかったことです。カメラ センサーが FPGAに正しく接続されていないか、まったく接続されていない可能性があります。
I2C 読み取り操作の実行方法
読み取り操作は 2 つの別々の操作で構成されるため、さらに複雑になります。
- 書き込み操作ですが、データ バイトはありません。このアクションの目的は、 register address を I2C slaveに送信することです。
- 読み取り操作。 registerの値は slave から masterに送信されます。
i2c_read() の機能を以下に示します。
static void i2c_read(int addr, unsigned char *data) {
int fdr;
unsigned char cmdbuf[2] = { i2c_addr << 1, addr };
unsigned char dummybuf[2] = { (i2c_addr << 1) | 1, 0 };
allwrite(cmdbuf, sizeof(cmdbuf));
// We open xillybus_read_8 only now. Had it been open during the first
// operation, there would have been a restart condition rather than a
// stop condition after the first command.
fdr = open("/dev/xillybus_read_8", O_RDONLY);
if (fdr < 0) {
perror("Failed to open /dev/xillybus_read_8 read-only");
exit(1);
}
allwrite(dummybuf, sizeof(dummybuf));
allread(fdr, data, sizeof(*data));
close(fdr);
}
この関数は、2 バイト (@cmdbuf) を slaveに送信することから始まります。
- I2C addressです。これは 0x42で、書き込み操作と同じです。
- 読みたい register の address 。
次に、i2c_read() は /dev/xillybus_read_8を開きます。 allwrite()とは異なり、 allread()ではこれが行われないことに注意してください。
次に、 i2c_read() は、 allwrite()によって 2 バイト (@dummybuf) を bus に書き込みます。
- I2C addressです。これは 0x43で、これが busでの読み取り操作であることを slave に伝えます。
- ゼロを含むバイト。 i2c_if module は、このバイトの内容を無視します。
FPGA 内の i2c_if module は、受信した最初のバイトの bit 0 を検査します。これに基づいて、 FPGA は、 busで書き込み操作を行うべきか、読み取り操作を行うべきかを推定します。読み取り操作が必要な場合、他のすべてのバイトの内容は無視されます。これらのバイトは、 FPGA に受信すべきバイト数を通知することのみを目的としています。
i2c_if module は、要求されたバイト数を bus から読み取り、 /dev/xillybus_read_8を介して host に送信します。 @dummybufへの書き込みによって bus での読み取り操作が開始される前に、この device file をオープンする必要があります。この後、 allread() は registerの値を読み取ります。ファイルは早めに開いておく必要があるため、 allread() はファイルを開いたり閉じたりしません。
Bus restart
このセクションは、 OV7670 カメラ センサーには関係ありません。ただし、この情報は、 i2c_if を別の slaveと一緒に使用する場合に役立つ可能性があります。
i2c_read() は allwrite() への関数呼び出しを 2 回実行することに注意してください。そのたびに /dev/xillybus_write_8 が開閉します。その結果、データが送信される前に I2C start condition があり、その後に I2C stop condition が存在します。
つまり、 registerの address を slaveに送った後に stop condition があるということです。次に、 master が読み取り操作を開始する前に start condition があります。
カメラ センサーは、この一連のイベントを予期します。ただし、次のように読み取り操作を試行すると、 I2C slaves として機能する他の電子コンポーネントは正しく動作しません。 これらのコンポーネントは、 stop conditionに応答して registerの address を忘れます。したがって、 busの最初のアクションと 2 番目のアクションの間に restart condition を生成する必要があります。
i2c_if module はこの可能性をサポートしています。 /dev/xillybus_read_8 が継続的に開いている間に /dev/xillybus_write_8 が閉じられ、その後再び開かれると、代わりに bus 上に restart condition が存在します。つまり、 slave が restart conditionを必要とする場合、 allwrite() への関数呼び出しを移動する必要があります。 i2c_read() は次のようになります。
fdr = open("/dev/xillybus_read_8", O_RDONLY);
if (fdr < 0) {
[ ... ]
}
allwrite(cmdbuf, sizeof(cmdbuf));
allwrite(dummybuf, sizeof(dummybuf));
allread(fdr, data, sizeof(*data));
close(fdr);
もう一度言いますが、このコードは OV7670には適していません。
結論
Xillybus IP core を使用してカメラセンサーの registersにアクセスすることが可能です。 I2C busとのインターフェースには追加のモジュールが必要です。 i2c_if。このモジュールは、他の I2C slavesとの通信にも役立ちます。
残念ながら、 OV7670 カメラ モジュールのレジスタに関する情報は不足しています。したがって、インターネットで解決策を検索するか、カメラ センサーの Linux driverの助けを借りる必要がある場合があります。