Walkaround for delay within interrupt / time critical coding

Hi all,
I want to use a camera flash with a camera that doesnt have a flash sync output signal.
My setup uses a remote control that triggers the camera and also sends a signal to an Arduino Nano as the flash would fire too early if I were to hook it up directly.
I want to use the Arduino to create an adjustable delay and then trigger the flash with it.

I am not completely new to Arduinos but I would still consider myself as a beginner so I am not familiar with many things that are or are not possible.

I was able to create a code with a hard coded delay, that worked out ok for my setup but when using a different flash I needed a slightly different delay. Always reprogramming it is not an option here so I wanted to make the delay adjustable. The adjustable range needs to be ~4ms in 100 μs steps with a base delay of 18ms. The flash needs to be fired within a few hundrered us if adjusted correctly.

With this adjustable code I also added an I²C display to see the current value.
This code did work but the actual delay was all over the place and not really what I set with a potentiometer. I wasn't able to sync up the flash with the camera that way.

I thought that maybe an interrupt based code would work here so I changed it accordingly just to find out that delays dont work within an interrupt.
So I know that the code below does not work.

Can anyone give me some advice on how I can code this to whenever I have a falling edge on my camera trigger pin (physically soldered to Pin D8 on my current board) ?

const int ExposureStartInterrupt = 0;     
const int FlashPin =  3; 
const int DelayPotentiometer = A7;

int buttonState = 0;        
int outputValue = 0;        
volatile int usDelay = 0;
volatile int BaseDelay = 18;

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


void setup() {
  pinMode(FlashPin, OUTPUT);
  attachInterrupt(ExposureStartInterrupt, FireFlash, FALLING);
  attachInterrupt(ExposureStartInterrupt, StopFlash, RISING);
  pinMode(DelayPotentiometer, INPUT); 
}

void loop() {
  usDelay = analogRead(DelayPotentiometer);
  usDelay = map(usDelay, 0, 1023, 0, 4000);
  usDelay = usDelay / 100;
  usDelay = usDelay * 100; //just to get rid of low μs values
  outputValue = usDelay + (BaseDelay * 1000);
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    for(;;);
  }
  
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 28);
  display.print(outputValue); 
  display.print("us");
  display.display(); 
}

void FireFlash() {
    delay(BaseDelay);
    delayMicroseconds(usDelay);
    digitalWrite(FlashPin, HIGH);
}

void StopFlash() {
    digitalWrite(FlashPin, LOW);
}

Welcome to the forum, and thanks for using code tags on your first post!

This is the problem to solve, but you left out all the important details. Explain what the program is supposed to do and what it does instead. Post a wiring diagram.

The general way to proceed is to take small steps. Write a standalone program that just allows to input a number using the potentiometer, and print the result on the serial monitor. Make sure that works to your satisfaction before adding the code to a larger program. Don't even think of using interrupts.

I think timers that start with interrupts can manage time more accurately.
By the way, how long does the flash activate pulse need to be output HIGH?

Hi jremington,
thanks for the reply.

The code is supposed to generate a delay between 18 ms and 22 ms whenever a falling edge or LOW signal is detected on pin D8 (based on current circuit).

This code with hard coded delays worked fine for that purpose:

const int ExposureStart = 8;
const int FlashPin =  3; 
int buttonState = 0;

void setup() {
  pinMode(FlashPin, OUTPUT);
  pinMode(ExposureStart, INPUT);
  pinMode(inputPin, INPUT);
}

void loop() {
  buttonState = digitalRead(ExposureStart);

  if (buttonState == LOW) {
    delay(21); // yn560 iv
    delayMicroseconds(928);
    digitalWrite(FlashPin, HIGH);
  } 
  else {
    digitalWrite(FlashPin, LOW);
  }
}

The coded that had the described issues is:

const int ExposureStart = 8;  
const int FlashPin =  3; 
const int DelayPotentiometer = A7;

int buttonState = 0;
int outputValue = 0;
int usDelay = 0;
int BaseDelay = 18;

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  pinMode(FlashPin, OUTPUT);
  pinMode(ExposureStart, INPUT);
  pinMode(DelayPotentiometer, INPUT);
}

void loop() {
  usDelay = analogRead(DelayPotentiometer);
  usDelay = map(usDelay, 0, 1023, 0, 4000);
  usDelay = usDelay / 100;
  usDelay = usDelay * 100;
  outputValue = usDelay + (BaseDelay * 1000);

  buttonState = digitalRead(ExposureStart);

  if (buttonState == LOW) {
    delay(BaseDelay);
    delayMicroseconds(usDelay);
    digitalWrite(FlashPin, HIGH);
  } else {
    digitalWrite(FlashPin, LOW);
  }
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    for(;;);
  }
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 28);
  display.print(outputValue); 
  display.print("us");
  display.display(); 
}

I assumed that displaying the value on the display takes so much time that it doesn't register it immediately if the camera trigger is pressed.
Is that possible? I have no clue if this can cause delays in the single or even double digit ms range.

My current board is wired up as shown here:

The issues have not actually been described. Explain what the program is supposed to do and what it does instead.

