Arduinoにjpegカメラを付けて一定間隔で撮影した画像をSDカードに保存しました

一定間隔で撮影ができるの?

  • 一定間隔で撮影した画像ファイルを都度SDカードに書き込みます。
  • Arduinoにjpegカメラを接続してSDカードに画像を保存するまでを[Arduinoでjpegカメラの画像をSDカードに保存しました]でまとめました。このスケッチは撮影を一回だけ行い画像をSDカードに保存します。
  • ①②③を追加してjpegカメラが一定間隔で撮影した画像データをSDカードに書き込む機能を加えます。
  1. jpegカメラ用の手製のシールドPCBを作成する。
  2. 一定間隔で画像を撮影するスケッチのコードを追加する。
  3. jpeg画像ファイルの作成時間を設定するスケッチのコードを追加する。

必要な部品・器具は?

jpeg カメラ1個
データ ロガーシールド1個
Arduino UNO R31個
抵抗 [10KΩ] (秋月電子通商のjpegカメラの部品に付属していました。)2個
手作りシールド用PCB (秋月電子通常でArduino UNO用のPCBを購入)1個

jpegカメラ用のシールドPCBを作りました

  • jpegカメラをブレッドボードを介してArduino UNO R3に接続していますが、カメラがきちんと固定されていないので長時間撮影向きではありません。回路の変更はありませんが、実際の配線をワイヤー接続でなく、手作りシールドPCBのスロットにjpegカメラを取り付けて接続する方法に変更します。Arduino UNO R3に接続して横置きにすれば上下左右が正しくなるようにjpegカメラを取り付けています。これで撮影時にテーブルの上に置いても安定します。バッテリー駆動で野外で撮影してもそんなにブレないだろうと目論んでいます。
  • jpegカメラは2.54mmのピンヘッダを取り付けてブレッドボード経由でArduino UNO R3と接続していましたが、カメラの固定が難しいです。
  • jpegカメラ用シールドPCBを手作りしてjpegカメラをスロットに取り付けます。ワイヤーが無くなるので設置後の振れが無くなり撮影が安定します。

一定間隔で画像を撮影する

  • jpegカメラで一定間隔で撮影し画像データをデータロガーシールドのSDカードに保存する機能をスケッチに追加します。
  • [Arduinoでjpegカメラの画像をSDカードに保存しました]では撮影を1回行って画像データをSDカードに保存しています。これを一定間隔で画像を撮影し画像データをSDカードに追加で保存する機能に変更します。Adafruit社のjpegカメラ用のスケッチ[Snapshot.ino]を編集・追加して作成しました。

jpeg画像ファイルの作成時間を設定する

  • 保存したjpeg画像ファイルのタイムスタンプは適当な日時を設定しないと[20001/01/01 1:00]になります。データロガーシードにはRTC(実時間)モジュールが実装されているので時計機能があります。この時間データを、タイムスタンプに実装します。Adafruit社のjpegカメラ用のスケッチ[Snapshot.ino]を編集・追加して作成します。
// This is a basic snapshot sketch using the VC0706 library.
// On start, the Arduino will find the camera and SD card and
// then snap a photo, saving it to the SD card.
// Public domain.

// If using an Arduino Mega (1280, 2560 or ADK) in conjunction
// with an SD card shield designed for conventional Arduinos
// (Uno, etc.), it's necessary to edit the library file:
//   libraries/SD/utility/Sd2Card.h
// Look for this line:
//   #define MEGA_SOFT_SPI 0
// change to:
//   #define MEGA_SOFT_SPI 1
// This is NOT required if using an SD card breakout interfaced
// directly to the SPI bus of the Mega (pins 50-53), or if using
// a non-Mega, Uno-style board.

#include <Adafruit_VC0706.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <DS3231.h>

#if defined(__AVR__) || defined(ESP8266)

