Strange behaviour using interrupts

I'm trying to understand interrupts in the UNO board.
I've read the attachInterrupts function docs . For this board there are two interrupts: INT0 in pin #2 and INT1 in pin #3.

The third parameter to this function is the MODE, which according to the docs can take four different values in this board:

  • LOW: trigger the interrupt whenever the pin is low
  • CHANGE: trigger the interrupt whenever the pin changes value
  • RISING: trigger when the pin goes from low to high
  • FALLING: for when the pin goes from high to low

Well, I've tested all four modes and what I'm getting instead is this:

  • LOW: ISR fired once when pin changes from low to high
  • CHANGE: ISR fired continuously while pin is low, no action when pin is high
  • RISING: works as expected
  • FALLING: Same as CHANGE.

Here's my code. I've attached a switch to the interrupt pin #2 (or #3 when testing INT1), and a buzzer to the pin #7 (the ISR plays a tone for 1 second).

static const int INTERRUPT_NUMBER = 0;

static const int INTERRUPT_MODE = RISING;

void setup() {
  attachInterrupt(INTERRUPT_NUMBER, beep, INTERRUPT_MODE);
}

void loop() {  
}

void beep(){
  tone(7, 440, 1000);
}

Is this normal? Could my board be faulty?

Do you have any debounce on the switch? The switch bounce will trigger multiple interrupts. How is the switch wired? Attached is the more accepted way to wire a switch. This way allows hardware debounce and the use of the internal pullup. If hardware debounce is not enough, you can add debounce in software.

I'd be very wary of using anything but very simple pin or variable manipulation in an ISR - how about using the LED on pin 13 for your indications?

groundfungus:
Do you have any debounce on the switch? The switch bounce will trigger multiple interrupts. How is the switch wired? Attached is the more accepted way to wire a switch. This way allows hardware debounce and the use of the internal pullup. If hardware debounce is not enough, you can add debounce in software.

I've added HW debouncing as in your picture. That fixed the CHANGE, RISING and FALLING modes, but the LOW mode is yet not working as expected.

Can you elaborate? Not working as expected in what way? Interrupt fires when button released?

Well, I've tested all four modes and what I'm getting instead is this:
I wonder if your notes as to seen behavior got mixed up as they seem wrong for the mode behavior.

LOW: ISR fired once when pin changes from low to high
This mode is seldom used as it causes a continuous interrupt as long as the signal remains low. As soon as your ISR finishes a new interrupt will be generated as soon as your sketch completes a single machine instruction, a very problematic situation.

CHANGE: ISR fired continuously while pin is low, no action when pin is high
Sounds like wrong observation as this mode causes an interrupt only on signal transition from low to high or high to low, continuous low or high signals will not generate a interrupt.

RISING: works as expected
Should interrupt only of signal going from low to high.
FALLING: Same as CHANGE
Should only interrupt on signal going from high to low but not also low to high as in CHANGE mode.

Note that interrupts generated from simple switch buttons will frequently cause big problems due to contact bounce. There must have good contact debouncing strategy employed to use mechanical switches to generate interrupt signals.
And the golden rule to using ISR coding is to keep the ISR as simple and short as possible, usually just setting or resetting byte size flag global static variable types and let the main loop code test the flag to actually accomplish the task that the interrupt represents and then set or reset the flag variable as applicable.

groundfungus:
Can you elaborate? Not working as expected in what way? Interrupt fires when button released?

When using the LOW mode, the ISR is fired only once when pin changes from low to high.

I'd expect it to fire continously while pin is low.
BTW Its a switch.

Also I’ve never seen this mode of passing the mode value to the attachInterrupt function. It might not be properly be setting up the mode correctly.

static const int INTERRUPT_MODE = RISING;

Try setting the mode directly in the attach statement:

attachInterrupt(INTERRUPT_NUMBER, beep,  RISING);  // or the other defined mode names.

And finally:

void beep(){
  tone(7, 440, 1000);
}

Typically it’s a bad practice to call a function while inside a ISR function unless you know exactlly what side effects that
might cause. As I said have the ISR set a flag, test the flag in your loop function, call the tone function if true and then reset the flag variable. Life will be more consistent that way. :wink:

