PR

ArduinoでSerial Cameraの画像を取得する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Grove-Serial Camera Kitはjpg(jpeg)の画像を生成することができます。画像データはパッケージサイズに分割して管理されており、Arduinoのシリアル通信を使ってデータを取得することができます。Arduino UNOで画像データを取得する方法をまとめました。

Arduino UNOの拡張基板であるSD CARD SHIELDを使ってSDカードに画像データを保存します。SD CARD SHIELDはアイキャッチ画像のようにArduino UNOに差し込むだけで使用することができます。Arduino UNO(以下Arduinoとします。)を対象とします。

Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Grove-Serial Camera Kitを使用する

Grove-Serial Camera KitはSeeed Studioが製作しているGroveモジュールです。マイコンのシリアル通信でコマンドを送信することでSnapShotの画像のデータを取得することができます。詳細は下記のリンクを参考にしてください。

Grove – Serial Camera Kit – Seeed Studio

リンクのDocumentsではDemo Codeとマニュアルが公開されています。本記事ではDemo Codeとマニュアルを引用してSnapShotによるjpeg画像を取得してSDカードに保存します。以下ではGrove-Serial Camera KitをCameraとします。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

動作確認の全体構成

動作確認用の回路図
動作確認用の回路図

SD CARD SHIELDはUNOのピンを延長してSDカードスロットやGrove端子を実装しているため配線はUNOのピン番号と同じです。ArduinoのSDカードライブラリの使い方については下記記事にまとめています。

ArduinoのSDカードライブラリでデータを保存する

Grove Mech keycap(以下keycapとします)とCameraはSD CARD SHIELDのGrove端子に接続します。SDカード用のチップセレクトが必要なのでD4を出力ピンで使用してSDカードが使用できる状態にします。

keycapのボタンでSnapShotするためA5ピンをDIで使用します。A4ピンはDOとして使用しkeycapのLEDランプの制御に使用します。

Arduinoのシリアル通信の専用ピンはCameraに使用するためシリアルモニターとして使用することができません。デバッグ用にシリアルモニターを使用する場合はCamera用にSoftwareSerialを使用する必要があります。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

Cameraの動作フロー

引用:CJ - CAM User Manual  OV528 Protocol  4.1 JPEG Snapshot Picture
引用:CJ – CAM User Manual OV528 Protocol 4.1 JPEG Snapshot Picture

Cameraのボーレート(シリアル通信の通信速度)を調整するためフローチャートの処理を行う前にSYNCコマンドを送信してCameraのボーレートをArduinoのボーレートと合わせます。ボーレートの調整後のフローチャートについて説明します。

Cameraの初期化は各項目のコマンドを送信して行います。Initial JPEG preview VGAは写真のフォーマットを指定するものです。

Set Package Sizeは画像データのパッケージ分割のサイズを指定します。例では512byte ですが、128バイトのサイズを指定します。

Arduino UNOのシリアル通信のデータを一時保存するバッファいはデフォルトで64バイトなので一時保存するバッファのサイズを変更するかバッファがオーバーフローする前にデータを読み込む必要があります。

本記事では一時保存するバッファがオーバーフローする前にデータを読み込むためバッファのサイズを変更する必要はありません。

Initial JPEG preview VGAとSet Package SizeはSnapShotの条件を変更しない限り設定する必要がないため初期化時のみのコマンドを送信しています。

SnapShotから画像データを取得までの手順がSnapshotからACK(package ID:F0F0h)続きます。その後、次のSnapshotを受け付けるまで Package Size終了後の位置で待機します。

シリアル通信のデータを一時保存するバッファのサイズを変更する(参考)

Grove – Serial Camera Kit – Seeed Studioで紹介されているソースコードは一時保管用のバッファのサイズを変更する必要があります。

#if !defined(SERIAL_RX_BUFFER_SIZE)
#if ((RAMEND - RAMSTART) < 1023)
#define SERIAL_RX_BUFFER_SIZE 16
#else
//#define SERIAL_RX_BUFFER_SIZE 64
#define SERIAL_RX_BUFFER_SIZE 128 //128サイズに変更する場合
#endif
#endif

設定変更するファイルはArduinoのファイルが格納されている階層を下っていきます。Arduino IDEをデフォルトの構成でインストールしている場合は下記のアドレスに変更するファイルがあります。XXXXはユーザー名です。