// On Uno: camera TX connected to pin 2, camera RX to pin 3:
#include <SoftwareSerial.h>         
SoftwareSerial cameraconnection(2, 3);
// On Mega: camera TX connected to pin 69 (A15), camera RX to pin 3:
//SoftwareSerial cameraconnection(69, 3);

#else
// On Leonardo/M0/etc, others with hardware serial, use hardware serial!
// Using hardware serial on Mega: camera TX conn. to RX1,
// camera RX to TX1, no SoftwareSerial object is required:
#define cameraconnection Serial1

#endif

Adafruit_VC0706 cam = Adafruit_VC0706(&cameraconnection);


// SD card chip select line varies among boards/shields:
// Adafruit SD shields and modules: pin 10
// Arduino Ethernet shield: pin 4
// Sparkfun SD shield: pin 8
// Arduino Mega w/hardware SPI: pin 53
// Teensy 2.0: pin 0
// Teensy++ 2.0: pin 20
#define chipSelect 10

// Pins for camera connection are configurable.
// With the Arduino Uno, etc., most pins can be used, except for
// those already in use for the SD card (10 through 13 plus
// chipSelect, if other than pin 10).
// With the Arduino Mega, the choices are a bit more involved:
// 1) You can still use SoftwareSerial and connect the camera to
//    a variety of pins...BUT the selection is limited.  The TX
//    pin from the camera (RX on the Arduino, and the first
//    argument to SoftwareSerial()) MUST be one of: 62, 63, 64,
//    65, 66, 67, 68, or 69.  If MEGA_SOFT_SPI is set (and using
//    a conventional Arduino SD shield), pins 50, 51, 52 and 53
//    are also available.  The RX pin from the camera (TX on
//    Arduino, second argument to SoftwareSerial()) can be any
//    pin, again excepting those used by the SD card.
// 2) You can use any of the additional three hardware UARTs on
//    the Mega board (labeled as RX1/TX1, RX2/TX2, RX3,TX3),
//    but must specifically use the two pins defined by that
//    UART; they are not configurable.  In this case, pass the
//    desired Serial object (rather than a SoftwareSerial
//    object) to the VC0706 constructor.

//実時間モジュール設定
DS3231 clock;
RTCDateTime dt;

//ファイル作成・更新時のタイムスタンプを記録するスケッチ------------------------------
char timestamp[30];

// call back for file timestamps
void dateTime(uint16_t *date, uint16_t *time) {
 sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
 // return date using FAT_DATE macro to format fields
 *date = FAT_DATE(dt.year,dt.month,dt.day);
 // return time using FAT_TIME macro to format fields
 *time = FAT_TIME(dt.hour,dt.minute,dt.second);
}
//------------------------------------------------------------------------------

void setup() {

  // When using hardware SPI, the SS pin MUST be set to an
  // output (even if not connected or used).  If left as a
  // floating input w/SPI on, this can cause lockuppage.
#if !defined(SOFTWARE_SPI)
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  if(chipSelect != 53) pinMode(53, OUTPUT); // SS on Mega
#else
  if(chipSelect != 10) pinMode(10, OUTPUT); // SS on Uno, etc.
#endif
#endif

  //実時間モジュールの初期化
  Serial.println("Initialize RTC module");
  // Initialize DS3231
  clock.begin();
 
  //スケッチのコンパイル時の時刻をRTCモジュールに書き込む
  //1回目はこのままコンパイル実行する、
  //2回目にコメントアウトして再度コンパイル実行する
  //clock.setDateTime(__DATE__, __TIME__);    

  Serial.begin(9600);
  Serial.println("VC0706 Camera snapshot test");
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }  
  
  // Try to locate the camera
  if (cam.begin()) {
    Serial.println("Camera Found:");
  } else {
    Serial.println("No camera found?");
    return;
  }
  // Print out the camera version information (optional)
  char *reply = cam.getVersion();
  if (reply == 0) {
    Serial.print("Failed to get version");
  } else {
    Serial.println("-----------------");
    Serial.print(reply);
    Serial.println("-----------------");
  }

  // Set the picture size - you can choose one of 640x480, 320x240 or 160x120 
  // Remember that bigger pictures take longer to transmit!
  
  cam.setImageSize(VC0706_640x480);        // biggest
  //cam.setImageSize(VC0706_320x240);        // medium
  //cam.setImageSize(VC0706_160x120);          // small

  // You can read the size back from the camera (optional, but maybe useful?)
  uint8_t imgsize = cam.getImageSize();
  Serial.print("Image size: ");
  if (imgsize == VC0706_640x480) Serial.println("640x480");
  if (imgsize == VC0706_320x240) Serial.println("320x240");
  if (imgsize == VC0706_160x120) Serial.println("160x120");

  //ファイル作成・更新時のタイムスタンプを記録する------------------------------
  SdFile::dateTimeCallback(dateTime);
  sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
  //------------------------------------------------------------------------------


  Serial.println("Snap in 3 secs...");
  delay(3000);

}

