こんにちは、ENGかぴです。
Grove-Serial Camera Kitはjpg(jpeg)の画像を生成することができます。画像データはパッケージサイズに分割して管理されており、シリアル通信で取得することができます。画像データを取得する方法をまとめました。
Seeeduino XIAO(以下、Seeed XIAOとする)で取得した写真データをSDカードに保存して動作確認を行いました。Seee XIAO用の拡張ボード(Seeed Studio)に実装されているSDカードを使用します。
Seeed XIAOを使って動作確認を行ったことを下記リンクにまとめています。
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
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:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
動作確認の全体構成

Seeed XIAOの拡張基板を使用するOLED及びSDカードスロットは実装されています。下記記事にOLEDライブラリ追加の方法をまとめています。
Seeeduino XIAOの拡張ボードのOLEDを使用する
SDカード用のチップセレクトが必要なためD2ピンを出力ピンで使用します。下記記事にSDカードライブラリの使用方法をまとめています。
Seeeduino XIAOの拡張ボードでSDカードのデータを表示
Grove Mech keycapのボタンでSnapShotするためD0を入力ピンで使用します。拡張基板のD0のシルク印刷されているGrove端子を使用します。
CameraはDC5VのGroveなので、Seeed XIAO(DC3.3V)のGrove端子に接続できません。問題なく動作しているように見えますが電圧不足により画像のデータが欠損していたりjpeg画像が取得できないなどの問題が発生します。
Seeed XIAOのUSB電圧がDC5.0VなのでCameraの電源に使用します。CameraとSeeed XIAOは電圧レベルが異なるため信号の電圧を変換するコンバータ(AE-FXMA2102:秋月電子で購入)を介して接続します。
AE-FXMA2102はI2Cバス用双方向電圧レベル変換モジュールと記載されていますが、2本の信号線のレベル変換であればI2Cバス以外でも使用することができます。
PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
Cameraの動作フロー

Cameraのボーレート(シリアル通信の通信速度)を調整するためフローチャートの処理を行う前にSYNCコマンドを送信してCameraのボーレートをSeeed XIAOのボーレートと合わせます。ボーレートの調整後のフローチャートについて説明します。
Cameraの初期化は各項目のコマンドを送信して行います。Initial JPEG preview VGAは写真のフォーマットを指定するものです。
Set Package Sizeは画像データのパッケージ分割のサイズを指定します。例では512byte ですが、128バイトのサイズを指定します。
Seeed XIAOは一時保存のバッファのサイズがデフォルトで256バイトのため、128バイトを指定することでオーバーフローしないように管理できます。
Initial JPEG preview VGAとSet Package SizeはSnapShotの条件を変更しない限り設定する必要がないため初期化時のみのコマンドを送信しています。
SnapShotから画像データを取得までの手順がSnapshotからACK(package ID:F0F0h)続きます。その後、次のSnapshotを受け付けるまで Package Size終了後の位置で待機します。
Cameraを初期化する
初期化はコマンドの送信とACKの確認を同時に行う方法で処理します。
/* JPEG preview VGA */
void Jpegpreview(void){
char cmd[] = { 0xaa, 0x01, 0x00, 0x07, 0x07, PIC_FMT };
unsigned char resp[6];
Serial1.setTimeout(100);
while(1){
clearRxBuf();
Serial1.write(&cmd[0],sizeof(cmd));
if (Serial1.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;
}
}
}
Serial1クラスのsetTimeout()関数はreadBytes()関数で受信待ちする最大時間です。readBytes()関数で指定したデータ数が取得できない場合、setTimeout()の時間処理が停滞するため応答が得られる時限から長くなりすぎないように管理します。
9行目のSerail1クラスのwrite()関数でCameraにコマンドを送信します。第1引数に送信するデータのアドレス、第2引数にデータ数を指定します。
コマンドを送信するとCameraはACKで応答するためreadBytes()関数で指定のデータ数の受信を待機します。第1引数に受信データを格納するアドレス、第2引数にデータ数を指定します。受信したデータ数が戻り値にセットされます。
戻り値が指定したデータ数と一致しない場合はタイムアウトしているため受信バッファをクリアしてもう一度コマンドを発行して応答を待ちます。
データ数が一致した場合はresp[]配列の内容を確認してCameraからの応答ACKであることが確認できればループから抜けて次の処理に進みます。
Cameraからデータを受信する
初期化以外はコマンド送信とCameraからの受信を別々に管理します。
while(Serial1.available()){
CameraRcv.buf[CameraRcv.wp] = Serial1.read(); //データをリード
if( ++CameraRcv.wp >= sizeof(CameraRcv.buf)){ //次に保管する位置を更新
CameraRcv.wp = 0;
}
}
Serial1クラスのavailable()関数でCameraから受信データの有無を確認します。データがあれば受信データをRead()関数で読み込みCameraRcv.buf[]の配列に格納します。
少量のデータであればreadBytes()関数で取得しても受信待ちで停滞する可能性は低いですが、128バイト等まとまったデータの場合は停滞する可能性が高くなります。
停滞を嫌う場合は例のようにavailable()の数だけ少しずつRead()関数でデータを格納する方法が良い場合があります。
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行目)します。
画像のデータを取得する

パッケージは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行目)してSeeed XIAOからACKを送信します。ソースコードのSendAck()関数を参照してください。
動作確認

電源を入れるとCameraの初期化を行います。OLEDの表示がPush ButtonになればSnapShotの受付ができるようになります。Grove Mech keycapのボタンを押すとCameraはSnapshotで画像を生成します。
CameraからDataサイズの応答を受けるとSeeed XIAOを起点にIDを更新してパッケージデータを要求します。複数回Snapshotで画像データを生成してSDカードを確認しました。

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

JPGファイルを確認しました。かわいいキングスライムのぬいぐるみがワンダーコアに座っている画像データが確認できました。
カメラのレンズの位置を調整するのが面倒なのが難点ですが、ある程度明瞭な画像データが確認できので動作確認を楽しむことができました。ある条件下でSnapshotして自動で画像を保存するようなシステムに応用ができそうです。
PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。