Infrared remote control for Nikon D50

Today, much to my consternation, I realized that I had lost my shutter release remote control for my DSLR. As it happened, I also had an infrared LED lying around. So I decided that it shouldn’t be too hard to find the right flash sequence somewhere online and use an Arduino to make my own.

As you can see, the basic prototype of the circuit could not be simpler: just the LED and a 220ohm resistor to throttle back that 5V the arduino so lovingly sends forth.

The timing sequence that was needed to control the Nikon shutter release came from Big Mike. If you go over to his site, you will see a detailed tutorial on how to build this remote using a ATTiny2313 using the AVR programmer. The code you will need for the Arduino is as follows:

int outPin = 8; // digital pin 8

void setup()
{
pinMode(outPin, OUTPUT); // sets the digital pin as output
}

void oscillationWrite(int pin, int time) { // this function will cause the LED to flicker at approximately 38KHz
for(int i = 0; i <= time/26; i++) {
digitalWrite(pin, HIGH);
delayMicroseconds(13);
digitalWrite(pin, LOW);
delayMicroseconds(13);
}
}

void loop()
{
oscillationWrite(outPin, 2000); // sets the pin on 2250us
delayMicroseconds(27830); // sets the pin off 27600us
oscillationWrite(outPin, 390); // sets the pin on 650us
delayMicroseconds(1580); // sets the pin off 1375us
oscillationWrite(outPin, 410); // sets the pin on 575us
delayMicroseconds(3580); // sets the pin off 3350us
oscillationWrite(outPin, 400); // sets the pin on 650us

delayMicroseconds(63200); //wait 63ms

oscillationWrite(outPin, 2000); // sets the pin on 2250us
delayMicroseconds(27830); // sets the pin off 27600us
oscillationWrite(outPin, 390); // sets the pin on 650us
delayMicroseconds(1580); // sets the pin off 1375us
oscillationWrite(outPin, 410); // sets the pin on 575us
delayMicroseconds(3580); // sets the pin off 3350us
oscillationWrite(outPin, 400); // sets the pin on 650us

delay(1000); // the sequence needs to loop twice and then cease

}

The only thing worth pointing out here is that we needed to create the oscillationWrite function because the protocol for most IR device requires the light to oscillate at a frequency of roughly 20KHz. We achieve this by causing the LED to go high 13us then low for 13us whenever we are sending light to the device. The common wisdom is that manufactuers implemented this so that the sun could not trigger any remote control functions.

So this simple IR code should work with every camera supported by ML-L1 and ML-L3 Nikon remote controls such as D40, D40X, D50, D60, D70, D70s, D80, Coolpix 8400 8800.

Beyond just building another remote for myself, I am wondering what else can be done with this easy access to my camera’s shutter function. A few ideas might be

  • Write a small web applet to trigger the camera.
  • Time lapse photography
  • Connect to biometric data to snap pictures of subject at moments of great stress or alarm
  • To make a device for capturing high speed events (eg place a microphone by a balloon and use the sound peak from that to trigger the shutter of the camera).
  • Set up a light continuity sensor in a public place and photograph people as they walk through it.

UPDATE: After some more testing with the code above using the microsecondDelay function I was only able to get somewhat sporadic success in getting the shutter to trigger. Matt at CiboMahto, however, created a library for the Arduino platform that instructs the Atmega168 directly with assembly commands and seems to work like a charm for the shutter trigger. At this point the only technical problem I am still having is keeping the camera live after fifty or so frames are shot, one every thirty seconds, using the remote.

[Matt at CiboMahto writes:] The most straightforward approach to this would be to take apart an existing remote and fire its button with a microcontroller. However, that didn’t sound like much of a challenge, so instead the goal became to train the micro to output the signals itself. A quick search fished out a well-documented solution by one San Bergmans. The routine is written in assembly to generate an accurate signal. I chose the Arduino as my platform, and figured out that it is pretty straightforward to include assembly inside of a C++ file. So, I ported San’s code to the Atmega168 that my Arduino is based on, and it appears to work quite swimmingly. The routine is wrapped up in a library, so using it is as simple as adding the library to your project and calling the Snap() function. There are a couple of caveats to this code,though: it (currently) only works on pin 12, and will only support the default clock frequency (16MHz?). Code after the break.