void loop() {

  //実時間取得
  dt = clock.getDateTime();
	
  if (! cam.takePicture()) 
    Serial.println("Failed to snap!");
  else 
    Serial.println("Picture taken!");
  
  // Create an image with the name IMAGExx.JPG
  char filename[13];
  strcpy(filename, "IMAGE00.JPG");
  for (int i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) {
      break;
    }
  }
  
  // Open the file for writing
  File imgFile = SD.open(filename, FILE_WRITE);

  // Get the size of the image (frame) taken  
  uint16_t jpglen = cam.frameLength();
  Serial.print("Storing ");
  Serial.print(jpglen, DEC);
  Serial.print(" byte image.");

  int32_t time = millis();
  pinMode(8, OUTPUT);
  // Read all the data up to # bytes!
  byte wCount = 0; // For counting # of writes
  while (jpglen > 0) {
    // read 32 bytes at a time;
    uint8_t *buffer;
    uint8_t bytesToRead = min((uint16_t)32, jpglen); // change 32 to 64 for a speedup but may not work with all setups!
    buffer = cam.readPicture(bytesToRead);
    imgFile.write(buffer, bytesToRead);
    if(++wCount >= 64) { // Every 2K, give a little feedback so it doesn't appear locked up
      Serial.print('.');
      wCount = 0;
    }
    //Serial.print("Read ");  Serial.print(bytesToRead, DEC); Serial.println(" bytes");
    jpglen -= bytesToRead;
  }
  imgFile.close();

  time = millis() - time;
  Serial.println("done!");
  Serial.print(time); Serial.println(" ms elapsed");

  Serial.println("camera reset and wait.");    
  cam.reset();
  delay(10000);
  Serial.println("camera reset done.");  

}
一定間隔で画像データを保存できる機能を追加
  • オリジナルの[Snapshot.ino]は撮影と保存の機能をsetup()の中に書いています。loop()の中には何も書いてないので繰り返しの処理はありません。setup()の中に書かれていた撮影とSDカードへの記録のコードをloop()に移しました。
  • 繰り返し撮影するには、撮影済みの画像をクリアしなければなりません。そのためにloop()の最後に、cam.rest()を追加してJpegカメラをリセットしました。
  • 最後のdelay関数で待ち時間を多くすると撮影インターバルが長くなります。
タイムスタンプを実時間に設定する
  • オリジナルの[Snapshot.ino]はRTC(実時間)モジュールの操作のスケッチが含まれていません。[Arduinoデータロガーシールド]でRTC(実時間)モジュールとSDモジュールを内蔵したデータロガーシールドを使いました。この中で使っているRTC(実時間)モジュールを操作するコードを[Snapshot.ino]に加えました。
  • RTC(実時間)モジュールを追加するためのスケッチの変更及び追加した個所を抜き出しています。 (追加個所は全体のコードを参照してください。)