C:\Users\XXXX\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino

このフォルダ内のHardwareSerial.hを開いてSERIAL_RX_BUFFER_SIZEを値を変更します。デフォルトは64になっていますが、6行目のように128以上の値に変更することでソースコードが使用できるようになります。

Cameraを初期化する

初期化はコマンドの送信とACKの確認を同時に行う方法で処理します。

/* JPEG preview VGA */
void Jpegpreview(void){
  char cmd[] = { 0xaa, 0x01, 0x00, 0x07, 0x07, PIC_FMT };
  unsigned char resp[6];

  Serial.setTimeout(100);
  while(1){
    clearRxBuf();
    Serial.write(&cmd[0],sizeof(cmd));
    if (Serial.readBytes((char *)resp, 6) != 6){
      continue;
    }
    //CameraのACKを確認
    if (resp[0] == 0xaa && resp[1] == 0x0e && resp[2] == 0x01 && resp[4] == 0 && resp[5] == 0){
      break;
    } 
  }
}

SerialクラスのsetTimeout()関数はreadBytes()関数で受信待ちする最大時間です。readBytes()関数で指定したデータ数が取得できない場合、setTimeout()の時間処理が停滞するため応答が得られる時限から長くなりすぎないように管理します。

9行目のSerailクラスのwrite()関数でCameraにコマンドを送信します。第1引数に送信するデータのアドレス、第2引数にデータ数を指定します。

コマンドを送信するとCameraはACKで応答するためreadBytes()関数で指定のデータ数の受信を待機します。第1引数に受信データを格納するアドレス、第2引数にデータ数を指定します。受信したデータ数が戻り値にセットされます。

戻り値が指定したデータ数と一致しない場合はタイムアウトしているため受信バッファをクリアしてもう一度コマンドを発行して応答を待ちます。

データ数が一致した場合はresp[]配列の内容を確認してCameraからの応答ACKであることが確認できればループから抜けて次の処理に進みます。

PR:技術系の通信教育講座ならJTEX

Cameraからデータを受信する

シリアル通信のデータがオーバーフローしないように管理する方法
シリアル通信のデータがオーバーフローしないように管理する方法

Cameraの画像データはパッケージのサイズが128バイトになります。そのためreadBytes()関数で128バイトを受信待ちすると一時保存するバッファのサイズがオーバーしてしまいます。

一時保存するバッファが64バイトに達する前にデータを読み込む必要があります。メインループ(Loop())1周あたりで一時保存するバッファが64バイト以内であればwhile()によるループで一時保管しているデータがすべて読み込こまれるためオーバーフローすることなくデータが取得できます。

Cameraから128バイトのデータが受信しながらデータを読み込むためボーレートを低くすることもオーバーフロー対策になります。本記事ではボーレートSerialのデフォルトである115200にしていますが、問題なくデータが読み込めています。

while(Serial.available()){
  CameraRcv.buf[CameraRcv.wp] = Serial.read(); //データをリード

  if( ++CameraRcv.wp >= sizeof(CameraRcv.buf)){ //次に保管する位置を更新
    CameraRcv.wp = 0;
  }
}

Serialクラスのavailable()関数でCameraから受信データの有無を確認します。データがあれば受信データをRead()関数で読み込みCameraRcv.buf[]の配列に格納します。

switch(cmode){
  case C_SNAPSHOT_WAIT:
    sz = 6;
    break;
}

if( rxsz >= sz ){
  for( i = 0; i < sz; i++  ){
    Rcvdata[i] = CameraRcv.buf[CameraRcv.rp];
    //CameraRcv.rpを更新
  }
}

Cameraから受信するデータはコマンドの応答によるACKと画像のデータになります。Snapshotコマンドの応答は6バイト(3行目)なので受信データが6バイト以上(7行目)あれば、Camera.buf[]配列から6バイト分のデータをRcvdata[]配列に格納します。(9行目)

