Using Interrupts

The last couple of hours I've been playing around with interrupts and I thought I would share my findings with the forum. The example for attachInterrupt in the Arduino reference won't work as is. The first thing I discovered was that the interrupt function must be declared before the attachInterrupt call is made.
The code I have below is tested and works. You can change it to use any of the interrupt modes you want.

I have found it very difficult to only trigger the interrupt a single time from an external switch. This is due to a couple of factors. The first is that I couldn't seem to use delay() in the interrupt function. It appears to freeze if you try it. I think it may be because the clock pauses when you are in an interrupt. At first I thought that the delay call in the interrupt may be interfering with they delay in the loop. So I switched to using a millis() based blink which removes this possibility. The other evidence of the pausing of the timer during an interrupt is the fact that the time displayed by the interrupt call stays the same when you use the interrupt mode LOW. When you return the switch to the HIGH state and then quickly switch back to the LOW state, the timer has only incremented since the end of the LOW state, not the beginning.

With the LOW interrupt mode, the interrupt repeatedly triggers as long as you are in the LOW state. The whole time, the clock is paused. This can be seen with the code example below. I tried calling detachInterrupt as soon as I enter the interrupt function, but that didn't seem to work.

The interrupt pin itself seems to need a very quick state change. Physical switches are "sloppy" and tend to double trigger, especially on CHANGE, RISING and FALLING modes. Maybe adding a debounce to the physical switching circuit may tighten these changes up. I haven't tried this yet.

The original purpose of my experimenting was to determine the usefulness of the interrupts as a debugging option using an external switch. In this sense it is a success. This is a great use of the interrupt as serial communication still works and can easily dump the values of all of your variables to the serial connection. I couldn't seem to use the internal pull-up option on the pin when used as an interrupt trigger, so you do need to use an external pull-up resistor, and wire the switch to ground.

Here's my code. Comments and contributions are extremely welcome.

// interrupt_test  -  The program is used to test the abilities of interrupts

// Define Constants
#define LED1 6

// Define Variables
long lastBlink = millis();
int ledState = LOW;

// Define Interrupt
void debug() 
{
  Serial.print( "INT " );
  Serial.println( millis() );
}

void setup()
{
  attachInterrupt( 0, debug, LOW );
  Serial.begin(9600);
  pinMode( LED1, OUTPUT );
  Serial.println( "Setup Done" );
}

void loop()
{
  if ( millis() - lastBlink > 200) {
    lastBlink = millis();
    if ( ledState == LOW )
      ledState = HIGH;
    else
      ledState = LOW;
    digitalWrite( LED1, ledState );
  }
}

Thanks for the thorough investigation and I'm glad it was useful as a debugging technique.

I'll have to investigate the fact that the function needs to be declared before it is used. In theory, the Arduino environment generates prototypes for your functions, but it's not perfect, and may not work in this case.

As for why delay() and millis() don't work: it's because the code that increments the millis() counter is triggered by an interrupt, and while one interrupt is running, another can't interrupt it. So the time doesn't get incremented while you're in an interrupt. You also may not be able to receive serial data from within an interrupt, or generate PWM outputs (though I'm not sure about this last one). I'll have to add a discussion of this to the documentation.

I'm not sure what to recommend about debouncing the interrupt. You may be right that a hardware solution is easiest.

Please let us know if you discover anything else; this is excellent information to have.

Good work. I like your idea and the example code. You could use the interrupt to pause and unpause your program, too.
In some cases it might be easier to send a debug request from your computer and use Serial.available() to listen for it from the microcontroller. But I understand how using interrupts guarantees your debug request will be performed, even if the program's in an infinite loop.

Unlike delay(), delayMicroseconds() is just a NOP loop that doesn't rely on other timer interrupts (in fact, it disables interrupts). This shouldn't freeze you program.
I wouldn't recommend putting delays in interrupts though, that'll really interfere with anything time critical in the rest of your program. Like mellis said, I think it'll affect the analog IO, too.

Hardware debouncing is the best way, if you can add parts to your circuit. Software debouncing will waste cycles.
For hardware debouncing of a momentary pushbutton, I decided to research that myself, since I couldn't think of a perfect answer. There's a really good PDF you can find through google, called "A Guide to Debouncing".

For a quick fix, there's another kind of software debounce, that won't slow down your program as much.
At the end of the interrupt function, set global variable "intDebounce=millis()+10;". At the very beginning of the interrupt function, "if (intDebounce>millis()) return;" to abort if it's been less than 10ms. That should execute the interrupt code immediately, allow loop() to continue operating, and prevent retriggering the interrupt for more than 10ms. The interrupt still gets triggered a lot of extra times, but with less overhead.

I know you mentioned that serial data can be sent from within the interrupt, but will data being received still be buffered during the interrupt?

