Jump to content
43oh

Camera Intervalometer With LCD


Recommended Posts

This is the updated version of my camera intervalometer posted here.

 

The first version was a proof of concept. This is a more feature rich version.

 

post-2194-135135512254_thumb.png

 

What is a camera intervalometer? It's a device that makes your camera take a photo at set intervals. These photos can then be stitched together into a movie. This is often referred to as time lapse photography. A sample video created using this intervalometer is this:

 

The hardware is what I would consider feature complete for this revision. The software is paused for now. It is functional, but there are several features/improvements that I will be adding in the future.

 

Compatible cameras

Currently, the intervalomter works with any Nikon camera that uses the Nikon ML-L3 remote. Support for Canon, Sony and Pentax cameras should be possible. However, I cannot test these so have not spent time on them. Shout up if you have one of the other makes of camera and can test for me! ;)

 

Operation

The intervalometer has three buttons: "Menu", "Down" and "Up/Shoot"

 

On start up the intervalometer is ready to shart shooting at a default interval of 60s. Pressing the "Up/Shoot" button will trigger the camera shutter then start the countdown to the next frame.

 

At any time the "Menu" button can be pressed to cycle through the various menu options. There are only 2 at the moment; Interval count and interval period.

 

Interval count is the number of seconds or minutes between frames. Use the "Down" and "Up/Shoot" button to adjust. The interval count can be set to any integer value between 1 and 99. The interval period can be configured to be either seconds or minutes.

 

Entering the menu will cause any shooting to pause and will wait for you to restart it.

 

During operation the LCD will display the current interval setting along with how long to go before the next shutter release. It also displays the total number of frames shot during the current session. An on board LED will flash every second to indicate that the intervalometer is currently operating.

 

Hardware

The hardware is built around the MSG430G2231. This particular device was chosen simply because it is supplied with the launchpad. Later versions may use a different device with more code space. The 2231 is configured to use the internal DCO clocked in such a way that we are able to get SMCLK to ~38KHz. This frequency is required for the carrier of the IR. SMCLK is connected to P1.4, which drives the IR LED via a 2N2222 transistor.

 

To save on IO space, the LCD is driven in 4-bit mode via a 74HC595 shift register. This uses only 3 IO instead of 7 (or even 11!).

 

The input buttons are configured to interrupt on edge changes. A really crafty debounce is possible by adding a delay after entering the interrupt then reading the input level. It's dirty, but it works in this application.

 

To do

The hardware supports external triggering (motion sensor, lightning sensor etc) but the software doesn't.

The hardware supports controlling the LCD backlight but the software doesn't.

Other camera support.

 

Resources

I'll add all the resources here as I've prepared them.

 

Schematic

post-2194-135135512263_thumb.png

 

Code

#include "msp430g2231.h"
#include 

#define SHUTTERBUTTON BIT5
#define DOWNBUTTON BIT3
#define MENUBUTTON BIT0
#define IROUT BIT4
#define STATUS BIT6
#define SCLOCK BIT6
#define SDATA BIT7
#define SLATCH BIT7

#define LCDRS 0x02
#define LCDRW 0x04
#define LCDE 0x08

#define SHUTTER nikonShutter()
//#define SHUTTER canonShutter()
//#define SHUTTER pentaxShutter()
//#define SHUTTER sonyShutter()

volatile int i;
volatile unsigned int shutterInterval = 60;
volatile unsigned int intervalCounter;
volatile unsigned int delayAcc = 0;
volatile unsigned int minAcc = 0;
volatile char clockRunning = 0;
volatile char period = 's';
volatile unsigned int shutterCount = 0;
volatile unsigned int menu = 0;

char buf[9] = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

void nikonShutter();
void shiftOut(char w, char latch);
void LCDdelay();
void resetLCD();
void LCDOut (char b, char d);
void LCDString ();
void itoa (unsigned int val, char *str);
void showMenu();

