Language: EN

interrupciones-en-todos-los-pines-de-arduino-con-pcint

Interrupts on all Arduino pins with PCINT

In this post, we will see how to use Pin Change interrupts on Arduino. This, for example, will allow us to have interrupts on all pins on boards based on the Atmega328P.

But wait a minute, Sacrilege, Arduinos only have 2 interrupt pins! Well, the story is not exactly like that.

In this post, we will see what Pin Change interrupts (PCINT) are and how they work, which are different from the standard interrupts (INT) that we are used to.

Of course, we will also see some code examples. However, we will usually use a library to manage the PCINT. We will see this more practical approach at the end of the article, and you will see that using Pin Change interrupts is very simple.

What are PCINTs

Processors like Atmel have different types of interrupts both internal and external. In our case, we are interested in external interrupts, i.e., those triggered by changing the state of one of the pins.

We have two types of external interrupts:

  • INT, hardware external interrupts.
  • PCINT, Pin Change interrupts.

Usually, when we talk about interrupts, we refer to the external interrupts of type INT, which we already 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), which operate similarly, but act on a much higher number of processor pins.

Logically, not everything was going to be so nice and PCINTs also have some disadvantages compared to the usual INTs. But nothing that we cannot overcome or prevent them from being used.

First, unlike the INT interrupts that act on a single pin, the PCINTs act on a group of pins simultaneously (usually on a port).

If we have a single pin associated with each PCINT, we can simply deduce that it has acted on this pin. But, in general, we will have more than one pin and we will have to make a subsequent query to a register to know the pin on which it has acted.

Secondly, unlike the INT interrupts that allow configuring the triggering of CHANGE, FALLING, RISING, LOW, and HIGH, the INT interrupts only distinguish CHANGE events.

If we want to detect rising or falling edges, we must save the state of the register in a variable and perform the comparison with the previous state in the ISR.

Finally, for the aforementioned reasons, they are slightly slower than the INT interrupts. But in general, it is not something we should worry about; it is 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 go step by step through the process, using the Atmega 328p as a reference, as it is the most used in Arduino Uno and Nano. Although later we will see how to extrapolate it to other Atmel processors.

Enable or disable PCINTs

First, we can enable or disable the PCINTs associated with a group of pins with the PCICR (Pin Change Interrupt Control Register) register.

Here we have 3 bits, which control the activation or deactivation of the PCINTs for each group of pins. PCICR

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
PCIE2PCIE1PCIE0

Enable or disable for a pin

Once the PCINT is enabled for a group of pins, we must specify which pins of the group can trigger the interrupt. For that, we have the PCMSK0, PCMSK1, and PCMSK2 (Pin Change Mask) registers, in which each bit indicates if the pin triggers the PCINT or not. PCMSK0

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
PCINT7PCINT6PCINT5PCINT4PCINT3PCINT2PCINT1

PCMSK1

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
PCINT14PCINT13PCINT12PCINT11PCINT10PCINT9PCINT8

PCMSK2

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
PCINT23PCINT22PCINT21PCINT20PCINT19PCINT18PCINT17PCINT16

Clear the flag register

On the other hand, we have the PCIFR (Pin Change Interrupt Flag Register) register. The bits of this register are activated each time a change occurs on a pin of the group. PCIFR

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
PCIF2PCIF1PCIF0

To reset it, we have to set a ‘1’ in the corresponding register. The flags are reset automatically when the associated ISR is triggered.

Define the ISRs

Finally, in the code, we have to associate the ISRs that we want to use. So, in the case of the Atmega 328p, we have the functions

  • ISR (PCINT0_vect) for pin group D8 to D13
  • ISR (PCINT1_vect) for pin group A0 to A5
  • ISR (PCINT2_vect) for pin group D0 to D7

These ISRs are associated, respectively, with each of the indicated groups.

Operation of PCINT

We now have all the components to explain the operation of the Pin Change interrupts. In summary, when a pin change is triggered in one of the groups, the corresponding flag is activated in PCIFR.

If this group is enabled in the PCICR, the pin that originated the trigger is enabled in its PCMSKx, and the group has its appropriate ISR defined in the code, the ISR is triggered.

After the execution of the ISR, the PCIFR flag register is cleared, leaving the system ready to receive another pin change event.

PCINT code example

Let’s put all of the above together in a code with a simple example of using Pin Change interrupts. For now, we will continue using the Atmega389p 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 of 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 enabled the three ISRs, but we do not distinguish in which pin has triggered, nor the type of event. You have a complete example with the management at this link.

The resulting code is, let’s say, not very intuitive. Fortunately, the community has developed several libraries that save us the trouble 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 the 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.

Atmega 128/328p (Arduino Uno and Nano)

PinPortINTArduino Pin
2PD2INT02
3PD3INT13
PinPortPCINTPinPortPCINTPinPortPCINT
2PD2PCINT188PB0PCINT0A0PC0PCINT8
3PD3PCINT199PB1PCINT1A1PC1PCINT9
4PD4PCINT2010PB2PCINT2A2PC2PCINT10
5PD5PCINT2111PB3PCINT3A3PC3PCINT11
6PD6PCINT2212PB4PCINT4A4PC4PCINT12
7PD7PCINT2313PB5PCINT5A5PC5PCINT13

Atmega 32u4 (Arduino Leonardo and Micro)

PinPortINT
0PD2INT2
1PD3INT3
2PD1INT1
3PD0INT0
7PE6INT6
PinPortPCINT
SCK/15PB1PCINT1
MOSI/16PB2PCINT2
MISO/14PB3PCINT3
8/A8PB4PCINT4
9/A9PB5PCINT5
10/A10PB6PCINT6
11PB7PCINT7

Atmega2560 (Arduino Mega)

PinPortINTArduino Pin
2PE4INT46
3PE5INT57
21PD0INT043
20PD1INT144
19PD2INT245
18PD3INT346
n/cPE6INT68 (fake 75)
n/cPE7INT79 (fake 76)
PinPortPCINTPinPortPCINTPinPortPCINT
10PB4PCINT4SSPCINT0PB0A8PK0PCINT16
11PB5PCINT5SCKPCINT1PB1A9PK1PCINT17
12PB6PCINT6MOSIPCINT2PB2A10PK2PCINT18
13PB7PCINT7MISOPCINT3PB3A11PK3PCINT19
14PJ1PCINT10A12PK4PCINT20
15PJ0PCINT9A13PK5PCINT21
A14PK6PCINT22
A15PK7PCINT23

We can adapt our code for the different processors or, much better, use a library that takes care of it and avoids the headaches as we will see next.

For more information, consult the Datasheet of the processor

PCINT in a library

As we said, there are many libraries to manage the Pin Change interrupts available in the library manager. Some examples are Sodaq_PcInt, PinChangeInterrupt, EnableInterrupt, PciManager.

Personally, I like the library YetAnotherArduinoPcIntLibrary 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 is a wonderful library and makes using Pin Change interrupts as comfortable as a normal INT. And here we have 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 can’t be more comfortable! That’s 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. github-full