Introduction: How to Do Arduino-Controlled Intelligent Time-Lapse Photography

Project: Light-Sensitive and Adjustable Dynamic Time-Lapse Photography

By Holden Leslie-Bole

Approximate cost: $70 without the camera

I've been doing some time-lapse photography with my GoPro HERO3 for a while now, and I've gotten some great footage.  There have been times, however, when I've wanted to be able to use my DSLR to have more control over the shots that I'm getting.  I had the idea to use an infrared shutter controlled by an Arduino to be able to operate the camera remotely—I could put the circuit in a weatherproof box so that I wouldn't have to mount it directly on the camera either.

I got to thinking that if I were using an Arduino, I could add some other cool features to my time-lapse photography rig.  One problem that I've encountered when doing GoPro time-lapse is that when I set it up for a sunset shoot, if I don't time it exactly right, I may end up with a few hundred dark and uninteresting pictures.  I decided to add a photoresistor to act as a sensor of ambient light levels.  Depending on what threshold the user programs, the camera will start taking pictures when it gets light enough in a morning shot, or stop taking pictures after the sun goes down and it gets dark.  Feel free to play around with the values on this to get a light response level that suits your needs!

I also added a potentiometer to allow the user to control the interval between pictures on the fly without having to reprogram the Arduino.  This way, you could make the time lapse to speed up or slow down over the course of a shot for dramatic effect.  There is a multi-digit seven-segment display that shows whatever delay (in seconds) that the user sets.

Since many time-lapse enthusiasts are interested in adding a dynamic element to their shots, I included functionality for a servo-controlled rig that could allow the camera to rotate or pan across a scene.

This circuit is designed to be used with an infrared remote shutter. I designed this for a Nikon D3100, but any other DSLR with a remote should work just as well. In this circuit, an LED currently acts as a stand-in for the remote shutter, but adapting it for the shutter is only a matter of bypassing the button in the remote and controlling it with the Arduino. Since it is difficult to tell when an infrared transmitter is activated, the seven-segment display flashes each time a picture is taken to inform the user of its progress.

Overall, this project was designed mostly as an exercise in becoming more proficient with Arduino.  For that reason, I haven't included any instructions for mechanical dynamic rigs, but if you come up with one, please link the design in the comments!  I'd love to see it.

Step 1: Parts and Materials

Parts list:

Arduino Uno
Breadboard (or two if you need the room)
Photoresistor
Potentiometer
Jumper wires
10kΩ resistor
Servo
Camera remote shutter
4-digit 7-segment display (12 pin)
Tripod
Camera

The parts for this project, with the exception of the camera and tripod, should be under $70.  The camera will obviously be more expensive, but I am not including that in the cost of this project.

If you choose to use the servo to develop a dynamic mechanical rig, then the cost may end up over $70 as well.

Step 2: Build the Circuit

The circuit may look fairly complex in the images, but in reality it is just several components wired independently of each other on a common breadboard.  I'll break this up into each part so that it's easier to understand.

Start by running jumper wires from the 5V and GND pins on the Arduino to the red and blue rails on the breadboard.  This simplifies the circuit construction and frees up some space on the Arduino.

The multi-digit seven-segment display looks the most complicated, but it's really just a bunch of LEDs all within the same housing.  Since there are four digits, and seven segments and a decimal point for each digit, if each LED had its own pin with a common cathode, there would be 29 pins!  This is obviously more than the Arduino can support, so this display is indexed so that each segment may be illuminated by using each digit as a ground and each segment as a positive input.For example, to display a segment A on digit 3, the Arduino would set pin 2, which controls segment A to HIGH, and pin 12, which is the ground for digit 3,
to LOW.  The seven-segment display is controlled by pins 2-8 and 10-12.

Connect the Arduino and display pins using jumper wires as follows (refer to the circuit diagram if you need help):

Pin 2 = segment A
Pin 3 = segment B
Pin 4 = segment C
Pin 5 = segment D
Pin 6 = segment E
Pin 7 = segment F
Pin 8 = segment G

Pin 10 = digit 1
Pin 11 = digit 2
Pin 12 = digit 3

Next, connect the remote shutter or LED to pin 13 and GND.  My remote shutter hasn't arrived yet, but I plan to use pin 13 to replace the function of the button.  I'll post plans when I do.

Mount the servo on the breadboard.  Run a jumper from pin 9 to the signal wire of the servo, and supply 5V and GND from the rails on the breadboard to the positive and negative servo terminals.

Now, make a voltage dividing circuit with the photoresistor.  Connect one end of the photoresistor to the 5V rail on the breadboard via a jumper wire.  Run a jumper from the other terminal of the photoresistor to analog pin A0.  Also, connect a 10kΩ resistor to that sane end of the photoresistor.  Run the other end to ground.  Refer to the circuit diagram for help.
For more information, see http://en.wikipedia.org/wiki/Voltage_divider

