Arduino Pin Change Interrupts

I recently needed to do some work with Pin Change Interrupts and it was a bit of a learning experience for me.  As it turns out, they’re actually pretty easy.  I’m posting this so that when I need to look it up in the future, I can easily find it and maybe I can help somebody else out too.

I’m going to talk specifically about the ATMEGA328 chip here since it is by far the most common in Arduinos and in my lab, but the information here should transfer easily other ATMEGAs as well.  Before we begin I want to make sure we’re all using the same terms.  There are two main categories of interrupts: Hardware and Software.  A Hardware interrupt is triggered by something outside of the chip like a button while a Software interrupt is triggered from inside the chip like a timer.

Within the Hardware interrupt there are two categories: External interrupts and Pin Change Interrupts.  The nomenclature here is confusing since all hardware interrupts are external to the chip.  But the things we are now calling External Interrupts are limited to only a couple pins, while the Pin Change interrupts can occur on all input pins.  For instance, on the ATMEGA328, there are two External Interrupts but 24 Pin Change Interrupts.

Each time an interrupt occurs, it triggers the associated ISR (Interrupt Service Routine) assuming you have turned that interrupt on.  Each External Interrupt has its own ISR and they can be triggered independently by either a rising signal, falling signal, or by both.  But the Pin Change Interrupts share an ISR between all the pins on a port (port B, C, and D).  And anytime a pin changes on that port, it calls the port’s ISR which must then decide which pin caused the interrupt.  So Pin Change Interrupts are harder to use but you get the benefit of being about to use any pin.

ATMEGA328 interrupt list


Hardware interrupts are also easier to use in the Arduino environment.  You just call the function attachInterrupt and input the interrupt number and the function to call when it triggers.  But up until recently, there wasn’t a good Pin Change Interrupt library and even now it isn’t included so you have to download it separately.  Plus, I’m a fan of not using libraries for simple tasks like this that could be accomplished in a few lines of code.  I just feel like it’s easier to debug and control what’s going on.  For these reasons, all of the below will use AVR C commands, which can be used regardless of whether you’re using the Arduino boot-loader and IDE or another.



There are three steps to using Pin Change Interrupts.
1) Turn on Pin Change Interrupts
2) Chose which pins to interrupt on
3) Write an ISR for those pins


1 – Turn on Pin Change Interrupts

The Pin Change Interrupts are turned on by setting certain bits in the PCICR register as seen below.  Bit 0 turns on port B (PCINT0 – PCINT7), bit 1 turns on port C (PCINT8 – PCINT14), and bit 2 turns on port D (PCINT16 – PCINT23).  This code shows how to turn them on or off. Note, I’m using |= instead of = because it is more versatile but either would work.  You can also use decimal or hexadecimal instead of binary, but I think the binary is the easiest to understand.

PCICR |= 0b00000001;    // turn on port b
PCICR |= 0b00000010;    // turn on port c
PCICR |= 0b00000100;    // turn on port d
PCICR |= 0b00000111;    // turn on all ports

atmega328 - pcicr

 2 – Choose Which Pins to Interrupt

I know I said earlier that a change on any of the pins in a port would trigger the port’s ISR, but that is true only when you turn that particular pin on.  This is down with what is called a mask.  Since the ATMEGA328 has 3 ports, it also has three masks: PCMSK0, PCMSK1, and PCMSK2.  These are set the same way the register for the PCICR was set.  Again, you can use |= or just = but |= gives you the ability to separate them onto different lines if you’d like.

PCMSK0 |= 0b00000001;    // turn on pin PB0, which is PCINT0, physical pin 14
PCMSK1 |= 0b00010000;    // turn on pin PC4, which is PCINT12, physical pin 27
PCMSK2 |= 0b10000001;    // turn on pins PD0 & PD7, PCINT16 & PCINT23

atmega329 - pcmsk0 atmega329 - pcmsk1 atmega329 - pcmsk2

3 – Write the ISR

The last step is to write the ISR that will be called for each of these interrupts.  The general guidelines on ISRs are to make them as short as possible and not use delays in them.  Also, you should spell and capitalize them correctly (see here).  Finally, if using variables in these ISRs you want to make the variable volatile; this tells the compiler that it could change at any time and to reload it each time instead of optimizing it.  To define these ISRs just type the function below your loop.

