Fake interrupts

Hi all,

I was learning the arduino interrupts, and I'm facing a weird issue. This is how I reproduce it.

I configured interrupt(1) on digital pin 3. In the code I attach the RISING interrupt when arduino receives character 'a'. The ISR is a simple function which prints "AAA" string. In the code I detach the interrupt when arduino receives character 'b'.

So, from serial data transfer I can manually attach and detach the interrupt.

I connected a switching button to the pin 3. I also connected a 10k pulldown resistor.

When I attach the interrupt by sending 'a', and when I press the button I receive the "AAA". When I detach the interrupt by sending 'b', and when I press the button I don't receive the "AAA". Here everything is OK.

Now. When I attach the interrupt back again by sending 'a', I automatically receive "AAA", although I didn't press the button. It means an interrupt occurs, but I cannot understand why.

When I do not press the button when interrupt is detached, and I attach the interrupt, then everything is OK. The weird things happen only when I press the button when the interrupt is detached.

Did anyone also faced this phenomena? Is it a normal behavior?

The code I used is below:

void setup() {
  pinMode(3, INPUT); //pin 3 for interrupt
  Serial.begin(9600);
}
void loop() {
  if (Serial.available()>0)
  {
    
    switch (Serial.read())
    {
      case 'a':
      attachInterrupt(1,pulses_in,RISING);
      break;
      case 'b':
      detachInterrupt(1);
      break;
    }
  }
}
void pulses_in()
{
  Serial.println("AAA");
}

Thanks in advance. Gevorg


Yes, I am seeing this too. You can understand why with this code:

int intcounter = 0;
int intcounterlast = 0;

void setup() {
  pinMode(3, INPUT); //pin 3 for interrupt
  Serial.begin(9600);
}
void loop() {
  if (Serial.available()>0)
  {
    char tempchar = Serial.read();

    // Print out INT1 registers before and after attachInterrupt/detachInterrupt
    Serial.print(tempchar);
    Serial.println(F(" recieved."));
    Serial.print(F("EIMSK before = "));
    Serial.println(EIMSK);
    Serial.print(F("EIFR before = "));
    Serial.println(EIFR);

    switch (tempchar)
    {
      case 'a':
      attachInterrupt(1,pulses_in,RISING);
      break;
      case 'b':
      detachInterrupt(1);
      break;
    }

    // Print out INT1 registers before and after attachInterrupt/detachInterrupt
    Serial.print(F("EIMSK after = "));
    Serial.println(EIMSK);
    Serial.print(F("EIFR after = "));
    Serial.println(EIFR);
    Serial.println(F("===================="));

  }

  // Look for ISRs and print message when they occur
  int intcountertemp;
  do
  {
    intcountertemp = intcounter;
  } while(intcountertemp != intcounter);
  if(intcounter != intcounterlast)
  {
    Serial.print(F("AAA:"));
    Serial.println(intcounter);
    intcounterlast = intcounter;
  }
}
void pulses_in()
{
  intcounter++;
}

Here are the results of the code after performing the actions you described:

a recieved.
EIMSK before = 0
EIFR before = 0
EIMSK after = 2
EIFR after = 0

AAA:6
AAA:14
AAA:20
AAA:27
b recieved.
EIMSK before = 2
EIFR before = 0
EIMSK after = 0
EIFR after = 0

a recieved.
EIMSK before = 0
EIFR before = 2
EIMSK after = 2
EIFR after = 0

AAA:28

If you look at sections 13.2.2 and 13.2.3 of the ATmega328P datasheet, you’ll see that INT1 has both an “enable” bit in EIMSK and a “flag” bit in EIFR. The condition that INT1 has been configured to detect will always set the flag, but will only run your ISR if the enable bit is set.

When you first send ‘a’, both the flag and enable start at zero. Your code then calls attachInterrupt(), and now you see that EIMSK is 2 indicating INT1 is now enabled. Your flag is still 0, however, since you haven’t pushed the button yet.

