マイコン工作実験日記

Microcontroller を用いての工作、実験記録

DCMIでカメラをつなぐ

2017-08-03 13:18:22 | CMOSカメラ

気をとりなおして改めてUVCカメラに挑戦することにします。 USB部分はそれなりに動くようになったので、今度はカメラをつなげました。用意してあったコネクタをSTM32F446のDCMIを使ってつなげてやっただけです。



現在のピン割り当ては次のようになっています。USBのHigh speed PHYと DCMIを使ってOV2640をつないだだけで 64ピンのうちの30ピンほどを使ってしまいます。電源とJTAG関係を除くと、使える端子は多くありません。


OV2640に供給するクロックは、TIM12 CH2を使ってシステムクロックを分周して供給することにしました。

UXGA画像の取得

2016-09-20 23:27:09 | CMOSカメラ



ようやくとNucleo-L476RGを使って、OV2640で撮影できる最大サイズのUXGA(1600×1200)の画像の取得ができました。

JPEG画像取得用のバッファ(jpeg_buffer)としては60KB分を用意しておき、

HAL_DMA_Start_IT(&hdma_tim2_ch1, (uint32_t)(&GPIOC->IDR), (uint32_t)jpeg_buffer, JPEG_BUFSIZE);

としてDMAでの読み出しを開始しています。DMAにはCircularモードを使っているので, バッファが一杯になったならば、バッファの最初に戻って読み込みデータが保存されていきます。バッファの半分まで読めた時と、最後まで読めた時に割り込みがかかりますので、それをトリガにすることでダブルバッファリングの処理を行うことができます。バッファの後半にカメラからのデータを読み出している間に、読み込みの終わったバッファ前半の内容をSDカードに書き出してやれば良いわけです。

このようにタイマーのキャプチャ機能を使ってDMAをかけることで画像を読み取ることはできましたが、UXGAでは1フレームの取得に1秒近くかかってしまっています。カメラの性能としては15fps出るはずですので、この点は残念なところです。

画像が安定するまで

2016-09-18 18:17:23 | CMOSカメラ
SDへの書き込みができるようなったので、連続して取得したJPEG画像をSDに保存してみました。

OV2640は連続して動かしていると、結構モジュールの基板が暖かくなります。データシートを読むと、YUVでデータ出力するよりもJPEG圧縮して出力する方が消費電集が増え、取得する画像のサイズにもよりますが100mA程度流れるようです。そこで普段はパワーダウンモードにしておき、撮影トリガが入ったら、カメラをアクティブにして画像を取得するのが良いだろうと考え、パワーダウンから回復した際の画像を確認してみました。実際の動作としては、パワーダウン状態を解除したのちに、カメラの初期化を行っています。露出やカラーバランスの設定は基本的にオートを選択していますが、オートの機能が有効に機能するためにはカメラモジュール内のDSPが取得した画像を調べて撮像するパラメータを調節するための時間が必要となります。そのため、初期化してから安定した画像が得られるまでにはしばらく時間がかかります。

今回は画像サイズとしてXGA(1024×768)を選択して、1フレーム取得したらそのデータをSDに保存してみました。


















最初のうちは妙に黄色っぽい画像だったのが、だんだんと白くなっていくのがわかります。STM32L476でXGAの画像データを安定して拾うためには、OV2640のCLKRCレジスタの分周比には8を設定する必要がありましたが、そうすると1フレーム分のデータを取得してデータを保存していると、次のフレームの始まりを検出するまでには4秒近くかかってしまっています。そのため、安定した画像を得られるまでには20秒以上も待たねばなりません。うーん、ちょっと時間かかりすぎるなぁ、やっぱDCMIを使って早いクロックで画像取り込まないと辛いなぁ。

今回はXGAで画像を取り込んでみましたが、JPEGでのデータサイズはおよそ60KBになっています。OV2640はUXGA(1600×1200)までのサイズの画像を撮影可能ですが、一旦すべての画像データを取り込んでからファイルへの書き込みを行っている現在の方式ではXGAが扱えるデータ量の限界です。STM32L476はSRAM1とSRAM2の領域が連続しており、合計で96KBの容量があるので画像の内容によってはSXGA(1280×1024)の画像を取得することもできるかもしれませんが、一度のDMAで指定できるデータ長が16ビット長, 64KBバイトという制限があるので、64KBを超える画像の取得のためにはDMA転送を複数回に分けて処理する必要が生じます。

