FIFOなしのOV7670をAVRで動かすにあたり、Arduinoを使用してPCに転送する例が代表的なようです。
一方、素(す)のAVR(ATmegaシリーズ)でLCDに描画している例が見あたらなかったのでドライバーから自作しました。
ATmega328Pを使用したアプリケーションでは、OV7670の映像をLCDに表示したり、画像をキャプチャしてフラッシュメモリーに保存したりしています。
OmniVision社のOV7670はマイコンで制御できる安価なカメラモジュールで、次のように使用します。
マイコンとカメラは3.3V駆動です。VCC=3.3VでのATmega328Pの動作クロックは13.3MHz以下が安全領域ですが
(データシートDS40002061B - 29.3 Speed Grades のグラフ参照)、16MHzでも良好に動作します。
※12MHz,16MHz,20MHzのセラロックで問題なく動作した。
カメラのD[7:0]をATmega328PのPortD[7:0]に割り当てています。これにより1サイクルで画素データ1byteを取得することができます (キャプチャの高速化、フレームレートの向上)。
カメラのHREFは使用しません。 HREF(水平1ライン)の無効期間はPCLKを停止するようにカメラを設定することで、HREFの開始/終了を監視する必要がなくなります(PCLKをカウントする)。 これによりマイコンのピンが節約できます。
実験用に専用基板を使用しています。 OV7670は必要な配線が多いので動作が怪しいときに原因の切り分けで苦労します。プログラムが悪いのか、断線か、接触不良か。 よって、ユニバーサル基板にハンダ付けするか、専用基板を起こすことをお勧めします。OV7670の実験にブレッドボードはお勧めしません。
基板を起こした際の回路図です。ATmega328P,OV7670,SPI-LCD, レギュレータで構成されています。
セラロックは実際には16MHzのものを使用しています。
フラッシュメモリーは後付けなので回路図にはありません。これの配線ヒントはサンプルプログラムのmain.cに載せたピンアサイン表を参照してください。
基板は失敗した箇所があります。ISPコネクタのピンの並びが左右逆でした。
OV7670のデータシート見て独自にドライバーを作成することは極めて困難です。
なぜなら「RSVD」(Reserved)という名称でデフォルト値が「XX」と説明されているレジスタに、値を設定しないといけない場合があるからです。
※データシートでレジスタ設定の理解を頑張ったが、このことが分かった時点で独自作成は諦めた。
調べたところ、OV7670のあらゆるドライバーは linux版 をベースにしていると言っても過言ではないようでした。 よって、linux版を解像度:QVGA/QQVGA、カラーモード:RGB565で出力できるよう改造することにしました。 これにはいくつかの派生ドライバを参考にしています。
ドライバーを作成するうえで、少しでもフレームレートを上げられるよう(XCLKの分周比を小さくできるよう)、画素データの取得処理で1サイクルのしのぎを削るコーディングを意識しています。
参考:ATmega328P@16MHz,QVGAで1.1fps。
画素データはPCLKのH/Lを見るポーリング方式で取得します。 開発中に割り込み方式も実装して比較したところ、ポーリング方式の方がQQVGA時に8~10倍速くLCDに描画できました。 割り込みルーチンの処理内容にもよりますが、そもそも割り込み処理のオーバーヘッドが大きな負荷になります(数十サイクルかかる)。
画素データはラインバッファに取り込みます。 1ラインキャプチャするごとにデータ利用(LCDへ描画、フラッシュメモリーへ記録など)します。 HREF有効期間内のPCLK出力期間で画素データをキャプチャし、残りの期間でデータ利用する必要があります。 データ利用に使える時間を延ばすにはフレームレートを下げます(XCLKの分周比を大きくする)。
レジスタを設定する際の通信方式はSCCBです。 I2C(AVRではTWI)と同等の通信方式ですが、重要な注意事項があります。 ある通信の最後にストップコンディションを送信した後、次の通信のスタートコンディションを送信する前に1.3us以上バスを空ける必要があります。 このことに最も関係する、データの読み込みシーケンスが落とし穴です。 当ドライバーではスタートコンディションの送信前に5us待つことで対策しています。
OV7670_DS(v1.4) - Table 4. Functional and AC Characteristics SCCB Timing - BUF Bus free time before new START - [Min] 1.3 μs →新しいスタート前のバスの空き時間:最小1.3us SCCBSpec_AN(v2.2) - 3.1.1 Start of Data Transmission Two timing parameters are defined for the start of transmission, tPRC and tPRA.(略) The tPRA is defined as the pre-active time of SCCB_E.(略) The minimum value of tPRA is 1.25 μs.(略) →TWI_START開始時、前回のTWI_STOPから1.25us以上経過している必要がある。
レジスタ設定の最適化を試みています。
linux版のレジスタ設定はVGA/YUVを想定した内容なので、他の解像度・カラーモードに対応させるため、
多くの派生ドライバーでは共通部分と解像度別・カラーモード別の部分に分けて設定値を定義しています。
当ドライバーもその形です。
また、linux版はレジスタ設定の量が多く、この章の冒頭のように非公開の値を設定しているレジスタも目立ちます。
当ドライバーでは動作に影響が出ない(ように見える)範囲で、これらをできるだけ削除しました。
プログラム実行中にQVGA/QQVGAを切り替えることができます。その際フレームレートも変更されます。 画素データを取りこぼさず、かつ、1ラインごとに出力(LCDやフラッシュメモリーへ)できる範囲で可能な限り小さな分周比としています。
アプリケーション側でラインバッファを確保する必要があります。 RGB565のデータ量は1画素あたり2byteなので、QVGA(320x240)の場合、バッファ容量は最低640byte必要です(320[pixel] x 2[byte/pixel])。 QQVGA(160x120)の場合は最低320byte必要です。 ATmega328PにはSRAMが2KB(2048byte)あるので対応できます。
LCDへ描画する以外の例として、フラッシュメモリーへの保存機能を実装しています。
ただし、フラッシュメモリーの使用効率(隙間なくデータを記録する)は考慮していません。
※1ページ256byteのフラッシュメモリーなら、QVGAの場合は2ラインで5ページ丁度のデータ量。
バッファを2ライン分(1280byte)用意すれば隙間なくデータを記録することができる。
フレームレートを極力落とさず実装するには、
1ライン目のデータ取得完了時に2ページ分を記録、2ライン目完了時に残り3ページ分を記録、これを繰り返す。
QQVGAの場合は4ライン分で5ページ丁度なので、同様のことを4ラインごとに繰り返す(1-1-1-2ページずつ記録)。
githubではシンプル版を公開しています。 「OV7670ドライバー AVR向け」
最初の3枚は正常に動作している状態の構成です。4枚目以降は試行錯誤中の様子です。 ドライバーを作成するうえで最初の目標はカラーバーを表示することでした。
ATmega328PでOV7670を動かすことに関する一連のツイートを追うことができます。
更なる解析より、自分好みに改造した方が(そしてハマる)よい経験になるかもしれない。そのためには一旦I2Sじゃなくてシンプルにカメラのデータを読み込む形でアプリを作りたい。ATmega328PとOV7670を接続してみようと思う。
— 『昼夜逆転』工作室 (@jsdiy) March 17, 2025
AVRでカメラ画像。動作中にQVGA-QQVGA切り替え、フラッシュメモリーに画像データ保存(起動時に保存済み画像を読み込んで描画。動画終盤)。ATmega328P@16MHz、OV7670@16MHz/23分周(QVGA時)、5分周(QQVGA時)。やりたいことはできた。一区切りかな。 pic.twitter.com/3nXCyZdJL3
— 『昼夜逆転』工作室 (@jsdiy) June 14, 2025
AVR(Atmel時代のATmegaシリーズ)向けのOV7670ドライバーを作成し、それを使用したアプリケーションを提示しました。 より高度なアプリケーションを作成する場合はMicrochip時代のATmegaシリーズ(SRAM容量が多い、動作クロック上限が少し高い)か、 32bitマイコンを使用した方がよいと思います。