Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
:
:
Don't worry -- youre-mail address is totally secure. I promise to use it only to send you MicroZine.

Arduino Interrupt

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).

On the software side (for Arduino code) there are two functions you need to use:

  1.     attachInterrupt( <interrupt>,my_isr_function, mode ).
  2.     digitalPinToInterrupt( INTPIN ).

Since different Arduino boards have interrupt capability on different pins, you should use the function digitalPinToInterrupt( INTPIN ) as follows:

    #define INTPIN 3

    attachInterrupt( digitalPinToInterrupt( INTPIN ), my_isr_function, FALLING );

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: The opposite function for detatching an interrupt is:
   detachInterrupt( digitalPinToInterrupt( 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 an interrupt - give me a break - its just an example for illustration ok].

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.

// By John Main © best-microcontroller-projects.com
// Simple LED flash and button interrupt using delay().
#define LED LED_BUILTIN
#define interruptPin 2  // Can only be pin 2 or 3 on the Uno.

volatile uint16_t delayTime= 500;

//////////////////////////////////////////////
void setup(void) {
  pinMode(LED,OUTPUT);
  pinMode(interruptPin,INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(interruptPin), myisr, RISING);
}

//////////////////////////////////////////////
// Arduino Delay LED flash.
void loop(){

  delay(delayTime);
  digitalWrite(LED,HIGH);
  delay(delayTime);
  digitalWrite(LED,LOW);
}

//////////////////////////////////////////////
// Interrupt Service Routine
// Do not use this ISR in 'real' code because
// of switch bouncing see this page for useful
// code:
// https://www.best-microcontroller-projects.com/easy_switch_debounce.html
//
void myisr() {
static byte state = 1;
 
state = !state; if (state) delayTime = 500; else delayTime = 50; }

[File:flash_led_on_interrupt_button.ino]


Example Sketch 2

Arduino Interrupt Hardware setup

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.

// By John Main © best-microcontroller-projects.com
// Simple LED flash and button interrupt using millis().
#define LED LED_BUILTIN
#define interruptPin 2  // Can only be pin 2 or 3 on the Uno.

volatile uint16_t delayTime= 500;

uint32_t timeWas = 0;

//////////////////////////////////////////////
void setup(void) {
  pinMode(LED,OUTPUT);
  pinMode(interruptPin,INPUT_PULLUP);
  digitalWrite(LED,LOW);
  Serial.begin(115200);
  attachInterrupt(digitalPinToInterrupt(interruptPin), myisr, RISING);
}

//////////////////////////////////////////////
// Arduino millis delay LED flash.
void loop(){
  static byte LEDstate=0;

  if (millis()-timeWas>delayTime) {
    timeWas = millis();
    LEDstate = LEDstate ? 0 : 1;
    if (LEDstate) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);
  }
}

//////////////////////////////////////////////
// Interrupt Service Routine
void myisr() {
static byte state = 1;
static uint32_t lastButtonTime=0;

  // Filter out too quick buttons = errors.
  if (millis()-lastButtonTime > 300) {
    state = !state;
    if (state) delayTime = 500; else delayTime = 50;
    lastButtonTime = millis();
  }
}

[File:flash_led_on_interrupt_button_millis.ino]

Arduino - When To Use Volatile

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:

  1. A single interrupt triggers for any of 8 pins.
  2. No indication of which pin triggered the interrupt.
  3. 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 if interrupt detections (intsFound) is incremented.

// By John Main © best-microcontroller-projects.com
// Using Pin Change interrupts.
const byte LED = LED_BUILTIN;
const byte TONE_PIN = A3;
const byte PC0_pin = A0;
const byte PC4_pin = A4;
volatile byte state = LOW;

volatile static byte oldPorta;
volatile static byte PCMask;
volatile uint16_t delayTime= 500;
volatile byte flgTone = 0,intsFound = 0;

void setup() {

  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  pinMode(PC0_pin, INPUT_PULLUP);
  pinMode(PC4_pin, INPUT_PULLUP);

  oldPorta = PINC;
  PCMSK1 |= (1<<PCINT8);
  PCMSK1 |= (1<<PCINT12);
  Serial.println(PCMSK1,HEX); // What is the total mask.
  PCMask = PCMSK1;

  PCICR |= (1<<PCIE1); // Enable PCINT on portc.
}

