In this tutorial, we’ll discuss Arduino PCINT (Pin Change Interrupts) from the fundamental concepts all the way to implementing interrupt-based systems. We’ll start off by discussing what are interrupts, and how they work.
We’ll create a couple of Arduino Pin Change Interrupt Example Projects in this tutorial to practice what we’ll learn all the way through. Without further ado, let’s get right into it!
Table of Contents
- How Interrupts Work?
- Arduino PCINT (Pin Change Interrupts)
- Arduino Pin Change Interrupts Library
- Arduino PCINT (Pin Change Interrupts) Code
- Arduino PCINT Example
- Arduino Pin Change Interrupt Speed
- Arduino PCINT Wrap Up
How Interrupts Work?
The interrupt handling mechanism in Arduino (Atmega328p) microcontroller is illustrated in the figure below. And it goes like this:
When the microcontroller’s CPU receives an interrupt signal, it pauses the main program execution and saves its current context.
The CPU then jumps to the interrupt vector (address) where the corresponding ISR handler function is located. And starts executing that ISR handler function till completion.
Then, the CPU restores back the context of the main program and jumps back to where it left off the main program. And everything resumes as it used to before the arrival of the interrupt signal.
When a new interrupt is fired, the CPU will immediately be notified and all previous steps will be repeated again. Otherwise, the CPU will keep executing the main program (super loop() function).
This is the complete Arduino Interrupts Tutorial. It’ll provide you with more in-depth information about interrupts in general and specifically in Arduino, how it works, how to write efficient ISR handlers, and other tips & tricks.
Arduino PCINT (Pin Change Interrupts)
External interrupt pins are usually referred to as IRQ pins (interrupt request pins). Which directly fire interrupt signals to the CPU when a certain external event occurs, that’s why those interrupts are known as external interrupts.
The most common types of IRQ pins are dedicated external interrupt pins (INTx), and IOC (interrupt-on-change) pins (PCINT). The difference is that dedicated external IRQ pins have separate interrupt vectors, while PCINT pins share a common interrupt signal for each port and you have to manually check which pin state has changed and caused that PCINTx global flag to fire the interrupt.
In a previous tutorial, we discussed the Arduino External interrupt pins (INTx) in detail. And in this tutorial, we’ll be focusing on Arduino PCINT (Pin Change Interrupts).
Arduino PCINT Pins
All Arduino UNO digital pins can be used as interrupt pins using the PCINT (Pin Change Interrupts) hardware. This is not a dedicated interrupt signal source unlike the dedicated external interrupt pints (INT0/INT1).
You can easily notice that every IO pin in the Arduino UNO pinout diagram has a PCINTx label. Where x is the number of the pin change interrupt signal. Those signals are grouped together to generate 3 interrupt signals one for each port of pins (combined together). And the only way to know which pins have their state changed is to manually check for it in your code logic.
Having to manually check which pin state has changed in each port is the major drawback for PCINT interrupts compared to the dedicated external interrupts (INT0/INT1).
Arduino PCINT Control Registers
We can initialize, configure, and control Arduino PCINT (pin change interrupts) using the associated registers as stated in the datasheet. The PCINT-associated registers are as follows:
- PCICR: Pin Change Interrupt Control Register, To enable/disable the 3 global PCINT interrupt signals (PCIE0, PCIE1, and PCIE2).
- PCIFR: Pin Change Interrupt Flag Register, To read or clear the 3 global PCINT interrupt flag bits (PCIF0, PCIF1, and PCIF2).
- PCMSK(0-2): Pin Change Mask Registers, To enable/disable pin-change interrupts for each pin individually. Each port of pins (B, C, and D) has a separate PCMSKx register (0, 1, and 2).
Using the Arduino UNO Pinout Diagram, you can easily identify which Arduino IO pin maps to which PORT in the AVR Atmega328p microcontroller. And then, you can easily use the PCINT control registers to configure it to whatever your need is.
Arduino PCINT ISR Handlers
To handle pin change interrupts, we have 3 separate interrupt vectors. One for each port pin change, namely (Port B, C, and D). Therefore, we’ll need to define an ISR function with the appropriate interrupt vector name according to the selected PCINT pins. Which will be as follows:
- ISR(PCINT0_vect) : For PORTB pins (Arduino pins: D8 to D13)
- ISR(PCINT1_vect) : For PORTC pins (Arduino pins: A0 to A5)
- ISR(PCINT2_vect) : For PORTD pins (Arduino pins: D0 to D7)
Arduino PCINT (Pin Change Interrupts) fires each time a pin state is changed. Pins in the same port will fire the same interrupt signal if any of them has changed. Therefore, it’s our responsibility to save the pin states and compare them each time a PCINT interrupt is received to figure out which pin has changed.
Arduino PCINT requires a lot of tasks that we need to do in code, like mapping Arduino IO pins to the PCINT registers, PCINT configuration, and ISR handling. We could have done that ourselves using register access and bit manipulations. But to make things a lot simpler, we’ll use an Arduino pin change interrupts library to handle those tasks and provide us with generic APIs (interfaces) to make the code cleaner.
Arduino Pin Change Interrupts Library
This is the Arduino PCINT Library we’ll be using in this tutorial. You can download it from GitHub or you’ll find it anyway in the attachments of this tutorial near the end which include all code examples, wiring diagrams, library files, and everything else.
Pin Change Interrupt Library Installation
After downloading, and unzipping the library folder, you need to copy it to your Arduino local sketchbook directory. Which by default is:
- Windows: C:\Users\{username}\Documents\Arduino
- macOS: /Users/{username}/Documents/Arduino
- Linux: /home/{username}/Arduino
You can also know the sketchbook directory in your system by opening your Arduino IDE, opening the file menu, and selecting preferences.
File > Preferences.
And this is the library folder after being copied to the sketchbook directory.
Now, you’re ready to use the library. To include the Arduino PCINT library, you’ll do it like this:
1 |
#include "PinChangeInterrupt.h" |
Pin Change Interrupt Library Functions
Using this Arduino PCINT library is very similar to standard Arduino functions for external interrupts (INTx). The only two differences that we can tell from a user perspective are as follows:
1- The Pin-Change Interrupts Trigger Modes available are: (RISING – FALLING – CHANGE). Yes, they are only 3 modes available.
2- The attachInterrupt() function that we use for external INTx, becomes now attachPCINT() for PCINT interrupt pins instead.
There are more details about how the library actually works under the hood, it’s well-documented on Github and performs really well in various tests that I’ve done and we’ll see later on in this tutorial.
Arduino PCINT (Pin Change Interrupts) Code
In this section, I’ll give you a step-by-step approach to what to do in order to configure and initialize an Arduino PCINT interrupt pin and assign to it an ISR handler function using the pin change interrupt library.
Step 1– Include the Arduino pin change interrupt library header file.
1 |
#include "PinChangeInterrupt.h" |
Step 2– Decide on the PCINT interrupt input pin that you’re going to use. All Arduino UNO pins have PCINT capability.
1 |
#define BTN_PIN 8 |
Step 3– Decide on the Interrupt Trigger Event that you need to have. (RISING – FALLING – CHANGE).
Step 4– Initialize that GPIO input pin & attachPCINT to it in the setup function, use the digitalPinToPCINT() function to map the IO pin number to PCINTx number, and attach the ISR handler function name that you’d like to implement.
1 2 3 4 5 |
void setup() { pinMode(BTN_PIN, INPUT); attachPCINT(digitalPinToPCINT(BTN_PIN), BTN_ISR, RISING); } |
Step 5– Now, write your own ISR handler function. In other words, what do you want to do when this input pin has a rising edge input?
1 2 3 4 |
void BTN_ISR(void) { // This is The ISR Handler, Do Whatever You Need To Do Here! } |
And that’s it! Let’s test this out in the following example project.
Arduino PCINT Example
In this example project, we’ll test Arduino PCINT (pin change interrupt) library. The major difference between INTx external interrupts & PCINT is that PCINT interrupts are grouped into 3 interrupt signals one for each port. If any pin in any port has changed state, the whole port is suspected to have changed. Therefore, we’ll always need to save pin states and do comparisons.
That’s why in this test project, I’ll use 2 pins in the same port for pin change interrupt. I’ll use Arduino Pin8 and Pin9, which maps to (B0 & B1 in PORTB). The 2 PCINT input pins will be connected to 2 push buttons. And we’ll use the interrupt events to toggle 2 output LEDs in the respective ISR handler function for each PCINT interrupt.
Wiring
Here is the wiring diagram for this example showing how to connect the LED outputs (6 & 7), and the push buttons to the PCINT interrupt input pins (8 & 9).
Arduino PCINT Example Code
Here is the full code listing for this Arduino Pin Change Interrupt Example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* * LAB Name: Arduino PCINT Example * Author: Khaled Magdy * For More Info Visit: www.DeepBlueMbedded.com */ #include "PinChangeInterrupt.h" #define BTN0_PIN 8 #define BTN1_PIN 9 #define LED0_PIN 6 #define LED1_PIN 7 void BTN0_ISR(void) { digitalWrite(LED0_PIN, !digitalRead(LED0_PIN)); } void BTN1_ISR(void) { digitalWrite(LED1_PIN, !digitalRead(LED1_PIN)); } void setup() { pinMode(LED0_PIN, OUTPUT); pinMode(LED1_PIN, OUTPUT); pinMode(BTN0_PIN, INPUT); pinMode(BTN1_PIN, INPUT); attachPCINT(digitalPinToPCINT(BTN0_PIN), BTN0_ISR, RISING); attachPCINT(digitalPinToPCINT(BTN1_PIN), BTN1_ISR, RISING); } void loop() { // Do Nothing } |
Code Explanation
We first need to define the IO pins to be used for the LED outputs & push buttons inputs.
1 2 3 4 |
#define BTN0_PIN 8 #define BTN1_PIN 9 #define LED0_PIN 6 #define LED1_PIN 7 |
BTN0_ISR() & BTN1_ISR()
Those are the ISR handler functions for the PCINT interrupt pins, in each ISR we’ll only do a LED toggle action for the respective LED. For each RISING edge on the PCINT interrupt pin (push button), the CPU will execute the corresponding ISR function which will toggle the corresponding output LED.
1 2 3 4 5 6 7 8 9 |
void BTN0_ISR(void) { digitalWrite(LED0_PIN, !digitalRead(LED0_PIN)); } void BTN1_ISR(void) { digitalWrite(LED1_PIN, !digitalRead(LED1_PIN)); } |
setup()
in the setup() function, we’ll initialize the IO pins to be used as input & output using the pinMode() function to set their modes.
1 2 3 4 |
pinMode(LED0_PIN, OUTPUT); pinMode(LED1_PIN, OUTPUT); pinMode(BTN0_PIN, INPUT); pinMode(BTN1_PIN, INPUT); |
Then we’ll enable the PCINT interrupt for (pin8 & 9) using the attachPCINT() function & set them to trigger on RISING edge events only.
1 2 |
attachPCINT(digitalPinToPCINT(BTN0_PIN), BTN0_ISR, RISING); attachPCINT(digitalPinToPCINT(BTN1_PIN), BTN1_ISR, RISING); |
loop()
in the loop() function, nothing needs to be done.
Testing Results
This example project is a very good example to showcase the Button Bouncing issue & how it can affect your Arduino projects. And it also shows you that we can’t always rely on results from the simulation environment only as it’s not going to simulate such real-world random events and noise.
Note that this example project will behave in a weird way due to an issue that’s commonly known as “Button Bouncing“. This is simply a random event due to the button’s mechanical contacts bouncing, which results in some glitches or unintended pulses being injected into the digital input pin causing it falsely trigger the interrupt multiple times “randomly”.
Despite the fact that there are so many software button debouncing techniques (algorithms) that you can learn about from the guide below, it still won’t protect an external interrupt pin because the hardware bouncing is sending a random triggering signal to the interrupt circuitry. The best way to deal with it and prevent false interrupt triggering is to use hardware button debouncing which is also demonstrated in the guide tutorial below with a lot of examples.
This article will provide you with more in-depth information about Arduino button debouncing techniques both hardware & software methods. With a lot of code examples & circuit diagrams.
Arduino Pin Change Interrupt Speed
Before concluding this tutorial, let’s do a final test for the Arduino PCINT library and see how fast it does respond to interrupt requests. We’ll measure the time between the hardware RISING edge on the button till the execution of the first instruction in the ISR handler function that corresponds to that PCINT interrupt input pin.
This time between the hardware IRQ and starting the execution of the ISR is called the Interrupt Latency and it’s demonstrated in more detail in the tutorial linked below.
But anyway, we know for sure that the dedicated external interrupt pins (INT0 & INT1) will always have a faster response time due to having separate interrupt vectors, and no software intervention is needed to handle any of each. On the other hand, PCINT interrupts require software to read, store, and compare pin states to trigger the corresponding ISR handler function. This adds a bit of delay in the IRQ servicing, and that’s what we’re going to measure.
It came to around 4.5μs for the PCINT compared to 3.5μs for dedicated external interrupt pins (INTx) which is amazingly good. The interrupt latency is almost negligible to be honest and Arduino PCINT library we’ve been using has already done really well so far. To learn more about the interrupt latency & response time, and how to measure them in Arduino, it’s highly recommended to check out the tutorial below.
This article will provide you with more in-depth information about interrupt latency & response time. With a couple of techniques for interrupt latency measurement with Arduino code examples.
Parts List
Here is the full components list for all parts that you’d need in order to perform the practical LABs mentioned here in this article and for the whole Arduino Programming series of tutorials found here on DeepBlueMbedded. Please, note that those are affiliate links and we’ll receive a small commission on your purchase at no additional cost to you, and it’d definitely support our work.
Download Attachments
You can download all attachment files for this Article/Tutorial (project files, schematics, code, etc..) using the link below. Please consider supporting my work through the various support options listed in the link down below. Every small donation helps to keep this website up and running and ultimately supports our community.
Arduino PCINT Wrap Up
To conclude this tutorial, we’d like to highlight the fact that the Arduino external interrupt pins are very useful in so many applications in which you need your Arduino to immediately respond to various events as soon as they arrive.
This tutorial is a fundamental part of our Arduino Series of Tutorials because we’ll build on top of it to interface various sensors and modules with Arduino in other tutorials & projects. So make sure you get the hang of it and try all provided code examples.
If you’re just getting started with Arduino, you need to check out the Arduino Getting Started [Ultimate Guide] here.
This is the ultimate guide for getting started with Arduino for beginners. It’ll help you learn the Arduino fundamentals for Hardware & Software and understand the basics required to accelerate your learning journey with Arduino Programming.