タイムラプスをやってみる

前回までで、ESP32CAMで写真が撮れる様になったので、今回はその応用として、”タイムラプス”をやって見ます。方針は、

  • タイマー機能を使用して、一定時間で割り込みを発生
  • 割り込み処理で写真の撮影とデータの保存
  • これを規定回数繰り返す

先ずは、必要な関数を調べる。

タイマー機能

  • hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp);
    • タイマーの初期設定 
    • —- 引数 ——-
      • uint8_t num:タイマー番号。0~3。
      • uint16_t divider:分周比。クロック周波数は80MHz。divider=80 -> 80/80M -> 1E-6 sec
      • bool countUp: true -> up / false -> down
    • — 戻り値 —
      • タイマーのハンドラ
  • void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload);
    • タイマーの設定値(割り込みのタイミング)を設定
    • —- 引数 ——-
      • hw_timer_t *timer:タイマーのハンドラ
      • uint64_t alarm_value:タイミング設定値
      • bool autoreload:自動再起動 -> true
  • void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge);
    • タイマー割り込み実行関数登録
    • —- 引数 ——-
      • hw_timer_t *timer:タイマーのハンドラ
      • void (*fn)(void):割り込み処理関数ポインタ(引数無し)
      • bool edge:割り込みのタイプ。ture -> edge / false -> level
  • void timerAlarmEnable(hw_timer_t *timer);
    • タイマ(割り込み)を開始 
    • —- 引数 ——-
      • hw_timer_t *timer:タイマーのハンドラ
  • void timerWrite(hw_timer_t *timer, uint64_t val);
    • タイマーの値(現在値)を設定する。
    • —- 引数 ——-
      • hw_timer_t *timer:タイマーのハンドラ
      • uint64_t val:スタート時間現設定値。
  • void timerEnd(hw_timer_t *timer);
    • タイマーを止める。
    • —- 引数 ——-
      • hw_timer_t *timer:タイマーのハンドラ

割り込み処理関係


portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTimer() 
    {
      portENTER_CRITICAL_ISR(&timeMux);
      interruptCounter++;
      portEXIT_CRITICAL_ISR(&timeMux);
    }
  • void IRAM_ATTR onTimer()
    • ISR関数
      • 引数を伴わないvoid型
      • IRAM_ATTR属性が必要
    • 関数内での処理
      • portENTER_CRITICAL_ISR(&timeMux); と portEXIT_CRITICAL_ISR(&timeMux);で挟まれた内部に記述
      • 例では、interruptCounter++;
      • 処理は最小限に抑え、処理時間を極力短くする。

スケッチ作成

これらの関数を使ってスケッチを作成しました。


#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h"                 // SD Card ESP32
#include "SD.h"                 

// Pin definition for CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

#define sd_sck  14
#define sd_mosi 15
#define sd_ss   13
#define sd_miso  2

volatile int i_num=0;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t * timer = NULL;

void IRAM_ATTR onTimer() 
{
  portENTER_CRITICAL_ISR(&timerMux);
    i_num ++;
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() 
{
  Serial.begin(115200);

  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; 
  config.frame_size = FRAMESIZE_UXGA; 
  config.jpeg_quality = 10;
  config.fb_count = 2;

  // Init Camera
  esp_camera_init(&config);

  timer = timerBegin(0, 8000, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 50000, true);
  timerAlarmEnable(timer);

  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  Serial.printf("Start\n");
}

void loop() {
  int t_num=1;
  String path;
  File file;
  camera_fb_t * fb = NULL;
    
  while(t_num != 5)
  {
    if(t_num != i_num)
    {
      // Take Picture with Camera
      fb = esp_camera_fb_get();  
  
      // Path where new picture will be saved in SD Card
      path = "/picture" + String(t_num) +".jpg";
    
      SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);
      SD.begin(sd_ss);
  
      // save (image)
      file = SD.open(path.c_str(), FILE_WRITE);
      file.write(fb->buf, fb->len); 
      file.close();
  
      esp_camera_fb_return(fb);
     
      t_num=i_num;
    
      digitalWrite(4, HIGH);
      delay(10);
      digitalWrite(4, LOW);
    }
  }

  Serial.printf("End\n");
  while(1) ;  
  • EEPROM関係を削除しました。
  • 30行:volatile int i_num=0;
    • 割り込み回数を、i_numでカウントする。
    • volatile属性は必須。これが無いと誤動作する。
  • 31行:portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
    • 割り込み関数の中で使用する変数の宣言
    • ほぼお約束?
  • 32行:hw_timer_t * timer = NULL;
    • タイマーハンドラの宣言
  • 34から39行:割り込み処理関数宣言
    • 処理の内容は、i_numの値を1つ増やす。
    • i_numの値が割り込み回数。
  • 73行から76行:タイマーの設定と割り込み関連付け
    • timerBegin()の2つ目に引数は、uint16_t 。基本周波数が80MHzなので80000を指定すればタイマーの周期が1msecとなるが、80000はuint16_t型ではオーバーフロー。2^16 = 65536 以上の値はセット出来ない。
    • 74行で割り込み関数を onTimerに指定
    • 73と75行で割り込み時間を5秒にセット
    • 76行で割り込み開始
  • 84行:int t_num=1;
    • メインルーティンでは、これとi_numの値を比較して割り込みの発生を判断
  • 87行:camera_fb_t * fb = NULL;
    • カメラ用ハンドラの宣言
  • 89行:while(t_num != 5)
    • 撮影の繰り返し回数。
  • 91行:if(t_num != i_num)
    • 割り込みを判断し、写真を撮影、SDに保存

スケッチでは、撮影終了を知らせるために、Flashを1回点灯させています。このスケッチを実行して下さい。SDに、”pictureX.jpg”、(Xが0から5)の6つのファイルが保存されているはずです。後はそれらを再生すれば、タイムラプスになるはずです。