Arduino
Interrupt: Find out Exactly
how to use INT0 and INT1 - learn all about these interrupts and how to
use them, including example code. Did you know, There are more than Two
external interrupts on an Uno! It's true; (available on virtually any
I/O pin) but how
do you use them? Find out here.
Arduino interrupts:
Let your program react instantly to events.
Are easy to setup when you know how.
Let your program work efficiently instead of polling for an event.
Find out why you need two functions attachInterrupt() and digitalPinToInterrupt().
An Arduino Interrupt is useful when you need a program to react
virtually instantly to an event. If you apply a digital signal to pins 2
or 3 of an Arduino Uno then these can trigger an interrupt (There are more).
There are also many more interrupt sources that are used internally
in the chip. For example there are serial port completion interrupts
(data received or transmitted), timer match or timer overflow interrupts etc.
These all allow efficient operation of the internal modules, while still
allowing the processor to get on with other tasks. But the focus of this
page is on external physical event i.e. pin voltage change in the real world that you use to generate
an interrupt.
Although this tutorial is about interrupts on the Uno, all external
interrupts work in the same way so it applies to any Arduino Board.
How to set up an external interrupt
The two functions you need to activate an interrupt:
detachInterrupt(digitalPinToInterrupt(pin)) -
Detaches a specified interrupt - so use digitalPinToInterrupt() to get
the interrupt number from the Arduino pin. This stops the interrupt
handler from executing when that pin triggers.
interrupts() - Globally enables or disables hardware
interrupts. Used to disable interrupts when sensitive code is executing
to prevent conflicts.
noInterrupts() - Disables interrupts globally. Shorthand for interrupts(false).
In general the above three functions are rarely used since:
Interrupts are already on, so that millis() and micros() can operate.
You also don't often detach an interrupt because you want the code
to generate an interrupt at an interrupt pin - unless you don't - maybe
you need to ignore the interrupt for a time while you do a
calculation etc.
You will only use noInterrupts() in special cases; see pulsein() for instance.
Interrupt pins on some Arduino Boards
Here's a list of interrupts and their Arduino pins, to which you connect up your interrupting signal.
Board
INT0
INT1
INT2
INT3
INT4
INT5
Uno/Nano
2
3
Leonardo
3
2
0
1
Mega
21
20
19
18
2
3
Note: Physical Arduino Uno/Nano pin 3 has INT1, and physical Arduino pin 2 has INT0.
Why do you need two functions to setup an external interrupt?
In Arduino code we are all used to specifying a pin to which to apply
a selected operation for example:
digitalRead(5); - will read the digital
value from Arduino pin 5.
For an external interrupt you can't select the pin
as the interrupt source because the attachInterrupt function is wanting
the interrupt number (0 or 1 - for an Arduino Uno/Nano).
Therefore a mediating function us used to translate the pin (on which
the interrupt must be attached - because it is physically fixed in the
microcontroller architecture). The function digitalPinToInterrupt
translates the
pin on which the interrupt is attached into the interrupt number. This
follows the Arduino convention that the pin is specified as the input to
the function.
You could say why bother with all this and just make a function that
accepts the pin number and translate in the background to the interrupt
number. The answer is that there are many different Arduino boards - all
have different pins and associated interrupts. So these functions allow
other boards to operate.
As long as you select the pin that is associated with the interrupt
then these functions return and attach the interrupt without you having
to think about the interrupt number. Just select the right pin and the
relevant interrupt is attached.
External interrupts are always input to a fixed pin on the
microcontroller chip defined by the manufacturer. So you have to know
which pin you are going to use as above but the interrupt code uses
interrupt numbers from zero upwards; so you need the second function
(below).
Interrupt functions
For attaching an interrupt to your code i.e. making your Interrupt
Service Routine (ISR) react to an interrupt, you use the function (here
<interrupt> is a value indicating the interrupt number):
The problem is that the <interrupt> argument is the interrupt
number and not the pin number, so you need a convenience function that
translates the Arduino pin to the required interrupt number; and that
function is:
digitalPinToInterrupt( <Arduino pin> ).
It is a bit cart-before-the-horse since you have to know which pin
uses an interrupt before you can decide to use that pin as an interrupt
pin! So really you know the interrupt number because you had to look up
the pin functionality anyway. This function is really just a convenience
function.
If you wanted to use INT1 on an Arduino Uno triggering it to execute
myIsr() - on the rising edge of the input signal - then you would write:
So the above means Interrupt 1. It looks like there was confusion
over that parameter being interpreted as a pin number hence the functiondigitalPinToInterrupt(3) which stops that mistake so it's a good idea to use it.
Arduino Interrupt MODEs
There are three modes for an interrupt (the last argument above):
The interrupt modes that can be specified when using attachInterrupt() on Arduino are:
- CHANGE - This will trigger the interrupt on any change of state on the input pin, either from LOW to HIGH or HIGH to LOW.
- RISING - The interrupt will trigger on a rising (LOW to HIGH) edge event on the input pin.
- FALLING - The interrupt will trigger on a falling (HIGH to LOW) edge event on the input pin.
So in summary:
- CHANGE - Triggers on any state change
- RISING - Triggers on rising (LOW to HIGH) edge
- FALLING - Triggers on falling (HIGH to LOW) edge
The interrupt mode determines exactly what type of transition or edge on
the input pin will cause the interrupt handler function to execute.
This allows triggering interrupts on specific events.
RISING and FALLING modes
The two modes RISING and FALLING are self explanatory.
The CHANGE mode
The CHANGE mode in attachInterrupt() is useful for triggering an
interrupt handler function on any transition (change of state) occurring
on the input pin.
How it works:
- The Arduino keeps track of the previous state of the input pin - whether it was last seen as HIGH or LOW.
- During each pass of the Arduino main loop, it checks the current state of the pin and compares it to the previous state.
- If the current and previous states are different, that signifies a change or transition has occurred on the pin.
- For example, if the pin was previously LOW but is now reading HIGH, that's a LOW to HIGH transition (rising edge).
- Or if it was previously HIGH but is now LOW, that's a HIGH to LOW transition (falling edge).
- Whenever a change of state is detected, it will trigger the interrupt handler function attached using attachInterrupt().
- The previous state is then updated to match the current pin state for the next comparison.
So in summary, the CHANGE mode essentially checks for either a rising or
falling edge. It will trigger the interrupt routine on any transition
between the HIGH and LOW states.
This is useful for situations where you need to detect either type of
transition, without distinguishing between rising and falling
specifically. The handler will execute anytime the pin changes value.
Stopping an external interrupt
You won't often need it but if you want to stop the interrupt action then use the function:
Again there are two functions detachInterrupt() and digitalPinToInterrupt() used as shown above.
Note: The opposite to attachInterrupt is: detachInterrupt(<INTn>).
This will remove the interrupt function from the related interrupt pin "INTPIN".
How Many Arduino Interrupts are there?
...On the Arduino Uno there only two external interrupt pins:
pin 2 - interrupt 0.
pin 3 - interrupt 1.
Do you think this is a true statement?: "There only two external interrupts"
See if you know the right answer:
How many external Interrupts are available on an Arduino Uno?
2
5
26
32
2, 5, 26, or 32? (click to find out)
Surprisingly there are 26!
There are the 2 external
interrupts (INT0, INT1) plus three sets of 8 pin change interrupts.
Arduino Interrupt Overview
Microcontrollers are very good at repeatedly doing a task by
executing one instruction after another. They do this by
incrementing the program counter and fetching the next instruction from
memory. The problem comes when the processor has to deal with unexpected
events.
For an external event you need the program to branch off and do
something without waiting around for the current task to complete. The
mechanism to do this is the interrupt.
Consider a program waiting for serial port data. it is in a polling
loop constantly reading the serial port register waiting for data.
However, you want the program to react to a button press to start
another activity.
While polling the serial port, your program does nothing else and
this is where interrupts come in. [Yes I know the serial port has its own
interrupt - give me a break - its just an example for illustration ok - lets assume it does not have an interrupt].
An external interrupt will temporarily divert the processor from its
current operation to a so-called ISR (Interrupt Service Routine) which
will perform the operation you need. Not only that, it will execute the
ISR virtually instantly providing an easy way to respond quickly to
events.
Arduino interrupt vectors
When an interrupt is triggered your program
has to do something. You tell the Arduino what to do by providing a
function that services the interrupt (an ISR or Interrupt Service
Routine).
The address of the ISR is placed into a specific location in a table
of interrupt vectors. Each location in that table is specific to each
interrupt. When the interrupt triggers, your ISR is called.
There are 6 dedicated external interrupts. These are the first six entries in the interrupt vector table:
Address
Vector Name
0
RESET
1
INT0
2
INT1
3
PCINT0
4
PCINT1
5
PCINT2
Two, you already know about: INT0 and INT1 but there is also the reset
vector which sits at address zero, so when the program counter is set
to its power-up state (0x0000) the code at the reset vector is executed -
you place a jump to your reset handing routine at this vector address. PCINT0 ~ 2 are the
pin interrupt change vectors.
The Arduino Uno has a total of 26 interrupt vectors, and most of these
are internal interrupts that are triggered by
internal peripherals such as the ADC, timers or UARTs etc.
The total number of interrupt sources (any single external signal or internal module that can cause an interrupt to trigger) is:
26-3+3*8 = 47
[ This includes internal module interrupts and 3x8 pin change signals. ]
Arduino Interrupt Call Stack
For normal function calls, a stack is used to save and restore the
Program Counter (PC). The PC contains the address of the current
instruction.
When a subroutine is called, the value of the PC is pushed onto the
stack, and when the subroutine is finished, the value is popped back
from the stack. Thus returning the PC to its original address before the
function call. The same process is used for an ISR subroutine.
For a subroutine call, the assembler instruction CALL pushes the PC
onto the stack. To return from the call, RET pops the PC from the stack.
The only difference between a subroutine return and an ISR return is
that the ISR return uses the RETI instruction, which re-enables
interrupts.
ISR operation
So an ISR is virtually identical to a normal subroutine call. The
problem is that an ISR can be executed at any time corrupting any
existing register values.
To allow the ISR to work current register values must be stored at
the beginning and restored at the end. Fortunately this is taken care of
for you within the
compiler. All relevant flags and registers are saved before, and then restored after an interrupt.
In this way the current program is
interrupted in the middle of its operation and does not know anything about the
interrupt.
Hardware connection
Connect a push button on one side to Arduino pin D2 and on the other side to Ground.
Connect a 100nF capacitor from Arduino pin D2 to Ground. To explore
interrupts and switch debouncing and why this works see the page on switch debouncing.
Examples Showing Arduino Interrupt Operation
Software
Arduino Ide: 1.8.9+
Libraries
None used.
Example Sketch 1
Arduino Interrupt Hardware setup
Connect a push button from pin D2 to ground.
Arduino Interrupt Code Description
To illustrate the concept, consider the simple program below,
all it does is flash an led on and off at 500ms intervals and then
repeats. If the button is pressed causing an interrupt, then the delay time is toggled to 50ms.
The delay function shows that the interrupt is
operating - even while most of the time the processor is sitting within
the delay function doing nothing.
Adding interrupt code allows the program to react instantly and
change the delay time for the LED flash (delayTime is updated
immediately in the interrupt) however the flash rate of the LED will not
update immediately since the delay operation must finish first. The
second example below,
shows how to avoid this using arduino millis.
Connect a push button from pin D2 to ground (the same as sketch 1).
Arduino Interrupt Code Description
This is a more realistic example, where the main loop reacts more
quickly to the information sent from the interrupt. Instead of using the delay function (which is a do nothing operation) the millis() function is used. This allows the processor to do other useful work; In this case executing the loop function more quickly.
Note: Because we use millis() in this code it is also used in the ISR
to filter out too much bouncing i.e. if we get 2 or more interrupts
within 300ms the routine only acts on one interrupt.
TIP: See the page on switch debouncing to see how much the capacitor filters out switch bounces.
The Arduino volatile command is only required if a variable can be changed outside normal program flow.
You can see examples of volatile in the Sketches above.
In a normal function, variables are input to a function as
parameters. These are then used within the function and can be returned
as a value from the function.
In this case the compiler knows all the operations that can happen on
that variable and when they happen. It is totally defined by the code
written within the function. Therefore the compiler can make assumptions
about the variable to optimise operations.
For a function that receives information from an interrupt routine,
communicated via a global variable, the interrupt can change the value
of that variable at any time even while the function is operating!
Compilers do fancy optimisations behind the scenes and the Arduino
volatile keyword tells the compiler to "leave this variable alone" and
not optimise it. The variable must be left available for both the
function and the ISR to read and update. It tells the compiler to re-read the
value and not read the cached version, since the variable may have been updated by the interrupt.
PCINT Arduino Interrupt
These are the pin change interrupts associated with the each port on
the Arduino. So there are 3 ports each with 8 pins that can be detected
for interrupt use. These are labelled PCINT0..23.
TIP: PCINT0..23 can be used to wake the part from sleep mode (other than idle).
Interrupts are PCINT2..0 registers. To use them you have to use
hardware register names ( read-the-datasheet ) i.e. its not quite as
easy as the normal Arduino interrupt method.
Warning: Warning: PCINT0..23 will trigger an interrupt even if pins are outputs.
How to use PCINT
Hardware setup
Connect a push button from pin A0 to ground.
Connect a push button from pin A4 to ground.
Connect a sounder from A3 to ground (piezo disc : optional).
How to Setup Pin Change Interrupts
Since these interrupts are simpler than INT0 and INT1, they only
trigger on a pin change. They also trigger only for blocks of 8 bits and
there is no indication on which pin caused the interrupt. The
three problems are:
A single interrupt triggers for any of 8 pins.
No indication of which pin triggered the interrupt.
These interrupts only trigger on a pin change.
The first thing to do is use a mask register to enable or disable
individual pins in an 8 bit block. Then only these will cause an
interrupt to trigger. You don't want outputs triggering the interrupt -
they can do that and could be used as a software interrupt mechanism.
The second thing to do is to record the port value before (and after)
interrupt operations. That way you can use an XOR expression to find
out the pin changes.
Lets get some interrupts from port C specifically PC4 and PC0 which
are on PCINT8 and PCINT12 respectively. The mask register for PORT C is
PCMSK1.
Code Description
The serial port shows some information as the buttons are pushed.
Pushing the A0 button will set the LED flash rate to 500ms and pushing
the A4 button will set the flash rate to 50ms.
Loop code
In the loop() code the LEDs are flashed at the rate set in delayTime
(changed in the ISR). if an interrupt is detected (flgTone is set) then the number of interrupts is printed to the serial
port. If there were more than one interrupt (intsFound ) a high frequency sound is
made otherwise a low frequency sound is made.
Both flgTone and intsFound are reset to zero ready for the next button push.
Warning: Since any change on the pin is detected
as an interrupt both falling and rising edges will cause an interrupt but
switch bounce also causes many more interrupts.
Due to switch bounce there can be many more than one bounce for each button press. See here for how to eliminate switch bounce in interrupt routines.
ISR code
In the ISR the old port value is XORed with the new value (any
different bits show up as ones). v1 and v2 are given the old values of
the port.
For the following code; If a change in A0 was detected, and v1 was zero then the delay time is set to 500ms.
if (v1==0 && change & (1<<PCINT8) ) delayTime = 500;
A similar process is done for A4.
If any rising edge is found then flgTone is incremented.
if (change && (v1==0 || v2==0) ) flgTone++; // rising edge only
The oldPorta value is updated and the number of interrupt detection's (intsFound) is incremented.
Note: PORTC bit 7 (PCINT15) is not available [ATMega328p datasheet].
Also PC6 is the reset pin - unless programmed by fuses as an I/O pin.
PCINT Pin Change Interrupts
PCINT0 for PORTB (Input using PINB) - compiler vector is PCINT0_vect.
PCINT1 for PORTC (Input using PINC) - compiler vector is PCINT1_vect.
PCINT2 for PORTD (Input using PIND) - compiler vector is PCINT2_vect.
PCIFR Interrupt Flag Register
Flag bit is set when an interrupt is triggered (useful for polling).
Bits: PCIF[2..0] : Interrupt Flag.
Written by John Main who has a degree in Electronic Engineering.
Note: Parts of this page were written using claude-instant as a research assistant.
How to get accurate DHT22 digital humidity sensor readings with an Arduino. Did you know it also measures temperature as Well? Find out why, in this page...
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.