hiro42のアイコン画像
hiro42 2022年09月26日作成 © MIT
製作品 製作品 閲覧数 718
hiro42 2022年09月26日作成 © MIT 製作品 製作品 閲覧数 718

留守の時、玄関を電源コードレスSpresenseが見守ります。

留守の時、玄関を電源コードレスSpresenseが見守ります。

概要

留守の時、玄関をカメラで記録する
本来は騒音や異常に大きい音で割り込みをかける予定でしたが想定よりハードルがあった為、何かしらの条件によってカメラで記録する方針へ変更しました。
カメラでの記録やそのためのライブラリが豊富だったので初心者の自分でも馴染みやすく嬉しかったです。
今回実装できなかった部分はコンテスト後も挑戦していきたいと思います!
ゆっくりと見ていってください。

部品

img 材料 金額 個数
Spresense メインボード M-14584 約6000円 1個
Spresense 拡張ボード M-14585 約3800円 1個
Spresense カメラボード M-14586 約3800円 1個
Mic&LCD KIT for SPRESENSE M-16589 約7800円 1個
ArduEZ ONE M-17101 約850円 1個
RPi UPSPack V3 Product Amazon 約3100円 1個

仕様と組み立て

  • 出かける時にボタン4を押す
  • 押されたことを検知し
  • 設定した時間が経過するまで設定した撮影間隔(分)カメラで記録する

組み立てはLCDが拡張ボードと水平になるように"ArduEZ ONE"のPinを曲げています。
LCDを使用するにあたって拡張ボードの電源PINをデフォルトの5Vから3Vに差し替えてください。
キャプションを入力できます

キャプションを入力できます

またRPI用のUPSを最近使用していなかったので、SPRESENSEのバッテリーとして使用してます。

前準備

"Mic&LCD KIT for SPRESENSE"に付属しているLCDを確認用に使用するためライブラリのインストールが必要です。

以下2つのライブラリをzipでダウンロードしArduinoIDEにて"zip形式のライブラリをインストール"からインストールしてください。
https://github.com/kzhioki/Adafruit_ILI9341
https://github.com/kzhioki/Adafruit-GFX-Library
キャプションを入力できます

ソースコード

mainCore.ino

#include <Camera.h> #include <SPI.h> #include <SDHCI.h> #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" #define TFT_RST 8 #define TFT_DC 9 #define TFT_CS 10 /* MAX撮影枚数(1分間隔*6時間(1分*60分*6時間=360枚))と撮影間隔(分)*/ #define TAKE_PIC_MAX 360 #define SHOOTING_INTERVAL_MINUTE 1 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS ,TFT_DC ,TFT_RST); SDClass theSD; bool bButton4Pressed = false; bool bButton5Pressed = false; int gCounter = 0; void changeState4 () { bButton4Pressed = true; } void changeState5 () { bButton5Pressed = true; } void CamCB(CamImage img){ if (img.isAvailable()) { img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); tft.drawRGBBitmap(0, 0, (uint16_t *)img.getImgBuff(), 320, 240); if (bButton4Pressed) { tft.setTextSize(4); tft.setCursor(20, 200); tft.setTextColor(ILI9341_RED); tft.println("Rec Start"); } } } void SaveCam(int Counter){ /*** * 引数で時間を受け取り * 受け取った時刻をファイルネームに付け写真を保存する関数 */ digitalWrite(LED0, HIGH); theCamera.startStreaming(false, CamCB); /* カメラインスタンスの生成?*/ CamImage img = theCamera.takePicture(); /* カメラ画像の保存処理*/ if (img.isAvailable()) { char filename[16] = {0}; sprintf(filename, "PICT_%03d.JPG",Counter); File myFile = theSD.open(filename,FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); Serial.println("Saved Picture"); } theCamera.startStreaming(true, CamCB); bButton4Pressed = false; digitalWrite(LED0, LOW); } void ModeRec(int Minutes, int MAX = 60){ /*** * * (引数の値)分置きに写真を撮影し保存する関数 */ int takeCounter = 0; do{ digitalWrite(LED0, HIGH); theCamera.startStreaming(false, CamCB); /* カメラインスタンスの生成?*/ CamImage img = theCamera.takePicture(); /* カメラ画像の保存処理*/ if (img.isAvailable()) { char filename[16] = {0}; char savetext[10] = {0}; sprintf(filename, "PICT_%03d.JPG",takeCounter); File myFile = theSD.open(filename,FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); sprintf(savetext, "saved %03d",takeCounter+1); Serial.println(savetext); } theCamera.startStreaming(true, CamCB); bButton4Pressed = false; digitalWrite(LED0, LOW); delay(Minutes*60*1000); ++takeCounter; }while(takeCounter<MAX); Serial.println("End ModeRec"); } int intPin4 = 4; int intPin5 = 5; void setup() { pinMode(LED0, OUTPUT); pinMode(intPin4, INPUT_PULLUP); pinMode(intPin5, INPUT_PULLUP); theSD.begin(); Serial.begin(115200); tft.begin(); tft.setRotation(3); theCamera.begin(); theCamera.startStreaming(true, CamCB); theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H ,CAM_IMGSIZE_QUADVGA_V ,CAM_IMAGE_PIX_FMT_JPG); attachInterrupt(digitalPinToInterrupt(intPin4) ,changeState4 ,FALLING); attachInterrupt(digitalPinToInterrupt(intPin5) ,changeState5 ,FALLING); } void loop() { /* port4と5のボタンが押されたかどうか*/ /* 押されなかったら何もせずloop*/ if (!bButton4Pressed && !bButton5Pressed) { return; }else if (bButton4Pressed){ Serial.println("button number4 was pressed"); Serial.println("Mode Rec Start"); /* 第一引数: 撮影間隔 分 */ /* 第二引数: 撮影回数 */ ModeRec(SHOOTING_INTERVAL_MINUTE,TAKE_PIC_MAX); // SaveCam(gCounter); // ++gCounter; }else if (bButton5Pressed) { bButton5Pressed = false; Serial.println("button number5 was pressed"); } }

