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.