The last element of the circuit that needs to be built is the potentiometer that acts as a sensor for the capture interval.  Potentiometers have three terminals: two that can be either positive or negative, and a wiper.  Connect the wiper (usually the middle terminal) to analog pin A1 via a jumper wire.  Connect one of the other two terminals to the 5V rail and the remaining one to the GND rail on the breadboard.

Congratulations! Your circuit is complete.


Step 3: Write the Code

Here's my code.  Feel free to tinker with different parameters to suit your application, and comment if you'd like me to explain anything.

Install the Arduino IDE from arduino.cc if you don't already have it.  Open up a new sketch and paste this code in.  Compile it, upload it, and test it.

/*

  Light-Sensitive and Adjustable Dynamic Time-Lapse Photography

This program and circuit are designed to control time-lapse photography
that is user controllable and responsive to ambient light levels.
The lighting on sunrise and sunset photo shoots can sometimes be difficult
to anticipate, so instead of ending up with hundreds of completely black
pictures, a photoresistor senses light levels and accordingly stops or starts
the photography. A potentiometer allows the user to set the delay between
photographs, and a multi-digit seven-segment display shows the current
delay value.

This circuit is designed to be used with an infrared remote shutter. I
designed this for a Nikon D3100, but any other DSLR with a remote should
work just as well. In this circuit, an LED currently acts as a stand-in for
the remote shutter, but adapting it for the shutter is only a matter of bypassing
the button in the remote and controlling it with the Arduino. Since it
is difficult to tell when an infrared transmitter is activated,
the seven-segment display flashes each time a picture is taken to inform the
user of its progress.

Since many time-lapse enthusiasts are interested in adding a dynamic element
to their shots, I included functionality for a servo-controlled rig
that could allow the camera to rotate or pan across a scene.

The circuit:
* The seven-segment display is controlled by pins 2-8 and 10-12. The display
works by using each digit as a ground and each segment as a positive input.
For example, to display a segment A on digit 3, the Arduino would set pin 2,
which controls segment A to HIGH, and pin 12, which is the ground for digit 3,
to LOW.

*The LED, or remote shutter, is connected to pin 13 and GND.

*The servo is connected to 5V and GND and receives a signal from pin 9.

*The photoresistor used in a voltage-dividing circuit in conjunction with a 10kΩ
resistor, and it is read by pin A0.

*The potentiometer is read by pin A1

Acknowledgements:
FireCGun's Instructables post on "Arduino 4 digit 7 segment display"

December 9, 2013
by Holden Leslie-Bole

I hereby declare this code public domain.

*/

#include <Servo.h> // Include the servo library
Servo cameraServo; // Create a servo object called cameraServo
const int potPin = A1; // Set analog pin A1 to read the potentiometer
const int photoresistorPin = A0; // Set analog pin A0 to read the photoresistor
int potValue = 0;
// Initialize potValue, the variable that stores the potentiometer value, to zero
int photoresistorValue = 0;
// Initialize photoresistorValue, the variable that stores the photoresistor value, to zero
int shutterDelay = 0; // This variable stores the user-inputted delay between photos
long lastPicTime = 0; // The time at which the last picture was taken
long lastServoTime = 0; // The time at which the servo last operated
long timeLapseDuration = 900000;
// For servo calibration - The expected duration of the shoot (in milliseconds)
int servoPosition = 0; // The servo's position in degrees

// This section assigns pin numbers to different items on the display
const int aPin = 2;
const int bPin = 3;
const int cPin = 4;
const int dPin = 5;
const int ePin = 6;
const int fPin = 7;
const int gPin = 8;
const int GND1 = 10;
const int GND2 = 11;
const int GND3 = 12;

const int shutterPin = 13; // pin 13 will control the shutter
int num; // The number entered by the potentiometer
int dig2Value = 0;
int dig3Value = 0;
int brightnessDelay = 3; // A delay of arbitrary quantity that controls the display brightness

// Run this method once when Arduino starts
void setup()
{
  cameraServo.attach(9); // Assign the servo to pin 9
  cameraServo.write(0); // Initialize the servo to 0
  // Set the display and shutter pins to output
  pinMode(aPin, OUTPUT);
  pinMode(bPin, OUTPUT);
  pinMode(cPin, OUTPUT);
  pinMode(dPin, OUTPUT);
  pinMode(ePin, OUTPUT);
  pinMode(fPin, OUTPUT);
  pinMode(gPin, OUTPUT);
  pinMode(GND1, OUTPUT);
  pinMode(GND2, OUTPUT);
  pinMode(GND3, OUTPUT);
  pinMode(shutterPin, OUTPUT);
  //Serial.begin(9600); // This is a useful bit of code to keep in for debugging and testing
}

