Homebrew Intervalometer

I wanted an intervalometer for my Canon DSLR, which at the time, rather miserly did not contain. An intervalometer is a device to take exposures at regular periods. It’s great for time-lapse work. Their own add-on was quite pricey. Even knock-offs were more than I thought was worth it. And besides, I wanted a project where I could make my own printed circuit board, or PCB.

So I thought I’d build one myself. The fanciest way would have been something to talk USB, which could have made all sorts of changes to the camera settings per-shot, but I went with the ultra-simple “bulb” interface. All you need to do is close the circuit between two contacts, present on an unscrupulously proprietary interface on the camera. A simple switch would work. So would a relay, if you want to automate it. But a relay uses quite a bit of current to operate, and one useful property any intervalometer can have is to run for a long time on a small battery.

So I elected to use an opto-isolator. These devices are totally electrically unconnected to the power they are switching, as they work by the input current switching on an LED inside an IC-style package, which shines onto a light-sensitive transistor, which switches on, allowing the current to flow through it. There is nothing other than light going between the two sides of the opto-isolator, and only in one direction at that!

I experimented to find what the shortest was that I could close the circuit and have the camera reliably take a photo, and wrote a short program to activate the pin that would turn on the opto-isolator for that time, and then sleep until time to fire again. I added an LED to visualise what was going on.

I designed the PCB in, erm, PCB.

PCB design

This was printed out with a mono laser printer, onto the very glossy, flimsy inserts you get in newspapers, advertising, usually, cash for gold, or takeaways that sell EVERY FOOD EVER. Place this onto some clean, copper PCB board, and apply a hot iron. The kind you do shirts with, not a soldering iron. This melts the plastic toner binder, and it should preferentially stick to the copper. You then wash and very gently clean off the paper, and you’re left with the black toner where you want the copper to remain.

toner masking the copper

This goes into the etchant, which was hot ferric chloride in solution. Once this is complete, you can, again, carefully, clean off the toner, to re-expose the copper.

after etching, remove toner

Next, drill the holes for the component pins. Highly recommend not doing this by hand, PCB drills are super brittle and will easily snap off. Then, solder the components in place. As a temporary housing, I think I used a box that some cufflinks came in.

finished article

The microcontroller was programmed with the following code:

/*
 * intervalometer.c
 *
 * Created: 22/07/2011 22:12:03
 * Author: Phil Bambridge <phil@swedeheart.net>
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>

#define FALSE 0
#define TRUE 1

unsigned volatile int systime = 0;

ISR(TIMER0_COMPA_vect) {
	// Given our setup, this being called means a millisecond has elapsed
	systime++;
}

void delay (int mssecs) {
	int timenow = systime;
	while(systime - timenow < mssecs) {
		MCUCR |= 1 << 5; // Disable sleep guard
		asm("sleep"); // We'll wake up when the timer counts a millisecond and throws an interrupt
		MCUCR &= ~(0|1<<5); // Enable sleep guard again
	}
}

int main(void)
{
	SREG |= 1 << 7; // Enable global interrupts flag
	ACSR |= 1 << 7; // Turns off the unneeded analog comparator
	PRR |= 1 << 0 | 1 << 1 | 1 << 3; // Turn off the ADC, the Serial and Timer 1 respectively

	// Set up timer for a 1KHz interrupt- for 8MHz operation, that's a 1/64 prescaler, and a count of 124.
	// For 1 MHz (default) operation we'd go with a 1/8 precaler.
	OCR0A = 124; // Count to 124
	TCCR0A |= 1 << 1; // Clear timer when we hit that count set above
	TIMSK |= 1 << 4; // Enable the interrupt
	TCCR0B = 0x2; // 1/8 prescaler, also enables the timer-counter.

	// Set all three output pins low, then set to output.
	PORTB = 0;
	DDRB = 0 | 1 << DDB0 | 1 << DDB2 | 1 << DDB3;

	while(1) // Run forever
	{
		// Put shutter and indicator high
		PORTB |= (1 << PORTB0 | 1 << PORTB3);
		delay(75);
		// Put shutter and indicator low
		PORTB &= ~(0 | 1 << PORTB0 | 1 << PORTB3);
		delay(2000);
	}
}

By way of comparison, if I’d done this with an Arduino, the code would look something like this as a close approximation- it’s basically the blink sketch:

#define OPTO 12

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 12 as an output.
  pinMode(OPTO, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(OPTO, HIGH);   // turn on the opto-isolator (open camera shutter)
  delay(75);                  // wait for 75ms
  digitalWrite(OPTO, LOW);    // turn off the opto-isolator (close camera shutter)
  delay(2000);                // wait for a 2 seconds
}

As you can see, it’s a lot fewer lines of code. The Arduino preprocessor builds the real code after you hit the compile button (if/when you know C, you realise that what you see in the Arduino IDE can’t be the whole story), we can find the actual code in a temp file. In this case it was 8036 lines, 90% of which were comments. Not a bad read for a lazy Sunday afternoon. Point is, it’s doing lots of things behind the scenes that you don’t know about. Doing it by hand not only allows you to target more chips, but you can speed up a lot of routines, and save battery power.

Leave a Reply

Your email address will not be published. Required fields are marked *