This article discusses interrupts on the Arduino Uno (Atmega328) and similar processors, using the Arduino IDE. The concepts however are very general. The code examples provided should compile on the Arduino IDE (Integrated Development Environment).
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.
What interrupts are NOT
Interrupts are not for simply changing your mind and doing something different. For example:
- I interrupted my television viewing to take a bath.
- I was reading a book when visitors arrived and interrupted me. We then went to the movies.
- The user pressed a red button to interrupt the robot walking around.
The above are examples of doing something different. They should not be done in an interrupt routine. The most that an interrupt (service) routine might do would be to remember that the red button was pressed. This fact would then be tested in the main program loop.
Example of interrupts
const byte LED = 13;
const byte BUTTON = 2;
// Interrupt Service Routine (ISR)
void switchPressed ()
{
if (digitalRead (BUTTON) == HIGH)
digitalWrite (LED, HIGH);
else
digitalWrite (LED, LOW);
} // end of switchPressed
void setup ()
{
pinMode (LED, OUTPUT); // so we can update the LED
digitalWrite (BUTTON, HIGH); // internal pull-up resistor
attachInterrupt (digitalPinToInterrupt (BUTTON), 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.
digitalPinToInterrupt function
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.
Summary of 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 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 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;
// 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;
return m;
}
Note the lines in bold 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.
What are "volatile" variables?
Variables shared between ISR functions and normal functions should be declared "volatile". This tells the compiler that such variables might change at any time, and thus the compiler must reload the variable whenever you reference it, rather than relying upon a copy it might have in a processor register.
For example:
volatile boolean flag;
// Interrupt Service Routine (ISR)
void isr ()
{
flag = true;
} // end of isr
void setup ()
{
attachInterrupt (digitalPinToInterrupt (2), isr, CHANGE); // attach interrupt handler
} // end of setup
void loop ()
{
if (flag)
{
// interrupt has occurred
}
} // end of loop
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 pinChange ()
{
flag = true;
} // end of pinChange
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" define, 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 pinChange ()
{
// handle pin change here
} // end of pinChange
void setup ()
{
attachInterrupt (digitalPinToInterrupt (2), pinChange, CHANGE); // attach interrupt handler for D2
} // end of setup
In this case the attachInterrupt function adds the function pinChange 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 (page 71).
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 pinChange ()
{
// handle pin change here
interrupts (); // allow more interrupts
} // end of pinChange
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.
Then an ISR routine (declared with the ISR define) does something like this:
// SPI interrupt routine
ISR (SPI_STC_vect)
118: 1f 92 push r1 (2) // save R1 - the "zero" register
11a: 0f 92 push r0 (2) // save register R0
11c: 0f b6 in r0, 0x3f (1) // get SREG (status register)
11e: 0f 92 push r0 (2) // save SREG
120: 11 24 eor r1, r1 (1) // ensure R1 is zero
122: 8f 93 push r24 (2)
124: 9f 93 push r25 (2)
126: ef 93 push r30 (2)
128: ff 93 push r31 (2)
{
That's another 16 cycles (the cycles are in brackets). So from the moment the interrupt occurs, to the first line of code being executed, would be 16 + 7 cycles (23 cycles), at 62.5 nS per clock cycle, that would be 1.4375 µS. That's assuming a 16 MHz clock.
Then to leave the ISR we have this code:
} // end of interrupt routine SPI_STC_vect
152: ff 91 pop r31 (2)
154: ef 91 pop r30 (2)
156: 9f 91 pop r25 (2)
158: 8f 91 pop r24 (2)
15a: 0f 90 pop r0 (2) // get old SREG
15c: 0f be out 0x3f, r0 (1) // restore SREG
15e: 0f 90 pop r0 (2) // now put old R0 register back
160: 1f 90 pop r1 (2) // restore old value of R1
162: 18 95 reti (4) // return from interrupt, turn interrupts back on
That's another 19 clock cycles (1.1875 µS). So in total, an ISR using the ISR define will take you 2.625 µS to execute, plus whatever the code itself does.
However the external interrupts (where you use attachInterrupt) do a bit more, as follows:
SIGNAL(INT0_vect) {
182: 1f 92 push r1 (2)
184: 0f 92 push r0 (2)
186: 0f b6 in r0, 0x3f (1)
188: 0f 92 push r0 (2)
18a: 11 24 eor r1, r1 (1)
18c: 2f 93 push r18 (2)
18e: 3f 93 push r19 (2)
190: 4f 93 push r20 (2)
192: 5f 93 push r21 (2)
194: 6f 93 push r22 (2)
196: 7f 93 push r23 (2)
198: 8f 93 push r24 (2)
19a: 9f 93 push r25 (2)
19c: af 93 push r26 (2)
19e: bf 93 push r27 (2)
1a0: ef 93 push r30 (2)
1a2: ff 93 push r31 (2)
if(intFunc[EXTERNAL_INT_0])
1a4: 80 91 00 01 lds r24, 0x0100 (2)
1a8: 90 91 01 01 lds r25, 0x0101 (2)
1ac: 89 2b or r24, r25 (2)
1ae: 29 f0 breq .+10 (2)
intFunc[EXTERNAL_INT_0]();
1b0: e0 91 00 01 lds r30, 0x0100 (2)
1b4: f0 91 01 01 lds r31, 0x0101 (2)
1b8: 09 95 icall (3)
}
1ba: ff 91 pop r31 (2)
1bc: ef 91 pop r30 (2)
1be: bf 91 pop r27 (2)
1c0: af 91 pop r26 (2)
1c2: 9f 91 pop r25 (2)
1c4: 8f 91 pop r24 (2)
1c6: 7f 91 pop r23 (2)
1c8: 6f 91 pop r22 (2)
1ca: 5f 91 pop r21 (2)
1cc: 4f 91 pop r20 (2)
1ce: 3f 91 pop r19 (2)
1d0: 2f 91 pop r18 (2)
1d2: 0f 90 pop r0 (2)
1d4: 0f be out 0x3f, r0 (1)
1d6: 0f 90 pop r0 (2)
1d8: 1f 90 pop r1 (2)
1da: 18 95 reti (4)
I count 82 cycles there (5.125 µS in total at 16 MHz) as overhead plus whatever is actually done in the supplied interrupt routine. That is, 2.9375 µS before entering your interrupt handler, and another 2.1875 µS after it returns.
How long before the processor starts entering an ISR?
This various 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. If you absolutely need to respond to an event within, say, a microsecond, then an interrupt would be too slow. 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 = 1; // clear flag for interrupt 0 (D2 on Uno)
EIFR = 2; // clear flag for interrupt 1 (D3 on Uno)
Or, for readability:
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. An example of this is going into sleep mode:
#include <avr/sleep.h>
// interrupt service routine in sleep mode
void wake ()
{
sleep_disable (); // first thing after waking from sleep:
} // end of wake
void sleepNow ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable (); // enables the sleep bit in the mcucr register
attachInterrupt (digitalPinToInterrupt (2), wake, LOW); // wake up on low level on D2
sleep_mode (); // here the device is actually put to sleep!!
detachInterrupt (digitalPinToInterrupt (2)); // stop LOW interrupt on D2
} // end of sleepNow
Note that the interrupt is detached immediately after we wake up. However we should be cautious about putting the detachInterrupt actually inside the "wake" ISR, because it is possible the interrupt might fire between attaching the interrupt and going to sleep, in which case we would never wake up.
[EDIT] (14th July 2013)
Correction:
The improved version below solves this problem by disabling interrupts before doing the attachInterrupt call. Since you are guaranteed that one instruction will be executed after re-enabling interrupts, we are sure that the sleep_cpu call will be done before the interrupt occurs.
#include <avr/sleep.h>
// interrupt service routine in sleep mode
void wake ()
{
sleep_disable (); // first thing after waking from sleep:
detachInterrupt (digitalPinToInterrupt (2)); // stop LOW interrupt on D2
} // end of wake
void sleepNow ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
noInterrupts (); // make sure we don't get interrupted before we sleep
sleep_enable (); // enables the sleep bit in the mcucr register
attachInterrupt (digitalPinToInterrupt (2), wake, LOW); // wake up on low level on D2
interrupts (); // interrupts allowed now, next instruction WILL be executed
sleep_cpu (); // here the device is put to sleep
} // end of sleepNow
These examples illustrates how you can save power with a LOW interrupt, because the LOW interrupt is activated even if the processor is asleep (whereas the other ones - RISING, FALLING, CHANGE - are not). (Amended 15 Dec 2013 : We now believe all four external interrupt types will wake the processor).
Tip:
I received this confirmation from Atmel that the datasheet is wrong about only LOW interrupts waking the processor.
Quote:
Commented by Manoraj Gnanadhas (Atmel)
2015-01-20 06:23:36 GMT
[Recipients: Nick Gammon]
Hello Nick,
Our design team has confirmed that “Note-3 mentioned under Table 10-1” is a datasheet bug. So you can use any type of interrupt (Rising edge/ Falling edge / Low level / Any logical change) to wake up from sleep mode. Sorry for the inconvenience caused.
Best Regards,
Manoraj Gnanadhas
Thus, all interrupt types will wake the processor.
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. You could make an interrupt handler to handle changes on pins D8 to D13 like this:
ISR (PCINT0_vect)
{
// one of pins D8 to D13 has changed
}
Clearly extra code is needed to work out exactly which pin changed (eg. comparing it to a "before" value).
Pin change interrupts each have an associated "mask" byte in the processor, so you could actually configure them to only react to (say) D8, D10 and D12, rather than a change to D8 to D13. However you still then need to work out which of those changed.
Example of watchdog timer interrupt
#include <avr/sleep.h>
#include <avr/wdt.h>
#define LED 13
// interrupt service routine for when button pressed
void wake ()
{
wdt_disable(); // disable watchdog
} // end of wake
// watchdog interrupt
ISR (WDT_vect)
{
wake ();
} // end of WDT_vect
void myWatchdogEnable (const byte interval)
{
noInterrupts (); // timed sequence below
MCUSR = 0; // reset various flags
WDTCSR |= 0b00011000; // see docs, set WDCE, WDE
WDTCSR = 0b01000000 | interval; // set WDIE, and appropriate delay
wdt_reset();
byte adcsra_save = ADCSRA;
ADCSRA = 0; // disable ADC
power_all_disable (); // turn off all modules
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // sleep mode is set here
sleep_enable();
attachInterrupt (digitalPinToInterrupt (2), wake, LOW); // allow grounding pin D2 to wake us
interrupts ();
sleep_cpu (); // now goes to Sleep and waits for the interrupt
detachInterrupt (digitalPinToInterrupt (2)); // stop LOW interrupt on pin D2
ADCSRA = adcsra_save; // stop power reduction
power_all_enable (); // turn on all modules
} // end of myWatchdogEnable
void setup ()
{
digitalWrite (2, HIGH); // pull-up on button
} // end of setup
void loop()
{
pinMode (LED, OUTPUT);
digitalWrite (LED, HIGH);
delay (5000);
digitalWrite (LED, LOW);
delay (5000);
// sleep bit patterns:
// 1 second: 0b000110
// 2 seconds: 0b000111
// 4 seconds: 0b100000
// 8 seconds: 0b100001
// sleep for 8 seconds
myWatchdogEnable (0b100001); // 8 seconds
} // end of loop
The above code, running on a "bare bones" board (that is, without voltage regulator, USB interface etc.) uses the following:
- With LED lit: 19.5 mA
- Awake, with LED off: 16.5 mA
- Asleep: 6 uA (0.006 mA)
You can see that using the watchdog timer combined with sleep mode, you can save a considerable amount of power during times when the processor might not be needed.
It also illustrates how you can recover from sleep in two different ways. One is with a button press (ie. you ground pin D2), the other is waking up periodically (every 8 seconds), although you could make it every 1, 2 4, or 8 seconds (or even shorter if you consult the data sheet).
Warning: after being woken the processor might need a short time to stabilize its clock. For example, you may see "garbage" on a serial comms port while things are synchronizing. If this is a problem you may want to build in a short delay after being woken.
Note re brownout detection: The figures measured above are with brownout detection disabled. To generate a reference voltage for the brownout detection takes a bit of current. With it enabled, the sleep mode uses closer to 70 uA (not 6 uA). One way of turning off the brownout detection is to use avrdude from the command line, like this:
avrdude -c usbtiny -p m328p -U efuse:w:0x07:m
That sets the "efuse" (extended fuse) to be 7, which disables brownout detection. This example assumes you are using the USBtinyISP programmer board.
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 serial data (and in the more recent library versions, outgoing serial data as well). 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:
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.
Read the data sheet!
More information about interrupts, timers, etc. can be obtained from the data sheet for the processor.
http://atmel.com/dyn/resources/prod_documents/8271S.pdf |