void main(void)
{
	intervalCounter = shutterInterval;

WDTCTL = WDTPW + WDTHOLD;  // stop WDT

DCOCTL |= DCO0 + DCO1;  // DCO = ~300kHz
BCSCTL1 |= RSEL0 + RSEL1;
BCSCTL1 &= ~(RSEL2 + RSEL3);

BCSCTL2 |= DIVS0 + DIVS1;  // SMCLK / 8 is ~38kHz

P1DIR |= IROUT;  // Connect 1.4 to SMCLK which give us our carrier
P1OUT &= ~IROUT;
P1SEL &= ~IROUT;

P1DIR |= STATUS;
P1OUT &= ~STATUS;

P1REN |= SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON;  // pull-up
P1OUT |= SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON;  // pull-up
P1IE |= SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON;  // interrupt enabled
P1IES |= SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON;  // hi/lo edge
P1IFG &= ~SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON;

P2SEL = 0x00; // User XIN/XOUT as output (P2.6, P2.7)
P2DIR |= SCLOCK + SDATA;
P1DIR |= SLATCH;

// Define timer interrupt
TACCR0 = 46;
TACTL = TASSEL_2 + MC_1 + ID_0;
TACCTL0 = CCIE;

__delay_cycles(5000);
resetLCD();
showMenu();

_EINT();
}

void nikonShutter() {
// 628, 8798, 119, 496, 125, 1128, 122, 19973
for (i = 0; i < 2; i++) {
	P1SEL |= IROUT;
	__delay_cycles(628);
	P1SEL &= ~IROUT;
	__delay_cycles(8798);
	P1SEL |= IROUT;
	__delay_cycles(119);
	P1SEL &= ~IROUT;
	__delay_cycles(496);
	P1SEL |= IROUT;
	__delay_cycles(125);
	P1SEL &= ~IROUT;
	__delay_cycles(1128);
	P1SEL |= IROUT;
	__delay_cycles(122);
	P1SEL &= ~IROUT;
	__delay_cycles(19973);
}
shutterCount++;
clockRunning = 1;
}

void shiftOut(char w, char latch) {
P1OUT &= ~SLATCH;
for (i = 0; i < 8; i++) {
	if ((w & 0x80) == 0x80) {
		P2OUT |= SDATA;
	} else {
		P2OUT &= ~SDATA;
	}
	P2OUT |= SCLOCK;
	w <<= 1;
	P2OUT &= ~SCLOCK;
}
if (latch) {
	P1OUT |= SLATCH;
	P1OUT &= ~SLATCH;
}
}

// This should be enough for most LCDs.  Technically, there should be
// at least three different delays but we're trying to save code space.
void LCDdelay() {
__delay_cycles(150);
}

void resetLCD() {
shiftOut(0x20, 1);  // Not in 4 bit mode yet so we have to do it by shifting
LCDdelay();
shiftOut(0x20 + LCDE, 1);
LCDdelay();
shiftOut(0x20, 1);
LCDdelay();
LCDOut(0x28, 0); // 4 bit, 2 lines, 5x7 font
LCDOut(0x08, 0); // Enable display, cursor off
LCDOut(0x01, 0); // Clear display, move cursor home
LCDOut(0x06, 0); // Move cursor
LCDOut(0x0c, 0); // Display on
}

void LCDOut(char b, char d) {
char btosend = 0;
btosend = (b & 0xf0); // High nibble
if (d) {
	btosend += LCDRS;
}
shiftOut(btosend, 1);
LCDdelay();
shiftOut(btosend + LCDE, 1);
LCDdelay();
shiftOut(btosend, 1);
LCDdelay();

btosend = (b & 0x0f); // Low nibble
btosend <<= 4; // Move it to the high nibble
if (d) {
	btosend += LCDRS;
}
shiftOut(btosend, 1);
LCDdelay();
shiftOut(btosend + LCDE, 1);
LCDdelay();
shiftOut(btosend, 1);
LCDdelay();
}

// Takes whatever is in buf[] and sends it to the LCD
void LCDString() {
int dp = 0;
while (buf[dp] != 0) {
	LCDOut(buf[dp], 1);
	dp++;
}
}

