lag of a Canon Digital Single Lens Reflex (DSLR) camera using an Arduino microcontroller Bill Brandt BSc CEng MIET MINCOSE October 2014 Abstract A simple low-cost circuit was constructed to measure the shutter lag of a Canon DSLR using an Arduino microcontroller and some LEDs. Analysis of the circuit concluded that it was suitable for measuring to a precision of 1ms and an accuracy of 250s and was capable of measuring to 100s precision with about 50s accuracy at the expense of limited range, although lack of calibrated test equipment meant that this could not be independently verified. The circuit was used to measure the shutter lag of different Canon DSLRs and found the shutter lag of a Canon EOS 400D to be 117ms 3ms and the shutter lag of a Canon EOS 700D to be 97ms 7ms.
Increased accuracy ...................................................................................................................... 17 Decreased number of LEDs ......................................................................................................... 18 Other timing triggers ................................................................................................................... 18 Areas for further study ................................................................................................................... 18 Trigger pulse ............................................................................................................................... 18 Variability .................................................................................................................................... 18 700D two spike distribution ........................................................................................................ 18 Shutter speed inaccuracy ............................................................................................................ 18 References .......................................................................................................................................... 18 Appendix 1 Measurement data ....................................................................................................... 19 Appendix 2 Second implementation, 100s precision ..................................................................... 19 High level software design .......................................................................................................... 19 Timeline ...................................................................................................................................... 19 Code execution time and optimisation ....................................................................................... 20 Code ............................................................................................................................................ 21 Experimental observations ......................................................................................................... 23 Appendix 3 - Binary methods .............................................................................................................. 24
4
Introduction The time delay between pressing the camera shutter button and the camera recording a digital image is referred to as the shutter lag. It is important to know this value for a particular camera for applications where the precise timing of the image capture is important. One such application is in stereo image recording where it is important to synchronise two or more cameras to capture images at precisely the same time since the stereo effect relies on the differences between the images and any difference caused by subject movement will cause errors in the stereo effect. Different makes and models of cameras have different shutter lag values and there is variation in the shutter lag between successive shots from the same camera. The experiment below provides a method for accurately measuring shutter lag. Aims The primary aim of the experiment was to create a method to accurately measure shutter lag for a Canon DSLR camera. In principle the same method, with minor modifications, could be used for any DSLR or indeed any camera with a way to remotely activate the shutter button. The primary success criterion was to achieve a precision of 1ms with an accuracy of 500s. The secondary goal was to study whether the method was suitable for adaptation to higher precision and accuracy. The third goal was to develop electronic circuits and software that could be reused to study other aspects of camera timing and to provide a method for compensating for variations in timing. To this end this document points out where adaptations could be made for other applications. Other measurement methods Historically the most popular method for measuring shutter lag used a CRT monitor and relied on capturing the position of the electron beam scanning the screen to provide an accurate timing reference [Reference 1]. This method is now becoming less and less convenient because of the obsolescence of CRT monitors. A modern LCD monitor displaying a software controlled clock can be used as a rough indication of timing but because the refresh rate of most monitors is only 50Hz or 60Hz this limits the precision to 20ms or 16.7ms, plus there may be accuracy issues due to the interaction between the computer, graphics card and monitor that may be hard to control. Mechanical methods such as photographing rapidly spinning discs, swinging pendulums, falling objects etc. suffer from being physical awkward to set-up and potentially hard to calibrate. Therefore the method chosen in this experiment was the use of LEDs controlled by an Arduino microcontroller. This method was preferred because of the low cost of Arduino microcontrollers and the ease with which the software control allows reconfiguration of the timing. 5
Initially an approach using a binary display pattern was attempted but this was later abandoned for the reasons given in Appendix 3 - Binary methods. Method Display Method The basic method used was a pseudo-analogue representation of time. This was achieved by having a bank of 10 LEDs each illuminated in sequence with a wrap-around from the last LED to the first. By seeing which LED was illuminated at any given time and knowing how fast the sequence repeated, the time could be calculated. Three banks of LEDs were used with the sequence in each bank taking ten times longer to repeat than the bank to its right. This is illustrated in Figure 1.
Figure 1 Display method This method was used to minimise the risk of errors caused by LEDs being captured in a transitional state (partially illuminated), since it is easy to infer the true value from this type of display. This is not true with binary or 7-segment displays where it can be much harder to infer what value is being displayed if several LEDs are in transition at once The LEDs in each section represented the numbers 0 to 9 from left to right. The rightmost set of LEDs appeared in two rows due to a limitation on the size of the prototype breadboard used. The value represented by each LED was determined by the code being run. In this experiment the code was configured to give a range of 000 to 999ms with 1ms precision. A secondary experiment configured the code to give a range of 00.0 to 99.9ms with 100s precision and this is described in Appendix 2 Second implementation, 100s precision. An example display is shown below in Figure 2
Figure 2 example display pattern (123ms) 6
In Figure 2 the LED representing 1 in the hundreds of milliseconds and the LED representing 2 in the tens of milliseconds are fully illuminated. Within the LEDs representing units of milliseconds the LEDs representing 2and 3 are partially illuminated. This is because during the camera exposure time the sequence moved between those LEDs. The reading is made from the LED at the front of the sequence, taking account of wrapping, and so the reading above corresponds to 123ms. If the code is changed so that each LED represents a shorter time period then one would expect to catch more LEDs in transition. It is always the LED at the front of the sequence (accounting for wrapping) that is used as the reading. A simple sanity check of the LEDs can be made since LEDs in a more significant bank should only be partially illuminated if the LEDs in a less significant bank have wrapped as in Figure 3.
Figure 3 example display pattern (120ms) In Figure 3 the LED representing 1 in the hundreds of milliseconds is fully illuminated. Within the LEDs representing tens of milliseconds the LEDs representing 1and 2 are partially illuminated and within the LEDs representing units of milliseconds the LEDs representing 0 and 9 are illuminated. The reading is made from the LED at the front of the sequence, taking account of wrapping, and so the reading above corresponds to 120ms. Electronic Circuit The microcontroller used was an Arduino Mega. With minor modifications cheaper alternatives could be made, see Alternative Implementations. The circuit was built using prototyping breadboard to allow rapid reconfiguration. This was found to have sufficient reliability that a printed circuit board was not made. Note that the Blinkenlight shield [Reference 2] is an off the shelf circuit that could be used to handle the display circuitry. This has a limitation of 20 LEDs. Physical circuit The physical circuit used is shown in Figure 4. 7
Figure 4 - Arduino based circuit Schematic The logical circuit schematic is shown in Figure 5. Each Arduino output pin was connected through a 220 resistor to the anode of the relevant output LED with the cathode of that LED connected directly to ground. Since the output voltage from the Arduino is approximately 5V this gives an output current of approximately 20mA. 8
Figure 5 - circuit schematic Output LEDS 30 LEDs were used which allowed the representation of shutter lag from 000 to 999ms with a precision of 1ms. All output LEDs were kept in a single line so that they could be recorded 9
simultaneously, see Flash synchronisation speed for explanation, but this was probably not strictly necessary at this level of precision and other display configurations might be more convenient. Different colours of LEDs were used for each digit to enable an easy way of working out which LED was illuminated on the resultant photographs. A further four LEDs were permanently illuminated and marked the 0 and 5 positions of the two rightmost sets of LEDs to further help in identifying which LED was illuminated on the resultant photograph although these were not strictly necessary. It should be noted that there is a commercially available shield for the Arduino called the Blinkenshield [Reference 2] which has 20 LEDs on a circuit board and this could be used instead of the entire circuit described so far if the limited display and display configuration is acceptable. Trigger Circuit The right hand side of the circuit contained a small circuit to trigger the camera shutter. The remote shutter socket on most Canon DSLRs is a 2.5mm stereo jack. Shorting the middle section to the body enables the autofocus and shorting the tip to the body fires the shutter. Higher end Canon DSLRs use a custom socket for the remote shutter but it contains the same three wires so a simple converter could be made. This circuit used an optoisolator to protect the cameras circuitry. One output pin from the Arduino was used as the input to the optoisolator and the output of the optoisolator was connected to the body and tip of a 2.5mm stereo jack inserted into the cameras remote shutter socket. This trigger circuit may need to be adapted slightly for other camera makes and models. Costs The total component cost for the circuit above, based on an Arduino Mega, was in the order of 15 (approx. $25) as parts were sourced from low cost suppliers. For those looking for a ready-made solution purchase of an Arduino Uno and Blinkenlight shield would be in the order of 30 (approx. $45) although a trigger circuit would still need to be created. Alternative Implementations The Arduino Mega is one of the more expensive Arduino versions costing in the region of 10 ($16). Cheaper Arduino versions are available principally the Arduino Uno at a cost of 7 ($11) and the Arduino Nano at a cost of 3 ($5). The main constraint is the number of digital output pins available which limits the number of LEDs that can be driven. If less pins are available then either precision can be sacrificed by making each LED represent a larger period of time, or alternatively a two stage measurement could be made by making a rough cut at low precision and then reconfiguring the program to make a fine cut at a higher precision but assuming that the measured value lies within a given range. For example if only ten pins and thus ten LEDs were available the experiment could be run first with each LED representing 10ms. If the experiment showed that the majority of results had the 80ms LED illuminated. Then the program could be reconfigured so that the 10 LEDs represented 80ms, 81ms 89ms with some error pattern displayed if the result moved out of that range. 10
Obviously this is not ideal so it is recommended that at least an Arduino Uno with 20 digital pins is used. This has the advantage that it interfaces with the blinkenlight shield which has 20 LEDs making an almost off the shelf solution. Control Software High level software design The three banks of LEDs are each modelled in software as an array with ten elements and these arrays are initialised with the pin numbers of the Arduino pins used to control the LEDs. Arduino code consists of two main functions. The Setup function is run once when the Arduino system is first powered on or rebooted. The main execution loop function runs continuously in a loop from then on. During setup the LED controlling pins were set to output and the function fireShutter was called. The fireShutter function saved the current value of the real time clock in milliseconds and applied a pulse to the camera to activate the shutter. The duration of the pulse was configurable by setting the variable triggerDuration to the wanted value in milliseconds. The recommended value is 20. It was found that if a shorter pulse was used then the camera did not always fire reliably. This is not expected behaviour since one would imagine that the camera should detect the edge of the pulse and not care about the duration and so further investigation is needed in this area, see Trigger pulse. Note: There is anecdotal evidence of misfires when using remote shutter activations. It is possible that this may be caused by too short a trigger pulse time since even with a mechanical push button the pulse time is determined by how quickly the operator releases the button. It should be noted that during this experiment, using a 20ms pulse time, there were no misfires recorded in 240 shots. During each iteration of the main execution loop the real time clock was read and compared with the value from the previous iteration to calculate if the clock had changed, in other words if one millisecond had passed, since the previous iteration. If it had then the elapsed time since the shutter was fired was calculated and converted into the appropriate indices for the LED arrays used to display the time. Because this conversion used division and mod operations it took about 220s to execute and meant that the displayed time was always about 220s behind the actual time. However this was considered acceptable and the code was written in this way for simplicity. For a more precise and accurate method see Appendix 2 Second implementation, 100s precision. Finally the program illuminated those LEDs that had changed, extinguishing the previous LED at the same time. In order to automate the taking of many readings the program was designed to detect when the timer had reached 900ms, wait for a configurable period of time and then fire the shutter again. This code could easily be commented out if single measurements were needed. Code
11
/* Shutter lag timer
This sketch fires a camera shutter via the remote shutter socket and then starts displaying the time since the start of the trigger signal to 1 millisecond resolution. The trigger duration is configurable. The display of time is via the position of the LED that is illuminated.
10 LEDs are used to show hundreds of milliseconds 10 LEDs are used to show tens of milliseconds 10 LEDs are used to show units of milliseconds this gives a display range of 000 to 999 milliseconds
An optional routine in the code will cause the camera to automatically fire the shutter at configurable interval after the clock reaches 900ms to allow the recording of many measurements.
The circuit: - LEDs attached to ground and via 220R resistor to digital pins for time display - A 4N35 optoisolator triggered by a 150R resistor to pin1, pin2 to ground, camera shutter between pins 4 and 5 to fire the camera shutter
*/
// set pin numbers: int hundredMillisecondPins[10] = { 22,23,24,25,26,27,28,29,30,31}; // pins for LEDs representing 100ms int tenMillisecondPins[10]={ 32,33,34,35,36,37,38,39,40,41}; // pins for LEDs representing 10ms int oneMillisecondPins[10] = { 42,43,44,45,46,47,48,49,50,51}; // pins for representing 1ms int hundreds = 0; // index into 100ms array int tens = 0; // index into 10ms array int ones = 0; // index into 1ms array int previousHundreds = 0; int previousTens = 0; int previousOnes = 0; int i; // general purpose index for initialisation
const int test = 52; // the test pin for execution time measurement const int shutter = 53; // the shutter output pin
// clock int triggerDuration = 20; // How long a trigger pulse in milliseconds needed for camera to detect reliably int repeatInterval = 3000; // number of milliseconds between successive shots in automatic mode unsigned long time = 0; // this will hold the time in microseconds since the program start unsigned long previousTime = 0; // this holds the time from the previous iteration unsigned long triggerTime = 0; // this holds the time in microseconds when the triger signal is first sent unsigned long elapsedTime = 0; // this holds the time in microseconds from when the trigger signal was sent
void setup() { // set the digital pin modes: for (i=0; i<10; i++) { pinMode(hundredMillisecondPins[i], OUTPUT); pinMode(tenMillisecondPins[i], OUTPUT); pinMode(oneMillisecondPins[i], OUTPUT); } pinMode(shutter, OUTPUT); pinMode(test, OUTPUT);
fireShutter(); // fire the shutter once }
void fireShutter() { // Fire the camera shutter once triggerTime = millis(); // record time since program start in milliseconds digitalWrite(shutter, HIGH); delay(triggerDuration); // set the duration to be what is necessary for reliable detection digitalWrite(shutter, LOW); // stop camera firing multiple shots previousTime = triggerTime; // initialise previousTime 12
}
void loop() { // this loop displays the time since the start of the trigger signal to 1ms precision // digitalWrite(test, HIGH); // uncomment to test loop execution time time = millis(); // record time since program start in milliseconds if (time != previousTime) // if milliseconds have changed since last time measurement { previousTime = time; // reset previous time elapsedTime = time - triggerTime; // contains number of milliseconds since start of trigger ones = int(elapsedTime % 10); // calculate milliseconds part of elapsed time tens = int((elapsedTime / 10) % 10); // calculate tens of milliseconds hundreds = int((elapsedTime / 100) % 10); // calculate hundreds of milliseconds if (ones != previousOnes) // if ones have changed { digitalWrite(oneMillisecondPins[ones], HIGH); // illuminate 1ms LED digitalWrite(oneMillisecondPins[previousOnes], LOW); // extinguish previous 1ms LED previousOnes = ones; } if (tens != previousTens) // if tens have changed { digitalWrite(tenMillisecondPins[tens], HIGH); // illuminate 10ms LED digitalWrite(tenMillisecondPins[previousTens], LOW); // extinguish previous 10ms LED previousTens = tens; } if (hundreds != previousHundreds) // if hundreds have changed { digitalWrite(hundredMillisecondPins[hundreds], HIGH); // illuminate 100ms LED digitalWrite(hundredMillisecondPins[previousHundreds], LOW); // extinguish previous 100ms LED previousHundreds = hundreds; } // The following five lines of code can be commented out for manual operation if (hundreds == 9) // if 900ms have passed since start of program { delay(repeatInterval); // wait for a configured amount of milliseconds fireShutter(); // trigger the shutter again } } // digitalWrite(test, LOW); // uncomment to test execution time } Alternative Implementations A second code implementation is shown in Appendix 2 Second implementation, 100s precision. Testing Timing One channel of an uncalibrated two channel oscilloscope was attached to each of the 1ms output pins and used to verify that the pin was high for 1ms and low for 9ms. The second channel was attached to an adjacent pin to check that the pins were synchronised so that as the preceding pin went low the next pin in the sequence went high. This was repeated for all 1ms pins and a sample check then done on the other pins. Sanity check Some simple sanity testing was conducted by changing the calculations from elapsed time to array indices to increase the divisors by a factor of 100 which had the effect of slowing the program down by a factor of 100 and making the LEDs represent tens of seconds, seconds and tenths of seconds so that the correct sequence could be verified manually. The value of triggerDuration was also set to several seconds to check that the time displayed included the trigger time. 13
Execution time An additional output pin was used for testing purposes and set to go HIGH and LOW at various points in the program. This pin was then monitored with an uncalibrated oscilloscope. In this way it was possible to measure the execution time of parts of the code to verify that the code was executing fast enough to prevent errors. The introduction of code to manipulate the test output pin would of course vary the execution time to some small degree but the results showed enough safety margin that this was not an issue. The execution time of the main loop if the millisecond clock had incremented was found to be about 220s. If the clock had not incremented it was about 15 s. Camera setup The cameras used were a Canon EOS 400D and a Canon EOS 700D. The camera was set to ISO1600 with a manual exposure of 1/4000s and F/2.8. These values were chosen as they represented the fastest shutter speed available on the camera and gave a good exposure on the resulting images. If other cameras are used the settings may need to be adjusted somewhat to suit the capabilities of the camera, but in general the fastest shutter speed should be used with the widest aperture on the lens and then whatever ISO setting is needed to get a good exposure. Image quality is not critical, all that is required is to determine whether an LED is illuminated or not. Note that a shutter speed of 1/4000s is equivalent to 250s. The camera was set to manual focus and the image quality set down to the smallest file-size possible, a small coarse jpeg. This was to minimise the amount of time taken writing data to the memory card and also to decrease the time spent opening the images for analysis. Image quality is not important as long as it is clear whether an LED is illuminated or not. With the camera in manual exposure and manual focus the variability in shutter lag should be minimised although there may be other factors contributing to variability that need to be investigated, see Variability. For cameras with a fold-out display, such as the 700D, the display was left in a visible state. This was to ensure a direct like-for-like comparison with other cameras with a fixed display. It is not known whether writing to the camera display could contribute to variability in shutter lag or not but for the purpose of this experiment this variable was held constant. Note that mirror lock-up was not used in this experiment. Although this would remove a mechanical operation that could be a factor in variability it was felt to be unrepresentative of normal use. Flash synchronisation speed The flash synchronisation speed of most DSLRs is around 1/200s which is 5000s. At shutter speeds in excess of the flash synchronisation speed the entire sensor is not uncovered at any one time but is exposed as a slit between two curtains that travel parallel to the long edge of the sensor. Thus in order to capture the whole display pattern at the same instant in time the shot must be composed so that the strip of LEDs is parallel to the long edge of the frame. 14
If a camera other than a Canon DSLR is used then the shutter design should be checked and this part amended accordingly. Note that even with the strip of LEDs parallel to the sensor, since the shutter curtains take 5000s to traverse the distance represented by the short edge of the sensor there may still be many microseconds between the camera exposing the top edge of the LED and the bottom edge of the same LED and this puts a practical limit on the precision of this method which is independent of the shutter speed. Taking a reading In automatic mode the program takes a reading at a period determined by the variable repeatInterval . Alternatively this code could be commented out and a reading taken by pressing the reset button on the Arduino, once for each reading. The interval between readings was set to ensure that any card-writing operations had finished before the next shot was taken since otherwise these might contribute to variability. This was double-checked by observing the card-writing LED on the back of the camera and ensuring that it had ceased activity prior to taking the next shot. Post-Processing The resultant images were viewed in an image editing program and the shutter lag determined by reading the pattern. If Adobe Photoshop is available then use of the File -> Scripts -> Load files into stack can be used to quickly load all images into a single Photoshop file separated as layers. This then makes it very quick to spot any variation by simply viewing each layer in turn and observing if the pattern changes. This is one reason why it is advisable to take the lowest resolution image possible since it then allows many images to be loaded at once without consuming large amounts of computer memory. A text layer may also be added at the top of the stack to label each LED if there is any doubt as to which LED is which. Results The results of 121 readings taken on a Canon EOS 400D are shown in Figure 6 Canon EOS 400D results histogram. 15
Figure 6 Canon EOS 400D results histogram From this it seems fairly clear that the normal shutter lag is in the range 115 to 121ms. However 5% of the measurements lie outside of this range and it requires further investigation as to why there is such variation. The full measurement data is shown in Appendix 1 Measurement data. The same experiment repeated on a Canon EOS 700D gave a mean shutter lag of 97 ms and a variation of 7 ms. However the results exhibited a curious two-spike distribution pattern of 93ms 3ms and 103ms 1ms which requires further investigation, see 700D two spike distribution. 0 5 10 15 20 25 30 35 40 115 116 117 118 119 120 121 122 123 124 125 126 More F r e q u e n c y
Shutter lag in ms Histogram 16
Figure 7 Canon EOS 700D results histogram Accuracy No calibrated test equipment was available to objectively test the accuracy of the measurements so it is not possible to state the accuracy of the system. However a subjective assessment is as follows. The accuracy of the timer depends mostly upon the accuracy of the real time clock in the Arduino system. It seems reasonable to expect that such a clock would be accurate to within a few seconds a day and thus over the small measurement periods of this experiment the error in the clock itself would be negligible. There will be some error caused by the delay between reading the Arduino real time clock and outputting the resultant pattern on the LEDs. This delay should remain fairly constant and is mainly due to the conversion from elapsed time to the array indices, but there will be some small variation caused by the if statements in the code and potentially some variability in the time taken to execute the in-built functions. Taking all the factors above into account would suggest that the absolute accuracy of the LEDs is within about 250s and the relative accuracy between two readings is within about 25s. The goal of this experiment was to achieve a precision of one millisecond and this has been met. For the purposes of synchronising two cameras the relative measurements between two cameras is more important than absolute accuracy so it is believed that the accuracy is sufficient for the primary purpose. 17
Further work Improvements to timer The code used has been made simple to aid understanding and to limit the possibility of error since calibrated test equipment was not available. It is believed that the basic techniques could easily be expanded to produce more precision and more accuracy if needed. A number of future improvements could be made as below: Increased precision A second implementation that displays to 100s precision and 50s accuracy is described in Appendix 2 Second implementation, 100s precision. This could be expanded further and more LEDs could be added to show higher precision. For instance another 10 LEDs representing 10 s could be added. The main restriction on this is that the main execution loop needs to run fast enough to execute in less than the period represented by each new LED otherwise there would be some gaps in the display sequence. This would require some more efficient code which would run the risk of introducing errors which might be hard to find without the use of calibrated test equipment. Note that once the time that an LED represents becomes less than the shutter speed of the camera there will be a greater probability of the LED switching on or off during the exposure . So for instance if a shutter speed of 1/4000s was used then LEDs representing 10s would switch on and off 2.5 times each within the camera exposure. This would have the effect of making all the 10s LEDs appear to be more or less the same brightness. One way to overcome this would be to have more LEDs at the lower precisions, so that the total time represented by the strip of LEDs is more than the camera exposure time, so for instance if there were a strip of 100 LEDs each representing 10us, so that the total time represented by the strip was thus 1000s then it would be possible to detect what time was represented. If this were done some sort of multiplexing would be needed as there would not be enough output pins to control each LED separately. In this situation, if each LED is only illuminated for the time it represents, for example 10s, the apparent brightness may be very low since it would only be illuminated for 1/25 of the exposure (assuming a 250s exposure). To overcome this each LED could be left illuminated for longer, say 50s. It should be noted that the Blinkenlight website [http://blog.blinkenlight.net] has a number of resources to do with fast counting and LED display and this could prove valuable if a more precise timer were required. Increased accuracy The main cause of inaccuracy is the fixed delay between sampling the real time clock and converting that to the array indices. This could be solved by pre-calculating the values for the next update immediately after the current update has been displayed, since when displaying at 1ms precision the program spends nearly all its time just waiting for the real time clock to advance by one millisecond. 18
If this were done it is believed that the delay between sample and display could be limited to about 10s, and the accuracy would then only be determined by the accuracy of the Arduino clock. Decreased number of LEDs If an implementation using less LEDs were needed then a binary clock is much more efficient. So for instance eight LEDs could cover a range of 0-255ms to 1ms precision or ten LEDs could cover the range 0-99ms at 100s precision. This runs the risk that LEDs caught in transition are more difficult to detect but using Gray codes rather than binary codes would minimise this risk. Alternatively a hybrid system of using binary for the milliseconds and analogue LEDs for the microseconds could cover both a large range and high precision with a small number of LEDs. Other timing triggers It might be possible to create a circuit to directly measure the shutter lag without taking a photograph. For instance monitoring the flash output pin on the camera hot shoe (or PC socket), or potentially using a sound activated trigger to detect movement of the shutter curtains. These methods would have the benefit that measurements could be totally automated and thus potentially a very large statistical sample could be made. The disadvantage is that they are only indirectly measuring the shutter lag and unless the entire system is understood they might lead to a false conclusion. Areas for further study Trigger pulse Experiment suggested that the trigger pulse needed to be 20ms long for reliable triggering. Does this imply some polling of the input and if so could this contribute to variability? Variability With the camera in manual focus and manual exposure what factors contribute to variability in the shutter lag? Is it all mechanical or could it be caused by scheduling within the camera software. 700D two spike distribution The 700D distribution showed two spikes. What could cause a distribution like this? Shutter speed inaccuracy When measuring at 100s precision, up to 6 LEDs were illuminated simultaneously. This would imply a shutter speed closer to 1/2000s rather than the 1/4000s set on the camera. Is the shutter speed inaccurate or are there other explanations? References Reference 1 http://www.3dtv.at/knowhow/Synctest_en.aspx Reference 2 http://blog.blinkenlight.net Reference 3 http://www.doc-diy.net/photo/shutter_lag
19
Appendix 1 Measurement data 400d.xlsx 700d.xlsx
Appendix 2 Second implementation, 100s precision High level software design The main problem to be solved when working at higher precision is to ensure that the main execution loop can be completed in all cases in less than the time represented by the least significant LED, in this case 100s. Most of the code can run this fast but the conversion from elapsed time to array indices takes about 250s due the divide and mod operations. To overcome this, the conversion is only done once in the initialiseTimer function and from then on the entire code works only on the difference between one iteration and the next. The initialiseTimer function reads the real time clock and calculates the elapsed time since the clock was saved in the fireShutter function. It then converts this elapsed time into the appropriate indices for the LED arrays to display the time. During each iteration of the main execution loop the real time clock is read and compared with the value from the previous iteration to calculate the time difference from the previous iteration. This is then used to calculate how much to increment each of the LED array indices. The code first checks to see if adding the time difference to the previous microsecond value has caused this to go over 1000 and if so calculates the new values for the milliseconds. Note that if the microseconds has increased to over 2000 this would not initially be detected but as long as the execution time of the loop is significantly less than this it will quickly correct. Timeline The time line involved is shown in Figure 8 which is not drawn to scale 20
Figure 8 code timeline When the program starts the real time clock is at 0. The first reading is made during the fireShutter function and is called triggerTime. The second reading is made during the initialiseTimer function and is called previousTime. The difference between these two readings is calculated as elapsedTime and will be at least as long as the trigger duration, probably about 20ms. The next reading is made during the first iteration of the loop and is called time and the difference between this and the previous reading is the timeDifference which will probably be around 250s. Time is then set as previousTime for the next iteration and a new reading of time is made in the next iteration. Each iteration calculates its own timeDifference which is in the order of 30s for all iterations except the first. Code execution time and optimisation The code has been kept quite simple in order to ease understanding and to try and minimise the possibility of errors. However because of the requirement to keep the execution time of the main loop to below 100s some optimisation has had to be made. The simplest and least error-prone way to display the time would be to simply sample the real time clock in the main execution loop, convert it to the array indices and then illuminate the LEDs according to those values. This has the advantage that a fresh calculation is made every time in the loop which prevents any errors building up. Unfortunately the division by 10 and mod by 10 instructions needed are very expensive in processor time, especially as the microsecond clock must be declared as a long integer. Thus the current code only converts the real time to array indices once in the initialiseTimer routine and from then on simply works on the difference from the previous iteration. This has more potential for error and can potentially allow errors to build up over time but was necessary in order to bring the execution time below 100s. 21
Potentially some more efficient code could be written but again this is likely to introduce more possibility of error. In general accuracy has been considered more important than precision in this experiment. Code /* Shutter lag timer
This sketch fires a camera shutter via the remote shutter socket and then starts displaying the time since the start of the trigger signal to 100 microsecond resolution. The trigger duration is configurable. The display of time is via the position of the LED that is illuminated.
10 LEDs are used to show tens of milliseconds 10 LEDs are used to show units of milliseconds 10 LEDs are used to show 100 microseconds each this gives a display range of 00.0 to 99.9 milliseconds
The code used in the initialiseTimer function is quite processor intensive taking about 200us to execute so it is only called once. From then on the program works on calculating only the difference in each iteration which is much less intensive taking only about 25us
The circuit: - LEDs attached to ground and via 220R resistor to digital pins for time display - A 4N35 optoisolator triggered by a 150R resistor to pin1, pin2 to ground, camera shutter between pins 4 and 5 to fire the camera shutter
Testing: By replacing the 'micros()' function calls with 'millis()' function calls the clock will run 1000 times slower allowing the LED patterns to be manually checked. The trigger duration should be set to 1000 times its normal value to check that part at the same time. During testing the two lines of code writing to the test pin can be uncommented in order to measure the execution time of the main program loop.
*/
// set pin numbers: int tenMillisecondPins[10] = { 22,23,24,25,26,27,28,29,30,31}; // pins for LEDs representing 10ms int oneMillisecondPins[10]={ 32,33,34,35,36,37,38,39,40,41}; // pins for LEDs representing 1ms int oneHundredMicrosecondPins[10] = { 42,43,44,45,46,47,48,49,50,51}; // pins for representing 100us int tens = 0; // index into 10ms array int units = 0; // index into 1ms array int oneHundredMicroseconds = 0; // index into 100us array int microseconds = 0; // value of microseconds int previousTens = 0; // value of tens from previous iteration int previousUnits = 0; // value of units from previous iteration int previousOneHundredMicroseconds = 0; // value of 100us from previous iteration int previousMicroseconds = 0; // value of microseconds from previous iteration int timeDifference = 0; // time difference between successive iterations int i; // general purpose index for initialisation const int test = 52; // the test pin for loop execution time const int shutter = 53; // the shutter output pin
// clock int triggerDuration = 20; // How long a trigger pulse in milliseconds needed for camera to detect reliably unsigned long time = 0; // this will hold the time in microseconds since the program start unsigned long previousTime = 0; // this will hold the time in microseconds since the program start from last iteration unsigned long triggerTime = 0; // this holds the time in microseconds when the triger signal is first sent unsigned long elapsedTime = 0; // this holds the time in microseconds from when the trigger signal was sent
void setup() { // set the digital pin modes: for (i=0; i<10; i++) 22
fireShutter(); // fire the shutter once initialiseTimer(); // initialise the timer }
void fireShutter() { // Fire the camera shutter once triggerTime = micros(); // note the time for start of the trigger pulse // triggerTime = millis(); // uncomment this line and comment line above for test digitalWrite(shutter, HIGH); delay(triggerDuration); // set the duration to be what is necessary for reliable detection digitalWrite(shutter, LOW); // stop camera firing multiple shots } void initialiseTimer() { // This routine preloads the 'previous' variables ready for the first iteration of the main loop previousTime = micros(); // get microseconds since program began // previousTime = millis(); // uncomment this line and comment line above for test elapsedTime = previousTime - triggerTime; // calculate microseconds since trigger signal previousMicroseconds = int(elapsedTime % 1000); // calculate microseconds part of elapsed time previousUnits = int((elapsedTime / 1000) % 10); // calculate units of milliseconds previousTens = int((elapsedTime / 10000) % 10); // calculate tens of milliseconds } void loop() { // this loop displays the time since the start of the trigger signal to 100us precision // to avoid a lot of processor intensive divide and mod operations on variables declared as // long, it works using the time difference since last iteration, or for the first iteration // the time difference since the initialiseTimer routine digitalWrite(test, HIGH); // uncomment this line to test loop execution time time = micros(); // get microseconds since program began // time = millis(); // uncomment this line and comment line above for test timeDifference = int(time - previousTime); // contains number of microseconds since last iteration previousTime = time; // save time for next iteration microseconds = previousMicroseconds + timeDifference; // contains new microseconds units = previousUnits; // assume units has not changed tens = previousTens; // assume tens has not changed if (microseconds > 999) { microseconds = microseconds - 1000; units = units + 1; } if (units > 9) { units = units - 10; tens = tens + 1; } if (tens >9) { tens = tens - 10; // the timer wraps at 99ms so no other action needed } // in almost all cases microseconds will now contain a value less than 1000 since the main execution loop // only takes about 18us to 36us to execute. The first iteration could potentially still have a value over 1000 // due to the time taken in the initialiseTimer routine but this will get caught over the next iterations // until microseconds is below 1000.
// if LEDs have changed since last time illuminate LEDs to show current elapsed time // and extinguish LEDs for previous elapsed time
if (units != previousUnits) { digitalWrite(oneMillisecondPins[units], HIGH); // illuminate 1ms LED digitalWrite(oneMillisecondPins[previousUnits], LOW); // extinguish previous 1ms LED 23
previousUnits = units; } if (tens != previousTens) { digitalWrite(tenMillisecondPins[tens], HIGH); // illuminate 10ms LED digitalWrite(tenMillisecondPins[previousTens], LOW); // extinguish previous 10ms LED previousTens = tens; } // microseconds will always have changed so no need to do a test // The microsecond LEDs represent 100us so need to convert from microseconds to array index // We want to do this without a divide or mod instruction to save execution time if (microseconds > 899) // note this value could be over 1000 still { oneHundredMicroseconds = 9; } else if (microseconds > 799) { oneHundredMicroseconds = 8; } else if (microseconds > 699) { oneHundredMicroseconds = 7; } else if (microseconds > 599) { oneHundredMicroseconds = 6; } else if (microseconds > 499) { oneHundredMicroseconds = 5; } else if (microseconds > 399) { oneHundredMicroseconds = 4; } else if (microseconds > 299) { oneHundredMicroseconds = 3; } else if (microseconds > 199) { oneHundredMicroseconds = 2; } else if (microseconds > 99) { oneHundredMicroseconds = 1; } else { oneHundredMicroseconds = 0; } if (oneHundredMicroseconds != previousOneHundredMicroseconds) { digitalWrite(oneHundredMicrosecondPins[oneHundredMicroseconds], HIGH); // illuminate 100us LED digitalWrite(oneHundredMicrosecondPins[previousOneHundredMicroseconds], LOW); // extinguish previous 100us LED previousOneHundredMicroseconds = oneHundredMicroseconds; } // save values for next iteration previousMicroseconds = microseconds; previousUnits = units; previousTens = tens; digitalWrite(test, LOW); // uncomment this line to test loop execution time }
Experimental observations It was noted that in the majority of photographs taken using this method, six or seven of the 100s LEDs were illuminated. This was not expected as theoretically only three or four should be illuminated in a 250s exposure. This requires more investigation, see Shutter speed inaccuracy. 24
Appendix 3 - Binary methods The initial approach that was taken was to output a binary pattern on the LEDs in a manner similar to that described in [Reference 3]. This was because it allows a very economical use of LEDs, only eight LEDs are needed to represent times from 0 to 255ms at 1ms precision. However this was found to be unsatisfactory because of the possibility of LEDs changing state during the camera exposure. If the fastest switching LED changes at an interval of 1ms and the camera exposure is 1/4000s then it can be predicted that the LED will be captured in a transitional state 50% of the time as shown in Figure 9 LED transition during exposure.
Figure 9 LED transition during exposure Any exposure which is started up to 250s before the pattern changes or finishes up to 250 s after the pattern changes will capture some LEDs in a transitional state. This will manifest itself as the LED looking less bright than LEDs that have not changed state but it would be hard for a human to detect the differences in most cases. So for instance if a 1/4000s (250s) exposure was started at 31.875ms it would end at 32.125ms. This would capture the transition between the pattern 00011111 and 00100000 and would be seen on the exposure as 00xxxxxx where the x represents an LED at half brightness. This could easily be misinterpreted as 00111111 which is 63 in decimal. Thus the true reading of 31 to 32ms could be misinterpreted as 63ms. For this reason the approach described in the rest of this document was used.
IEC SYSTEM FOR CONFORMITY TESTING AND CERTIFICATION OF ELECTRICAL EQUIPMENT: TESTING AND MEASURING EQUIPMENT/ALLOWED SUBCONTRACTING FOR TERRESTRIAL PHOTOVOLTAIC (PV) MODULES