In this post, we are going to see how to use Pin Change Interrupts in Arduino. This, for example, will allow us to have interrupts on all pins on boards based on the Atmega328P.
But wait a moment! Sacrilege, Arduinos only have 2 interrupt pins! Well, the story isn’t exactly like that.
In this post, we will see what Pin Change Interrupts (PCINT) are and how they work, which are different from the normal interrupts (INT) we are used to.
Of course, we will also see some code examples. However, we will normally use a library to manage PCINTs. This more practical view will be seen at the end of the article, and you will see that using Pin Change Interrupts is very simple.
What are PCINTs
Processors like Atmel ones have different types of interrupts, both internal and external. In our case, we are interested in external interrupts, i.e., those triggered by a change in the state of one of the pins.
We have two types of external interrupts:
- INT, external hardware interrupts.
- PCINT, pin change interrupts (Pin Change INTerrupt).
Normally, when we talk about interrupts, we refer to the external INT type interrupts, which we saw in this post. And it’s true that we have a very limited number of pins with INT interrupts.
Much less known are the pin change interrupts (PCINT), whose mode of operation is similar, but they act on a much larger number of processor pins.
Logically, not everything could be so wonderful, and PCINTs also have some disadvantages compared to the usual INTs. But nothing we can’t overcome or that prevents us from using them.
First, unlike INT interrupts that act on a single pin, PCINTs act on a group of pins simultaneously (usually on a port).
If we have only one pin associated with each PCINT, we can deduce without further ado that it has acted on this pin. But, in general, we will have more than one pin and we will have to subsequently query a register to know which pin triggered it.
Secondly, unlike INT interrupts that allow configuring the trigger as CHANGE, FALLING, RISING, LOW, and HIGH, PCINT interrupts only distinguish CHANGE events.
If we want to detect rising or falling edges, we must save the register state in a variable and perform the comparison with the previous state in the ISR.
Finally, for the reasons above, they are slightly slower than INT interrupts. But in general, this is not something we should worry about; it’s an irrelevant difference except in very extreme cases.
How to use PCINTs
There are several registers involved in the activation and use of pin change interrupts. Let’s see the process step by step, using the Atmega 328p as a reference because it is the most used in Arduino Uno and Nano. Although later we will see how to extrapolate it to other Atmel processors.
PCINT Code Example
Let’s put everything above together in a code with a simple example on the use of pin change interrupts. For now, we will continue using the Atmega328p as a reference.
The following example shows how to activate the three available ISRs for the three groups and how to associate them with certain pins in each group.
// Enable PCINT on a PIN
void pciSetup(byte pin)
{
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // activate pin in PCMSK
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear interrupt flag in PCIFR
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group in PCICR
}
// Define ISR for each port
ISR (PCINT0_vect)
{
// manage for PCINT for D8 to D13
}
ISR (PCINT1_vect)
{
// manage PCINT for A0 to A5
}
ISR (PCINT2_vect)
{
// manage PCINT for D0 to D7
}
void setup()
{
// Enable PCINT for different pins
pciSetup(7);
pciSetup(8);
pciSetup(9);
pciSetup(A0);
}
void loop()
{
}
In this example, we have only activated the three ISRs, but we are not distinguishing which pin triggered it, nor the type of event. You have a complete example with management in this link.
The resulting code is, let’s say, not very intuitive. Fortunately, the community has developed several libraries that save us the work of having to handle this code ourselves. We will see them at the end of the post.
PCINT on Other Processors
In the examples, we have used the Atmega 328p processor, but what about other Atmega models? Well, in general it is very similar, but each one has its own pin definition.
Below, you have tables with the INTs and PCINTs of some of the most common Atmega processors.
For more information, consult the processor Datasheet
PCINT in a Library
As we said, there are many libraries for managing pin change interrupts available in the library manager. Some examples are Sodaq_PcInt, PinChangeInterrupt, EnableInterrupt, PciManager.
Personally, I like the YetAnotherArduinoPcIntLibrary library because it is easy to use, the code is small and efficient, and it is well written. Also, it distinguishes between RISING/FALLING/CHANGE modes and allows passing variables to the ISR callback functions.
The truth is that it’s a wonderful library and makes using pin change interrupts as comfortable as a normal INT. And here is an example of how to use the library.
#define PCINT_PIN A5
#include <YetAnotherPcInt.h>
void pinChanged(const char* message, bool pinstate) {
Serial.print(message);
Serial.println(pinstate ? "HIGH" : "LOW");
}
void setup() {
Serial.begin(115200);
pinMode(PCINT_PIN, INPUT_PULLUP);
PcInt::attachInterrupt(PCINT_PIN, pinChanged, "Pin has changed to ", CHANGE);
}
void loop() {}
It couldn’t be more comfortable! This is how easy it is to use pin change interrupts in our projects, which allows, in the case of the Atmega 328p, to have interrupts on all pins.
Download the Code
All the code from this post is available for download on Github.