After you push the button, you likely get a bunch of interrupts unless you have filtering on your circuit you didn’t mention.

When you next send ‘b’ you see that the enable is set before you call detachInterrupt() but 0 afterwards. When you push the button now, your ISR does not run because it’s not enabled.

It is still setting the flag, however, as you can see the next time you send ‘a’. EIFR is now 2 before you call attachInterrupt(), although it is 0 after. This is because as soon as you enable the interrupt, your ISR immediately fires and clears the flag.

If you think about it, however, it’s the safest way for the Arduino to have implemented this. For all they know, the programmer might want to get an interupt for any events that might have occurred while they are turned off.

If this isn’t the behavior you want, however, then it seems you need to manually clear the INT1 flag in EIFR before you call attachInterrupt()

Edited to Add:

  • A couple other things too. You don’t typically want to call Serial.print() in an ISR since it could possibly lock up forever.
  • It is unusual to use an INT pin to read a button. One of the reasons is demonstrated by my example, which shows how many times the ISR runs even when I try to push the button quickly. The other reason is that it is usually unnecessary to respond to human input that quickly (with the exception of a Big Red Emergency Power Off button).

The other reason is that it is usually unnecessary to respond to human input that quickly (with the exception of a Big Red Emergency Power Off button).

For the reasons you've set out, the Big Red Button should be wired to the power supply, and not have its actions moderated by any software-controlled system. Particularly not by ones subject to interrupts.

You CANNOT use Serial.print in interrupt handlers. You WILL have problems.

Regards, Ray L.

BigBobby: Edited to Add:

  • Detaching an interrupt handler is nearly always a bad idea. If for no other reason, it creates a race condition.

gevorgparsyan: Did anyone also faced this phenomena? Is it a normal behavior?

It's a very silly way to use interrupts.

...R

Haha...jeez guys...could you maybe say "what a thorough explanation for the symptom that had the OP puzzled" before you jump on that extra stuff at the end? :confused:

I think I mostly agree with you anyway.

EPOs certainly are implemented in hardware, but they are usually handled in software for redundancy (and just so the software knows it happened). It is not common for it to just cut the power to the whole machine, as the "safe state" for most machines is not "suddenly have power cut."

I mostly agree with "CANNOT use Serial.print in interrupt handlers" but I think "WILL have problems" might confuse some readers. They can run the OPs sketch and likely find it doesn't lock up (like I did). You certainly could make it lock up, but most readers would find it running as they expect and wonder why you said "WILL."

Before attaching/enabling an interrupt it is a good idea to clear the corresponding interrupt flag (always).

You certainly could make it lock up, but most readers would find it running as they expect and wonder why you said "WILL."

Because you will come to rely on it working, and sooner, rather than later, it will bite you in the ass. Big time.

  Serial.begin(9600);

This makes it bite pretty soon. ;)

AWOL: Because you will come to rely on it working, and sooner, rather than later, it will bite you in the ass. Big time.

Yes, I think we're agreeing like 95%. I'd just change it to something like "You SHOULD NEVER use Serial.print in interrupt handlers. You WILL USUALLY have problems."

Whandall: Before attaching/enabling an interrupt it is a good idea to clear the corresponding interrupt flag (always).

This is why I was interested in this question, actually. I was wondering if attachInterrupt() cleared the flag or not. It seems it'd be better with an option to specify whether you wanted to clear the flag or not.

BigBobby:
I was wondering if attachInterrupt() cleared the flag or not.

Why wonder, when you have the source code?

static volatile voidFuncPtr intFunc[EXTERNAL_NUM_INTERRUPTS];
// volatile static voidFuncPtr twiIntFunc;

