- A way of controlling the Canon XSi camera
- A bright enough LED with a short flash time while the camera was in total darkness
- An accurate accounting for delay from control of camera to LED flash
The LED I used was a Luxeon I, which was controlled via a simple transistor driver (2N2222 + base resistor). The flash time for the LED was chosen to be only 1msec. To get an accurate delay I found I had to write my own delay routines. The delay routines provided by the Arduino library were not really adequate for my purpose.
My software hardware algorithm was simple. Have the arduino close the camera shutter, have it delay a predetermined (but user variable) amount, and flash the LED at a piece of paper, for which the camera was taking a picture. I then manually looked at the resulting picture to see the amount of light that was exposed in the picture. I recorded the results and changed the amount of delay and repeated. Because the LED flash time was only 1msec, I had to shoot with an ISO of 1600.
I did two different sets of pictures. The first picture set was just of an arbitrary piece of paper I had laying around. It had some garbage writing on it, but this was to determine if the setup worked ok. The shutter speed was set at 1/100. I approximated from these pictures the actual shutter speed at 1/100.6, which is within 0.6% of the camera's setting.
Also the delay begins at 84msec through 86msec depending upon the position in the camera's sensor. Notice how the sensor is opened from the bottom and the 2nd curtain also starts at the bottom (as it should).
The second set of pictures was of a sheet of paper that had printed on it lines labelled from 0 through 10. This ruling would permit me to better estimate the delay time to % position of the shutter open or closed. I carefully positioned the paper and camera zoom so that the "0" line was just at the bottom and the "10" line was just at the top of the camera's sensor.
Here is an example of one of these shots.
Lastly, I used the perceived % opening/closing of shutter to plot the delay of the shutter. I posted these as two excel charts, the first for the shutter opening, the second for closing.
I'll add the code soon.
/***************************************************************
This is an example of arduino driving a Canon XSi camera that
will be taking a picture of an LED being strobed at different
latencies from the shutter.
The purpose of this program is to help with the characterization
of the camera's delay from manual shutter input to when the
actual shutter happens. Knowing this delay is essential when
doing motion photography capture studies.
The method used is simple. The arduino controls both the focus
and shutter input, it waits a selected amount of time, then it
strobes a high-output LED for a very brief time. The program
was developed to provide an accurate accounting of the latency
between the shutter request and the LED strobe.
Author: Bill Grundmann, Tigard, Oregon. 2009
***************************************************************/
// the following is an inline delay method for delays from
// 750ns to 16.38ms, with 750ns increments
// delay = (4*__count -1) cycles
#include "WProgram.h"
void inline delay_int(unsigned int __count);
void inline volatile delay_long(unsigned long volatile __count);
void new_interval_clocks(unsigned long clocks);
unsigned long ms_to_clocks(float ms);
void setup();
void inline shutter();
void inline release();
void inline focus();
void inline pulse_led();
void do_interval(unsigned long count);
void loop();
void inline delay_int(unsigned int __count)
{ // 4N - 1
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (__count)
: "0" (__count)
);
}
// the following is an inline delay method for delays from
// 937.5ns to ~14 seconds, at 1.125usec increments
// delay = 18N - 3 cycles
void inline volatile delay_long(unsigned long volatile __count) {
label: if (--__count) goto label;
}
//*********************************************************************
// The following are the port definitions
// this program does not use the digitalWrite operations
// as they have too much instruction cycle overhead
// the shutter is digital 2, focus on digital 3 and led on digital 4
#define shutter_port PORTD
#define shutter_pin PORTD2
#define focus_port PORTD
#define focus_pin PORTD3
#define led_port PORTD
#define led_pin PORTD4
// declare the operators for setting and clear port outputs
#ifndef cbi
#define cbi(port, pin) port &= ~_BV(pin);
#endif
#ifndef sbi
#define sbi(port, pin) port = _BV(pin);
#endif
unsigned long cur_interval; // the current interval amount
//***********************************************************************************************
// calculate a new parameter for the interval between shutter and led
// the actual clock cycles may be different from the requested amount
// the resolution of the interval is 1.125 usec because the interval
// delay is 18N + 3 cycles @ 16 MHz. The longest interval is ~14.9 seconds
#define fraction_digits 6
void new_interval_clocks(unsigned long clocks)
{
if (clocks < 10) clocks = 10;
cur_interval = (clocks - 3 + 9) / 18; // +9 = effective +0.5 to round better
Serial.println();
Serial.print("new clocks: ");
Serial.print(clocks);
Serial.print(", cur_interval: ");
Serial.print(cur_interval);
Serial.print(", delay: ");
clocks = (cur_interval * 18) + 3;
Serial.print(clocks);
Serial.print(" ");
// compute the fixed-point output number for the delay, scaled for milliseconds
char frac[8], *pfrac = frac + fraction_digits; // string array and pointer to end
unsigned long delay = clocks * 62 + (clocks >> 1); // *62.5 in ns
if (((clocks & 1) != 0) && ((delay & 1) & 1) != 0) delay++; // a bit of rounding
unsigned int Int = delay / 1000000; // integer part
Serial.print(Int); // output the fractional part
Serial.print(".");
// remove the integer part to get the fraction
unsigned long fract = delay - (Int * 1000000);
// terminate the output string first.... we'll work backwards
*pfrac-- = 0;
// compute the next digit, working from right-most digit to left-most digit
// this will also pad out on the left any leading zeroes
for (char ii = 0; ii < fraction_digits; ii++) {
*pfrac-- = (fract % 10) '0';
fract /= 10;
}
Serial.print(frac); // output the fractional part
Serial.println("ms");
}
//***********************************************************************************************
// convert a millisecond value into a number of instruction clocks
unsigned long ms_to_clocks(float ms) {
return (unsigned long) (ms * 1e6/62.5);
}
//***********************************************************************************************
// hardware and software setup
void setup() {
DDRD = _BV(shutter_pin) _BV(focus_pin) _BV(led_pin);
PORTD = 0;
Serial.begin(9600);
new_interval_clocks(ms_to_clocks(90.0)); // start with 90 ms interval
}
//***********************************************************************************************
// close the camera shutter
void inline shutter() {
sbi(shutter_port, shutter_pin);
}
//***********************************************************************************************
// release both camera's shutter and focus
void inline release() {
cbi(shutter_port, shutter_pin);
cbi(focus_port, focus_pin);
}
//***********************************************************************************************
// initiate camera focus
void inline focus() {sbi(focus_port, focus_pin);}
// pulse the led
// width = 4n + 1 cycles
// ~1.1 usec, visible at 1600 ISO
// 1k base resistor, 2n3055, 5v, luxeon I white 1w LED
// unknown LED current
//#define led_pulse_width 4 // 1.1usc
// #define led_pulse_width 40 // 10 usec
#define led_pulse_width 4000 // 1000 usec
void inline pulse_led() {
sbi(led_port, led_pin); // 2
delay_int(led_pulse_width); // 4n-1
cbi(led_port, led_pin); // 2
}
//***********************************************************************************************
// time from shutter to led = 18N + 3
// good to ~14seconds @ 1.125us accuracy
void do_interval(unsigned long count) {
focus();
delay_long(888889); // ~1sec
shutter(); // 2180
delay_long(count); // 18N + 1
pulse_led(); // 4
release(); // 4
} // 4
//***********************************************************************************************
void loop() {
if (Serial.available()) {
unsigned char c = Serial.read();
switch(c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
// a number; we'll treat it as a new interval clock delay value
unsigned long v = 0;
// get all of the digits until something that is not a digit
while(c >= '0' && c <= '9') {
v = (v * 10) + (c - '0'); // add the digit to the accumulator
while(!Serial.available()) ; // wait for next character
c = Serial.read(); // get the character
}
new_interval_clocks(v);
}
break;
case 'T':
case 't':
case ' ':
// these are the camera "take" operation
// which does focus, shutter, interval, led & release sequence
do_interval(cur_interval);
break;
case 'F':
case 'f':
// these will activate the focus pin only
sbi(focus_port, focus_pin);
break;
case 'R':
case 'r':
// these releases both focus and shutter
release();
break;
case 'S':
case 's':
// these will activate the shutter pin only
sbi(shutter_port, shutter_pin);
break;
}
}
}
int main(void)
{
init();
setup();
for (;;)
loop();
return 0;
}
