How many interrupt pins can an Uno handle?
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 &amp; bit (4))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &amp;= ~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 &amp; bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &amp;= ~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 &amp; bit (2))  // if it was high
   PORTD |= bit (5);  // turn on D5
 else
   PORTD &amp;= ~bit (5); // turn off D5
 }  // end of INT0_vect

void setup ()
  { 
  // activate external interrupt 0

  EICRA &amp;= ~(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