#120 ATMEGA 328P Pin Change Interrupts

The 328P has 3 ports B, C, D you will need to know the basics of how they work to before reading this.
The 328P only has 2x external interrupts however there are also 23x pin change interrupts and I will be focusing on the latter for now.
INTx (external interrupts) can report events under four situations: low, any, falling, rising. PCINTx (pin change interrupts) can report events on only one situation: any, which is basically a change in the pin so we can categorise the 23x as change interrupts only.
Using these pin change interrupts are surprisingly easy but require a few steps. However once they are configured code size and complexity can be reduced significantly. Also never forget you paid for the IC and it’s hardware peripherals.. so USE THEM.
You can run a loop checking button states with multiple variables including timing and state variables etc. or…. you can run a similar reduced loop with the use of the pin change interrupts making your life so much easier while not compromising much resources.
So to get started you need to follow 3 steps:
- Turn the pin change interrupts on
- Choose the pins to interrupt
- Use the ISR for the chosen pins
STEP 1:
To turn on the pin change interrupts you will need to use the PCICR register.
Writing 1 to bit 0 will turn on the portB (PCINT0 – PCINT7)
Writing 1 to bit 1 will turn on the portC (PCINT8 – PCINT14)
Writing 1 to bit 2 will turn on the portD (PCINT16 – PCINT23)
From bit 3 onwards (from right to left) bits are ignored.
1 2 3 4 |
PCICR |= 0b00000001;//PCIE0 // turn on port b PCICR |= 0b00000010;//PCIE1 // turn on port c PCICR |= 0b00000100;//PCIE2 // turn on port d PCICR |= 0b00000111;//ALL // turn on all ports |
STEP 2:
Now you need to choose which pins you want to interrupt.
You will need to use a mask and the 328P has 3 masks: PCMSK0, PCMSK1, and PCMSK2
These masks are set in the same way as the PCICR register was set.
1 2 3 |
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 |
STEP 3:
Now you need to use the correct ISR for the chosen pins.
make sure to keep the ISR as fast as possible and use as little code as possible in the ISR.
Also if you have any variables in the ISR make sure to make them volatile. This tells the compiler that it could change at any time and to reload it each time instead of optimizing it.
1 2 3 |
ISR(PCINT0_vect){}// Port B, PCINT0 - PCINT7 ISR(PCINT1_vect){}// Port C, PCINT8 - PCINT14 ISR(PCINT2_vect){}// Port D, PCINT16 - PCINT23 |
Full Super Simple 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 |
#include <avr/interrupt.h> volatile int value = 0;//make var volatile void setup() { cli();//disable interrupts PCICR |= 0b00000011; // Enables Ports B and C Pin Change Interrupts PCMSK0 |= 0b00000001; // PCINT0 choose pin PB port PCMSK1 |= 0b00001000; // PCINT11 choose pin PC port sei();//enable interrupts Serial.begin(9600);//start serial } void loop() { Serial.println(value);//print value when interrupt is triggered } ISR(PCINT0_vect)// Port B, PCINT0 - PCINT7 { value++;//increase value } ISR(PCINT1_vect)// Port C, PCINT8 - PCINT14 { value–;//decrease value } |
Links:
- Using Pin Change Interrupts on AVR® MCUs
- AVR Cheat Sheet
- External interrupts on the ATmega168/328
- Arduino Pin Change Interrupts
- Assign an interrupt to any pin of the atmega328 microcontroller
- Best way to handle multiple PCINT in AVR
- https://gammon.com.au/interrupts
- Determining which pin triggered a PCINTn interrupt?
- Binary to Hex converter