コードの説明

ベースになっているのはチュートリアルで公開されている以下のサンプルプログラムです。
https://github.com/TE-YoshinoriOota/Spresense-Tech-Seminar-Basic/blob/master/Sketches/Spresense_camera_take_picture/Spresense_camera_take_picture.ino

今回、出かける際にボタン4を押すことで元々設定している

  • 撮影間隔(分)
  • 撮影時間

の分だけカメラで記録するプログラムにしています。
そのため以下の関数を追加しています。

void ModeRec(int Minutes, int MAX = 60){ /*** * * (引数の値)分置きに写真を撮影し保存する関数 */ int takeCounter = 0; do{ digitalWrite(LED0, HIGH); theCamera.startStreaming(false, CamCB); /* カメラインスタンスの生成?*/ CamImage img = theCamera.takePicture(); /* カメラ画像の保存処理*/ if (img.isAvailable()) { char filename[16] = {0}; char savetext[10] = {0}; sprintf(filename, "PICT_%03d.JPG",takeCounter); File myFile = theSD.open(filename,FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); sprintf(savetext, "saved %03d",takeCounter+1); Serial.println(savetext); } theCamera.startStreaming(true, CamCB); bButton4Pressed = false; digitalWrite(LED0, LOW); delay(Minutes*60*1000); ++takeCounter; }while(takeCounter<MAX); Serial.println("End ModeRec"); }

分かっている問題点

カメラを物理的に逆さまに配置してしまったのでコード内のtft.setRotation(1);でLCD表示の向きを強引に変更し表示していますが、保存されている画像は上下逆になっているため改善が必要です。
また、現状ではスレッド処理やメインコア/サブコア処理が実装できてないため、撮影処理が始まると電源を切る以外止める方法がありません。

今後の発展

WiFi Add onボードも購入したのですが今回はそこまで触れませんでした。
ネットワークにつながると今度はWebUIからの操作や結果データの引き上げなど色々実装できそうだと感じています。
SPRESENSE Cameraで保存した画像がJPGに圧縮されているのもIoTネットワークと相性がいいと思います。

最後に

今回SPRESENSEボードを初めて触りましたが、ArduinoやRasPiとは違うSONYらしいボードだと思いました。
特に興味を惹かれたのが、メインコアの他にサブコアが存在し、それぞれに別のスケッチを書き込む事が出来ることです。
コンテスト投稿には間に合いませんでしたが、ボタン動作などのUI/UX操作をメインコアにし、各処理をサブコアで処理など明らかに今までのマイコンとは違うプログラミングが出来ると思います。

またカメラについてですが、ストリーミングの配信こそ出来ないものの、エッジ開発や確認用ストリーミング用途では省電力なのに驚くほどフレームレートが出ており、正直マイコンの域を飛び出ていると感じました。

アイディアとしては今後、電池で動くSPRESENSEを用いたギターの自動チューニング(弦を巻く)など作れそうだなぁと淡い期待を持っています。

  • hiro42 さんが 2022/09/26 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する