Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 14 of 14

Thread: digitalWrite in Interrupt Service Routine of Teensy 4.0

  1. #1
    Junior Member
    Join Date
    Dec 2018
    Posts
    8

    digitalWrite in Interrupt Service Routine of Teensy 4.0

    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
    }

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,081
    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

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,235
    Have you tried changing digitalWriteFast to digitalWrite ?

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,081
    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.

  5. #5
    Junior Member
    Join Date
    Dec 2018
    Posts
    8
    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.

  6. #6
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,071
    Also try turning off compiler optimization.

    #pragma GCC optimize ("O0")

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,743
    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

  8. #8
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,071
    You could detect multiple triggers by changing "triggerOccurred" to a counter and printing it in loop().

  9. #9
    Junior Member
    Join Date
    Dec 2018
    Posts
    8
    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.

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,292
    Quote Originally Posted by uutzinger View Post
    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.

  11. #11
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    1,071
    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.

  12. #12
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,292
    Quote Originally Posted by jonr View Post
    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.

    Yes but an interrupt is not the solution.

  13. #13
    Junior Member
    Join Date
    Dec 2018
    Posts
    8
    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.

  14. #14
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    9,292
    Your camera produces a bouncing signal?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •