Optical slave trigger is the easiest way of getting the flash off camera without wires. However, most of the optical slave triggers are quite dumb. They trigger the flash as soon as a light pulse is detected. This works fine if all of your flashes are in manual mode. If you want to use a dumb flash in TTL flash system, or wireless flash system such as Nikon’s Advance Wireless Lighting (AWL), you will run into trouble. The flash will be triggered prematurely when the TTL metering pulse or wireless communication pulses are emitted.
There are some smarter flash triggers that can work around the problem. Here are some examples I found.
- A Programmable Optical Slave Flash Trigger for Digital Cameras with Processor PIC 12F675 – This one has a programming mode that allows the trigger to learn new flash patterns.
- Slave Flash Trigger Kit, Optical with PIC Controller, Ver.III – This one uses the same PIC chip but the circuit is slightly different. It is targeted for point-and-shoot cameras but it was reported to work with Canon EX Flashes.
- Slave Flash Trigger using AVR micro controller – This is a simple pulse counter that triggers on 1st, 2nd, 3rd, or 4th flash.
All of the above is based on the assumption that the flash synchronization (or main flash) pulse is the last one. This is true in most cases. The problem is uncertain number of pulses before the last one. If the camera emits fixed number of pulses before the main flash, it is easier to handle. But for the complicated system such as Nikon’s Advanced Wireless Lighting system, the light pulses can have virtually unlimited variations. I have previous post a series article on the communication details of the Nikon system. Interested readers may refer to the 1st article and all the articles in the Related Posts section.
After studying the details, I discovered some characteristic patterns in the pulses and came up with a optical slash flash trigger that can work in Nikon AWL setup as well as normal TTL flash setup. The trigger is built upon the Arduino prototyping platform with some limited and inexpensive parts. Most of the trick is done by the software.
The hardware
The following is the schematic.
A photodiode (BPW34) connected in photo-conductive mode is used to detect the flash pulses. The signal is filtered through a RC network (C1, R2) to remove the ambient light component. The signal goes to an analog pin on the Arduino Duemilanove board. Built-in analog-to-digital conversion (ADC) unit converts the analog signal to digital value representing the signal intensity. A custom software code analyze the pulses and determine when to trigger the flash.
S1 is a simple button switch for test firing the flash. S2 is a 2-bit DIP switch for configuration purposes. The first bit controls the trigger mode. The default is the “smart” mode. The other mode is the dumb mode. The second bit controls the sensitivity of the trigger. Only two sensitivity levels are available: low (default) and high.
An optocoupler (MOC3041M) is used to trigger the flash. Sync voltage lower than 400V is safe based on the datasheet. Other variants of MOC30XX may also work. I simply used the parts I already have.
The software
The software is the heart and soul of the smart trigger, and it is not overly complicated. No attempt was made to fully decode the pulse communication of Nikon’s AWL although it may be possible. So how exactly does the software figure out when to trigger the attached flash?
The communication protocol of Nikon’s AWL is quite compact. It sends out unmodulated pulses that are normally ~70 microsecond long. The pulses follow simple binary format except for the channel indicator bits. Between groups of pulses, there are delays of various lengths. It turns out that the longest delay between the pulse groups is before the final sync pulse or main flush. That’s when the camera raises the mirror and fully opens the shutter. So if a pulse comes after a specially long delay time (more than ~40ms) of a previous pulse, it is most likely the sync or final flash pulse. There are exceptions but it can be handled by examining the characteristics (number of pulses) of the previous pulse groups preceding a pulse.
For anyone who is interested in testing it out, here is the code for Arduino IDE. Arduino source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | // Defines for setting and clearing register bits #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif // Arduino pins #define PHOTODIODE 0 #define MODE 3 #define SENSITIVITY 4 #define XSYNC 7 #define BUTTON 8 // 4 microsecond precision timing using 16MHz Arduino extern volatile unsigned long timer0_overflow_count; // For timeing measurements volatile unsigned long prev_pulse, time_passed; int ar0; // Photodiode input int threshold; // Pulse height threshold. DIP #2 off: 10, on: 5 int mode; // DIP #1 off: 1st pulse, on: first pulse. int pulse_count; // Pulse count int prev_pulse_count; // Pulses in previous pulse groups // Each tick is 4 microsecond unsigned long hpticks (void) { return (timer0_overflow_count < < 8) + TCNT0; } // Fire the flash with a 15 microsecond pulse on the xsync terminal void fire() { digitalWrite(XSYNC, HIGH); delayMicroseconds(15); digitalWrite(XSYNC, LOW); } void dip_config(int info) { // DIP switch #1 if(digitalRead(MODE) == HIGH) mode = 1; // Switch Off: smart mode else mode = 0; // Switch On: dumb mode // DIP switch #2 if(digitalRead(SENSITIVITY) == HIGH) threshold = 50; // Switch off: low sensitivity else threshold = 15; // Switch on: high sensitivity if(info == 1) { Serial.print("Mode: "); Serial.print(mode); Serial.print(", Sensitivity: "); Serial.println(threshold); } } void setup() { int start; Serial.begin(57600) ; // Use internal reference voltage of 1.2v for ADC analogReference(INTERNAL); // Set prescale to 8 for much faster analog read. // 16MHz/8 = 2MHz ADC clock speed. Each ADC conversion takes 13 cycles. // So 2MHz/13 = 154KHz sampling rate. // With other overhead considered, each analogRead takes ~10 microseconds. cbi(ADCSRA,ADPS2) ; sbi(ADCSRA,ADPS1) ; sbi(ADCSRA,ADPS0) ; pinMode(PHOTODIODE, INPUT); pinMode(MODE, INPUT); digitalWrite(MODE, HIGH); // Use the internal pull-up register pinMode(SENSITIVITY, INPUT); digitalWrite(SENSITIVITY, HIGH); // Use the internal pull-up register pinMode(XSYNC, OUTPUT); pinMode(BUTTON, INPUT); // I don't why. The first analog read always return 1023 so a dummy read is added analogRead(PHOTODIODE); ar0 = analogRead(PHOTODIODE); dip_config(1); Serial.println("Trigger Ready."); } void loop() { int new_ar, delta; unsigned long now; new_ar = analogRead(PHOTODIODE); delta = new_ar - ar0; now = hpticks(); time_passed = now - prev_pulse; // Pulses come in bundles. The minimal seperation seems to be around 4000 microseconds if(pulse_count > 0 && time_passed > 1000) { prev_pulse_count = pulse_count; pulse_count = 0; } // Reset if > 125000*4 = 500ms. if (time_passed > 125000) { pulse_count = 0; prev_pulse_count = 0; prev_pulse = now; dip_config(0); // Check the DIP switches. No serial xfer to save time. } if(delta > threshold) { if(mode == 0) { // Dumb mode. Fires upon seeing any light pulse fire(); } else { // Smart mode // If the delay since the previous pulse is longer than 10000*4 microseconds (40ms)... // Checking the previous pulse group is needed to avoid mis-fire. if((time_passed > 10000) && (prev_pulse_count == 1 || prev_pulse_count > 5)) { // Fire the flash now! fire(); // Reset the pulse counter pulse_count = 0; prev_pulse_count = 0; } else { // Get to the end of the pulse. Some pulses are specially wide. while(analogRead(PHOTODIODE) > threshold); // Increase the pulse count pulse_count++; // Keep track of the time a pulse is detected prev_pulse = now; } } } else { // Background. It should be zero most of the time. ar0 = new_ar; } // Read the button status. if (digitalRead(BUTTON) == HIGH) { fire(); pulse_count = 0; prev_pulse_count = 0; dip_config(1); delay(300); // Avoid button contact problem so delay 300ms } } |
Sensitivity and distance
The sensitivity is quite good thanks to the large sensing area of the photodiode. Since my current setup is still on a messy breadboard, I was not able to test the trigger distance. But it doesn’t require line-of-sight to work. Reflected flashes from walls can trigger it from at least 15 ft away.
Limitations
It works in TTL mode and in wireless mode (only tested with Nikon D200). Front curtain sync seems to work in all cases but rear curtain only works when the shutter speed is faster than 1/2 second. This limitation needs a complex fix to overcome. It will probably be included in future releases.
Future plans
I will be refining the software for the near term to make it work in all situations, hopefully. Another plan is to design a circuit with minimal number of parts. Using the Arduino board is ok for prototyping purposes but it is a overkill for the application. I welcome any feedbacks, suggestions, and feature requests.
Update
Check out the part II. I have put it on a perfboard so I can use the Arduino Duemilanove board for something else. New schematics is available. Code is unchanged.
Keywords: Arduino, Flash Trigger, Optical Flash Trigger, Optical Slave

