気の向くままに辿るIT/ICT/IoT
IoT・電子工作

ESP32-CAM/deep sleep/PIRセンサー/ext0復帰で映像表示

ホーム前へ次へ
ESP8266って?

ESP32-CAM/deep sleep/PIRセンサー/ext0復帰で映像表示

ESP32-CAM/deep sleep/PIRセンサー/ext0復帰で映像表示

2021/10/10
ESP32-CAMとタヌキのベル

 Wi-Fi(wifi)モジュールESP8266/ESP32のバリエーションでカメラ付きESP32-SベースのESP32-CAMボードで電池駆動も見据えディープスリープしつつ、PIR/人感センサーHC-SR501で動体検知、ストリーミング再生してみました。

 これはドアフォンや宅内ビデオ通信の自作を想定した途中経過に過ぎず、ESP32-CAMを買った動機でもあります。

 当初は、検証のみと考えていましたが、技適済みESP32チップをリフローすることもできることから、これができれば日本でも実用品として使用可能になりますね。

 というか、ESP32-CAMみたいなものが早々に技適に対応することを切に願う今日この頃。

 nRF24L01+でも書きましたが、こういう面白くて安価なものほど即使える体制を整えて欲しい。

 せっかくラズパイやESP32は早々に技適対応してくれているのだから、ESP32-Sも...(ESP32-CAMを使ったM5Cameraは技適済みなんですけどね...)。

 小学生からプログラミング必修とか、多少なりとも相対的にIoT自作人口も増えるはず。

 ソフトウェアだけじゃなく、ハードウェアも力を入れないと、それが無理なら輸入品も含めて気兼ねなく使えるように体制を。

 いざ興味をもって、やってみよう、作ってみようと思っても法律の壁で使えない、国産を調達しようと思うと異様に価格が高いという状況じゃ、伸びるものも伸びないと思いますし、こんなことだからITでもIoTでも日本は何かと遅れをとるんじゃないかと。

 1つ、2つだけ作って終わり...というなら多少高くてもと言う人も多いかもしれませんが、商売ではなく、興味本位で幅広く、あれもこれもやりたい、作りたいと思うと都度高額な費用をかけられる人は極々限られ、盛り上がるものも盛り上がらないと思いますし、そこから商機を見出す起業家の卵の可能性、ひいては産業育成機会をも潰していると思うんですよね。

[2022/06/26]

 ESP32-CAMと同価格帯でESP32 WROVER IE + OV2460カメラを購入、が、他ESP32ボードの技適番号が刻印されている偽...。

[2023/08/04]

 ちょっとお高いですが、技適番号201-220052が刻印されているカメラ付きESP32-S3-WROOM-1開発ボードを購入。

ESP32-CAMとブレッドボード回路

 さておき、当然、ESP32-CAMを買った時点でWebカメラとして動作確認済みなわけですが、当時はチップが熱を持つこと、それもあって(M5Stack/)M5Camera含め、ESP32-CAMメーカーも短時間の利用を想定していたこともあり、単なるライブ映像では使いみちがありません。

 また、電池駆動を前提に考えるとESP32で消費電力の最も低いディープスリープが使えないと意味がありません。

 そんな折、つい先日、気づけば、Arduino IDE対応ボードにAI Thinker ESP32-CAMが追加されており、Arduino OTAが使えるようになっていたり、以前のように時間経過でチップの熱が急上昇することもないことを確認。

 よってWebカメラ張りに使っても大丈夫そうな雰囲気を醸し出しまくり上がり。

ESP32-CAMとPIRセンサー回路外観

 他方、ディープスリープとmicroSDカードへの写真保存などはネットに情報があるけど、ストリーミングはないよねとあぐねていたところでした。

 が、今日、ふと懸案だったディープスリープと外部割り込み・外部復帰で映像ストリーミングする単純な方法を思いついちゃいました。

 外部入力の状態をチェックしてHIGHまたはLOWである間、whileで回せば、その間、ディープスリープしなくて済むじゃんということに気づいちゃったわけです。

 しかも併用しようと考えていたPIRセンサーは、検出中の時間を決められるため、まさに都合が良いじゃんかと。

