TL;DR :
When writing an Interrupt Service Routine (ISR):
- Keep it short
- Don't use
delay ()
- Don't do serial prints
- Make variables shared with the main code volatile
- Variables shared with main code may need to be protected by "critical sections" (see below)
- Don't try to turn interrupts off or on
What are interrupts?
Most processors have interrupts. Interrupts let you respond to "external" events while doing something else. For example, if you are cooking dinner you may put the potatoes on to cook for 20 minutes. Rather than staring at the clock for 20 minutes you might set a timer, and then go watch TV. When the timer rings you "interrupt" your TV viewing to do something with the potatoes.
Example of interrupts
const byte LED = 13;
const byte SWITCH = 2;
// Interrupt Service Routine (ISR)
void switchPressed ()
{
if (digitalRead (SWITCH) == HIGH)
digitalWrite (LED, HIGH);
else
digitalWrite (LED, LOW);
} // end of switchPressed
void setup ()
{
pinMode (LED, OUTPUT); // so we can update the LED
pinMode (SWITCH, INPUT_PULLUP);
attachInterrupt (digitalPinToInterrupt (SWITCH), switchPressed, CHANGE); // attach interrupt handler
} // end of setup
void loop ()
{
// loop doing nothing
}
This example shows how, even though the main loop is doing nothing, you can turn the LED on pin 13 on or off, if the switch on pin D2 is pressed.
To test this, just connect a wire (or switch) between D2 and Ground. The internal pullup (enabled in setup) forces the pin HIGH normally. When grounded, it becomes LOW. The change in the pin is detected by a CHANGE interrupt, which causes the Interrupt Service Routine (ISR) to be called.
In a more complicated example, the main loop might be doing something useful, like taking temperature readings, and allow the interrupt handler to detect a button being pushed.
Converting pin numbers to interrupt numbers
To simplify converting interrupt vector numbers to pin numbers you can call the function digitalPinToInterrupt()
, passing a pin number. It returns the appropriate interrupt number, or NOT_AN_INTERRUPT
(-1).
For example, on the Uno, pin D2 on the board is interrupt 0 (INT0_vect from the table below).
Thus these two lines have the same effect:
attachInterrupt (0, switchPressed, CHANGE); // that is, for pin D2
attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);
However the second one is easier to read and more portable to different Arduino types.
Available interrupts
Below is a list of interrupts, in priority order, for the Atmega328:
1 Reset
2 External Interrupt Request 0 (pin D2) (INT0_vect)
3 External Interrupt Request 1 (pin D3) (INT1_vect)
4 Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
5 Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
6 Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)
7 Watchdog Time-out Interrupt (WDT_vect)
8 Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9 Timer/Counter2 Compare Match B (TIMER2_COMPB_vect)
10 Timer/Counter2 Overflow (TIMER2_OVF_vect)
11 Timer/Counter1 Capture Event (TIMER1_CAPT_vect)
12 Timer/Counter1 Compare Match A (TIMER1_COMPA_vect)
13 Timer/Counter1 Compare Match B (TIMER1_COMPB_vect)
14 Timer/Counter1 Overflow (TIMER1_OVF_vect)
15 Timer/Counter0 Compare Match A (TIMER0_COMPA_vect)
16 Timer/Counter0 Compare Match B (TIMER0_COMPB_vect)
17 Timer/Counter0 Overflow (TIMER0_OVF_vect)
18 SPI Serial Transfer Complete (SPI_STC_vect)
19 USART Rx Complete (USART_RX_vect)
20 USART, Data Register Empty (USART_UDRE_vect)
21 USART, Tx Complete (USART_TX_vect)
22 ADC Conversion Complete (ADC_vect)
23 EEPROM Ready (EE_READY_vect)
24 Analog Comparator (ANALOG_COMP_vect)
25 2-wire Serial Interface (I2C) (TWI_vect)
26 Store Program Memory Ready (SPM_READY_vect)
Internal names (which you can use to set up ISR callbacks) are in brackets.
Warning: If you misspell the interrupt vector name, even by just getting the capitalization wrong (an easy thing to do) the interrupt routine will not be called, and you will not get a compiler error.
Reasons to use interrupts
The main reasons you might use interrupts are:
- To detect pin changes (eg. rotary encoders, button presses)
- Watchdog timer (eg. if nothing happens after 8 seconds, interrupt me)
- Timer interrupts - used for comparing/overflowing timers
- SPI data transfers
- I2C data transfers
- USART data transfers
- ADC conversions (analog to digital)
- EEPROM ready for use
- Flash memory ready
The "data transfers" can be used to let a program do something else while data is being sent or received on the serial port, SPI port, or I2C port.
Wake the processor
External interrupts, pin-change interrupts, and the watchdog timer interrupt, can also be used to wake the processor up. This can be very handy, as in sleep mode the processor can be configured to use a lot less power (eg. around 10 microamps). A rising, falling, or low-level interrupt can be used to wake up a gadget (eg. if you press a button on it), or a "watchdog timer" interrupt might wake it up periodically (eg. to check the time or temperature).
Pin-change interrupts could be used to wake the processor if a key is pressed on a keypad, or similar.
The processor can also be awoken by a timer interrupt (eg. a timer reaching a certain value, or overflowing) and certain other events, such as an incoming I2C message.
Enabling / disabling interrupts
The "reset" interrupt cannot be disabled. However the other interrupts can be temporarily disabled by clearing the global interrupt flag.
Enable interrupts
You can enable interrupts with the function call "interrupts" or "sei" like this:
interrupts (); // or ...
sei (); // set interrupts flag
Disable interrupts
If you need to disable interrupts you can "clear" the global interrupt flag like this:
noInterrupts (); // or ...
cli (); // clear interrupts flag
Either method has the same effect, using interrupts
/ noInterrupts
is a bit easier to remember which way around they are.
The default in the Arduino is for interrupts to be enabled. Don't disable them for long periods or things like timers won't work properly.
Why disable interrupts?
There may be time-critical pieces of code that you don't want interrupted, for example by a timer interrupt.
Also if multi-byte fields are being updated by an ISR then you may need to disable interrupts so that you get the data "atomically". Otherwise one byte may be updated by the ISR while you are reading the other one.
For example:
noInterrupts ();
long myCounter = isrCounter; // get value set by ISR
interrupts ();
Temporarily turning off interrupts ensures that isrCounter (a counter set inside an ISR) does not change while we are obtaining its value.
Warning: if you are not sure if interrupts are already on or not, then you need to save the current state and restore it afterwards. For example, the code from the millis() function does this:
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG; // <--------- save status register
// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();
m = timer0_millis;
SREG = oldSREG; // <---------- restore status register including interrupt flag
return m;
}
Note the indicated lines save the current SREG (status register) which includes the interrupt flag. After we have obtained the timer value (which is 4 bytes long) we put the status register back how it was.
Tips
Function names
The functions cli
/ sei
and the register SREG are specific to the AVR processors. If you are using other processors such as the ARM ones the functions may be slightly different.
Disabling globally vs disabling one interrupt
If you use cli()
you disable all interrupts (including timer interrupts, serial interrupts, etc.).
However if you just want to disable a particular interrupt then you should clear the interrupt-enable flag for that particular interrupt source. For example, for external interrupts, call detachInterrupt()
.
What is interrupt priority?
Since there are 25 interrupts (other than reset) it is possible that more than one interrupt event might occur at once, or at least, occur before the previous one is processed. Also an interrupt event might occur while interrupts are disabled.
The priority order is the sequence in which the processor checks for interrupt events. The higher up the list, the higher the priority. So, for example, an External Interrupt Request 0 (pin D2) would be serviced before External Interrupt Request 1 (pin D3).
Can interrupts occur while interrupts are disabled?
Interrupts events (that is, noticing the event) can occur at any time, and most are remembered by setting an "interrupt event" flag inside the processor. If interrupts are disabled, then that interrupt will be handled when they are enabled again, in priority order.
How do you use interrupts?
- You write an ISR (interrupt service routine). This is called when the interrupt occurs.
- You tell the processor when you want the interrupt to fire.
Writing an ISR
Interrupt Service Routines are functions with no arguments. Some Arduino libraries are designed to call your own functions, so you just supply an ordinary function (as in the examples above), eg.
// Interrupt Service Routine (ISR)
void switchPressed ()
{
flag = true;
} // end of switchPressed
However if a library has not already provided a "hook" to an ISR you can make your own, like this:
volatile char buf [100];
volatile byte pos;
// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR; // grab byte from SPI Data Register
// add to buffer if room
if (pos < sizeof buf)
{
buf [pos++] = c;
} // end of room available
} // end of interrupt routine SPI_STC_vect
In this case you use the "ISR" macro, and supply the name of the relevant interrupt vector (from the table earlier on). In this case the ISR is handling an SPI transfer completing. (Note, some old code uses SIGNAL instead of ISR, however SIGNAL is deprecated).
Connecting an ISR to an interrupt
For interrupts already handled by libraries, you just use the documented interface. For example:
void receiveEvent (int howMany)
{
while (Wire.available () > 0)
{
char c = Wire.receive ();
// do something with the incoming byte
}
} // end of receiveEvent
void setup ()
{
Wire.onReceive(receiveEvent);
}
In this case the I2C library is designed to handle incoming I2C bytes internally, and then call your supplied function at the end of the incoming data stream. In this case receiveEvent is not strictly an ISR (it has an argument) but it is called by an inbuilt ISR.
Another example is the "external pin" interrupt.
// Interrupt Service Routine (ISR)
void switchPressed ()
{
// handle pin change here
} // end of switchPressed
void setup ()
{
attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE); // attach interrupt handler for D2
} // end of setup
In this case the attachInterrupt function adds the function switchPressed to an internal table, and in addition configures the appropriate interrupt flags in the processor.
Configuring the processor to handle an interrupt
The next step, once you have an ISR, is to tell the processor that you want this particular condition to raise an interrupt.
As an example, for External Interrupt 0 (the D2 interrupt) you could do something like this:
EICRA &= ~3; // clear existing flags
EICRA |= 2; // set wanted flags (falling level interrupt)
EIMSK |= 1; // enable it
More readable would be to use the defined names, like this:
EICRA &= ~(bit(ISC00) | bit (ISC01)); // clear existing flags
EICRA |= bit (ISC01); // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0); // enable it
EICRA (External Interrupt Control Register A) would be set according to this table from the Atmega328 datasheet.
That defines the exact type of interrupt you want:
- 0: The low level of INT0 generates an interrupt request (LOW interrupt).
- 1: Any logical change on INT0 generates an interrupt request (CHANGE interrupt).
- 2: The falling edge of INT0 generates an interrupt request (FALLING interrupt).
- 3: The rising edge of INT0 generates an interrupt request (RISING interrupt).
EIMSK (External Interrupt Mask Register) actually enables the interrupt.
Fortunately you don't need to remember those numbers because attachInterrupt does that for you. However that is what is actually happening, and for other interrupts you may have to "manually" set interrupt flags.
Low-level ISRs vs. library ISRs
To simplify your life some common interrupt handlers are actually inside library code (for example INT0_vect and INT1_vect) and then a more user-friendly interface is provided (eg. attachInterrupt). What attachInterrupt actually does is save the address of your wanted interrupt handler into a variable, and then call that from INT0_vect / INT1_vect when needed. It also sets the appropriate register flags to call the handler when required.
Can ISRs be interrupted?
In short, no, not unless you want them to be.
When an ISR is entered, interrupts are disabled. Naturally they must have been enabled in the first place, otherwise the ISR would not be entered. However to avoid having an ISR itself be interrupted, the processor turns interrupts off.
When an ISR exits, then interrupts are enabled again. The compiler also generates code inside an ISR to save registers and status flags, so that whatever you were doing when the interrupt occurred will not be affected.
However you can turn interrupts on inside an ISR if you absolutely must, eg.
// Interrupt Service Routine (ISR)
void switchPressed ()
{
// handle pin change here
interrupts (); // allow more interrupts
} // end of switchPressed
Normally you would need a pretty good reason to do this, as another interrupt now could result in a recursive call to pinChange, with quite possibly undesirable results.
How long does it take to execute an ISR?
According to the datasheet, the minimal amount of time to service an interrupt is 4 clock cycles (to push the current program counter onto the stack) followed by the code now executing at the interrupt vector location. This normally contains a jump to where the interrupt routine really is, which is another 3 cycles. Examination of the code produced by the compiler shows that an ISR made with the "ISR" declaration can take around 2.625 µs to execute, plus whatever the code itself does. The exact amount depends on how many registers need to be saved and restored. The minimum amount would be 1.1875 µs.
The external interrupts (where you use attachInterrupt) do a bit more and take around 5.125 µs in total (running with a 16 MHz clock).
How long before the processor starts entering an ISR?
This varies somewhat. The figures quoted above are the ideal ones where the interrupt is immediately processed. A few factors may delay that:
If the processor is asleep, there are designated "wake-up" times, which could be quite a few milliseconds, while the clock is spooled back up to speed. This time would depend on fuse settings, and how deep the sleep is.
If an interrupt service routine is already executing, then further interrupts cannot be entered until it either finishes, or enables interrupts itself. This is why you should keep each interrupt service routine short, as every microsecond you spend in one, you are potentially delaying the execution of another one.
Some code turns interrupts off. For example, calling millis() briefly turns interrupts off. Therefore the time for an interrupt to be serviced would be extended by the length of time interrupts were turned off.
Interrupts can only be serviced at the end of an instruction, so if a particular instruction takes three clock cycles, and has just started, then the interrupt will be delayed at least a couple of clock cycles.
An event that turns interrupts back on (eg. returning from an interrupt service routine) is guaranteed to execute at least one more instruction. So even if an ISR ends, and your interrupt is pending, it still has to wait for one more instruction before it is serviced.
Since interrupts have a priority, a higher-priority interrupt might be serviced before the interrupt you are interested in.
Performance considerations
Interrupts can increase performance in many situations because you can get on with the "main work" of your program without having to constantly be testing to see if switches have been pressed. Having said that, the overhead of servicing an interrupt, as discussed above, would actually be more than doing a "tight loop" polling a single input port. You can barely respond to an event within, say, a microsecond. In that case you might disable interrupts (eg. timers) and just loop looking for the pin to change.
How are interrupts queued?
There are two sorts of interrupts:
Some set a flag and they are handled in priority order, even if the event that caused them has stopped. For example, a rising, falling, or changing level interrupt on pin D2.
Others are only tested if they are happening "right now". For example, a low-level interrupt on pin D2.
The ones that set a flag could be regarded as being queued, as the interrupt flag remains set until such time as the interrupt routine is entered, at which time the processor clears the flag. Of course, since there is only one flag, if the same interrupt condition occurs again before the first one is processed, it won't be serviced twice.
Something to be aware of is that these flags can be set before you attach the interrupt handler. For example, it is possible for a rising or falling level interrupt on pin D2 to be "flagged", and then as soon as you do an attachInterrupt the interrupt immediately fires, even if the event occurred an hour ago. To avoid this you can manually clear the flag. For example:
EIFR = bit (INTF0); // clear flag for interrupt 0
EIFR = bit (INTF1); // clear flag for interrupt 1
However the "low level" interrupts are continuously checked, so if you are not careful they will keep firing, even after the interrupt has been called. That is, the ISR will exit, and then the interrupt will immediately fire again. To avoid this, you should do a detachInterrupt immediately after you know that the interrupt fired.
Hints for writing ISRs
In brief, keep them short! While an ISR is executing other interrupts cannot be processed. So you could easily miss button presses, or incoming serial communications, if you try to do too much. In particular, you should not try to do debugging "prints" inside an ISR. The time taken to do those is likely to cause more problems than they solve.
A reasonable thing to do is set a single-byte flag, and then test that flag in the main loop function. Or, store an incoming byte from a serial port into a buffer. The inbuilt timer interrupts keep track of elapsed time by firing every time the internal timer overflows, and thus you can work out elapsed time by knowing how many times the timer overflowed.
Remember, inside an ISR interrupts are disabled. Thus hoping that the time returned by millis() function calls will change, will lead to disappointment. It is valid to obtain the time that way, just be aware that the timer is not incrementing. And if you spend too long in the ISR then the timer may miss an overflow event, leading to the time returned by millis() becoming incorrect.
A test shows that, on a 16 MHz Atmega328 processor, a call to micros() takes 3.5625 µs. A call to millis() takes 1.9375 µs. Recording (saving) the current timer value is a reasonable thing to do in an ISR. Finding the elapsed milliseconds is faster than the elapsed microseconds (the millisecond count is just retrieved from a variable). However the microsecond count is obtained by adding the current value of the Timer 0 timer (which will keep incrementing) to a saved "Timer 0 overflow count".
Warning: Since interrupts are disabled inside an ISR, and since the latest version of the Arduino IDE uses interrupts for Serial reading and writing, and also for incrementing the counter used by "millis" and "delay" you should not attempt to use those functions inside an ISR. To put it another way:
- Don't attempt to delay, eg:
delay (100);
- You can get the time from a call to millis, however it won't increment, so don't attempt to delay by waiting for it to increase.
- Don't do serial prints (eg.
Serial.println ("ISR entered");
)
- Don't try to do serial reading.
Pin change interrupts
There are two ways you can detect external events on pins. The first is the special "external interrupt" pins, D2 and D3. These general discrete interrupt events, one per pin. You can get to those by using attachInterrupt for each pin. You can specify a rising, falling, changing or low-level condition for the interrupt.
However there are also "pin change" interrupts for all pins (on the Atmega328, not necessarily all pins on other processors). These act on groups of pins (D0 to D7, D8 to D13, and A0 to A5). They are also lower priority than the external event interrupts. However they are a bit more fiddly to use than the external interrupts because they are grouped into batches. So if the interrupt fires you have to work out in your own code exactly which pin caused the interrupt.
Example code:
ISR (PCINT0_vect)
{
// handle pin change interrupt for D8 to D13 here
} // end of PCINT0_vect
ISR (PCINT1_vect)
{
// handle pin change interrupt for A0 to A5 here
} // end of PCINT1_vect
ISR (PCINT2_vect)
{
// handle pin change interrupt for D0 to D7 here
} // end of PCINT2_vect
void setup ()
{
// pin change interrupt (example for D9)
PCMSK0 |= bit (PCINT1); // want pin 9
PCIFR |= bit (PCIF0); // clear any outstanding interrupts
PCICR |= bit (PCIE0); // enable pin change interrupts for D8 to D13
}
To handle a pin change interrupt you need to:
- Specify which pin in the group. This is the PCMSKn variable (where n is 0, 1 or 2 from the table below). You can have interrupts on more than one pin.
- Enable the appropriate group of interrupts (0, 1 or 2)
- Supply an interrupt handler as shown above
Table of pins -> pin change names / masks
D0 PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1 PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2 PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3 PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4 PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5 PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6 PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7 PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8 PCINT0 (PCMSK0 / PCIF0 / PCIE0)
D9 PCINT1 (PCMSK0 / PCIF0 / PCIE0)
D10 PCINT2 (PCMSK0 / PCIF0 / PCIE0)
D11 PCINT3 (PCMSK0 / PCIF0 / PCIE0)
D12 PCINT4 (PCMSK0 / PCIF0 / PCIE0)
D13 PCINT5 (PCMSK0 / PCIF0 / PCIE0)
A0 PCINT8 (PCMSK1 / PCIF1 / PCIE1)
A1 PCINT9 (PCMSK1 / PCIF1 / PCIE1)
A2 PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3 PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4 PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5 PCINT13 (PCMSK1 / PCIF1 / PCIE1)
Interrupt handler processing
The interrupt handler would need to work out which pin caused the interrupt if the mask specifies more than one (eg. if you wanted interrupts on D8/D9/D10). To do this you would need to store the previous state of that pin, and work out (by doing a digitalRead or similar) if this particular pin had changed.
You are probably using interrupts anyway ...
A "normal" Arduino environment already is using interrupts, even if you don't personally attempt to. The millis() and micros() function calls make use of the "timer overflow" feature. One of the internal timers (timer 0) is set up to interrupt roughly 1000 times a second, and increment an internal counter which effectively becomes the millis() counter. There is a bit more to it than that, as adjustment is made for the exact clock speed.
Also the hardware serial library uses interrupts to handle incoming and outgoing serial data. This is very useful as your program can be doing other things while the interrupts are firing, and filling up an internal buffer. Then when you check Serial.available() you can find out what, if anything, has been placed in that buffer.
Executing the next instruction after enabling interrupts
After a bit of discussion and research on the Arduino forum, we have clarified exactly what happens after you enable interrupts. There are three main ways I can think of that you can enable interrupts, which were previously not enabled:
sei (); // set interrupt enable flag
SREG |= 0x80; // set the high-order bit in the status register
reti ; // assembler instruction "return from interrupt"
In all cases, the processor guarantees that the next instruction after interrupts are enabled (if they were previously disabled) will always be executed, even if an interrupt event is pending. (By "next" I mean the next one in program sequence, not necessarily the one physically following. For example, a RETI instruction jumps back to where the interrupt occurred, and then executes one more instruction).
This lets you write code like this:
sei ();
sleep_cpu ();
If not for this guarantee, the interrupt might occur before the processor slept, and then it might never be awoken.
Empty interrupts
If you merely want an interrupt to wake the processor, but not do anything in particular, you can use the EMPTY_INTERRUPT define, eg.
EMPTY_INTERRUPT (PCINT1_vect);
This simply generates a "reti" (return from interrupt) instruction. Since it doesn't try to save or restore registers this would be the fastest way to get an interrupt to wake it up.
Critical sections (atomic variable access)
There are some subtle issues regarding variables which are shared between interrupt service routines (ISRs) and the main code (that is, the code not in an ISR).
Since an ISR can fire at any time when interrupts are enabled, you need to be cautious about accessing such shared variables, as they may be being updated at the very moment you access them.
First ... when do you use "volatile" variables?
A variable should only be marked volatile if it is used both inside an ISR, and outside one.
- Variables only used outside an ISR should not be volatile.
- Variables only used inside an ISR should not be volatile.
- Variables used both inside and outside an ISR should be volatile.
eg.
volatile int counter;
Marking a variable as volatile tells the compiler to not "cache" the variables contents into a processor register, but always read it from memory, when needed. This may slow down processing, which is why you don't just make every variable volatile, when not needed.
Turn interrupts off while accessing a volatile variable
For example, to compare count
to some number, turn interrupts off during the compare in case one byte of count
has been updated by the ISR and not the other byte.
volatile unsigned int count;
ISR (TIMER1_OVF_vect)
{
count++;
} // end of TIMER1_OVF_vect
void setup ()
{
pinMode (13, OUTPUT);
} // end of setup
void loop ()
{
noInterrupts (); // <------ critical section
if (count > 20)
digitalWrite (13, HIGH);
interrupts (); // <------ end critical section
} // end of loop
Read the data sheet!
More information about interrupts, timers, etc. can be obtained from the data sheet for the processor.
http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf
Further examples
Space considerations (post size limit) prevent my listing more example code. For more example code see my page about interrupts.