Making it Tick

Just a quick post. The weather has continued to be miserable, so I’ve not had chance to use the intervalometer. So onto the source code:

/*
Nikon D5000 Controller

 */

// include the library code:
#include <LiquidCrystal.h> 

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int IRledPin = 9;                // assign the Infrared emitter/ diode
                                 //  to pin 13
int exposureInterval = 2000;       // default exposure interval
int exposureLength = 10000;       // default exposure length
int Btn1 = 7;                      // Up button
int Btn2 = 6;                      // Down button
int menuoption = 1;                // Start of menu system
int runOption = 0;                 // 0 Stopped, 1 Time Lapse, 2 ...
int exitmenu = 0;                  // ready to exit the menu and start
                                   //  running
int Btn1State;                    // the Up Button 1 reading
int lastBtn1State = LOW;          // the previous Button 1 reading
int Btn2State;                    // the Down Button 2 reading
int lastBtn2State = LOW;          // the previous Button 2 reading

int days, hours, mins, secs;      // For formatting times
int fractime;                     // Fractions of a second

void setup() {
  pinMode(IRledPin, OUTPUT);    // Infra red remote control led
  pinMode(Btn1, INPUT);      // UI Button
  pinMode(Btn2, INPUT);   // UI Button 2

  Serial.begin(9600); //debug only remove from final

  lcd.begin(16, 2);             // set up the LCD's number of rows and
                                //columns:
  splashScreen();
}

void splashScreen() {
  lcd.clear();
  lcd.print("Nikon Control"); // Print a message to the LCD.
  lcd.setCursor(0, 1);        // Next line
  lcd.print("Press both btns");    

}

// argument is time in milliseconds
void print_time(int t_milli)
{
  char buffer[16];                  //buffer for printing interval
  int inttime;

  inttime = t_milli / 1000;     // inttime is the total number of
                                // number of seconds
  fractime = t_milli % 1000;     // fractimeis the number of
                                 //thousandths of a second

  // number of days is total number of seconds divided by 24 divided
  // by 3600
  days = inttime / (24 * 3600);
  inttime = inttime % (24 * 3600);

  // Now, inttime is the remainder after subtracting the number of
  // seconds in the number of days
  hours = inttime / 3600;
  inttime = inttime % 3600;

  // Now, inttime is the remainder after subtracting the number of
  // seconds in the number of days and hours
  mins = inttime / 60;
  inttime = inttime % 60;

  // Now inttime is the number of seconds left after subtracting the
  // number in the number of days, hours and minutes. In other words,
  // it is the number of seconds.
  secs = inttime;

  // Don't bother to print days and hours
  sprintf(buffer, "%02d:%02d.%03d", mins, secs, fractime);
  lcd.print(buffer);
}

// This procedure sends a 38KHz pulse to the IRledPin
// for a certain # of microseconds. We'll use this whenever we need to
// send codes
void pulseIR(long microsecs) {
  // Count down from the number of microseconds we are told to wait

  cli();  // this turns off any background interrupts

  while (microsecs > 0) {
    // 38 kHz is about 13 microseconds high and 13 microseconds low
    digitalWrite(IRledPin, HIGH);  // takes about 3 microseconds
    delayMicroseconds(10);         // hang out for 10 microseconds
    digitalWrite(IRledPin, LOW);   // Also takes about 3 microseconds
    delayMicroseconds(10);         // hang out for 10 microseconds

    // so 26 microseconds altogether
    microsecs -= 26;
  }

  sei();  // this turns them back on
}

void SendNikonCode() {
  // This is the code for Nikon D5000 etc

 pulseIR(1650);
  delay(27);
  pulseIR(362);
  delayMicroseconds(1500);
  pulseIR(362);
  delayMicroseconds(3440);
  pulseIR(362);
  delay(63); // wait 65 milliseconds before sending it again

  pulseIR(1650);
  delay(27);
  pulseIR(362);
  delayMicroseconds(1500);
  pulseIR(362);
  delayMicroseconds(3440);
  pulseIR(362);

}

boolean debounce(int button, boolean last)
{
  boolean current = digitalRead(button);
  if (last != current)
  {
    delay(5);
    current = digitalRead(button);
  }
  return current;
}

void setExposureInterval() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set Period"); // Print a message to the LCD.

  while (debounce(Btn1, lastBtn1State) == HIGH && debounce(Btn2,
                                            lastBtn2State) == HIGH) {
  } //Wait for let go

  while (exitmenu == 0) {
    if (debounce(Btn1, lastBtn1State) == HIGH && debounce(Btn2,
                       lastBtn2State) == HIGH) { //Exit menu and start
      exitmenu = 1;
    }
    lcd.setCursor(0, 1);
    print_time(exposureInterval);
    while (debounce(Btn1, lastBtn1State) == HIGH && debounce(Btn2,
                                           lastBtn2State) == HIGH) {
    } //Wait for let go
    delay(200);
    if (debounce(Btn1, lastBtn1State)) {
      exposureInterval = exposureInterval + 250;
    }
    if (debounce(Btn2, lastBtn2State) == HIGH &&
                                            exposureInterval > 250) {
      exposureInterval = exposureInterval - 250;
    }    
  }
  exitmenu = 0;
  lcd.clear();
  lcd.print("Time Lapse: Up  ");    
  lcd.setCursor(0, 1);        // Next line
  lcd.print("Long Exp:   Down");    

  while (exitmenu == 0) {
    if (debounce(Btn1, lastBtn1State) == HIGH) {  //Time Lapse    
      runOption = 1;
      exitmenu = 1;
    }
    if (debounce(Btn2, lastBtn2State) == HIGH) {  //Long Exposure     
      runOption = 2;
      exitmenu = 1;
    }
  }
}

