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.
Current projecting: using my TI-84 Plus graphing calculator as an intervalometer for my Nikon D3100. Speed bumps galore.
That sounds fun. As we’ll see when I get that far writing this up, the biggest problem is getting the IR pulses accurate enough. Does the TI-84 have an IR diode, or are you interfacing to the camera in a different way?