9/19訂正
カメラからの画像は、5フレーム分を読み捨てた後に、1フレーム分のデータを取得しています。したがって、6フレーム毎にデータを取得して、それをSDカードに保存していますが、その間隔が4秒程度になっています。

画像サイズを変えてみる

2016-09-03 16:30:43 | CMOSカメラ
STM32L476を使ってのOV2640からのJPEG画像取得はフレームレートが上がらないようなので、今度は取得する画像サイズを変えて試してみました。画像サイズが大きくなると、サイズが小さい時と同じレートで画像を出力するにはPCLKの周波数を高くする必要が生じます。今回の実験ではPCLK周波数が高くなると画像をうまく読み取れないので、PCLKを低く抑えなければなりません。そのt目には画像サイズを大きくするに従い、PCLKを設定するための分周比を大きめに設定してやる必要があります。以下に、実験の結果取得できた画像とCLKRCレジスタに設定した分周比(DIV_CLK)の値を示します。

QVGA (DIV_CLK = 3)


VGA (DIV_CLK = 6)


SVGA (DIV_CLK = 7)


SVGAの画像ではおよそ1.7フレーム/秒程度になってしまいました。JPEGデータのサイズはSVGAでも32KBに満たない程度しかありませんので、STM32L476のRAM上に設けったバッファーにDMAで一度に読み取って保持しておくことができます。


JPEGデータの終わり

2016-09-01 20:45:50 | CMOSカメラ
OV2640の出力するJPEGデータについては、これまで始まりの部分にばかり着目していましたが、終わりの部分にも目を向けてみます。JPEGデータ出力の終了はVSYNC信号の変化で検出できますので、これを検出してGPIOからのDMA読み出しを止めてやることができます。実際に受信してみると、こうなりました。




左側のメモリ表示ウィンドウが、受信データ内容を示します。JPEGデータのDMA受信に先立って、あらかじめメモリは0xa5でフィルしてあります。EOIである0xFFD9を受信した後に、0x00を173(0xad)バイト受信していることがわかります。

右側のウインドウはVSYNCがかかる度ごとに、DMA受信したバイト数とEOIまでの有効データバイト数を示しています。このように、EOIの後ろに不要な0x00が何バイトが続いているようです。DMA受信バイト数がいつもキリのいい数字になっているので、おそらく出力処理の際にある程度バッファリングしている都合上、このようなデータが出力されるのでしょう。余分なデータは、そのままJPEGファイルに落としてしまっても害はありませんが、EOIの後のデータは捨ててしまうことにします。

フレーム数が上がらない

2016-08-27 10:50:55 | CMOSカメラ
OV2640でのJPEG取得には成功したものの、使用しているPCLKが5MHzだと4フレーム/秒にしかなりません。これだと、動画撮影に使うには苦しいので、もう少し早くならないものかと試してみたのですが、うまくいきません。

これまでの設定だとXCLKとして20MHzを与えて、それをOV2640で4分周して5MHzのPCLKを作っていました。OV2640の設定を変更して、3分周にするとカメラからは次のように6FPSの信号が出力されるのですが、これを正しく受信できないでいます。実際の問題点は次の2点です。
  • JPEGデータの最初の1バイトが欠けてしまう
  • データ内容が化ける

まずは6FPSになった時の全体的なタイミングを確認。


4FPSの時と基本的に同じです。各フレームの間、VSYNCがLになっている期間はおよそ10msあります。VSYNCの立ち上がり部分を拡大してみると...


ここで示したPCLKはカメラからのPCLKとHREF信号のANDをとったもので、実際にデータを拾うべきタイミングを示しています。有効なPCLKは、VSYNCの立ち上がりから3μsほどで始まっっています。さらにZoomしてPCLKの周波数を確認してみると...


PCLKが7.5MHz近くになっています。20MHzを3分周する設定に変更したので、PCLKは6.6MHz程度になるものと思っていたのですが、そういうわけでもなさそうです。予想以上にPCLKが高いことがわかったので、対策としてPCKとHREFのANDをとるのに使っていた74HC00を74LCX08に変更することでゲート遅延を短縮してみたりもしてみましたが、状況に大きな変化無し。ちょっと残念ではありますが、4FPSが限界かな。

JPEG取得成功