As mentioned above, the times werent matching the ones in the previous code.
The actual delay was all over the place. It was longer and fluctuating.
A time of 21928 us worked fine in the first smaller code, it didnt work at all in the one with the display. Sometimes the delay was correct and the flash was in the image but most of the time it wasn't visible eventhough the set delay wasn't changed.
It behaved as if a random timer was added somewhere inbetween.

I cannot give an exact answer to that as I havent tested that specifically. I can do that later.
I would guess that 100ms would be well enough.

What I can say for the time being now...

  • One pin can't handle two interrupts.
  • Nano's external interrupts can only be done on pin 2 or 3.
  • Need to specify the interrupt number instead of the pin number (or use digitalPinToInterrupt).
  • Your old fixed delay code is not designed to output a 100ms pulse.

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

You have integer overflow issues. Make these values unsigned long.

unsigned long usDelay = 0;
unsigned long BaseDelay = 18;

Additionally you should modify these use of constants to get unsigned long values

usDelay = usDelay * 100UL;
 outputValue = usDelay + (BaseDelay * 1000UL);

No overflow has occurred within the range that can be using by the OP's requirement definition.

Also, shouldn't the outputValue be the same changes when tell such changes?

I was responding to post #4.
Yes you are correct that outputValue should also be unsigned long.

The interrupt code is not correct. delay() will not advance within an ISR.

void FireFlash() {
    delay(BaseDelay);

ISR should be as short as possible.
Ideally example:

  • Just set a flag like FlashDelayTimerStart.
  • Just start a timer for time keeping.

You should also use a timer for accurate time management so that the not disturbed by POT reading and OLED display.

A hardware timer based pseudo-code would look like this



void start_timer1()
{
	flash_ON
	start hardware timer here
}


void timer1_overflow()
{
	flash_OFF
	stop hardware timer here
}

setup()
{
}


loop()
{
	if(pot_is_adjusted)
	{
		increase_or_decrease timer1 TOP value (determines the flash duration)
		update_LCD_display
	}
	
	if(flash_button_is_pressed)
	{
		call start_timer1
	}
}

Isn't this just controlling the pulse length and not including the delay from the input to the pulse output, which is the most important in the OP project?
I think the OP wants to be able to accurately and adjust the time from capturing the input to the start of the pulse output with a fixed output pulse length (= 100 ms is enough).

1 Like

ooopps, missed that part. Should be easy to modify though



void start_timer1()
{
	start hardware timer here
}


void timer1_overflow()
{
	flash_ON << start the flash here
	stop hardware timer here

// if you only needed a few clocks to trigger the flash
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");

flash_OFF;

}

setup()
{
}


loop()
{
	if(pot_is_adjusted)
	{
		increase_or_decrease timer1 TOP value
		update_LCD_display
	}
	
	if(flash_button_is_pressed)
	{
		call start_timer1
	}
}

Yes, just to make this clear: The length of the flash trigger pulse doesn't matter. I do not control the flash duration with that output, the flash duration is entirely controlled on the flash itself by changing the output power.
The Arduino just sends a trigger signal to start the flash. If that trigger signal is 100ms or 1000ms wouldn't change anything and doesn't need to be adjusted.

This is just do sync up the the flash to the internal delay of the camera when it gets the external shutter signal to when the sensor actually starts the exposure.

I have tried to implement a timer but im not sure if this is how you meant it.

I also took a video of my setup so you may better understand what my problem with the adjustable code is.

https://www.youtube.com/watch?v=0sNrRngIj54

The short code in the video is the upper one from comment #4.
The adjusted code in the video with an interrupt is this:

const int ExposureStartInterrupt = 0;     
const int FlashPin =  3; 
const int DelayPotentiometer = A7;
const int ExposureStart = 8;  

int buttonState = 0;        
long outputValue = 0;        
int usDelay = 0;
int BaseDelay = 15;

long DelayStartTime = 0;

bool FireFlash = false;

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


void setup() {
  pinMode(FlashPin, OUTPUT);
  attachInterrupt(ExposureStartInterrupt, StartDelayTimer, FALLING);
  pinMode(DelayPotentiometer, INPUT); 
  pinMode(ExposureStart, INPUT);
}

void loop() {

  if (FireFlash == true && micros() > (DelayStartTime + outputValue)){
    digitalWrite(FlashPin, HIGH);
    delay(100);
    digitalWrite(FlashPin, LOW);
    FireFlash = false;
  }
  
  usDelay = analogRead(DelayPotentiometer);
  usDelay = map(usDelay, 0, 1023, 0, 8000);
  usDelay = usDelay / 100;
  usDelay = usDelay * 100;
  outputValue = usDelay + (BaseDelay * 1000);
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    for(;;);
  }
  
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 28);
  display.print(outputValue); 
  display.print("us");
  display.display(); 
}

void StartDelayTimer() {
  DelayStartTime = micros();
  FireFlash = true;
}
//long DelayStartTime = 0;
volatile unsigned long DelayStartTime = 0;

This is taking the value of micros() so it should be unsigned long.
It is being changed within the context of an ISR so should be declared volatile.

1 Like

Also, when reading at loop that variable larger than 8 bits that is may changed by an interrupt, the interrupt must be disabled when read timing.

 

See the int or long volatiles section below.
https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/