// Run this method continuously
void loop()
{
  unsigned long currentTime = millis(); // Track how long the Arduino has been runnign
  potValue = analogRead(potPin); // Read the potentiometer and store it in potValue
  num = map(potValue, 0, 1023, 99, 0); // Adjust the output range to fit the delay
  shutterDelay = num*1000;
  // Since Arduino's natural time measurement is in milliseconds, this converts num
  // into a delay between photos in milliseconds
  dig2Value = num / 10; // Use integer truncation to make dig2Value the tens digit
  dig3Value = num - (dig2Value *10); // Arithmetic that delivers the ones digit

  // Set the grounds to high
  digitalWrite(GND1, HIGH);
  digitalWrite(GND2, HIGH);
  digitalWrite(GND3, HIGH);

  // Display a d for delay on digit one
  digitalWrite(GND1, LOW); // Turn it on
  d(); // See method void d()
  delay(brightnessDelay); // Wait for 3 ms so that you can see it
  digitalWrite(GND1, HIGH); // Turn it off

  digitalWrite(GND2, LOW);
  number(dig2Value); // See number(int i) method
  delay(brightnessDelay);
  digitalWrite(GND2, HIGH);

  digitalWrite(GND3, LOW);
  number(dig3Value); // See number(int i) method
  delay(brightnessDelay);
  digitalWrite(GND3, HIGH);

  servoPosition = cameraServo.read();
  // Set the servoPosition to be whatever is currently is in degrees

  photoresistorValue = analogRead(photoresistorPin); // Read the photoresistor
  if(photoresistorValue > 100)
  // Try different values for this. I chose 100 just based on experimentation.
  {
    if(currentTime - lastPicTime > shutterDelay)
    // Assess whether the shutter delay has elapsed since the last capture
    {
      // Take the picture
      digitalWrite(shutterPin, HIGH);
      lastPicTime = currentTime;
      delay(200);
      digitalWrite(shutterPin, LOW);
    }

    // Assess the servo activity
    if(currentTime - lastServoTime > timeLapseDuration/180 && servoPosition < 180)
    {
      servoPosition++;
      cameraServo.write(servoPosition); // Move the servo
      lastServoTime = currentTime;
    }
  }
}

// This method uses switch/case notation to call the methods for the digits in response to an integer input
void number(int i){
  switch(i){
    case 1: one();
    break;
    case 2: two();
    break;
    case 3: three();
    break;
    case 4: four();
    break;
    case 5: five();
    break;
    case 6: six();
    break;
    case 7: seven();
    break;
    case 8: eight();
    break;
    case 9: nine();
    break;
    default: zero();
    break;
  }
}

// This method displays the d for delay
void d()
{
  digitalWrite( aPin, LOW);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, HIGH);
  digitalWrite( fPin, LOW);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit one
void one()
{
  digitalWrite( aPin, LOW);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, LOW);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, LOW);
  digitalWrite( gPin, LOW);
}

// This method displays the digit two
void two()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, LOW);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, HIGH);
  digitalWrite( fPin, LOW);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit three
void three()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, LOW);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit four
void four()
{
  digitalWrite( aPin, LOW);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, LOW);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit five
void five()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, LOW);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit six
void six()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, LOW);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, HIGH);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, HIGH);
}

void seven()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, LOW);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, LOW);
  digitalWrite( gPin, LOW);
}

// This method displays the digit eight
void eight()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, HIGH);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit nine
void nine()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, LOW);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, HIGH);
}

// This method displays the digit zero
void zero()
{
  digitalWrite( aPin, HIGH);
  digitalWrite( bPin, HIGH);
  digitalWrite( cPin, HIGH);
  digitalWrite( dPin, HIGH);
  digitalWrite( ePin, HIGH);
  digitalWrite( fPin, HIGH);
  digitalWrite( gPin, LOW);
}

Step 4: Set Up Your Camera

My camera is a Nikon D3100.  If you have a different model, you may have to look at the instruction manual or play around with the shutter and camera until you get a setup that works.  I found a pushbutton remote shutter for my camera, but unfortunately it hasn't arrived yet.  When it does, I imagine that I'll be able to open it up and hack it to bypass the button and instead control it with the Arduino.  I'll post an update when I do this.

If you have the circuit working in a manner that satisfies your needs, you may want to make a permanent setup with perfboard and soldering.  At that point, you could make a weatherproof enclosure as well and keep the camera mounted by itself on the tripod.

You may also be interested in developing a dynamic time-lapse photography rig at this point like the one below.  There are other tutorials online for this, and if you have any success, I'd be interested in hearing about it.

Set your camera up, make sure to charge the battery, and have fun!