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.