void itoa(unsigned int val, char *str) {
int temploc = 0;
int digit = 0;
int strloc = 0;
char tempstr[5];

do {
	digit = val % 10;
	tempstr[temploc++] = digit + '0';
	val /= 10;
}
while (val > 0);

//  The digits are now reversed.  Let's fix that.
while(temploc>0) str[strloc++] = tempstr[--temploc];

str[strloc]=0;
}

void showMenu() {
LCDOut(0x02, 0);
if (menu == 0) {
	strcpy(buf, "Shoot to\0");
	LCDString();
	LCDOut(0xc0, 0);  // Line 2
	strcpy(buf, "start.  \0");
	LCDString();
}

if (menu == 1) {
	strcpy(buf, "Interval\0");
	LCDString();
	LCDOut(0xc0, 0);  // Line 2
	itoa(shutterInterval, buf);
	LCDString();
	strcpy(buf, "       \n");
	LCDString();
}

if (menu == 2) {
	strcpy(buf, "Period  \0");
	LCDString();
	LCDOut(0xc0, 0);  // Line 2
	if (period == 's') {
		strcpy(buf, "Seconds \0");
	} else {
		strcpy(buf, "Minutes \0");
	}
	LCDString();
}
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
// REALLY cheap debounce.  
// We can get away with this because we're edge triggering but reading the level.
__delay_cycles(10000);

if ((~P1IN & SHUTTERBUTTON) == SHUTTERBUTTON) {
	if (menu == 0) {
		SHUTTER;
		intervalCounter = shutterInterval;
		delayAcc = 0;
		minAcc = 0;
	}
	if (menu == 1) {
		shutterInterval++;
		if (shutterInterval == 100) {
			shutterInterval = 99;
		}
	}
	if (menu == 2) {
		if (period == 's') {
			period = 'm';
		} else {
			period = 's';
		}
	}
}

if ((~P1IN & MENUBUTTON) == MENUBUTTON) {
	clockRunning = 0;
	shutterCount = 0;
	menu++;
	if (menu == 3) {
		menu = 0;
	}
}

if ((~P1IN & DOWNBUTTON) == DOWNBUTTON) {
	if (menu == 1) {
		if (shutterInterval != 1) {
			shutterInterval--;
		}
	}
	if (menu == 2) {
		if (period == 's') {
			period = 'm';
		} else {
			period = 's';
		}
	}
}

if (!clockRunning) {
	showMenu();
}

P1IFG &= ~(SHUTTERBUTTON + MENUBUTTON + DOWNBUTTON);
}

#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A (void)
{
if (clockRunning) {
	delayAcc++;
	if (delayAcc == 836) {  // Near as damn it 1Hz
		P1OUT ^= STATUS;
		LCDOut(0x02, 0);
		itoa(intervalCounter, buf);
		LCDString();
		LCDOut(0x2f, 1);
		itoa(shutterInterval, buf);
		LCDString();
		LCDOut(period, 1);
		strcpy(buf, "    \0");
		LCDString();

		if (period == 's') {
			intervalCounter--;
		} else {  // Not seconds so must be minutes
			minAcc++;
			if (minAcc == 60) {
				intervalCounter--;
				minAcc = 0;
			}
		}
		if (intervalCounter == 0) {
			intervalCounter = shutterInterval;
			SHUTTER;
		}

		itoa(shutterCount, buf);
		LCDOut(0xc0, 0);  // Line 2
		LCDString();
		strcpy(buf, "       \0");
		LCDString();

		delayAcc = 0;
	}
}
}

 

Board layout

post-2194-135135516458_thumb.png

If there's enough interest, I'll be getting several boards made up for sale. Possibly even a kit of parts.

 

Archive of all files

Gerber are included in this file.

LCDIntervalometer.zip

Link to post
Share on other sites
  • 3 weeks later...
  • 1 year later...

 

Compatible cameras

Currently, the intervalomter works with any Nikon camera that uses the Nikon ML-L3 remote. Support for Canon, Sony and Pentax cameras should be possible. However, I cannot test these so have not spent time on them. Shout up if you have one of the other makes of camera and can test for me! ;-)

I have a Canon EOS 400D Camera and i can test this with my camera ;)

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...