I am looking for guidance on using digitalWrite in an interrupt service routine (ISR).
It appears that it is not possible to call digitalWrite twice in an ISR (e.g.to turn off a pin and to turn on an other pin).
Project: In my application I receive a frame trigger from a camera and for each new frame trigger I want to switch to a different light source, so that ultimately I can record images at many different wavelengths. The teensy controls the light source and images are recorded with an other device.
The light source is a custom circuit and each channel includes an enable signal and a PWM input to adjust intensity.
Requirement: The teensy will need to respond to the rising edge of the frame trigger, turn off the current light source channel by setting the associated pin to low and enable the next light source channel by setting its associated pin to high. At the end of the sequence, I will want to take an image with no light source enabled or all pins set to low.
Solution: For this, I created a program that allows me to define the light source sequence (output pins of teensy), adjust the PWM settings etc. The ISR is properly called at each frame trigger but it does not advance or unpredictably advances to the next light source.
For this post I reduce my program to simplest possible version with only three channels and posted it below.
In order to observe capturing the frame trigger, I toggle the built-in LED in the main loop whenever the ISR indicates in a global variable that it was called. This works as expected.
In the main program, I also monitor the index pointer of the current LED channel using serial print. The index pointer (a global variable) should increase from 0 to 1 and 2 and then start over again at 0.
For debugging purpose I run the camera at low frame rate such as 5 frames per second. Also I observe the frametrigger and the two enable pins with oscilloscope.
Problem: What I notice is that the index pointer in the ISR is correctly incrementing as long as HIGH or LOW in the digitalWriteFast(pin, HIGH or LOW) statements in the ISR are the same for all calls. This of course, does not allow me to turn off one channel and the other on.
It does not matter if variables are declared volatile. It does not matter if pin in digitalWrite(pin,..) is a constant or variable. Using digitalWriteFast versus digitalWrite changes the behavior slightly but it still does not advance properly (stuck at different index or advances infrequently). Similarly optimizing code in the IDE settings for Fast, Faster, Fastest or Short does not solve the problem.
It appears, as soon as I have two digitalWrite statements in the ISR (one set HIGH the other LOW), it no longer works.
In the program below I setup PWM at one pin as I am using it to adjust intensity of the LEDs. To reproduce the problem without a camera/external trigger on could connect pin 22 and pin 21 on a breadboard and set it to low frequency. The behavior does not change if PWM statements in the setup are commented out.
I used latest Arduino IDE and Teensyduino as of 8/11/2021.
It appears that it is not possible to call digitalWrite twice in an ISR (e.g.to turn off a pin and to turn on an other pin).
Project: In my application I receive a frame trigger from a camera and for each new frame trigger I want to switch to a different light source, so that ultimately I can record images at many different wavelengths. The teensy controls the light source and images are recorded with an other device.
The light source is a custom circuit and each channel includes an enable signal and a PWM input to adjust intensity.
Requirement: The teensy will need to respond to the rising edge of the frame trigger, turn off the current light source channel by setting the associated pin to low and enable the next light source channel by setting its associated pin to high. At the end of the sequence, I will want to take an image with no light source enabled or all pins set to low.
Solution: For this, I created a program that allows me to define the light source sequence (output pins of teensy), adjust the PWM settings etc. The ISR is properly called at each frame trigger but it does not advance or unpredictably advances to the next light source.
For this post I reduce my program to simplest possible version with only three channels and posted it below.
In order to observe capturing the frame trigger, I toggle the built-in LED in the main loop whenever the ISR indicates in a global variable that it was called. This works as expected.
In the main program, I also monitor the index pointer of the current LED channel using serial print. The index pointer (a global variable) should increase from 0 to 1 and 2 and then start over again at 0.
For debugging purpose I run the camera at low frame rate such as 5 frames per second. Also I observe the frametrigger and the two enable pins with oscilloscope.
Problem: What I notice is that the index pointer in the ISR is correctly incrementing as long as HIGH or LOW in the digitalWriteFast(pin, HIGH or LOW) statements in the ISR are the same for all calls. This of course, does not allow me to turn off one channel and the other on.
It does not matter if variables are declared volatile. It does not matter if pin in digitalWrite(pin,..) is a constant or variable. Using digitalWriteFast versus digitalWrite changes the behavior slightly but it still does not advance properly (stuck at different index or advances infrequently). Similarly optimizing code in the IDE settings for Fast, Faster, Fastest or Short does not solve the problem.
It appears, as soon as I have two digitalWrite statements in the ISR (one set HIGH the other LOW), it no longer works.
In the program below I setup PWM at one pin as I am using it to adjust intensity of the LEDs. To reproduce the problem without a camera/external trigger on could connect pin 22 and pin 21 on a breadboard and set it to low frequency. The behavior does not change if PWM statements in the setup are commented out.
I used latest Arduino IDE and Teensyduino as of 8/11/2021.
Code:
#define BAUDRATE 115200 // Serial communication speed
// *********************************************************************************************//
// Pin Settings
// ------------------------------------------------------------------------
#define NUM_CHANNELS 3 // Number of channels
#define CH1 2 // Connect pin 2 to channel 1 (output)
#define CH2 3 // Connect pin 3 to channel 2 (output)
#define CH_BG -1 // Connect no pin to channel 3
#define CLK 22 // Connect pin 22 to CLK (output)
#define TRG 21 // Connect pin 21 to trigger (input)
#define LEDPIN 13 // Built in LED
#define TURN_ON HIGH // Define Enable Channel
#define TURN_OFF LOW // Define Disable Channel
// PWM Settings
// ------------------------------------------------------------------------
#define PWM_Frequency 50000
#define PWM_Resolution 8
#define PWM_MaxValue 255
#define DutyCycle 5.0
// Variables
// ------------------------------------------------------------------------
volatile int LEDs[NUM_CHANNELS] = {CH1, CH2, CH_BG }; // LED channel array
volatile int currentChannel = 0; // Index to current LED in LED array
volatile bool triggerOccurred = false; // Signal to main loop
bool ledStatus = false; // Blinking of the built in LED
// *********************************************************************************************//
// SETUP //
// *********************************************************************************************//
void setup(){
// Configure Output Pins, set them all to off/low
pinMode(CH1, OUTPUT); digitalWrite(CH1, TURN_OFF);
pinMode(CH2, OUTPUT); digitalWrite(CH2, TURN_OFF);
pinMode(CLK, OUTPUT); digitalWrite(CLK, HIGH);
pinMode(LEDPIN, OUTPUT); digitalWrite(LEDPIN,LOW );
// Configure Input Pins
pinMode(TRG, INPUT_PULLUP); // trigger
// Configure Interrupts
attachInterrupt(digitalPinToInterrupt(TRG), myISR, RISING);
// Configure PWM output
analogWriteResolution(PWM_Resolution); // change resolution
analogWriteFrequency(CLK, PWM_Frequency); // set PWM frequency on CLK pin
analogWrite(CLK, uint16_t(DutyCycle / 100.0 * float(PWM_MaxValue))); // set Duty Cyle and enable PWM on CLK pin (i have inverted PWM logic)
// Start Serial IO
Serial.begin(BAUDRATE);
Serial.println("System started");
} // end setup
// *********************************************************************************************//
// Main LOOP //
// *********************************************************************************************//
void loop(){
// Blink LED if ISR was called
// ------------------------------------------------------------------------
if (triggerOccurred) {
ledStatus = !ledStatus;
digitalWriteFast(LEDPIN, ledStatus); // blink
triggerOccurred = false; // reset signal
Serial.println(currentChannel); // debug
}
}
// *********************************************************************************************//
// Support Functions //
// *********************************************************************************************//
// If TURN_OFF or TURN_ON is the same in the two digitalWrite statements below,
// currentChannel increments properly, but I need to turn off the previous pin and enable the next pin
// in my sequence.
void myISR() {
// Turn OFF previous channel, skip the background channel
if (LEDs[currentChannel] != CH_BG) { digitalWriteFast(LEDs[currentChannel], TURN_OFF); }
// Increment channel index
currentChannel = currentChannel + 1;
if ( currentChannel == NUM_CHANNELS ) { currentChannel = 0; }
// Turn ON next LED, skip the background channel
if (LEDs[currentChannel] != CH_BG) { digitalWriteFast(LEDs[currentChannel], TURN_ON); }
triggerOccurred = true; // signal to main loop
}