2016-08-20 22:53:32 | CMOSカメラ
部品箱から74HC00を探し出したので、次のようにカメラとの接続を変更。


前回同様、PCLK 5MHz, 画像サイズQVGAで実験したところ、JPEG画像の取得に成功しました。



試行錯誤しながら実験してみてわかったかのは
  • タイマーの設定においてFilterを0に設定していると しばしば正常に画像がキャプチャできないことがあったので、フィルタ値として1を設定したところ安定して画像が取得できるようになった。
  • CLKRCの値を変更してPCLK周波数を6MHz以上にあげてみると、正しく画像データが取得できないみたい。
  • カメラからはSOIに始まってEOIで終わる完全なJPEGフォーマットが出力されるので、そのままファイルに落としてやれば画像ファイルとして使える。

タイマーキャプチャのクロックがHREFでゲートされたので、DMAバッファには綺麗にSOIで始まるデータが取得できるようになりました。



PCLKを早くすると、DMAでデータを取得するタイミングがうまく合わなくなるのか、データが正しく取得できないようです。74HC00を入れたためにPCLKにゲート遅延が生じてしまいキャプチャするタイミングがそれだけ遅くなってしまうことも悪影響を与えていると思われます。5MHzのPCLKでは、4フレーム弱程度の速度でしか画像を取得できないので動画撮影には遅すぎます。7フレーム/秒程度の動画撮影は出来るんじゃないかと想像していたので、ちょっと残念な実験結果です。

JPEG出力信号を確認する

2016-08-19 00:13:18 | CMOSカメラ
OV2640の出力する信号を確認するために、SaleaeのLogic 8を接続。8chなので、VSYNC, HREF, PCLKに加えてD7..D3の5ビット分のデータをキャプチャしてみました。

まずはJPEGモードではない、通常形式での出力の場合。



ひとつのVSYNC区間の間に、HREFがライン数回変化していることがわかります。PCLKは出っぱなし。
出力形式をJPEGに変更すると次のようになりました。



通常形式とは全く異なる、なんだかちょっと不思議なところもある信号です。
  • VSYNCにはひとつヒゲが出ていますが、SYSCLKよりも短いようなので無視しても大丈夫そうです。
  • HREFにもヒゲのように見える部分がありますが、こちらは10us程度の長さがあり、無視はできません。
  • HREFがLの時にPCLKが続けて出ていますが、この時のデータは無視しなければいけないようです。
  • HREFがHでもPCLKがでていない時の方が多い。


JPEG出力の始まりの部分をズームしてみると...



HREFが立ち上がって、すぐにSOI (0xFF, 0xD8) と APP0 (0xFF, 0xE0)がでているようです。
HREFでヒゲのように見えている部分も拡大してみると...



PCLKがしっかりと出ているので、この部分のデータは読み捨てなければいけないようです。GitHubにあった参考コードでは、HREFの変化を割り込みで検出してタイマーのキャプチャ動作を許可/禁止する仕掛けになっていましたが、PCLKの速度に比べてMCUのクロックが充分に早くないと処理が間に合いません。5MHzのPCLKでも画像がきちんと取得できなかったのは、これが原因ではないかと思われます。

HREFがLの期間のPCLKを無視するために、TIMERにはHREFとPCLKの論理積(AND)をとった信号を入れてやれば、問題を解決できるように思われます。

頭は見つかったものの...

2016-08-17 21:55:58 | CMOSカメラ
今回, OV2640を使ってみようと思い立ったのは、こちらの記事にて、「DCMIを使わずにSTM32にOV2640をつなぐ」というアイデアが紹介されていたのを見つけたのがきっかけです。また、STからはAN4666というAPノードが出ていることもわかりました。TIMERのインプットキャプチャ機能を使って、画素クロックの立ち上がりを検出してDMA要求を出させるというアイデアに、「なるほど、その手があったか!!」と感心したので、ぜひとも自分でも試してみたくなったのです。この記事では仕組みの概要やSTM32F4のコードも示されているものの、実際に取得してみた画像は示されておらず、あくまでもコンセプトを実証してみたという扱いにとどまっていることもちょっと気になったので、自分で確認してみようと思い立ったのです。わたしの場合、下図のようにつなげています。

  • PB6/PB7はもともとI2Cとして使っていましたが、OVとの相性が悪いことがわかったので、GPIOとして使い、ソフトI2Cにてレジスタ操作を行うことになりました。
  • PA8はMCOとして使い、SYSCLKの80Mhzを分周した20Mhzをカメラに供給。
  • PCLKからはカメラの設定に応じてXCLKを分周したものが出力されますが、TIM2をInput Captureモードで使うことで、その立ち上がりを検出します。
  • PB4とPB5はEXTIに設定して、同期信号の立ち上がり/立ち下がりを割り込みで検出します。
  • 実際にはPWDNとRESET信号もつないでいますが、図では省略。


ソフトI2Cにしたら、OV2640のレジスタアクセスは問題なく動作するようになったので、サンプルのコードを参考にしてSTM32CubeMXで生成したHALとFreeRTOSで動作するコードを作成。TIM2の入力キャプチャ動作を開始しておいて、VSYNCの立ち上がりを検出したらDMAでGPIOCの読みとりを開始させまます。JPEG画像情報の出力が終わるとVSYNCが落ちるのでDMAを止めて取得できたJPEG情報を確認するという具合です。

PCLKが5MHzになるようにOV2640のCLKRCレジスタを設定、画像サイズはQVGAに設定してやって、カメラから出力されるJPEGデータを取り込んだところ、次のようにJPEGファイルの頭の部分を見つけることができました。



0x2000135E番地からの 0xFF, 0xD8の2バイトがJPEGファイル構造の始まりを示すSOIになっており、続く2バイトの0xFF, 0xE0がAPP0の始まりになっています。そして、その後には量子化テーブルとハフマンテーブル定義が続いていますので、実際のフレーム情報はさらにその後ろに続いているだろうと思わます。DMAでメモリに読み込んだデータをファイルに落としてやれば、JPEGファイルとなるはずです。しかし、まだSDカードへの書き込みは作っていないので、確認のためにデバッガーを使ってメモリの内容をファイルに書き出してやることにしました。近頃、お気に入りのOzoneを使えばメモリ表示ウインドウからメニューを出してダンプしてやることができます。こんな手順で、JPEG画像ファイルが取得できるはずだったのですが....







何度試してみても綺麗な画像が取得できません。画像の途中でズレが生じてしまっています。途中で画素情報の取りこぼしが生じてしまっているように思われます。どうやら、1枚の画像情報を出力している間に何度かHREF信号が変化するらしく、この信号により画素情報の有効期間が通知されているようなのですが、その処理がうまくできていないようです。

そもそもカメラからどんな信号が出力されているのかを、ちゃんと確認しておかねばいけませんね。何しろ、ネットで入手できるOV2640のデータシートではそのあたりの詳細は説明されていませんので。

I2Cとの相性

2016-08-12 21:16:52 | CMOSカメラ
OV2460とNucleo-L476RGをつなぐための最低限の配線ができたので、続いてソフトの準備です。

まずはカメラのレジスタの読みとりができることの確認から。よく知られているようにOmnivisionのカメラの制御信号はSCCBと呼ばれていますが、物理的なインタフェースはI2Cと同じです。そこでSTM32L476側のI2CとつないでCubeMXのHAL_I2C_Mem_Read() APIを使ってアクセスを試みたものの、レジスタの読みとりができません。調べてみると、カメラに対してデバイスアドレスを送ったのに、ACKが返ってこないのでエラーとなっているようです。これがSCCBとI2Cの規格の違いで、I2Cにおいて ACKを返すべきところがSCCBではDon't careになっているために、このような不都合が生じます。この違いは想定内だったので、このエラーは無視して続いてレジスタの番号を送ろうとしたのですが、送信することができません。

マニュアルを読んで確認してみると、STM32L476のI2Cはアドレス指定の際にスレーブからのNAKを検出した時点で自動的にSTOPを送ってI2Cの動作を停止してしまうことが判明。確かにI2Cの機能としては便利な機能なんですが、これではSCCBとの接続には使えません。世の中にはSTM32とOminivionのカメラを接続している例はゴマンとあるので、みんなどうやっているのだろうかと思いSTM32F0やSTM32F4のマニュアルを確認してみると、I2C_CR1やI2C_CR2の構成が全然違います。HALレベルで使っていると、ハードの差異が隠蔽されてしまって気がつきませんが、どうやらこれまでのシリーズのI2CとSTM32L4のI2Cは別物と考えた方が良いようです。

こんなワケで、I2Cを使うのは断念して、GPIOを使ってのソフトI2Cでつなげることにします。