材料・段取り・回路から実行まで

 材料と準備、プログラムのアップ、実行については、ESP32-CAM動作確認にある通りで追加で必要なのは、次の通り。

 プログラム(スケッチ)アップ時と実証実験時で電源を替えるなら、電源ほか。

 電池駆動ならMOSFETの方がより良いとは思いますが、PIRセンサーの出力はトランジスタで増幅などしないといけませんでした。

スケッチ

 今回は、PIR・人感・焦電赤外線センサー1つなのでディープスリープからの復帰には、ext0を使いましたが、復帰に複数のセンサー値を使いたい場合は、ext1を使うことができます。

 というか、ext1が、今ひとつ上手くいかなかったので今回、ext0に留めることにました。([2021/10/11]と思ったら、全ての不具合、エラーは、配線、特に5V/GNDのジャンパワイヤの接触不良が原因でした。よってext1復帰の記事もアップ。)

// ESP32-CAM+PIRセンサーでディープスリープから復帰
#include "esp_camera.h"
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <ESPmDNS.h>
#include <Arduino.h>
#include <FS.h>
#include "soc/soc.h"     // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
 
RTC_DATA_ATTR int bootCount = 0; // RTCスローメモリに変数を確保
 
//
// WARNING!!! Make sure that you have either selected ESP32 Wrover Module,
//     or another board which has PSRAM enabled
//
 
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER
 
#include "camera_pins.h"
 
const char* ssid = "Wi-Fi_SSID";
const char* password = "Wi-Fi_PASSPHRASE";
 
void startCameraServer();
 
const char common_name[40] = "esp32cam";
const char *OTAName = common_name;    // A name and a password for the OTA service
const char *mdnsName = common_name;    // Domain name for the mDNS responder
 
void print_wakeup_reason(){
 esp_sleep_wakeup_cause_t wakeup_reason;
 
 wakeup_reason = esp_sleep_get_wakeup_cause();
 
 switch(wakeup_reason)
 {
  case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
  case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
  case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
  case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
  case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
  default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
 }
}
 
void startWiFi() { // Start a Wi-Fi access point, and try to connect to some given access points. Then wait for either an AP or STA connection
 // WiFi.softAP(ssid, password);      // Start the access point
 WiFi.mode(WIFI_STA);      // Start the access point
 WiFi.begin(ssid, password);      // Start the access point
 while ( WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
 }
 Serial.println("");
 Serial.print("SSID \"");
 Serial.print(ssid);
 Serial.println("\" started\r\n");
 
 Serial.print("Connected to ");
 Serial.println(ssid);
 
 Serial.print("IP address: ");
 Serial.println(WiFi.localIP());
 
 Serial.print("hostname : ");
 // Serial.println(WiFi.hostname());
 Serial.println("");
}
 
void startMDNS() { // Start the mDNS responder
 MDNS.begin(mdnsName);         // start the multicast domain name server
 Serial.print("mDNS responder started: http://");
 Serial.print(mdnsName);
 Serial.println(".local");
}
 
void startOTA() { // Start the OTA service
 ArduinoOTA.setHostname(OTAName);
 // ArduinoOTA.setPassword(OTAPassword);
 
 ArduinoOTA.onStart([]() {
  Serial.println("Start");
  // turn off the LEDs
  for (int i = 0; i < 6; i++) {
   //digitalWrite(LED_BUILTIN, HIGH);
   //digitalWrite(LED_BUILTIN, LOW);
  }
 });
 ArduinoOTA.onEnd([]() {
  Serial.println("\r\nEnd");
 });
 ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
  Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
 });
 ArduinoOTA.onError([](ota_error_t error) {
  Serial.printf("Error[%u]: ", error);
  if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
  else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
  else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
  else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
  else if (error == OTA_END_ERROR) Serial.println("End Failed");
 });
 ArduinoOTA.begin();
 Serial.println("OTA ready\r\n");
}
 
