Code and schematic for an M-to-X flash trigger delay. The intent of this project is to enable the use of electronic (i.e. X-sync) flash with shutters that are only designed to trigger M-sync’d flashbulbs.

Background
In Ye Olden Times there were flashbulbs, many different designs and types exist, but generally some kind of flammable metal (magnesium, etc) is enclosed inside a gas-filled bulb. When ignited by an electric pulse, that filler burns very quickly and brightly to provide illumination for an image. Different bulbs existed with different brightness patterns, but one of the more common were “M” class bulbs (safe to crash your shuttlecraft into if you hit a subspace anomaly). These bulbs hit peak brightness approx 20ms after ignition and stay bright (measured by the time the bulb is at least 1/2 its peak brightness) for ~20ms (depends on the bulb).
Electronic flash units are much faster, typically reaching peak brightness in under a millisecond and generally having a duration under 1ms (and much less at lower power settings).
SO, when triggering a flash, generally one would want to trigger an electronic flash the instant the shutter is fully open, and an m flashbulb about 20ms before the shutter opens.
When designing a shutter (in particular leaf shutters), generally the easiest thing to do is trigger the flash as soon as the blades open (X-Sync). Many shutters have a fairly simple mechanisms where the shutter ring closes a contact once it hits the fully open position. The problem with this, for flashbulbs, is that the flashbulb intensity will not peak for another 25ms. The simple way around this problem is to use shutter speeds of 1/30th second or slower (33.33 ms). This lets the shutter stay open long enough to capture most of the useful light from the flashbulb. The problem with that is that it precludes you from using a flashbulb as fill flash during bright daylight OR using freezing motion with a fast shutter speed (since the duration of a flashbulb is much longer than an electronic flash, it will not freeze motion all by itself very well).
X sync would generally be used for electronic flash, Wikipedia says that electronic flash was developed in 1931, but it wouldn’t become common/affordable until the 60’s and 70’s. Long after electronic flash became widely available, flashbulbs still had an edge in that they are small and lightweight, and they are their own power source (they require electricity to be triggered, but the energy comes from the burning filaments inside).
Because of all this, many leaf shutters include a delay mechanism to trigger the flash, then wait 20ms, then trigger the shutter (M-sync). On nicer/fancier shutters, there’s usually a switch to select between M and X sync (and often this switch is shared with the self-timer function if present since the M-sync delay mechanism frequently serves double duty as the self timer delay). Many shutters however (particularly older and less expensive ones), lack any external mechanism to switch between M and X sync. Sometimes these shutters X-sync (i.e. they trigger the flash as soon as the shutter opens), and sometimes they M-sync (i.e. delay the shutter until ~20ms after the flash is triggered).
I wasn’t designing shutters in the 50’s, but I assume the cost hierarchy went something like X-sync only (no delay mechanism at all) -> M-sync only (non-switchable delay mechanism) -> M/X sync (switchable delay). Electronic flash was expensive and big and generally only used in studios or by pros before the 60’s (ish) so I assume that many older family cameras that are x-sync only were made that way for cost reasons rather than to target electronic flash (just guessing!).
What’s my shutter?
Let’s say that you just bought a nice old camera, great news it has no bellows leaks and the shutter is working like a charm (yay!). But can you use it with flash? Although you aspire to get a flashbulb device working eventually, you are a futuristic human from the 21st century and you will be using only the most modern 1980’s electronic flashes you got on ebay in a box of junk you really shouldn’t have bought but you did anyways. The shutter has no M/X selector so who knows if it’s M or X sync’d.
How do you figure out? Maybe the manual if you can find it, maybe searching around some forums. Easiest thing to do though is hook up an electronic flash, open the back of the camera, open the aperture all the way, set the speed to 1/100 or faster, point the flash into the back of the camera at the lens, an pop the shutter. If you see a bright flash projected out of the front of the lens, great news, it’s X-sync’d! If not, it’s either M-sync’d or something is broken.
Let’s talk about some of my favorite things, me & cameras
I picked up a really nice little zenobia 6x4.5 folder. Lovely simple little 120 camera with a nice lens. This one was in great shape and the shutter was in excellent working condition (although these, like most folders) are very easy to repair if you’re not so lucky. The manual is vague about flash I assume because no one buying this camera when it was first made would have been likely to shoot it with electronic flash. A quick test determined that it is in fact M-sync’d.
To get this camera working with electronic flash, I designed a simple setup to delay the flash trigger an appropriate amount to get the electronic flash going off right after the shutter blades open. The result is a magic little box with two pc flash sync cables (a male and a female) that sits between the shutter and the flash.
Operation is simple, switch on the power and fire away. A microcontroller detects when the flash sync contacts in the shutter close, waits a bit, then fires the flash. The microcontroller is probably massive overkill, a 555 timer or simple r-c circuit could probably work fine, but this was pretty easy to get going, and the microcontroller board was < $5 and included a battery charge manager and a USB port to charge it off of so that seemed like a better deal all around to make a complete package.
The little box contains a Seeed studios Xiao RA4M1 microcontroller which is a teeny (~1” square) microcontroller on a little board with an LED and a USB-C port. Some notes on the design:
- U2 is a solid state relay to actually trigger the flash. You could trigger the flash directly with the microcontroller but if you ever hook up a flash with a high trigger voltage you’ll fry the microcontroller. I used an AQH0223 but many are probably suitable for this appplication. I think an AC SSR is preferable since I don’t know that all flashes have the same trigger polarity.
- Choose a value for R1 that is suitable to give enough power to the triggering LED in the SSR. Look at the SSR’s datasheet for that. In my case I think I used a 100ohm (double check or you’ll fry your SSR/micro!)
- My first version of this omitted the RV1 pullup potentiometer and relied solely on the built-in pullup resistor in the microcontroller. The unit would constantly trigger the flash when plugged into a camera, presumably because the D1 input on the microcontroller is extremely high impedance and the camera was acting like an antenna and floating that input all over the place. You could I’m sure choose a fixed resistor instead of a pot, but I found a pot was easier to tune, you need the pullup resistance low enough that you don’t get phantom triggers, but high enough that it reliably fires when the shutter triggers. Maybe start with a 5Kohm or 10Kohm guy.
- The switch on the battery is not strictly necessary, the standby power consumption is EXTREMELY low, but I like to have it anyways so it can sit in a bag for months and still be usable. I use a 40mA Li-Po and it will last for weeks and hundreds of triggers.
- It’s very hard to source board or case mounting PC connectors (it’s not helped by the fact that “pc connector” is the least useful google search of all time). Possibly they exist but I couldn’t find any that were easily orderable. I just chopped up an old PC extension cable (the kind with a male and female end). On the input side, I grounded the outer shell and used the inner as the trigger, but I don’t think the polarity actually matters much there (the shutter contact is either open or closed).
- The delay is hard-coded currently to 25ms. M-sync is nominally a 20ms delay, but I chose 25ms because:
- Old shutters may have slower or faster delay mechanisms. They might change if you clean and lube
- There’s no easy way for this device to know when the shutter opens, only when the m-sync was triggered
- If you trigger the electronic flash too soon, you get nothing, if you trigger it a little late, no problem unless you’re using a really fast shutter speed (so don’t)
- Depending on the camera, it might be hard to get electronic flash working correctly with this device at very fast shutter speeds (1/250 and above). At 1/250th you have 4ms to trigger the flash, so if the flash delay is .5ms and a full flash is 1ms you only have a 2.5ms window in which to trigger the flash and get the full output. If there’s any variability in the flash delay you’ll miss flashes sometimes.
- I fiddled around with this value until I was consistently hitting flashes at 1/100th and called it a day. If you had an oscilloscope or logic analyzer I’m sure you could do better but I don’t plan to do really technical fill flashing with this, mostly I just want to be able to blast my friends with flash at night.