void runTimeLapse() {          //Make the exposure at the set interval
  Serial.println("Exposure");
  SendNikonCode();                 // take the picture
  unsigned long currentMillis = millis();
  unsigned long endMillis = currentMillis + exposureInterval;
  while (currentMillis < endMillis) {   // delay in milliseconds which
                                        // allows us to do timelapse
    if (debounce(Btn2, lastBtn2State) == HIGH) { //Stop the time lapse
      runOption = 0;
      exitmenu = 0;
      splashScreen();
    }
    currentMillis = millis();          // Getting closer
  }
}

void longExposure() {
  SendNikonCode();                 // take the picture
  delay(exposureInterval);         // No need to 'interrupt', so delay
                                   // OK
  SendNikonCode();                 // take the picture
}

void showMenu() {
  if (debounce(Btn1, lastBtn1State) == HIGH && debounce(Btn2,
                                       lastBtn2State) == HIGH) {
    setExposureInterval();
  }    
}

void loop() {
  char buffer[16];  //buffer for building display string
  showMenu();       // Back to set interval

  // Option 1 is for time lapse
  if (runOption == 1) {
    lcd.clear();
    sprintf(buffer, "Int: %02d:%02d.%03d", mins, secs, fractime);
    lcd.print(buffer);
    lcd.setCursor(0, 1);         // Next line             
    lcd.print("Down To Stop    ");    
  }

  while (runOption == 1) {
    runTimeLapse();
  }

  // Option 2 is for long exposure on Bulb setting
  if (runOption == 2 ) {
    lcd.clear();               
    sprintf(buffer, "Exp: %02d:%02d.%03d", mins, secs, fractime);
    lcd.print(buffer);
    lcd.setCursor(0, 1);         // Next line               
    sprintf(buffer, "Up To Start     ");
    lcd.print(buffer);
  }

  while (runOption == 2) {

    if (debounce(Btn1, lastBtn1State) == HIGH) { //make the exposure
      lcd.setCursor(0, 1);
      lcd.print("Running...      ");
      longExposure();
      runOption = 0;    // Done so back to waiting for two buttons
      exitmenu = 0;
      splashScreen();
    }
  }
}

Now this is not all my own code. I found help for the InfraRed control from the excellent tutorial here on www.ladyada.net. I’ll go into this code in more detail in a future post, as it didn’t quite work out of the box. The print_time function came from a digital clock project, which for the moment I can’t find. When I do, I’ll post the details.

There are quite a few examples of using an Arduino to control Nikon IR, but none I found went as far as making it a useful tool. They would allow you to set one interval when you programmed the device, then you’d have to reprogram to get a different interval. So a large part of my code is driving a display to provide a menu and set the interval. I decided to go for a two-button interface, with hindsight three buttons would have been much easier. I also discovered that the same control could be used to start and stop a long exposure in Bulb mode so I incorporated that.

Next time I’ll  go through the code and explain what it’s doing.

Time Lapses

Today I’d hoped to go out and create a time lapse video with my intervalometer, but this being Scotland it’s raining and the sky is a uniform grey. So a good opportunity to go over the intervalometer design.

The circuit is very simple, most of the details can be cobbled together using the examples on the Arduino website. So this morning I’ve been playing with an Open Source drafting tool to draw a circuit diagram. Fritzing is provided by the Interaction Design Lab of the University of Applied Sciences Potsdam. I just set about drawing my circuit and learning Fritzing along the way. The result is not pretty, but it will do. That is no fault of Fritzing! For my next project I’ll spend some time learning how to use it properly.

Intervalometer Circuit Diagram

Intervalometer Circuit Diagram

Now this is far from perfect, but that’s what happens when you don’t RTFM. To get the circuit diagram into the post I used GIMP for the first time, the open source graphics tool. The UI is very unfamiliar, but I managed to crop the export from Fritzing. When it comes to processing the images from the camera I’ll need to get a lot more familiar with GIMP. It looks very different to Photoshop!

For this project I source my components from Ebay. A bag of 5 IR LEDS for about £1.00, the 16×2 LCD panel for £2.95, two panel momentary pushbuttons for £2.95, a bag of 5 trimmer potentiometers for about £1,  and a clone Arduino Nano board for £11.00. All prices including postage.

I built the circuit first on a breadboard then, once I had the code working, moved it to a piece of veroboard and mounted the whole lot in a small plastic box I got from Farnell.

Next time I’ll post and explain the code I’m using now and how I got there.

Just spotted a small error in the circuit diagram, R6 should be 10k Ohm the same as the pull down on the other switch.

What’s It All About

Recently while trying to find bits of the Internet that I’d missed I found Arduino. A simple to program micro-controller with some great community contributions. It’s open source hardware. I soon got an idea for a project and set to. I’d make an intervalometer for my Nikon D5000 DSLR camera. The D5000 in common with a few Nikon DSLRs has an infra-red remote control to fire the shutter. I’d build an infrared remote control and a timer to fire it at preset intervals so I could do time lapse photography. This project is now complete and I hope to create a video very soon to upload here.

But that was just the start. I decided to give myself a further challenge. I may pay for a few electronic components, but all the software I’ll use with my projects will be free, open source. So far I’ve changed from Windows 7 to the Ubunto 11.04 Linux distribution and I’m developing Arduino code on using the open source Arduino IDE. But the challenge is about to begin. No more Photoshop, or Windows tools to process photographs and create the video.