ISR(PCINT0_vect){}    // Port B, PCINT0 - PCINT7
ISR(PCINT1_vect){}    // Port C, PCINT8 - PCINT14
ISR(PCINT2_vect){}    // Port D, PCINT16 - PCINT23

Putting It All Together

Below is code putting all of these together.  You’ll notice I’m using cli() before enabling the interrupts and sei() afterward.  cli() turns interrupts off while we’re messing with them and then sei() turns them back on. Also, you should include avr/interrupt.h at the top of the sketch.

#include <avr/interrupt.h>

volatile int value = 0;

void setup()
PCICR |= 0b00000011; // Enables Ports B and C Pin Change Interrupts
PCMSK0 |= 0b00000001; // PCINT0
PCMSK1 |= 0b00001000; // PCINT11


void loop()





34 thoughts on “Arduino Pin Change Interrupts

  1. It’s a pity, you write a magnificent article about pin change interrupts then end it with a terrible example.

    1. It is a pity that you leave such a nice comment and the end it by being a jerk. But I’m going to go ahead and approve it because I’m having a good day. Rather than leave such a dumb comment, it’d actually be useful if you said what you didn’t like about it. If what you don’t like is that it’s a very simple example, that is on purpose; it’s supposed to be the minimum necessary to use pin change interrupts so that people who are just learning don’t get distracted by other parts of the code. Alternatively, you could write a better example to put in the comments and then everybody would win.

  2. I’m sorry, I didn’t mean to be nasty. I have been researching pin change interrupts for a while. I’ve read some articles that suggest you have to have a degree in rocket science to use PCIs. Your approach is VERY clear, very simple and truly the best I read. You make it real easy to understand without calls to weird and wonderful methods to change pin numbers to mask bits etc.
    Your article shows simply how to switch on the port then switch on the pins…. Beautiful! No special macros (sbi/cbi). No calls to libraries. No weird calls to digitalPinToPCMSK and digitalPinToPCICRbit methods….
    With such a clear and simple approach I was hoping for a solution to my real problem, debouncing switches. My problem wasn’t/isn’t YOUR problem. I was wrong to expect MY solution in YOUR marvellous article.
    I apologise for the second half of my my remark 😦 The first half I still stand by 🙂

    1. To debounce using interrupts, one option is:
      1) check if a certain amount of time has passed since the interrupt was last called, and if enough time has passed, actually handle the interrupt
      2) regardless of the above, store the time that the interrupt was called

      Note that “handling the interrupt” usually means “set a flag to tell the non-interrupt code to do stuff”. Then you return from the interrupt call.

      In your main body of code you will check for the “interrupt handler asked us to do stuff” flag, and do the appropriate stuff. Then you can go to sleep or do other processing.

      This works for systems which do not require “real time” processing, i.e.: you only have to get around to handling the interrupt sometime before it’s legitimately called again. This is sometimes referred to as “soft real time”, meaning that stuff has to be done to a time budget, but neither response time or response delay are critical.

      As untested pseudo-Arduino-code, something like this (using milliseconds or microseconds as you see fit, noting that the debounce period must be less than the expected period of the event being monitored, for obvious reasons)

      unsigned long last_interrupted = 0;
      volatile bool interrupted = false;

      ISR INT0 {
      if (millis() – last_interrupted > 50) interrupted = true;
      last_interrupted = millis();

      loop {
      if interrupted {
      # do stuff;
      interrupted = false;

  3. Thank you for this tutorial!
    I’ve a specific question: how can I incorporate conditions like CHANGE, RISING, FALLING like in arduino’s attachInterrupt() function?

    More specifically I’m trying to set a switch with 3 analog sensors using interrupt so each of the sensors are read independently.

    Thank you!!!

    1. The only real way to do that is to keep a record of all the pin states. Then when the interrupt is triggered, you compare that to the record you have, see which is different and in which direction, and go from there. You’d have to remember to update that record each time the interrupt is called and each time you change a pin. Alternatively, you can use a microprocessor that lets each pin have external interrupts like most 32 bit professors (examples include the teensy and Arduino Due) or a different 8bit chip with more external interrupts like the Arduino mega, which has four. Also, maybe you don’t need to read all of the different types of changes. A lot of applications can be adapted to work by only knowing that there is a change. Finally, if you are using the Arduino Uno or similar that uses the ATmega328 chip, remember that it has 3 ports. You could set up your interrupts so that each interrupt goes to a pin on a different port. Then, based on which interrupt gets called, you’ll know which sensor triggered it.

      1. AN article somewhere says that arduino UNO board has only 2 external interrupt pins (Digital pins 2 and 3). Kindly help me if I’m wrong.

    2. Actually, I’m going to amend that a bit, but I’ll leave it so that I and others will know the though process I went through. Again, I’m going to assume you’re using an Uno or other 328 chip. Attach each of the sensors to a pion in a different port of the chip like I said in the last comment. However, you don’t have to keep a record of the previous states to know. Since the ISR will only get called when there is a change, you know that the previous state is the opposite of the current state. So if the ISR gets triggered, you can check to see if the pin is currently high; if it is, that means that it was a rising interrupt. If the pin is currently low, it means that it was a falling interrupt.

      Also, you might want to do two of them in the hardware interrupts and the third with a pin change interrupt.

  4. Thank you for your reply! I’m working with an Arduino Uno, yes. My pins are already setup physically so I was hoping to find a code-only solution. Yes, in the future I’ll look into the 3 different ports pin arrangement.
    I’m a beginner level in Arduino and your suggestions will take me sometime to learn. I’ll just have to craft a long, boring, amateur code… Thank you for your time and posts regardless!

  5. I understand the disappointment of Louis. I had the same feeling and I’ll try to explain why.
    Your excellent article explains in great detail to the (absolute) beginner how things work. When you say ‘putting all of these together’ it looks as if your IDE has stripped out all comments.
    Like: what is this code supposed do? Complicating factor for beginners like me is that in various steps of your explanation, you use specific pins, ports and routine addresses, but these particular items don’t seem to return in the example code.
    It is e.g. hard to grasp why you enable pin 1 and 11 (isn’t that what “PCMSK0 |= 0b00000001; // PCINT0 and PCMSK1 |= 0b00001000; // PCINT11 ” does?) and then define routines ISR(PCINT0_vect) and ISR(PCINT1_vect), why PCINT1_vect and not PCINT11_vect?
    You make up quite a bit by helpful replies to comments, so I hope you are inclined to add a bit more comments to the example code, or harmonise the items as explained in the article with the example code. Thanks!

    1. Thanks for your helpful comment, ard. Let me have a look at it again and see if I can make it a little better. However, I just got back from my honeymoon so it might take a little bit because I’m behind a lots of stuff.

  6. Thank you for writing such a great article! I do have one question for you. Can I have individual ISRs run for different individual interrupts within a port? I.E. could I have an ISR for PCINT0 (physical pin 14), and another, separate ISR for PCINT1 (physical pin 15), etc. even though they are all within Port B? I am confused as to whether the PCINT0_vect ISR is running just when PCINT0 is triggered, or when any port B interrupt that happens to be turned on is triggered. I am trying to control 4 encoders with one 328, so I would have 8 inputs that would need to trigger 4 ISRs. Alternatively, they could all trigger one ISR, as long as the code within it knows which pin triggered it. I hope this makes some sense. If not, please let me know and I will try to clarify. I am new to pin change interrupts, but this article has helped me a great deal.

  7. I’ve been chasing pin change interrupt for a few days now and your example is what what made the difference. Maybe it’s the arduino hodgepodge documentation, but for me the difference between the attachInterrupt( ) and ISR( ) is very obscure (still totally unclear in my mind) as is what to use for vector naming. Using your example I finally tried the ISR method _and_ finally used the vector PCINT1_vect, which at first I thought to be a typo because I was expecting to use a vector for the actual pin I’m using (forgetting how the port pins are grouped for pin-change-ints). Many thanks, you will be mentioned in my will (don’t expect much).

  8. I’ve been fairly comfortable with the AVR implementation of C for a while now, and I’ve worked with several different types of AVR microcontrollers, but I’ve been avoiding pin change interrupts because I couldn’t wrap my head around the PCMSK registers. Your article made so much sense, I feel silly for not understanding it in the first place. I really appreciate the time you took to write this up.
    Happy upcoming anniversary!

  9. Thank you for taking the time and effort to write such an excellent article. Using it, I have managed to get PCIs working at the first attempt – which for me is a miracle (I’m very much a novice at coding). For the benefit of those asking about debouncing switches I’d like to explain how I overcame the bounce problem: My code drives an stepper motor which powers the rotation of an astronomical observatory using the accelstepper library. The accelstepper command is called in the void loop routine. I needed to monitor the state of a switch which closes whenever the dome passes through north but found that a digitalRead within an “if” or a “while” loop in the void loop badly affected the performance of the motor run command and made the motor run very ragged. I overcame this using a PCI triggered by the switch to set a volatile boolean variable “domeNorthInterrupt” to TRUE – only that – nothing more – just one line of code. Then, within the void loop, I have an if(domeNorthInterrupt==TRUE) loop which then carries out a digitalRead of the switch (just to rule out any spurious operation). This if() loop is so fast that it has no impact on the motor performance whatsoever…and because the PCI ISR is so short and only sets the variable to TRUE, it is very fast and any bounce has no effect on the motor either. Very pleased indeed – thanks again!

  10. I second Andy’s comment as of 20160608 – how do I detect which of the 8 possible pins of the given port actually caused the interrupt to be thrown?

    1. ISR(PCINT0_vect){
      char PBNOW = PINB ^ PBLAST;
      PBLAST = PINB;
      switch (PBNOW){
      case (1 << PINB4):
      case (1 << PINB3):
      case (1 << PINB2):

  11. Great article! Very simple.
    One note: Since PCIEx and PCINTxx are introduced in the table, maybe would be better to use them after explaining what they mean and do as binary numbers.
    PCICR |= (1<<PCIE0); //PB
    PCMSK0 |= (1<<PCINT2)|(1<<PCINT3)|(1<<PCINT4); // PB2,PB3,PB4

  12. You should always clear the PCIF ( Pin change interupt flag) by writing a 1 to it, before enabling global interrupts with a SEI instruction, otherwise you might end up inside your ISR as soon as you SEI

  13. Very good and simple article. Without all that “read datasheet” bullshit. Nice and clear. Thank you and keep posting!

  14. Dieses Mal in Deutsch 🙂
    Ein sehr lehrreiches Beispiel – mit Deiner Hilfe konnte ich meinen Drehencoder an einen Arduino-Nano ‘häkeln’.
    Die 4 Impulse pro Raste werden exakt erkannt und verarbeitet, habe mir gestattet, Deinen Code mit einigen weiteren Variablen zu ergänzen, um die Drehrichtung meines Encoder bestimmen zu können. (ALPS EC11, 15 Rasten mit Button)
    Werde dieses Wochenende ein Wenig damit spielen – vorerst wird’s eine Stroposkop-Lampe, bei Der man mittels Drehencoder die Intervall-Zeit verstellen kann.
    Durch den Button möchte ich mir entweder das Stroposkop blitzen lassen, oder mir die Wartezeit ‘vorblinken’ lassen – also die Wartezeit in ms in Blinkimpulsen als Tausender, Hunderter, Zehner, Einer.
    1032 wäre dann
    1x kurz für 1
    1x lang für 0
    3x kurz für 3
    2x kurz für 2

    Die Idee nicht von mir, gibt ein Blink-Thermometer in einem Petling, Link:
    (soll keine Werbung sein, Das werde ich aber auch noch Mal nachbauen … irgend wann…)


  15. Thanks for the article.
    This doesn’t play nicely with software serial.
    Being new to this, I’m scratching my head as to why.
    (Your example above does, of course, compile fine).

    Arduino: 1.8.1 (Windows 7), TD: 1.37, Board: “Arduino/Genuino Uno”

    libraries\SoftwareSerial\SoftwareSerial.cpp.o (symbol from plugin): In function `SoftwareSerial::read()’:

    (.text+0x0): multiple definition of `__vector_3′

    sketch\CIV_Test_ic-7600.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

    collect2.exe: error: ld returned 1 exit status

    exit status 1
    Error compiling for board Arduino/Genuino Uno.

    1. Did it occur to you that SoftwareSerial is also using pinchange interrupts? Try moving what you’re doing to a different port than the one you’re using SofwareSerial on. This may or may not work depending on how that library is coded. You can always comment out the call to your port within the SS .h file.

  16. I’ve been looking for a way to read multiple PPM signals reliably without stepping on my timers, which are busy doing other stuff. I’ve attempted pin change interrupts before without success, but your clear, concise, and no BS article was finally what I needed to understand without getting a PHD in AVR RTFM. Thanks!

  17. What a great article. I learned a lot about ATmega328’s ports and interrupts. This article will help others to dig deeper into this great microcontroller. Thank you for sharing your insight so clearly. Please continue your great work. Information of this quality and clarity is very hard to find on the Internet.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s