HARDWARE PIN INTERRUPT DURING ISR EXECUTION - TEENSY 4.1

Rennie Johnson

Active member
I'm adding a momentary contact switch to a crowded circuit board, and I don't have room for a debouncing circuit. If I trigger an ISR on a Teensy pin using AttachInterrupt(), will the Teensy ignore any dirty signals from the pushed button during ISR execution, or will it wait until the routine has finished and run it again? The ISR routine will last a good 500ms or longer. Also, can I do software debouncing with an interrupt?
 
Not written a sketch in a months :( so this seemed like fun.
YES, a bouncing input will queue an interrupt and the ISR will re-enter on exit.
There may be a 'proper' way to handle this but, detach the interrupt on entry [anISR2 and re-attach on exit] - if it will really run longer than the bounce time - works in the code below.

Change line #1 to alternate between the two ISR variants. anISR2() works safely for this test. anISR1() effectively never leaves the ISR without instantly returning and deadlocks the Teensy and it won't reprogram without the Button - except an escape clause was added.

Observe:
anISR1 - FAST 50ms LED Blink and exit with abort message - and slower 200ms fast blink when it can be re-programmed
anISR2 - one second LED Blink loop() controlled blink (plus the 50ms in the ISR). [up to 2^32]

As commented lines #2-#4 wire PinA to PinB and edit the #defines for the pins chosen
Code:
#define AN_ISR anISR2 // set to anISR1 or anISR2
// wire pin A to pin B
#define PinA 3 // interrupt input
#define PinB 4
volatile uint32_t changeCnt = 1;
void setup() {
  Serial.begin( 1);
  pinMode( PinA, INPUT );
  pinMode( PinB, OUTPUT );
  pinMode( LED_BUILTIN, OUTPUT );
  digitalWriteFast( PinB, LOW );
  Serial.println( "Hello World" );
  attachInterrupt( PinA, AN_ISR, CHANGE );
}

void anISR1() {
  digitalToggleFast( PinB );
  digitalToggleFast( LED_BUILTIN );
  changeCnt++;
  if ( changeCnt> 100) {
    changeCnt = 0;
    detachInterrupt( PinA ); // ABORT - THIS ISR WILL NEVER ALLOW loop() to run
  }
  delayMicroseconds( 50000 ); // 50 ms
}

void anISR2() {
  detachInterrupt( PinA );
  digitalToggleFast( PinB );
  digitalToggleFast( LED_BUILTIN );
  changeCnt++;
  delayMicroseconds( 50000 ); // 50 ms
  attachInterrupt( PinA, AN_ISR, CHANGE );
}

void loop() {
static uint32_t lastChange = 1;
  if ( 0 == changeCnt ) {
    Serial.println( "ISR deadlock aborted!" );
    delay(200);
    digitalToggleFast( LED_BUILTIN );
  }
  else if ( lastChange != changeCnt ) {
    Serial.println( changeCnt );
    lastChange = changeCnt;
    delay(900);
  }
  digitalToggleFast( PinB );
  delay(100);
}
 
Thanks Defragster. I assume the interrupt pin number doesn't matter on Teensy 4.1. Your anISR2() seems very understandable, and I think you indicated it was safer to use. Question: What is the approximate delay time added by detachInterrupt(), and will its delay be reasonably constant between iterations?
 
Yes, any digital pin can be used. It handles the indicated case where the ISR will take longer than any bounce period. If it exits during bounce it will re-enter ASAP like anISR1() does.

I used it on T_3.6 code to detect start of incoming message and disabled the ISR, recorded the TIME and set a flag. When the message was processed in loop() the ISR was re-enabled to detect the next message.

detachInterrupt()
Is a fast trivial function (PJRC source: ...\hardware\teensy\avr\cores\teensy4\interrupt.c):
Code:
void detachInterrupt(uint8_t pin)
{
    if (pin >= CORE_NUM_DIGITAL) return;
    volatile uint32_t *gpio = portOutputRegister(pin);
    uint32_t mask = digitalPinToBitMask(pin);
    gpio[IMR_INDEX] &= ~mask;
}

Code for attachInterrupt() is marginally more involved with mask, pin checks and settings.
 
I'd like to mention that it is generally considered poor form to have an ISR execute for half a second or longer. They're supposed to be very fast and then exit. If at all possible, the way to go is to merely set a flag then do the actual work outside of the interrupt routine. There's exceptions to every rule so maybe you have to do it the way you're doing it. But, if possible, at least consider not running in an ISR for so long.
 
poor form to have an ISR execute for half a second or longer
Indeed, in above sketch I started with the 'given' 500ms and it was unsettling, so dropped it to 50ms to get a usable LED blink.
An unburdened T_4.x will do over 10M loop() cycles/second. It can easily drop to mere ~millions with some simple polling and casual use.
If the primary single task can be handled in the ISR (without triggering other ISR code) that might be the needed operation for best resolution.
If loops()/sec can be maintained checking a flag from the ISR as needed that is better, but the interrupt still needs to be debounced and as noted that is how it was used on T_3.6.

If the ISR code can take 500ms the event rate must be at least a few times lower so YMMV based on other processing.
 
I use this debouncing approach most often:

It’s my favourite so far. It’s a polling approach instead of an interrupt approach. (Assuming that’s suitable for your application.)
 
Arduino has a Bounce library for polling buttons. PJRC has this page: https://www.pjrc.com/teensy/td_libs_Bounce.html

This use case wants interrupt detection. On exiting the interrupt it is also possible IIRC reading to clear pending interrupts, which would probably be better depending on the use case - and if one knew what that code looked like.

@PaulStoffregen : Note that Bounce page has this broken link:

Links​

Arduino Bounce Library page has more details to use Bounce.
 
I've updated the broken link.

Just in case it hasn't been said clearly enough already, you should NOT use an interrupt. Use the Bounce library, as demonstrated on that page or in examples like File > Examples > Teensy > USB_Keyboard > Buttons. Have your program call the Bounce update() function, and then check for falling edge. Very easy and very reliable.

Don't use interrupts for signals from switches with mechanical chatter. They're simply not the right tool for this job.
 
Back
Top