esp32-cameraライブラリを読み解く ~OV2640, SCCB, DMA, I2S 編~

esp32,と,OV2640,でDMA,,I2S転送をやってみた ESP32 ( ESP-WROOM-32 )

OV2640からテスト用カラーバーを出力させる

ロジックアナライザーやオシロスコープでOV2640からの信号を見るためには、OV2640からテスト用のカラーバーを表示させた方が良いです。
その方法は、先に述べたようにSCCBインターフェースで、OV2640へレジスタ設定してやります。

まず、先に紹介したOV2640の初期化とフレームサイズ(画像サイズ)を設定し、次に以下のようにSCCBインターフェースでレジスタ設定してやります。

writeSCCB(0xff, 0x01); //bank sensor
writeSCCB(0x12, 0b00100010); //COM7 CIF mode, color bar en

OV2640のレジスタ0xFF でバンク設定を0x01にして、センサー設定モードにしてやります。
その後、OV2640データシートに書いてあるように、レジスタ0x12 つまり、レジスタ名COM7 でCIFモードして、bit[1]を1にしてカラーバーON状態にしてやります。
すると、こんな感じに表示されます。

Framesize : 100 x 72 pixel
Display Size : 96 x 64 pixel
OUTW=25, OUTH=18

先ほど紹介したように、OV2640から出力するフレームサイズは、ディスプレイの 96×64 pixel に近い近似値のフレームサイズ 100 x 72 pixel としています。
当然、OV2640からのサイズの方が大きいので、ディスプレイ表示のところのプログラミングで、はみ出たところは捨てて表示させています。
ですから、この画像のカラーバーは、右側の黒色4pixel分カットされています。

それにしても、このテスト用のカラーバーは普通のカラーバーとちょっと違って不思議ですね。
ピンクと赤色のところに淵があります。
これは何なんでしょうね???

さて、CIFモードに限って言えば、画像は4倍ズームまで可能です。
そうすると、カラーバーの表示も変わりますので、それについては次の項で説明します。

ズーム設定

先ほどフレームサイズ設定で紹介したように、フレームサイズを替えると、あたかもズームしたようになります。

先に紹介したようにOV2640 のデジタルズームはCIFモードでは4倍までです。
SVGAモードは2倍まで。
UXGAモードはズーム不可。

これはどういうことかというと、個人的な想像で述べます。
先に紹介した画素のベイヤー配列を見て分かる通り、CIFモードでは画素を間引いて出力していて、SVGA では間引きがCIFの2分の1になっています。
UXGAでは目一杯画素全部を出力していて間引きはありません。
それに、先に紹介したロジックアナライザー波形を見ても、4倍までのサイズしかフレームサイズに入り切りません。
要するに、デジタルズームと言っても、低い解像度から最大の解像度までに切り替えているだけであって、あとはディスプレイで切り取っているだけということのようです。

分かってしまえば、
「な~んだ! そういうことか・・・」
と拍子抜けしてしまいますね。
実際、イメージセンサはそんなもんだろうと思います。

ズームをテストするためには、先ほど紹介したようにテスト用のカラーバーを表示させると良いです。
OUTW および OUTH の値を変えてやればよいです。
つまり、OV2640から出力させるフレームサイズを変えてやれば良いわけです。

96×64 pixel の SSD1331 に出力させると以下のようになります。
ディスプレイ表示側のプログラミングで、96×64に切り取って、はみ出た画素データは捨てています。

【等倍】
Framesize : 100 x 72 pixel
Display Size : 96 x 64 pixel
OUTW=25, OUTH=18

【2倍ズーム】
Framesize : 200 x 148 pixel
Display Size : 96 x 64 pixel
OUTW=50, OUTH=37

【等倍】
Framesize : 400 x 296 pixel
Display Size : 96 x 64 pixel
OUTW=100, OUTH=74

どうですか?
ちゃんとカラーバー表示もちゃんと2倍、4倍ズームしていますね。
ズームと言っても「なんちゃって」ズームですけど・・・。

DMA, I2S 割り込みみついて

さて、先に紹介したOV2640からのオシロやロジックアナライザー波形を見ると、ESP32 のGPIO端子で電圧のHIGHおよびLOWレベルを検知するプログラムを組めば、OV2640から送られてくる画素データのFIFOメモリ取り込みは出来そうな気がします。

例えば、以下のようにすれば、HREFがHIGHレベルになったかどうか検知できます。

while (getGpioLevel((gpio_num_t)cam_pin_HREF) == 1) {
    //HREFがHIGHレベルになっている間の処理
}

しかし、VSYNC(垂直同期)信号がLOWからHIGHレベルに立ち上がってから HREF(水平同期)信号が立ち上がるまでにかなりの時間(71.63μs)はありますが、HREFが立ち上がってからはPCLK ( Pixel Clock )信号で画素データが有効になるまではほぼ同時で、他の処理をする時間は全くありません。
これでは、GPIOのHIGH-LOWを検知するプログラミングでトリガーをかけても手遅れで、画素を取りこぼしてしまいます。
それに、先ほど説明したように、解像度を上げると、HREFがHIGHになる間が極端に短くなります。HREF信号のHIGH-LOWレベル検知なんてやっていると、その間にFIFOメモリから画素データを取り出す時間が圧倒的に足りなくなっていき、Arduinoプログラミングでは難しくなってきます。

そんな時、ESP32には嬉しい機能がありました。
それは、DMA, I2S用の割り込み機能です。
ESP32 Technical Reference Manual独自和訳 も合わせて参照してみてください。

先ほど紹介した DMA, I2S初期化を済ませておけば、Arduino core for the ESP32 で以下のようにすればDMA, I2S用割り込みが使えます。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include <driver/i2s.h>

intr_handle_t i2s_intr_handle;
static void IRAM_ATTR i2s_isr(void* arg);

setup(){
  esp_intr_alloc(ETS_I2S0_INTR_SOURCE,
                 ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM,
                 &i2s_isr, NULL, &i2s_intr_handle);

  I2S0.int_clr.val = I2S0.int_raw.val;
  I2S0.int_ena.val = 0;
  I2S0.int_ena.in_done = 1;

  esp_intr_enable(i2s_intr_handle);
}

loop(){
  ・・・
}

static void IRAM_ATTR i2s_isr(void* arg){
  ・・・
}

esp_intr_alloc関数で、I2S0モジュールの割り込みを許可し、esp_intr_enable関数でI2S割り込みスタートという感じです。
すると、HREFがLOWからHIGHレベルになって、FIFOメモリにデータを取り込み、HREFがLOWレベルになった時点で強制的に割り込みが発生し、i2s_isr関数を実行するようです。

これは最近使ってみて、とっても便利だなと思いました。
正直言って詳しい仕組みは良く分かりませんが、これも改めてESP32の高機能にスゲーなと思ったところです。

と言っても、今時のマイコンではDMA, I2S割り込みなんて当たり前の機能かもしれませんね。

コメント

  1. Gustavo Murta より:

    Hi aMiGO,
    Excellent job! I appreciate it very much.
    Although you don’t know much about DMA, you have come a long way.
    Congratulations

    Thanks
    Gustavo Murta (from Brazil)
    amigo (portuguese) = friend

タイトルとURLをコピーしました