digitalWrite in Interrupt Service Routine of Teensy 4.0

Status
Not open for further replies.

uutzinger

Member
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.

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
}
 
Could it be a logic/coding error ?

Two tests against CH_BG that does not change, but currentChannel does change :: if (LEDs[currentChannel] != CH_BG)

with this :: #define CH_BG -1 // Connect no pin to channel 3

There are three entries in that array - so on two entries to the _isr() one of those will be false. Unless it enters with currentChannel==0 only one digitalWrite will happen
 
Given the pin# coming from a runtime array - digitalWriteFast will default to digitalWrite given the compiler won't see it as CONSTANT needed to inline specific code.

Might be interesting to try in case the exchange fails ... but Fast() isn't helping as written.
 
To remove confusion about checking for CH_BG, I simplified the ISR and I also changed digitalWriteFast to digitalWrite.

To be clear the issue is that my index currentChannel is not incrementing properly.

With ISR as below I have on terminal:
Code:
System started
0
0
0
0
0
0

but I need to see
Code:
System started
0
1
2
0
1
2
0
1
2

The simplified ISR is:
Code:
#define CH_BG 4
...
void myISR() {
  // Turn OFF previous channel
  digitalWrite(LEDs[currentChannel], TURN_OFF);
  // Increment channel index
  currentChannel = currentChannel + 1;
  if ( currentChannel == NUM_CHANNELS ) { currentChannel = 0; }
  // Turn ON next LED,
  digitalWrite(LEDs[currentChannel], TURN_ON); 
  triggerOccurred = true;  // signal to main loop
}

With the ISR modified to

Code:
void myISR() {
  // Turn OFF previous channel
  digitalWrite(LEDs[currentChannel], TURN_ON);  //<<<<< TURN_OFF changed to TURN_ON
  // Increment channel index
  currentChannel = currentChannel + 1;
  if ( currentChannel == NUM_CHANNELS ) { currentChannel = 0; }
  // Turn ON next LED
  digitalWrite(LEDs[currentChannel], TURN_ON); 
  triggerOccurred = true;  // signal to main loop
}

(note that the only change is TURN_OFF to TURN_ON)

I get the desired behavior for currentChannel.

It appears as calling digitalWrite twice in the ISR (one HIGH the other LOW), modifications to variable currentChannel are lost. This is not a coding error as changing the argument to digitalWrite should not affect global variables.
 
I tried the original code in your #1 (with a few mods to match what's on my breadboard). I trigger the interrupt with a button and the code does not work well in the presence of contact bounce.
I then modified the code so it used the Bounce library to debounce the TRG pin and called the myISR routine from within loop() when the rising edge is detected. It works perfectly.
Have you debounced the frame trigger signal from the camera?

Pete
 
You could detect multiple triggers by changing "triggerOccurred" to a counter and printing it in loop().
 
Thank you all for your comments and help solving my ISR issue.

Indeed when I converted triggerOccurred into a counter it turned out that I have a bouncing problem.

I experimented with all possible trigger options of my camera (Blackfly S USB) and found that when using the non-optoisolated output with inverted logic (camera is collecting light when the trigger is low) I had "best" trigger signal that does not bounce.

If anyone reads this post and is in need to debounce an ISR, one can record when the ISR occurred with micros() and measure the interval between successive calls.
 
Thank you all for your comments and help solving my ISR issue.

Indeed when I converted triggerOccurred into a counter it turned out that I have a bouncing problem.

I experimented with all possible trigger options of my camera (Blackfly S USB) and found that when using the non-optoisolated output with inverted logic (camera is collecting light when the trigger is low) I had "best" trigger signal that does not bounce.

If anyone reads this post and is in need to debounce an ISR, one can record when the ISR occurred with micros() and measure the interval between successive calls.

Or just use the bounce library. 99.99% there is no need to use interrupts for buttons. It is a bad habit.
There is no need for a reaction within nanoseconds. The finger which presses the button is a million times slower.
 
If you only check for button presses in loop(), there is a good chance that you will miss some entirely. Because people often use substantial delay() or other slow code.
 
In my application I dont use buttons but an external sync signal from my camera at to 500 Hz and with other cameras potentially even faster. I need to switch settings (turn on/off) LEDs as soon as I get the trigger (micro seconds range) and that is best done with interrupt service. For user input I agree that using events and handling them in the main loop, is good solution.
 
Status
Not open for further replies.
Back
Top