void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) {
  if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
    intFunc[interruptNum] = userFunc;
    
    // Configure the interrupt mode (trigger on low input, any change, rising
    // edge, or falling edge).  The mode constants were chosen to correspond
    // to the configuration bits in the hardware register, so we simply shift
    // the mode into place.
      
    // Enable the interrupt.
      
    switch (interruptNum) {
#if defined(__AVR_ATmega32U4__)
	// I hate doing this, but the register assignment differs between the 1280/2560
	// and the 32U4.  Since avrlib defines registers PCMSK1 and PCMSK2 that aren't 
	// even present on the 32U4 this is the only way to distinguish between them.
    case 0:
	EICRA = (EICRA & ~((1<<ISC00) | (1<<ISC01))) | (mode << ISC00);
	EIMSK |= (1<<INT0);
	break;
    case 1:
	EICRA = (EICRA & ~((1<<ISC10) | (1<<ISC11))) | (mode << ISC10);
	EIMSK |= (1<<INT1);
	break;	
    case 2:
        EICRA = (EICRA & ~((1<<ISC20) | (1<<ISC21))) | (mode << ISC20);
        EIMSK |= (1<<INT2);
        break;
    case 3:
        EICRA = (EICRA & ~((1<<ISC30) | (1<<ISC31))) | (mode << ISC30);
        EIMSK |= (1<<INT3);
        break;
    case 4:
        EICRB = (EICRB & ~((1<<ISC60) | (1<<ISC61))) | (mode << ISC60);
        EIMSK |= (1<<INT6);
        break;
#elif defined(EICRA) && defined(EICRB) && defined(EIMSK)
    case 2:
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
      break;
    case 3:
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
      break;
    case 4:
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
      break;
    case 5:
      EICRA = (EICRA & ~((1 << ISC30) | (1 << ISC31))) | (mode << ISC30);
      EIMSK |= (1 << INT3);
      break;
    case 0:
      EICRB = (EICRB & ~((1 << ISC40) | (1 << ISC41))) | (mode << ISC40);
      EIMSK |= (1 << INT4);
      break;
    case 1:
      EICRB = (EICRB & ~((1 << ISC50) | (1 << ISC51))) | (mode << ISC50);
      EIMSK |= (1 << INT5);
      break;
    case 6:
      EICRB = (EICRB & ~((1 << ISC60) | (1 << ISC61))) | (mode << ISC60);
      EIMSK |= (1 << INT6);
      break;
    case 7:
      EICRB = (EICRB & ~((1 << ISC70) | (1 << ISC71))) | (mode << ISC70);
      EIMSK |= (1 << INT7);
      break;
#else		
    case 0:
    #if defined(EICRA) && defined(ISC00) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      EIMSK |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GICR |= (1 << INT0);
    #elif defined(MCUCR) && defined(ISC00) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
      GIMSK |= (1 << INT0);
    #else
      #error attachInterrupt not finished for this CPU (case 0)
    #endif
      break;

    case 1:
    #if defined(EICRA) && defined(ISC10) && defined(ISC11) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      EIMSK |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(ISC11) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GICR |= (1 << INT1);
    #elif defined(MCUCR) && defined(ISC10) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
      GIMSK |= (1 << INT1);
    #else
      #warning attachInterrupt may need some more work for this cpu (case 1)
    #endif
      break;
    
    case 2:
    #if defined(EICRA) && defined(ISC20) && defined(ISC21) && defined(EIMSK)
      EICRA = (EICRA & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      EIMSK |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(ISC21) && defined(GICR)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GICR |= (1 << INT2);
    #elif defined(MCUCR) && defined(ISC20) && defined(GIMSK) && defined(GIMSK)
      MCUCR = (MCUCR & ~((1 << ISC20) | (1 << ISC21))) | (mode << ISC20);
      GIMSK |= (1 << INT2);
    #endif
      break;
#endif
    }
  }
}

Whandall: Why wonder, when you have the source code?

Heh...you could say that about everything I don't know about the Arduino yet, and until I spend the minutes to look them up I'm left wondering.

Helping the OP motivated me to spend the minutes on a Sunday afternoon to figure it out :) Figuring it out by modifying his code probably took the same number of minutes that it takes to search the source in the Arduino directory for "attachInterrupt".