//RTC(実時間)モジュール用のヘッダファイルをインクルードする
#include <Wire.h>
#include <DS3231.h>

//実時間モジュール設定
DS3231 clock;
RTCDateTime dt;

//ファイル作成・更新時のタイムスタンプを記録する------------------------------
char timestamp[30];

// call back for file timestamps
void dateTime(uint16_t *date, uint16_t *time) {
 sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
 // return date using FAT_DATE macro to format fields
 *date = FAT_DATE(dt.year,dt.month,dt.day);
 // return time using FAT_TIME macro to format fields
 *time = FAT_TIME(dt.hour,dt.minute,dt.second);
}
//------------------------------------------------------------------------------

// setup()に以下を追加する (追加個所は全体のコードを参照の事)

  //実時間モジュールの初期化
  Serial.println("Initialize RTC module");
  // Initialize DS3231
  clock.begin();
 
  //スケッチのコンパイル時の時刻をRTCモジュールに書き込む
  //1回目はこのままコンパイル実行する、
  //2回目にコメントアウトして再度コンパイル実行する
  clock.setDateTime(__DATE__, __TIME__);    

  //ファイル作成・更新時のタイムスタンプを記録するスケッチ------------------------------
  SdFile::dateTimeCallback(dateTime);
  sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
  //------------------------------------------------------------------------------

//loop()に以下を追加する (追加個所は全体のコードを参照の事)

  //実時間取得
  dt = clock.getDateTime();

データ保存中にLEDを点灯する機能を追加しました

  • ArduinoをPCに接続している場合はシリアルモニタの表示でデータをSDカードに書き込み中であるかどうかが解ります。しかしArduino単体で使う場合はデータをSDカードに保存中かどうかが解りません。データ保存中に電源を止めるとSDカードを壊してしまうかもしれません。データ保存中にLEDを点灯して保存中であることを表示するようにしました。
  • LEDはデジタルポートのPin#7を使い、電流制限抵抗は220Ωを使いました。
  • デジタルポートのPin#7は、OUTPUTポートとして設定します。
  • スケッチにLEDを点灯させる機能を追加しました。
  • スケッチ中の英文の長い注釈は削除して見やすくしました。
  • スケッチ中のコンパイル時に不要と思われる #ifの部分を削除して見やすくしました。
// =============================================
// AdafruitのスケッチにLEDを点灯する機能を追加
// =============================================

#include <Adafruit_VC0706.h>
#include <SoftwareSerial.h> 
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <DS3231.h>

// SoftwareSerial      
SoftwareSerial cameraconnection(2, 3);

//カメラの設定
Adafruit_VC0706 cam = Adafruit_VC0706(&cameraconnection);

//SDカードのChipselectポートの設定
#define chipSelect 10

//SDカードのデータ保存中LED
#define StoringLED 7

//実時間モジュール設定
DS3231 clock;
RTCDateTime dt;

//ファイル作成・更新時のタイムスタンプの設定
char timestamp[30];

// call back for file timestamps
void dateTime(uint16_t *date, uint16_t *time) {
 sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
 // return date using FAT_DATE macro to format fields
 *date = FAT_DATE(dt.year,dt.month,dt.day);
 // return time using FAT_TIME macro to format fields
 *time = FAT_TIME(dt.hour,dt.minute,dt.second);
}

//------------------------------------------------------------------------------