Below is the arduino sketch I used. It depends on the adafruit neopixel library to drive the onboard RGB led in an attempt to provide a battery life indicator, but I couldn’t really ever get that to work right - doesn’t affect functionality though. Maybe you can? (let me know!). You’ll likely need to tune the flash delay (SYNC_DELAY) to a value that gives you the most reliable flashing at the highest desired shutter speed.
/*
RA4M1 Datasheet: https://cdn.sparkfun.com/assets/b/1/d/3/6/RA4M1_Datasheet.pdf
(section numbers referenced in several comments)
Xiao RA4M1 schematic: https://files.seeedstudio.com/wiki/XIAO-R4AM1/res/XIAO-RA4M1_SCH_PDF_v1.0_240719.pdf
General intent is:
- boot up
- check battery, display batt life via RGB LED
- turn off everything we don't need
- set an interrupt (to be triggered by camera flash sync contact closing)
- on interrupt, wait 25ms then close the SSR for 500ms
- why 25ms?
seems like a safe time to ensure shutter is fully open, and still allow not-the-slowest shutter speeds, i.e. 1/100 (10ms) is probably still safe assuming the camera's sync delay is close to 20ms. Will need further testing with actual cameras. Ideally this could be adjusted, maybe a trimpot that's read only on boot? but seems unecessary, for now can just test and re-set in code
- go to deep sleep to save as much power as possible and wait for another interrupt
- chatgpt sez that deep sleep wakeup time is 100-200 usec, so probably doesn't really need to be accounted for unless you were trying to sync at super high shutter speeds (which probably wouldn't be reliable enough anyways)
*/
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
//ms to wait after camera trips flash
const unsigned long SYNC_DELAY = 25;
//how long to keep the output closed, really doesn't need to be long at all, flash will pop off very fast
//Also serves as a debounce to prevent multiple flashes going off too fast
const unsigned long OUTPUT_PULSE_LEN = 500;
//Do flash triggering and stuff, but don't enter deep sleep for the this many ms. Gives us time to program the board after a reset
const unsigned long STARTUP_SLEEP_DELAY = 20000;
// ------------------- Pin definitions -------------------
const uint8_t wakePin = 1; // D1 - external interrupt input
const uint8_t outPin = 2; // D2 - output pulse
// WS2812 RGB LED
#define LED_PIN RGB_BUILTIN
#define NUM_PIXELS 1
Adafruit_NeoPixel pixels(NUM_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
volatile bool triggered = false;
volatile unsigned long startup_millis;
volatile bool startup_done = false;
// ------------------- Interrupt -------------------
void wakeISR() {
triggered = true; // flag interrupt
}
// ------------------- Battery reading -------------------
float getBatteryPercent() {
digitalWrite(BAT_READ_EN, HIGH);
delay(10);
int raw = analogRead(BAT_DET_PIN);
digitalWrite(BAT_READ_EN, LOW);
float voltage = raw * (3.3 / 4095.0);
// Disable battery measurement to save power
digitalWrite(BAT_READ_EN, LOW);
float percent = (voltage - 3.0) / (4.2 - 3.0) * 100.0;
if (percent > 100.0) percent = 100.0;
if (percent < 0.0) percent = 0.0;
return percent;
}
// ------------------- LED helper -------------------
void showBatteryColor(float percent) {
digitalWrite(PIN_RGB_EN, HIGH);
pixels.begin();
uint8_t red = 0;
uint8_t green = 0;
if (percent >= 50) {
red = map(percent, 50, 100, 0, 255);
green = 255;
} else {
red = 255;
green = map(percent, 0, 50, 0, 255);
}
pixels.setPixelColor(0, pixels.Color(red, green, 0));
pixels.show();
delay(1000); // Show for 1 second
// Turn off LED
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
pixels.show();
// Disable RGB power completely
digitalWrite(PIN_RGB_EN, LOW);
}
// ------------------- Deep sleep -------------------
void enterDeepSleep() {
// https://forum.seeedstudio.com/t/xiao-ra4m1-power-consumption-in-software-standby-mode-still-8-ma/283927
R_SYSTEM->PRCR = 0xA503;
//Dataseheet sez not to set the sleepdeep bit this way for the ra4m1 See section 10.9.5
//SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // enable deep sleep
R_SYSTEM->SBYCR_b.SSBY = 1; // deep software standby
R_SYSTEM->PRCR = 0xA500;
__DSB();
__WFI(); // wait for interrupt
}
// ------------------- Setup -------------------
void setup() {
// Setup pins
pinMode(wakePin, INPUT_PULLUP);
pinMode(outPin, OUTPUT);
pinMode(BAT_READ_EN, OUTPUT);
digitalWrite(BAT_READ_EN, LOW);
digitalWrite(outPin, LOW);
digitalWrite(LED_BUILTIN, HIGH); //LED is active low
pinMode(USB_VBUS, INPUT);
pinMode(PIN_RGB_EN, OUTPUT);
digitalWrite(PIN_RGB_EN, LOW);
// Attach interrupt on D1 falling edge
attachInterrupt(digitalPinToInterrupt(wakePin), wakeISR, FALLING);
//Allow this interrupt to wake from deep sleep
//IRQ6 -> P000 -> D1
//see datasheet 1.7 and schematic
R_ICU->WUPEN_b.IRQWUPEN6 = 1;
//doesn't work, don't bother
// Show battery level on RGB LED
//float battery = getBatteryPercent();
//showBatteryColor(battery);
}
// ------------------- Loop -------------------
void loop() {
// Enter deep sleep until interrupt, but don't go into deep sleep until the system has been on for a bit, give us time to flash firmware
// Also never sleep if we've got USB power
if (startup_done && digitalRead(USB_VBUS) == LOW) {
enterDeepSleep();
}
if (triggered) {
delay(SYNC_DELAY);
digitalWrite(outPin, HIGH);
delay(OUTPUT_PULSE_LEN);
digitalWrite(outPin, LOW);
triggered = false;
}
if (millis() - startup_millis > STARTUP_SLEEP_DELAY) {
startup_done = true;
}
}