BTW - I am still wondering what Arduino does for the Interrupt Vector Table. It obviously does a bunch of things before compilation to make building code simple for the user, and part of that must be looking for attachInterrupts to get the addresses for the IVT. I imagine it has its own sets of errors it throws if you try to attach two things, but who knows...maybe it's brilliant and makes a subfunction for the IVT that then runs the two attached ISRs. Until I look into it I'm left wondering.

Figuring it out by modifying his code probably took the same number of minutes that it takes to search the source in the Arduino directory for "attachInterrupt".

If it takes minutes, you're using the wrong tools, or a tragically slow PC. Even knife-and-forking it using "find . - name "*" | xargs grep "attachInterrupt" " should only a few seconds. A decent "find in files" control in an editor should be just as quick.

AWOL: If it takes minutes, you're using the wrong tools, or a tragically slow PC. Even knife-and-forking it using "find . - name "*" | xargs grep "attachInterrupt" " should only a few seconds. A decent "find in files" control in an editor should be just as quick.

Well yes...navigating a search tool to the Arduino directory and searching for attachInterrupt is probably 1 minute compared to 5 minutes to modify the OPs code.

But helping the OP understand the symptom that was confusing him step-by-step motivated me to spend the extra minutes. Sheesh...I've spent more time defending the code than I did writing it.

I don't see how all of this is helping the OP.

...R

I would think any possible race condition could be eliminated with something like this:

  1. Enabling INT1 while interrupts are off or within an ISR:
EIFR |= (1 << INT1);   // clear pending INT1 interrupt flag
EIMSK |= (1 << INT1);  // enable INT1 interrupt on pin 3
  1. Disabling INT1 while interrupts are off or within an ISR:
EIMSK &= ~(1 << INT1);  // disable INT1 interrupt on pin 3
EIFR |= (1 << INT1);    // clear pending INT1 interrupt flag

BigBobby: Yes, I think we're agreeing like 95%. I'd just change it to something like "You SHOULD NEVER use Serial.print in interrupt handlers. You WILL USUALLY have problems."

See Almost Surely at Wikipedia.

Thank you all for responses.

BigBobby, I would like to give a feedback to your answer.

The code I included in my question is not my original code. I just created the code as an example to demonstrate the issue. So using serial print in ISR is just for a quick example. Assigning an interrupt to push button pin was also for quick example.

So I changed the code to avoid other confusions. I made the interrupt on pin 3, and configured pin 13 as output, which is connected to pin 3. I also moved the serial print out of ISR.

volatile int count=0;
int last_count=0;
void setup() 
{
  pinMode(3, INPUT); //pin 3 for interrupt
  pinMode(13, OUTPUT);
  Serial.begin(9600);
}
void loop() {

  if (count!=last_count)
  {
    Serial.print("Count= ");
    Serial.println(count);
    last_count=count;
  }
  if (Serial.available()>0)
  {
    
    switch (Serial.read())
    {
      case 'a':
      EIFR=0x00;
      EIMSK=0x00;
      Serial.print("EIMSK before a= ");
      Serial.println(EIMSK);
      Serial.print("EIFR before a= ");
      Serial.println(EIFR);
      attachInterrupt(1,pulses_in,RISING);
      Serial.print("EIMSK after a= ");
      Serial.println(EIMSK);
      Serial.print("EIFR after a= ");
      Serial.println(EIFR);
      break;

      case 'b':
      detachInterrupt(1);
      Serial.print("EIMSK after b= ");
      Serial.println(EIMSK);
      Serial.print("EIFR after b= ");
      Serial.println(EIFR);
      last_count=count=0;
      break;

      case 'c':
      digitalWrite(13, HIGH);
      delay(10);
      digitalWrite(13, LOW);
    }
  }
}
void pulses_in()
{
  count++;
}

From your answer I understood I should manually clear the EIFR and EIMSK flag before reattaching the interrupt. I guess, I should apply 0 to both of them. However, the issue exists again.

Have I done anything wrong?

gevorgparsyan:
However, the issue exists again.

Describe what you expect to happen with the new code.

Describe what does happen with the new code.