20

I am looking at using a 7 channel RC receiver with the Arduino Uno R3. In the documentation, there are mentions of a maximum of 2 interrupt pins, whereas on certain other blogs I have seen mentions of using upto 20 pins as interrupts, with PinChangeInt library. So, how many interrupts can the Arduino handle natively? And is this different from how many can be handled with software support such as PinChangeInt?

2 Answers 2

18

There are two types of "pin change" type interrupts. The external interrupts, of which there are two on the Uno. They are called 0 and 1, however they refer to digital pins 2 and 3 on the board. These can be configured to detect rising, falling, change (rising or falling) or LOW.

In addition to that are "pin change" interrupts, which detect a change to the pin state in any of the 20 pins (A0 to A5, and D0 to D13). These pin change interrupts are also hardware based so, in themselves, will be as fast as the external interrupts.

Both types are slightly fiddly to use at the register level, but the standard IDE includes attachInterrupt(n) and detachInterrupt(n) which simplifies the interface to external interrupts. You can also use the Pin Change Library to simplify the pin change interrupts.

However, steering clear of the library for a minute, we can establish that pin change interrupts can be as fast, or faster, than external interrupts. For one thing, although pin change interrupts work on batches of pins, you don't have to enable the whole batch. For example, if you want to detect changes on pin D4, this will suffice:

Example sketch:

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 if (PIND & bit (4))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of PCINT2_vect

void setup ()
  { 
  // pin change interrupt (example for D4)
  PCMSK2 |= bit (PCINT20);  // want pin 4
  PCIFR  |= bit (PCIF2);    // clear any outstanding interrupts
  PCICR  |= bit (PCIE2);    // enable pin change interrupts for D0 to D7
  pinMode (4, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

My testing indicates that it took 1.6 µs for the "test" pin (pin 5) to react to a change on the interrupt pin (pin 4).


Now if you take the simple (lazy?) approach and use attachInterrupt() you will find the results are slower, not faster.

Example code:

void myInterrupt ()
 {
 if (PIND & bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of myInterrupt

void setup ()
  { 
  attachInterrupt (0, myInterrupt, CHANGE);
  pinMode (2, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

This takes 3.7 µs to change the test pin, a lot more than the 1.6 µs above. Why? Because the code the compiler has to generate for the "generic" interrupt handler has to save every conceivable register (push them) on entry to the ISR, and then restore them (pop them) before returning. Plus there is the overhead of another function call.


Now we can work around that by avoiding attachInterrupt() and doing it ourselves:

ISR (INT0_vect)
 {
 if (PIND & bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &= ~bit (5); // turn off D5
 }  // end of INT0_vect

void setup ()
  { 
  // activate external interrupt 0

  EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
  EICRA |=  bit (ISC00);    // set wanted flags (any change interrupt)
  EIFR   =  bit (INTF0);    // clear flag for interrupt 0
  EIMSK |=  bit (INT0);     // enable it

  pinMode (2, INPUT_PULLUP);
  pinMode (5, OUTPUT);
  }  // end of setup

void loop ()
  {
  }

That is the fastest of them all at 1.52 µs - it looks like one clock cycle got saved somewhere.


There is one caveat though, for pin-change interrupts. They are batched, so if you want to have interrupts on lots of pins, you need to test inside the interrupt which one changed. You could do that by saving the previous pin status, and comparing it to the new pin status. This is not necessarily particularly slow, but the more pins you need to check, the slower it would be.

The batches are:

  • A0 to A5
  • D0 to D7
  • D8 to D13

If you just want a couple more interrupt pins, you could avoid any testing by just choosing to use pins from different batches (eg. D4 and D8).


More details at http://www.gammon.com.au/interrupts

8

There are two types of interrupts. What the Arduino Playground said:

The processor at the heart of any Arduino has two different kinds of interrupts: “external”, and “pin change”. There are only two external interrupt pins on the ATmega168/328 (ie, in the Arduino Uno/Nano/Duemilanove), INT0 and INT1, and they are mapped to Arduino pins 2 and 3. These interrupts can be set to trigger on RISING or FALLING signal edges, or on low level. The triggers are interpreted by hardware, and the interrupt is very fast. The Arduino Mega has a few more external interrupt pins available.

On the other hand the pin change interrupts can be enabled on many more pins. For ATmega168/328-based Arduinos, they can be enabled on any or all 20 of the Arduino's signal pins; on the ATmega-based Arduinos they can be enabled on 24 pins. They are triggered equally on RISING or FALLING signal edges, so it is up to the interrupt code to set the proper pins to receive interrupts, to determine what happened (which pin? ...did the signal rise, or fall?), and to handle it properly. Furthermore, the pin change interrupts are grouped into 3 “port”s on the MCU, so there are only 3 interrupt vectors (subroutines) for the entire body of pins. This makes the job of resolving the action on a single interrupt even more complicated.

Basically, the external interrupts are extremely fast since they're all hardware based. However, there is also the pin change interrupts, but those seem to be much slower because they are mostly software based.

tl;dr: the 20 interrupt pins together are much slower. The 2 interrupt pins are the fastest and most efficient.


EDIT: I just looked at the datasheet and it says that a pin change interrupt is triggered for any of the pins selected with no indication of which pin has changed (although it is split into three interrupt vectors).

  • For external interrupts, it'll tell you pin 3 just changed
  • For pin change it'll tell you a pin changed that you were monitoring!

As you can see, a pin change interrupt adds a lot of overhead into the ISR that you have to handle by storing previous states and seeing if it's the pin you're worried about. It might be fine for a sleep state, but it's better to use the external interrupts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.