Arduino Interrupt: There are more than Two external interrupts! It's true; it is a little known fact that there are more
than 2 external interrupts (available on virtually any I/O pin) but how do you use them? Find out here.
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 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)
Setting up an external interrupt
Why do we need two functions to setup an external interrupt?
There are two aspects to an external interrupt:
Which physical Arduino pin is in use.
Which software interrupt are we talking about. For the Arduino Uno it us INT0 or INT1.
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).
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
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
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.
On the software side (for Arduino code) there are two setup functions you need to use:
This would set an interrupt on pin3 (INT1) to call "my_isr_function"
on the falling edge of the digital input signal. Note: mode can be LOW,
CHANGE, RISING or FALLING.
Note that "my_isr_function" is simply a standard Arduino function
that you write - you can call it anything you like but it is best to
indicate it is an interrupt routine in the function name (for your own
Note: Physical Arduino Uno pin 3 has INT1, and physical Arduino pin 2 has INT0.
Stopping an external interrupt
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 function for detatching an interrupt is: detachInterrupt().
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, 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
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
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
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
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:
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
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
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
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
Arduino Ide: 1.8.9+
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
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
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.
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.
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
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 if interrupt detections (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).