retrolefty:
Note that interrupts generated from simple switch buttons will frequently cause big problems due to contact bounce. There must have good contact debouncing strategy employed to use mechanical switches to generate interrupt signals.
And the golden rule to using ISR coding is to keep the ISR as simple and short as possible, usually just setting or resetting byte size flag global static variable types and let the main loop code test the flag to actually accomplish the task that the interrupt represents and then set or reset the flag variable as applicable.

I didn't know switches bounce that much. Mine is like this one:

In fact I used a switch instead of a push button because I thought it would be more stable.

But the problem must be probably related to bouncing, since adding HW debounce fixed 3 of the 4 modes. Now only the LOW mode keeps producing unexpected results.

retrolefty:
Also I’ve never seen this mode of passing the mode value to the attachInterrupt function. It might not be properly be setting up the mode correctly.

static const int INTERRUPT_MODE = RISING;

Try setting the mode directly in the attach statement:

attachInterrupt(INTERRUPT_NUMBER, beep,  RISING);  // or the other defined mode names.

Makes no sense to me, but I’ve done as you said and still does not work.

I added another comment to my last posting about what code you have in your ISR, check it out.

New code. Now the ISR only toggles a var. Still does not work.

static const int INTERRUPT_NUMBER = 0;
static const int INTERRUPT_MODE = LOW;
static int mustBeep = 0;

void setup() {
  attachInterrupt(INTERRUPT_NUMBER, requestBeep, INTERRUPT_MODE);
}

void loop() {  
  if(mustBeep){
    mustBeep = 0;
    beep();
  }
}

void beep(){
  noTone(7);
  tone(7, 440, 1000);
}

//ISR. Must take no parameters and return nothing.
void requestBeep(){
  mustBeep = 1;
}

buffer_overfly:
New code. Now the ISR only toggles a var. Still does not work.

static const int INTERRUPT_NUMBER = 0;

static const int INTERRUPT_MODE = LOW;
static int mustBeep = 0;

void setup() {
 attachInterrupt(INTERRUPT_NUMBER, requestBeep, INTERRUPT_MODE);
}

void loop() {  
 if(mustBeep){
   mustBeep = 0;
   beep();
 }
}

void beep(){
 noTone(7);
 tone(7, 440, 1000);
}

//ISR. Must take no parameters and return nothing.
void requestBeep(){
 mustBeep = 1;
}

Again don't use LOW mode, it's a snake pit, use FALLING.

retrolefty:
Again don't use LOW mode, it's a snake pit, use FALLING.

This is just a test, what fun is it if I can't do bad things? :stuck_out_tongue_closed_eyes:

I've found the problem. When in LOW mode, the ISR was continuously being fired, hogging the CPU, so the main loop never hardly had a chance to run. When the switch was turned on, then the ISR stopped being fired, and the main loop then executed the handler for the last ISR request. Thus the beep was only heard in the change from low to high.

Interestingly, in my initial sketch i did not flip a variable in the ISR, but directly called tone. This produced the same behaviour probably due to the internals of the tone function. The effect was the same as if it were called in a for loop, no sound was produced.

So the LOW mode works as expected after all. The proof is in this new sketch:

static const int INTERRUPT_NUMBER = 0;
static const int INTERRUPT_MODE = LOW;
static int isrPasses = 0;

void setup() {
  Serial.begin(9600); 
  attachInterrupt(INTERRUPT_NUMBER, isr, INTERRUPT_MODE);
}

void loop() {  
  if(isrPasses){
     Serial.print("ISR was executed ");
     Serial.print(isrPasses);
     Serial.println(" times");
    isrPasses = 0;
    beep();
  }
}

void beep(){
  //noTone(7);
  tone(7, 440, 1000);
}

//ISR. Must take no parameters and return nothing.
void isr(){
  isrPasses++;
}

For the CHANGE, RISING and FALLING modes, the log output shows the ISR is only called once:

ISR was executed 1 times

However for the LOW mode, the log shows the ISR has been executed multiple times:

ISR was executed 1815 times
ISR was executed 5366 times
ISR was executed 7695 times

Mystery solved then. Thanks everybody.

static int mustBeep = 0;

sp.volatile int mustBeep = 0;