OV7670というカメラモジュールを使って、写真を撮ってみようと思います。使うのは、DUEとTFTシールドと、カメラモジュールです。
カメラモジュールは、かなり昔に購入してあったのですが:
今回はDUE+TFTシールドを使った三度目の挑戦となります。「三度目の正直」となるのか?「二度あることは三度ある」で弾き返されるのか?ここにその顛末を記します。
購入したのは、OV7670と言うカメラ📷を搭載したモジュール(下図)です。
このモジュールは、VGAサイズ(640×480[ドット])の画像を30FPS(Frames Per Second)で出力可能なカメラで、YUV/YCbCr/RGBなどのフォーマットで読み出すことが可能となっています。
OV7670のデータシートを検索(別ウインドウが開きます)
Arduinoとのインターフェイス信号は、以下の通りです。
データシートによると、3V I/Oのチップみたいなので、本当はDUEの3.3V I/Oを接続してはダメなのだけど、モジュールのピンヘッダ部分にあるシルクにはしっかと「3.3V」と記されているので、DUEから3.3V電源と I/Oを直結してしまいます。
※定格内で使用したい場合には、適宜3V電源とL/Sなどを使用してください。
クロック入力XCLKに10〜48[MHz]のクロックを入力してやると、後は勝手に以下図の様なストローブが出力されて、画像データが垂れ流しになります。
※OV7670データシートから引用
「垂れ流し」です。「ちょっと待って!」って言っても待ってはくれません。「今!今、最初のデータを頂戴」って言っても、言う事は聞いてくれません。受け取るDUE側は、出力されるストローブ信号(VSYNC、HREF、PCLK)に合わせて、適切なタイミングでデータD7〜D0を取り込んで行くしかないという、ある意味シンプルな制御になります。
問題はデータレートです。以下の様になっています。前節で示した図と合わせてご覧ください。
DUE @84MHzの実力をもってしても、24MHzでトグルするPCLKに同期して8ビットデータ出力を取り込み続けることは不可能でしょう。従って、DUEでハンドリング可能な周波数までPCLKの周波数を落として使います。
PCLKは、OV7670内部で入力クロックXCLKから分周して作り出しています。この分周比は、内部のCLKRCレジスタで作り出すことが出来るようになっています(↓)。
fPCLK=fXCLK/(2×(CLKRC+1))
CLKRCレジスタの有効ビット数は6ビットなので、1/128分周まで設定することが出来ます。
データシートによると、XCLKには10〜48[MHz]のクロックを入力する必要があります。このクロックは、DUEのPWMユニットからデューティ50%のPWMを出力することで供給する予定です。CLKRCレジスタによって、十分広い範囲で分周して周波数を調整することが出来るので、XCLKには14MHzのクロックを供給することに決めました。
14MHzは、DUEの動作周波数84MHzから偶数の分周比(1/6分周)で生成可能な周波数です。他に以下の周波数でも良いと思います。
F_CPU | 分周比 | XCLK周波数 |
84MHz | ×1/8 | 10.5MHz |
×1/6 | 14MHz | |
×1/4 | 21MHz | |
×1/2 | 42MHz |
偶数分周比にするのは、PWM波形でデューティを作り出す時にサイクル数が奇数になってしまって、デューティが50%からずれてしまうことを防ぐのが目的です。
また、DUEからデュポンワイヤなどでOV7670と配線する以上、そこそこ長い配線をXCLKが伝搬することになります。あまり高い周波数を選択してしまうと、OV7670に到達した時に波形が鈍ってしまって、不都合が生じるかもしれませんので、それも考慮する必要があります。結果、10.5MHzまたは14MHzが選択肢かと思われます。
OV7670には、非常に多くの設定レジスタがあるのですが、これらのレジスタへはI2Cバスを通してアクセスします。OV7670のI2Cスレーブアドレスは0x21で、高速モード400kHzでアクセス可能です。アクセスプロトコルはSMBusのサブセットみたいな感じです。OV7670のデータシートでは、「SCCBプロトコル」となっていて、別途仕様書があるみたいです。
I2Cバスはレジスタの設定にしか使わないので、最低限必要となる以下のプロトコルだけを関数に実装しておきます。
OV7670モジュールには、手動のピント調整があります。ピントはレンズの周りの筒状部分(ノブ)を回して調整します。調整ノブを固定する小さいねじが付いているので、これを緩めておかないと調整できません。
カメラモジュールを繋いで、最初に画像が表示されなかった場合には、回路やレジスタ設定の誤り以外にも、ピントが合っていないケースがあるので注意してください。
機械式のピント調整なので、ピント調整ノブに触って動かしてしまわない限り、一度合わせたらずれることはありません。不用意にノブを回してしまわぬよう、ピント調整が済んだら、ねじを締めてノブを固定しておきましょう。
まず、DUEにTFTシールドを装着します(下図)。
更に、OV7670モジュールと電源モジュール、オプションでSDカードI/Fを以下表の通り接続します。
SDIF | 接続 | Arduino DUE | 接続 | OV7670 | 接続 | 電源モジュール | |
5V | → | 5V(Vin) | |||||
3.3V | ← | 3.3V(Vout) | |||||
4 | VDD | ← | |||||
3 | VSS | ⇔ | GND | ⇔ | GND | ⇔ | GND |
6 | VSS2 | ||||||
1 | CS | ← | D22(PB26/CTS0/TCLK0/WKUP15) | ||||
2 | DI | ← | D75(MOSI) | ||||
5 | SCLK | ← | D76(SCK) | ||||
7 | DO | → | D74(MISO) | ||||
GND | PWDN | ||||||
D20(SDA) | ⇔ | SIOD | |||||
D21(SCL) | ⇔ | SIOC | |||||
D41(PC9/D7/PWMH3) | ← | D7 | |||||
D40(PC8/D6/PWML3) | ← | D6 | |||||
D39(PC7/D5/PWMH2) | ← | D5 | |||||
D38(PC6/D4/PWML2) | ← | D4 | |||||
D37(PC5/D3/PWMH1) | ← | D3 | |||||
D36(PC4/D2/PWML1) | ← | D2 | |||||
D35(PC3/D1/PWMH0) | ← | D1 | |||||
D34(PC2/D0/PWML0) | ← | D0 | |||||
D43(PA20/MCCDA/PWML2) | → | XCLK | |||||
D42(PA19/MCCK/PWMH1) | → | RESET | |||||
D46(PC17/D15/ETXER) | ← | VSYNC | |||||
D47(PC16/D14/ETX3) | ← | HREF | |||||
D48(PC15/D13/ETX2) | ← | PCLK |
※橙色文字にしたPCxは、1回のI/Oレジスタアクセスで同時に参照可能。
全て、TFTシールドで使用できない端子を避けて配置してあります。DUEとSDカードI/Fの接続については「#12-6 DUEにSDカードI/Fを接続する」をご参照ください。
※「#14-7 DUEでTFTシールドのSDカードI/Fを使う」にてリンクしたDUE用のSOFTWARE_SPIパッチを使うと、TFTシールド上のSDカードスロットを使ってSDカードにアクセスすることが出来るようになります。超、便利です(1^_^)v。
DUEの3.3VピンソケットはTFTシールドが挿さっていて使えないので、5Vピンソケットから電源モジュール(下左図)へ5V電源を供給し、3.3Vに降圧してOV7670とSDカードI/F(下右図)に3.3Vを供給しています。
OV7670モジュールは折角のVGAサイズ(640×480[ドット])出力なのですが、DUEにはそれをハンドリングするに十分なRAMが不足しています。しかも、今回はTFTシールド(240×320[ドット])へ表示することを目的としていますので、画像サイズはTFTシールドの表示サイズであるQVGA(Quarter VGA、320×240[ドット])あれば十分です。
OV7670は、VGAサイズよりも小さい画像サイズの出力にも対応していて、QVGAなどフルサイズ(VGA)よりも小さい画像サイズを選択した場合、PCLKやHREFなどのストローブ信号が分周されたり、歯抜けになったりします(下図)。
※OV7670データシートから引用、追記
実はQVGAサイズであっても、320×240[ドット]×2バイト=150Kバイトの記憶領域が必要で、DUEの内蔵RAM 96Kバイトでは不足しています。そこで、HREF=HIGHの期間中(上図緑矢印)に1ライン分のデータを取り込んだら、歯抜けになった休止期間中(同オレンジ矢印)にTFTシールドへデータを転送(描画)してしまおうと考えました。
この場合、DUEのRAM上に確保が必要なデータバッファサイズは1ライン分で済むので、320ドット×2バイト=640バイトで事足ります。
これにより、OV7670からのデータ取り込み速度(フレームレート)を決定する要因は以下の通りとなります。
DUEから供給する、OV7670への入力クロック(XCLK)周波数14MHzから、PCLKの周波数を決めるには、CLKRCレジスタの分周比を設定すれば良いことは解説済みです。ソフトウェア含めてシステム構築後に実験して、CLKRCレジスタに設定すべき分周比の限界値を確かめたいと思います。
OV7670から読み出す画像(ピクセル)データは、RGB565フォーマットを選択しようと思います。RGB565とは、16ビットのMSB側からR(赤)5ビット、G(緑)6ビット、B(青)5ビットの順でデータが並ぶフォーマットです。
一方、TFTシールドのデフォルトではBGR565の順なのですが、BとRを交換してRGB565にするレジスタ設定ビットがあるので、これを使って合わせ込んで使います(↓)。
// Entry Mode
tft_write (0x03,0x1030); // swap B and R data
※「Entry Mode」レジスタなので、データを書き込む時だけ有効なビットです。読み出す時は常にBGR565となります。
これで、OV7670から読み出したデータに対して、ビット演算などの処理が一切不要で、そのまま、TFTシールドへ書き込むことが出来ます。以下、ラインバッファcam_lbuf[ ]に取り込んだデータをTFTシールドへ描画する関数の抜粋です。
// set rectangle
tft_write (0x50,iy);
tft_write (0x51,iy);
tft_write (0x52,0);
tft_write (0x53,tft_SIZV - 1);
tft_write (0x20,iy);
tft_write (0x21,0);
// draw scan line
tft_putpix (0, tft_OPT_INIT);
for (ix=0; ix<cam_BUF_SIZ; ix += 2)
{
dat=(cam_lbuf[ix]<<8) + cam_lbuf[ix + 1];
tft_putpix (dat);
}
tft_putpix (0, tft_OPT_FLUSH);
// clear rectangle
tft_write (0x50,0);
tft_write (0x51,tft_SIZH - 1);
tft_write (0x52,0);
tft_write (0x53,tft_SIZV - 1);
大した処理量ではないビット演算と言えども、前節で指摘した通り、最終的なフレームレートを決定する重要項目であるので、最小限の処理で転送できる利点は大きいです。
今回のお買い物は、以下の通りです。
去年(2019年)夏ごろに購入してあった部品ですが、中々使いこなせなくて、お蔵入り寸前でした。
次回、ソフトウェアの設計を進めて、ひとまず、OV7670から取り込んだ画像をTFTシールドに表示する所くらいまでは到達したいと思います。