Do ISRs block further interrupts? See both in my sample code

I have some code that I am writing that uses both a timer and an interrupt based on a GPIO that is connected to a button. In the original code it appeared that I was reentering a subroutine due to a bounce issue with my button. I simplified my code a bit to see what would happen if I interrupted during either of my two ISRs.

The following is what I have in the code now:

void loop()
{
  millis();
  
  attachInterrupt(digitalPinToInterrupt(BUTTON), buttonChgdState, CHANGE);

  Timer1.initialize(3000000);
  Timer1.attachInterrupt(blink);

  while(1)
  {;}
}

void blink(void)
{
  Serial.println("entering blink...");
  if (led_state == OFF)
  {
    led_state = ON;
    digitalWrite(ERROR_LED, led_state);
    noInterrupts();
    Serial.println("starting wait...");
    delay(2000);
    Serial.println("... ending wait.");
    interrupts();
  }
  else
  {
    led_state = OFF;
    digitalWrite(ERROR_LED, led_state);
  }
}

void buttonChgdState(void)
{
  /* the button changed state */
  /* read current state */
  while(!digitalRead(BUTTON))
  {
  digitalWrite(TEMP_OK_LED, LOW);
  }
digitalWrite(TEMP_OK_LED, HIGH);
}

This is set up so that I can hold the button to light up an LED (TEMP_OK_LED) to see whether or not my timer interrupt fires while in the button ISR. It does not.

I also have a 2-second delay in my timer ISR (timer is on a 3-second timer), to give me time to press the button and see if the button will interrupt my timer ISR. It does.

I then added the calls to noInterrutps() and interrupts() in an attempt to keep that from happening. They have no effect.

Is there something I am missing here? I am not ruling out the possibility that I missed some setup, but from what I have read that does not appear to be the case as long as my timer is interrupting at the correct time, which it is.

Please note that I am not interested in working on the debounce issues as that problem SHOULD be easy enough to fix in the ISR. I am trying to get a better understanding of how these interrupts work.

Any help is greatly appreciated.

It will not verify or upload because setup() is missing.

Point is: don't post only the parts of the code you think are relevant. Always post complete code. Because you might be wrong about where the problem is. And because none else can test your code unless they have all of it.

What is this line intended to to?

These lines should be in setup(), not in loop(). Then you would not need the while(1).

Do ISRs block further interrupts?

Yes

1 Like

You didn't say what processor you are using, but for most (all?) of the Arduino processors and interrupt will disable other interrupts. Exiting the interrupt will re-enable interrupts.

Using interrupts for switch debounce is usually a bad idea.

1 Like

Interrupts are not needed or recommended for buttons. Polling the button during loop() should be fine, provided you are not using blocking code. If you are using blocking code, re-write it without using blocking code.

One exception to this advice is if the button will wake the Arduino from a sleep mode.

Absolutely nothing. It was a relic that I forgot to remove in the pared down test that I am now running.

I'm on an Arduino Uno R3, so an ATmega328P.

That is the entire purpose of this function. It blocks specifically so that I can see if the button ISR affects the timer ISR. Which it does. When I hold the button down it blocks the ERROR_LED from changing state.

The other ISR (timer) also has a delay(2000) to block for two seconds for exactly the same reason. It allowed me to see if the timer ISR blocked the button ISR, which it does not contrary to popular opinion that all ISRs block other interrupts.

This doesn't appear to be the case in the code I am using. The button ISR blocks the timer interrupt, but the opposite is not true. The timer ISR does not block the button interrupt.

I have 5 different states in my original code that all need to register a 3-second press in order to turn the device off. The device sits in each state until it passes to the next or powers off. Not sure of a better way to do that.

In the end I would still like to understand the original question of why one of my ISRs blocks further interrupts and the other does not.

I'll be more emphatic: don't use delay() or do any serial I/O in an interrupt.

The Arduino company put in a workaround so that beginners can sometimes get away with this serious error on the Uno R3 and related, but most other MCUs will either crash or hang up.

Just curious, what's the issue with running a delay in test code as a way of teasing out some of the finer intricacies of how interrupts work on this platform?

The timing for delay() is typically driven off the mills() counter. On some platforms, mills() works by setting up a hardware timer to provide a periodic interrupt signal. If you're messing around with delay() in an ISR, the timer interrupt not be serviced because you're in the ISR. So, millis() will never advance, so your delay() will never end, so you'll never exit the ISR, so the timer interrupt won't be serviced, so millis() will never advance ..... get the picture?

Good point. Fortunately, that at least doesn't seem to be what's happening. It appears to be taking the correct amount of time. I was also fortunate that millis() seems to use Timer0 and the timer interrupt I'm creating uses Timer1.

The original, non-test, version of that function only sets or clears the 3-second interrupt timer based on button status then returns. Unfortunately, Arduino does not give very good debug capability so the Serial.println() statements kind of have to be there for now. And the lack of a call stack means that I cannot verify if my ISRs are stacked. Therefore, I am forced to test by putting delays in my ISRs.

What problem are you trying to solve?

I've had that problem before where one interrupt comes in and clobbers the other before it can finish, but it's the type of thing that once you see it you know to look for it in the future.

Despite all information saying that an ISR should block interrupts, my original code executed as if they were not blocked. That's how the test code above came about. Removed the state machine, the control algorithm, etc and left a button interrupt and a timer interrupt to see how the two would work together. I was surprised to see that it runs as if the button will interrupt the timer ISR but not vice versa. That doesn't make any sense to me, and it's why I'm here.

Original code made it look like a button interrupt was interrupting itself due to bounce. In theory that's not an issue if the interrupts queue up FIFO style. You'll eventually work your way through everything and end up at a steady state of either pressed or released.

I can't necessarily rule out some sort of more nuanced thing such as what happens if there is another interrupt when I'm pushing or popping on or off the stack at the beginning or end of the interrupt, but I haven't gotten that far yet.

Arduino developers put in some workarounds on AVR-based MCUs so that in ISRs, delay() is basically ignored (does not delay significantly), and Serial.print() still works under some circumstances. I suspect they did that because beginners make so many mistakes like this. However, attempting to perform I2C operations within an ISR will freeze an AVR instantly.

Neither delay() nor Serial.print() functions correctly within ISRs on other MCUs, and will simply crash or hang up the processor.

So what you just said is exactly why I'm here. A lot of this does not make sense and I'm looking to see if something makes sense to anyone else. I would say that I don't "know that" it is not true. As a general rule when I debug anything I forget that I know anything and look for proof of my assumptions. Once again see code above as my searching. :slight_smile:

For the most part I understand the way that print works and get that the text does not come out at exactly the time the print statement executes. However, in this horrible mess of code:

void blink(void)
{
  Serial.println("entering blink...");
  if (led_state == OFF)
  {
    led_state = ON;
    digitalWrite(ERROR_LED, led_state);
    noInterrupts();
    Serial.println("starting wait...");
    delay(2000);
    Serial.println("... ending wait.");
    interrupts();
  }

I know this is running due to the print statements showing up, and I accept that they are showing up at some point after the statement runs, and in between those two statements is a delay for 2 seconds which is likely also working from an interrupt on Timer0. However, if I sit there pressing my button which also interrupts to light the LED, at no point will it not light even pressing at a period much less than 2 seconds.

So if (keep in mind that I try to assume that I know nothing, no need for anyone to agree here) the two second delay ties me up in that timer ISR for 2 seconds, shouldn't my button interrupt stop servicing for 2 seconds at some point?