//////////////////////////////////////////////
// Arduino Delay LED flash.
void loop(){

  delay(delayTime);
  digitalWrite(LED,HIGH);
  delay(delayTime);
  digitalWrite(LED,LOW);

  if(flgTone) {
    Serial.print("Found ");Serial.print(intsFound);
    Serial.print(" interrupts, using: ");Serial.println(flgTone);

    if(intsFound>1)
       tone(TONE_PIN,600,100);
    else
       tone(TONE_PIN,300,100);

    intsFound = flgTone = 0;
  }
}

ISR( PCINT1_vect ) {
  byte change,v1,v2;
  change = oldPorta ^ PINC;

  v1 = oldPorta & (1<<PCINT8);
  v2 = oldPorta & (1<<PCINT12);

  // v1==0 previously so detecting rising edge.
  if (v1==0 && change & (1<<PCINT8) ) delayTime = 500;
  if (v2==0 && change & (1<<PCINT12)) delayTime = 50;
  if (change && (v1==0 || v2==0) ) {
    intsFound++; // Only count rising edge interrupts (as this interrupt reacts to both).
    flgTone = 1; // rising edge only
  }

  oldPorta = PINC;
}

[File:arduino-pinchange-interrupt.ino]


Warning: Button presses will cause multiple bounces. To properly debounce the button inputs on interrupt pins see this page.

Pin Change Registers

PCICR Interrupt Control Register

Control which bank of 8 interrupts is active (enabled).

Bits: PCIE[2..0] : Interrupt Enable for PORT B(0), C(1), D(2).

PCMSK Interrupt Mask Register[2..0]

Enable individual pins to interrupt with the mask register.

PCMSK2 : Contains PCINT[23..16] - PORTD[7..0].
PCMSK1 : Contains PCINT[14..8] - PORTC[6..0].
PCMSK1 : Contains PCINT[7..0] - PORTB[7..0].

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.





New! Comments

Have your say about what you just read! Leave me a comment in the box below.




Privacy Policy | Contact | About Me

Site Map | Terms of Use



Visit our Facebook Page:
To Visit Click Here


Recent Articles

  1. Arduino EEPROM - how it works and how to use it - with examples.

    Arduino EEPROM: How to use it and How to presrve the life of EEPROM. Two examples sketches to save multiple values to EEPROM.

    Read more

  2. How to use the ADS1115

    A tutorial on using the ADS1115 precision 16 bit ADC for low power use.

    Read more

  3. The TP4056: Lithium Ion/polymer Battery Charger IC

    Learn how to use the TP4056 properly. There's a right, and a wrong way, to use it to safely charge Lithium Ion batteries.

    Read more

  4. DW01A Battery Protector IC

    The DW01A chip is a Lithium Ion battery protector commonly used on TP4056 boards.  Find out Exactly how it works and how to use it the correct way.

    Read more

  5. Arduino String: How to read commands from the serial port.

    For Arduino string operations you can use Object Class Strings or C style strings but which should you use? Also find out how to decode commands and control variables in your programs using strings.

    Read more

  6. A Real Time Clock design (DS1307) with a PIC microcontroller

    Real Time Clock Design (FREE): A Free and Complete RTC design using the DS1307 and a PIC micro (16F88) also re-targetable. This PIC project uses an I2C Clock chip and 7-segment display to create a fou…

    Read more

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's

SUPERB and FANTASTIC."

- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan

bursach<at>gmail.com<

"Awesome site,
very, very easy and nice
to navigate!"


- Matt
matt_tr<at>
wolf359.cjb.net


Learn Microcontrollers

"Interested in
Microcontrollers?"

Sign up for The
Free 7 day guide:

FREE GUIDE : CLICK HERE


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."


- Dave

de_scott<at>bellsouth.net

"Your site is a great
and perfect work.
congratulations."


- Suresh

integratredinfosys<at>
yahoo.com

"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.
Regards."


- Anon

Back to Top