void setup() {

  //SDデータ保存中LED
  pinMode(StoringLED, OUTPUT);
  digitalWrite(StoringLED, LOW); 
  
  // Chipselect ピンモードの設定
  pinMode(10, OUTPUT);

  //実時間モジュールの初期化
  Serial.println("Initialize RTC module");
  // Initialize DS3231
  clock.begin();
 
  //スケッチのコンパイル時の時刻をRTCモジュールに書き込む
  //1回目はこのままコンパイル実行する、
  //2回目にコメントアウトして再度コンパイル実行する
  clock.setDateTime(__DATE__, __TIME__);    

  Serial.begin(9600);
  Serial.println("VC0706 Camera snapshot test");
  
  //SDカードの起動
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }  
  
  //jpeg カメラの起動
  if (cam.begin()) {
    Serial.println("Camera Found:");
  } else {
    Serial.println("No camera found?");
    return;
  }
  
  // jpeg カメラのバージョンの表示
  char *reply = cam.getVersion();
  if (reply == 0) {
    Serial.print("Failed to get version");
  } else {
    Serial.println("-----------------");
    Serial.print(reply);
    Serial.println("-----------------");
  }

  // jpeg カメラの画像サイズの設定
  cam.setImageSize(VC0706_640x480);

  // peg カメラの画像サイズはjpegカメラから読み取ることも可能
  uint8_t imgsize = cam.getImageSize();
  Serial.print("Image size: ");
  if (imgsize == VC0706_640x480) Serial.println("640x480");
  if (imgsize == VC0706_320x240) Serial.println("320x240");
  if (imgsize == VC0706_160x120) Serial.println("160x120");

  //ファイル作成・更新時のタイムスタンプを記録する------------------------------
  SdFile::dateTimeCallback(dateTime);
  sprintf(timestamp, "%02d:%02d:%02d %2d/%2d/%2d \n",dt.hour,dt.minute,dt.second,dt.month,dt.day,dt.year-2000);
  //------------------------------------------------------------------------------

  Serial.println("Snap in 3 secs...");
  delay(3000);

}

void loop() {

  int j;
  //実時間取得
  dt = clock.getDateTime();
	
  if (! cam.takePicture()) 
    Serial.println("Failed to snap!");
  else 
    Serial.println("Picture taken!");
  
  // データファイル名を決める。IMGE-000.JPGを基本とし3桁の数字で決める。
  // 保存しているデータを確認し、次の番号のファイルを作成する
  char filename[13];
  strcpy(filename, "IMGE-000.JPG");
  for (int i = 0; i < 1000; i++) {
    filename[5] = '0' + i/100;
    j = i/100;
    j = i - 100*j;
    filename[6] = '0' + j/10;
    filename[7] = '0' + j%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) {
      break;
    }
  }

  // ファイルにデータを書き込む。書き込み中はLEDを点灯する
  //jpegのデータサイズを読みだし、32バイトごとに読み込む
  
  File imgFile = SD.open(filename, FILE_WRITE);
  digitalWrite(StoringLED, HIGH); 
  
  // Get the size of the image (frame) taken  
  uint16_t jpglen = cam.frameLength();
  Serial.print("Storing ");
  Serial.print(jpglen, DEC);
  Serial.print(" byte image.");

  int32_t time = millis();

  // Read all the data up to # bytes!
  byte wCount = 0; // For counting # of writes
  while (jpglen > 0) {
    // read 32 bytes at a time;
    uint8_t *buffer;
    uint8_t bytesToRead = min((uint16_t)32, jpglen); // change 32 to 64 for a speedup but may not work with all setups!
    buffer = cam.readPicture(bytesToRead);
    imgFile.write(buffer, bytesToRead);
    if(++wCount >= 64) { // Every 2K, give a little feedback so it doesn't appear locked up
      Serial.print('.');
      wCount = 0;
    }
    //Serial.print("Read ");  Serial.print(bytesToRead, DEC); Serial.println(" bytes");
    jpglen -= bytesToRead;
  }
  imgFile.close();
  digitalWrite(StoringLED, LOW); 

  time = millis() - time;
  Serial.println("done!");
  Serial.print(time); Serial.println(" ms elapsed");

  //jpegカメラをリセットし、次の撮影を可能とする
  //撮影間の時間を設定する
  Serial.println("camera reset and wait.");    
  cam.reset();
  delay(5000);
  Serial.println("camera reset done.");  

}
関連する記事は?

ご質問、誤植の指摘などありましたら。「問い合わせ 」のページからお願いします。