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.

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.


Related Posts

Comments