To use these, place them in a folder named NikonRemote, which should be located in the hardware/libraries/ section of your Arduino installation. Then, restart (or just start) the Arduino program, add the library to the project, and create the object in your sketch:

NikonRemote remote(12);

Whenever you want to trigger the camera, just do this:

remote.Snap();

Oh, and don’t forget to attach an IR LED to pin 12, with some sort of current-limiting resistor. Tip: You can test if the IR LED is in the correct orientation by powering it up and looking at it with a digital that has a live preview.

NikonRemote.h:

#ifndef NikonRemote_h
#define NikonRemote_h

#include "WConstants.h"

class NikonRemote {
  public:
    NikonRemote(unsigned int port);

    void Snap();

  private:
    unsigned int _port;
};
#endif

NikonRemote.cpp:

//
// NikonRemote
//
// This is an evil Arduino library that uses inline assembly to generate an IR
// signal capable of triggering a Nikon SLR camera.
//
// By Matt Mets, October, 2008.
//
// Adapted from San Bergman's 'ML-L1 / ML-L3 IR remote control replacement'
// http://www.sbprojects.com/projects/nikon/index.htm
//
// Note: At time of writing, it isn't clear what license it can be used under.
//       I will contact the author about this.
// Note: This is an incomplete implementation.  It requires the output LED to
//       be on pin 12.

#include "NikonRemote.h"

NikonRemote::NikonRemote(unsigned int port)
{
  pinMode(port, OUTPUT);
  _port = port;
}

void NikonRemote::Snap()
{
// Definitions:
// r16: port ON value
// r17: port OFF value
// r18: burst delay counter
// r19: burst counter
// r20: end delay counter
// r21: port mask

// 0x03: Port B input register
// 0x05: Port B output register

  asm volatile (

"\n\t cli"              // Disable interrupts

"\n\t push r16"         // Save context
"\n\t push r17"
"\n\t push r18"
"\n\t push r19"
"\n\t push r20"
"\n\t push r21"

"\n\t in   r16, 0x03"   // Calculate on and off port values
"\n\t ori  r16, 0x10"
"\n\t in   r17, 0x03"

"\n\t rjmp COMMAND"     // Jump to the command routine

"\nNEXTPULSE:"
"\n\t ldi r18, 68"      // Count to 9 for one low pulse
"\nSENDBURST:"          // Low period delay loop
"\n\t dec r18"
"\n\t brne SENDBURST"
"\n\t nop"
"\n\t out 0x05, r21"    // Set the output to mask
"\n\t ldi r18, 68"      // Count to 9 for one high pulse
"\nBURSTLOOP:"

"\n\t dec r18"
"\n\t brne BURSTLOOP"
"\n\t out 0x05, r17"    // Set the output low
"\n\t dec r19"          // Determine if another burst should be performed.
"\n\t brne NEXTPULSE"
"\n\t ret"

"\nCOMMAND:"

// 2000us on
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 76"      // count = 76
"\n\t mov r21, r16"     // Mask on
"\n\t rcall SENDBURST"

// 28ms off
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 250"     // count = 250
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 250"     // count = 250
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 250"     // count = 250
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 250"     // count = 250
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 64"      // count = 64
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"

// 400us on
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 15"      // count = 15
"\n\t mov r21, r16"     // Mask on
"\n\t rcall SENDBURST"

// 1580us off
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 60"      // count = 60
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"

// 400us on
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 15"      // count = 15
"\n\t mov r21, r16"     // Mask on
"\n\t rcall SENDBURST"

// 3580us off
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 136"     // count = 136
"\n\t mov r21, r17"     // Mask off
"\n\t rcall SENDBURST"

// 400us on
"\n\t ldi r18, 65"      // initial pulse = 65
"\n\t ldi r19, 15"      // count = 15
"\n\t mov r21, r16"     // Mask on
"\n\t rcall SENDBURST"

"\n\t pop  r21"         // Restore Context
"\n\t pop  r20"
"\n\t pop  r19"
"\n\t pop  r18"
"\n\t pop  r17"
"\n\t pop  r16"

"\n\t sei"              // Enable interrupts
"\n\t"
  );
}

Link
Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google