switch(cmode){
  case C_SNAPSHOT_WAIT:
  //CameraのACKを確認
  if (Rcvdata[0] == 0xaa && Rcvdata[1] == 0x0e && Rcvdata[2] == 0x05 && Rcvdata[4] == 0 && Rcvdata[5] == 0){
    cmode = C_GETPICTURE;
  }
break;

Snapshotの応答のACKは[0xaa 0x0e 0x05 0x00 0x00]固定なのでACKのデータが確認できれば次のGet Picture snapshot pictureの処理に進みます。

if (Rcvdata[0] == 0xaa && Rcvdata[1] == 0x0e && Rcvdata[2] == 0x04 && Rcvdata[4] == 0 && Rcvdata[5] == 0){
  if (Rcvdata[6] == 0xaa && Rcvdata[7] == (0x0a) && Rcvdata[8] == 0x01 ){
    picTotalLen = (Rcvdata[9]) | (Rcvdata[10] << 8) | (Rcvdata[11] << 16);
    pktCnt = (picTotalLen) / (PIC_PKT_LEN - PACKET_OFSET);
    remainderLen =  picTotalLen % (PIC_PKT_LEN - PACKET_OFSET);

    if(remainderLen > 0 ){
      pktCnt += 1;
    }
  }
}

Get Picture snapshot pictureの応答はACKとData snapshot pictureの2つあるので、それぞれの応答を合計した12バイトで受信データを確認します。

Data snapshot pictureは画像データのサイズが3バイト分格納されているため画像データサイズに換算(3行目)します。この画像データサイズからCameraに要求するパッケージの数を算出します。

下の項でパッケージの構成を示していますが、画像データはID番号・Data Size・Verify Codeの6バイトを含めて最大で128バイトのパッケージで構成されています。パッケージ当たり122バイトが画像データになるため、画像データサイズを122で割ってパッケージ数を算出します。

割った余りが0よりも大きければ端数のパッケージがあるためパッケージサイズを更新(8行目)します。

画像のデータを取得する

引用:CJ - CAM User Manual  OV528 Protocol  4.Set Package Size
引用:CJ – CAM User Manual OV528 Protocol 4.Set Package Size

パッケージはID~Verify Codeまでの128バイトで構成されています。受信した128バイトのデータからImage Dataを抜き取ってデータを結合して1つの画像データに生成してSDカードに保存します。

unsigned char sum = 0;
for ( uint16_t i = 0; i < sz - 2; i++){
  sum += Rcvdata[i];
}

if (sum == Rcvdata[sz-2]){//LSB FIRST
  myFile.write((const uint8_t *)&Rcvdata[4], sz-PACKET_OFSET);
  ++pktid;
}

Verify CodeはパッケージのIDからImage Dataまでのデータを加算した値が格納されています。Verify Codeと受信データから算出したチェックサム(1~4行目)の値を比較してデータの健全性を確認します。

2バイトのデータはLSB FIRSTなのでRcvdata[sz-2]にLOWバイトが格納されています。データの一致が確認できればImage Data部分をSDカードのファイルに書き込みます。

次のパッケージを要求するためACKのIDコードを+1(8行目)してArduinoからACKを送信します。ソースコードのSendAck()関数を参照してください。

スポンサーリンク

動作確認

電源を入れるとCamerano初期化を行います。初期化中はkeycapのLEDランプを赤色にして初期化中であることを通知します。初期化が終了しSnapShotの受付ができるようになるとkeycapのLEDランプを緑色にして受付可能であることを通知します。

keycapのボタンを押すとSnapShotをスタートし画像データの取得を開始します。取得中はkeycapのLEDランプを青色にして取得中であることを通知します。

動作確認:SDカードのJPGファイル
動作確認:SDカードのJPGファイル

複数回Snapshotを行った後SDカードのファイルを確認しました。JPGファイルが複数生成できていることが分かります。更新日時は時刻のデータとリンクしていないため表示形式が合わず2000/01/01 1:00になっていますが問題なく開くことができます。一度開いて保存し直すと保存時の時間に更新されます。

動作確認:JPGファイルの確認
動作確認:JPGファイルの確認

JPGファイルを確認しました。スライムのぬいぐるみがワンダーコアに座っている画像データが確認できました。

カメラのレンズの位置を調整するのが面倒なのが難点ですが、ある程度明瞭な画像データが確認できので動作確認を楽しむことができました。

PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。

ソースコードをダウンロード

ファイルの拡張子をtxtからinoに変更するかファイルの内容をコピーすると使用できます。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

広告

最後まで、読んでいただきありがとうございました。

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