void setup() {
 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 Serial.begin(115200);
 Serial.setDebugOutput(true);
 Serial.println();
 
 camera_config_t config;
 config.ledc_channel = LEDC_CHANNEL_0;
 config.ledc_timer = LEDC_TIMER_0;
 config.pin_d0 = Y2_GPIO_NUM;
 config.pin_d1 = Y3_GPIO_NUM;
 config.pin_d2 = Y4_GPIO_NUM;
 config.pin_d3 = Y5_GPIO_NUM;
 config.pin_d4 = Y6_GPIO_NUM;
 config.pin_d5 = Y7_GPIO_NUM;
 config.pin_d6 = Y8_GPIO_NUM;
 config.pin_d7 = Y9_GPIO_NUM;
 config.pin_xclk = XCLK_GPIO_NUM;
 config.pin_pclk = PCLK_GPIO_NUM;
 config.pin_vsync = VSYNC_GPIO_NUM;
 config.pin_href = HREF_GPIO_NUM;
 config.pin_sscb_sda = SIOD_GPIO_NUM;
 config.pin_sscb_scl = SIOC_GPIO_NUM;
 config.pin_pwdn = PWDN_GPIO_NUM;
 config.pin_reset = RESET_GPIO_NUM;
 config.xclk_freq_hz = 20000000;
 config.pixel_format = PIXFORMAT_JPEG;
 //init with high specs to pre-allocate larger buffers
 if(psramFound()){
  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = 10;
  config.fb_count = 2;
 } else {
  config.frame_size = FRAMESIZE_SVGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;
 }
 
#if defined(CAMERA_MODEL_ESP_EYE)
 pinMode(13, INPUT_PULLUP);
 pinMode(14, INPUT_PULLUP);
#endif
 
 // camera init
 esp_err_t err = esp_camera_init(&config);
 if (err != ESP_OK) {
  Serial.printf("Camera init failed with error 0x%x", err);
  return;
 }
 
 sensor_t * s = esp_camera_sensor_get();
 //initial sensors are flipped vertically and colors are a bit saturated
 if (s->id.PID == OV3660_PID) {
  s->set_vflip(s, 1);//flip it back
  s->set_brightness(s, 1);//up the blightness just a bit
  s->set_saturation(s, -2);//lower the saturation
 }
 //drop down frame size for higher initial frame rate
// s->set_framesize(s, FRAMESIZE_QVGA);
 s->set_framesize(s, FRAMESIZE_SVGA);
 s->set_vflip(s, 1);//flip it back
 
#if defined(CAMERA_MODEL_M5STACK_WIDE)
 s->set_vflip(s, 1);
 s->set_hmirror(s, 1);
#endif
 
 startWiFi();
 startMDNS();
 startOTA();
 startCameraServer();
 
 ++bootCount;
 Serial.println("Boot number: " + String(bootCount));
 print_wakeup_reason();
 
 pinMode(GPIO_NUM_13, INPUT_PULLUP);
 esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 0);
 
 while (!digitalRead(GPIO_NUM_13)) { }
 
 Serial.println("Going to sleep now");
 delay(1000);
 esp_deep_sleep_start();
 Serial.println("This will never be printed");
}
 
void loop() {
 ArduinoOTA.handle();         // listen for OTA events
}

 これでPIRセンサーが検知するとWi-Fiに接続、IPが配布され、mDNS名が振られ、mDNS.localでブラウザからアクセス、映像が表示され、PIRセンサーが検知している間、表示し続けられるようになります。

 PIRセンサーHC-SR501は、検知後の遅延時間を設定でき、ジャンパピンを挿す位置で検知中の再検知をするかしないか設定できるので再検知するようにしておけば、遅延時間に加え、その間、ストリーミング表示されるようになります。

ESP32-CAMメニューなし映像

 尚、デフォルトでメニューを隠して映像のみ表示するようにしてあります。

ESP32-CAMメニューあり映像

 ハンバーガー(横三本線)メニューボタン押下でメニューを表示することもできます。

 ちなみに以前は、ESP32-CAMのサンプルスケッチだとESP32-CAMを横向きにした状態で天地方向が合ってしまいV-Flipでカメラ(ESP32-CAM)を縦にした状態にすることができたのですが、AI Thinker ESP32-CAMボードが追加になってから、V-Flipをいじっても横にしかならなくなりましたが、V-Flip機能としては、こっちの方が正常なのかも。

備考

 これだとHC-SR501が不検出状態となった時点で映像が止まる...、それを以て閉じる...、何に使えるかにもよりますが、そうなるタイミングを見て前もってブラウザ(タブ)を閉じるとかになるかな。

ホーム前へ次へ