I'm worried about missing data from our GPS module, and since it only sends at 1hz missing anything is a bit of a bother.

Thanks!

I believe that serial data received while you're in an interrupt will be lost.

Hmm, alright.

How about using SoftwareSerial.read() inside an interrupt handler? I took a look at the library, and it uses delayMicroseconds(), but if we're already in the interrupt, disabling any more of them shouldn't matter, correct?

Thanks

ISRs need to be small and fast, else things start to go badly for other interrupts that need to be serviced. To put it in perspective, a single bit-time at 9600 baud takes the same amount of time as executing 1667 instructions; a single byte, ten times that. Maybe your system can deal with that, but maybe it can't.

If you've got very much work to do based on an interrupt, use the ISR to set a flag and return quickly, then look at that flag in your main even loop and execute the bulky/slow interrupt code later.

If you're debugging and need to check values, use the ISR to store those values; return from the ISR quickly, then print them out later (inside your main event loop).

-j

Thanks, I'm still considering the different options.

A bit about the problem: we have 4 MCs that need to pass data:

  • 2 of them query sensors
  • 1 controls a few servos
  • 1 handles communications and passes data between the other 3I was planning to have the central/communications MC interrupt the other 3 and transmit a few variables a few times a second, either by SoftwareSerial or through a simple SPI interface.

If you've got very much work to do based on an interrupt, use the ISR to set a flag and return quickly, then look at that flag in your main even loop and execute the bulky/slow interrupt code later.

This seems like a good option as well, but only if you think the code I'm planning would be too bulky for an interrupt handler.

Thanks for your help!

Do you need interrupts at all? You could have the communications one send serial data to the others, which is received (in the background) with the normal serial code. Or use I2C (the Wire library).

You could have the communications one send serial data to the others, which is received (in the background) with the normal serial code.

That would make sense, wouldn't it. Doh. I guess I just always assumed we'd be using the hardware serial connections for sensors, but if we leave them open on all of the slave devices we can send them messages with SoftwareSerial. This seems like the best choice at the moment...

Also, it looks like I would still need interrupts to have the other devices be I2C slaves, unless I'm reading wrong.

Thanks again!

You do need interrupts for them to be I2C slave, but I believe that the Wire library takes care of them for you, so you don't need to deal with the interrupts directly.

Try putting this custom delay_ms() function in your interrupt, it should work to replace the delay() function when called inside the interrupt.

#include <util/delay.h>

void delay_ms(unsigned int time)
{
while (time--)
_delay_ms(1);
}

Let me know if it works!

I have a problem of using SoftwareSerial in combination
with interrupts. I do not use serial connection within
the interrupt handler.

I use the SoftwareSerial to send data to a Pololu servo controller.
It works fine. However, once I uncomment following line

attachInterrupt(1, interruptHandler, CHANGE);

I get the red light blinking on Pololu.
I guess this has something to do with the
serial line timing not working properly
anymore, once an interrupt is attached.

Let me apologize in advance if this post is better for another topic area - but it's so close to what I'm working with right now, I think this might be the best audience to address it.

I'm new to the arduino, and new to this sort of thing in general. I'm developing a project which will require the use of a soft power switch (momentary pushbutton on input 2). When pushed, the button will be used as soft of a soft on/off switch for another pin (13 I'm using at the moment, just turning an LED on and off - later, I'll be driving a transistor and in turn driving a larger relay).

The approach I'm taking is to try and trigger an interrupt on the state of pin 2 going LOW (I'm using a pullup to keep it otherwise HIGH) which will change the state of output 13 to HIGH) - and then let the program go on its merry way - until I push it again to yet again trigger the interrupt and (taking pin 2 to LOW) and then triggering pin 13 to go back LOW. A pretty straightforward on/off button for an external componet.

I'm having on heck of a time getting it working. Feels like im 80% there, but just keep banging my head at things. Seems like setting variables (set up as volatile) within the interrupt isn't quit working the way it should - and before i bang my head against the table any more - let me ask a stupid question and I'm open to opinions - Am I even taking the right approach to this? Should I even be using interrupts at all based on what I'm trying to accomplish, or is there a better way?

Josh

I am not sure what your problem is actually.

However, a common issue with this sort
of pushbutton solutions is that you create
not one single LOW with your button but
quite often several LOWs when you press
the button. (or HIGHs when you use a
a pull-down resistor instead)

You can solve it by either
a) put in some time delay until the
next button-signal is allowed. Something like
100ms or less should be ok. You can experiment
to find out a satisfying delay.
or
b) use two buttons. (and two Inputpins) One
button signals for the LED going up,
the second is for the LED going down.

Yeah I'm familiar with the debounce related issues.

Im actually locking up the arduino (or so it seems) in ways that it takes a reset to clear.

I'm gonna play with some fresh code and see if I can sort this out.