Programming ATMega328 Pin Change Interrupt

ATmega328 Pin change interrupt circuit diagram

The ATMega328 microcontroller, which is commonly used in Arduino boards, has Pin Change Interrupts as one of its powerful features. Pin Change Interrupts allow you to detect changes in the state of a pin and trigger an interrupt service routine (ISR) in response. This allows you to respond quickly to changes in input signals, such as button presses, sensor readings, or other events, without having to continuously poll the pin in your code. All pins of ATmega328p(except power pins) can be set to use pin change interrupt. In this ATmega328 guide it will be shown how to use Pin Change Interrupt by monitoring the Pin change interrupt pin and when there is change in the input a LED is turned on.

The Pin Change Interrupts in ATMega328 work by monitoring the state of multiple pins in a group, referred to as a Pin Change Interrupt Vector. When a change in state, such as a rising or falling edge, is detected on one or more of the pins in the group, the corresponding interrupt flag(s) is set, and the ISR associated with the interrupt is executed.

To use Pin Change Interrupts in ATMega328, you need to configure the necessary registers and register bits. This typically involves setting the interrupt mask register (PCMSKx) to specify which pins in the group you want to monitor, and enabling the Pin Change Interrupts in the Pin Change Interrupt Control Register (PCICR). Optionally there is also pin change interrupt flag register which are set if interrupt are enabled. You also need to implement the ISR function in your code, which will be executed when the interrupt is triggered. It is a bit different from Programming Atmega328P External Interrupt. The difference being mainly due to masking of bits required and the pin change interrupt having lower priority than the external hardware interrupt.

The following are registers associated with Pin-Change interrupt.

PCICR(Pin Change Interrupt Control Resister)

PCICR(pin change interrupt control resister)

 PCIFR(Pin Change Interrupt Flag Register)

PCIFR(Pin Change Interrupt Flag Register)

 PCMSK2(Pin Change Mask Register 2)

PCMSK2(Pin Change Mask Register 2)

 PCMSK1(Pin Change Mask Register 1)

PCMSK1(Pin Change Mask Register 1)
PCMSK0(Pin Change Mask Register 0)

PCMSK0(Pin Change Mask Register 0)

Thus there are mainly 3 types of registers- a control register, a flag register and mask register. The flag register to monitor interrupt flag is usually not required as interrupt happens automatically. But the control register and mask register needs to be configured. The mask register is required because the Pin Change Interrupt are grouped into banks of ports. Thus there are 3 banks for pin change interrupt for three ports; B, C and D.

See the atmega328 interrupt vector table table below.

atmega328 interrupt vector table

 So the steps to enable pin change interrupt on a pin are:

1. Select the pin for pin change interrup, say PD7

2. Enable pin-change interrupt on PD7. 

To do this, first find the bank where PD7 is located which is port D and the port D pin change interrupt belongs to PCINT2 bank. This means that the PCIE2 bit in the PCICR register has to be set. In program code we do this in the following manner

PCICR |= (1 << PCIE2);     

3. Mask the PD7 pin 

Since PD7 belongs the PCINT2 we need to mask the PCINT23 bit in the PCMSK2 register. This is done in the following way in program code.

PCMSK2 |= (1 << PCINT23);

4. Enable to global interrupt.


5. Write ISR 

Since we are using the PCINT2 we need to write the ISR routine as follows.

ISR(PCINT2_vect) {           

6. Write task in the ISR

Here we will toggle LED connected to pin PB4 whenever ISR() is invoked. The program code is,

ISR(PCINT2_vect) {              
  PORTB ^= (1 << PB4);

This is the basic example of configuring a ATmega328 pin for pin-change interrupt.

Pin Change Interrupt Example Code

The following is basic example program code of configuring ATmega328 pin PD7 as pin-change interrupt. A push button is connected to PD7 and whenever logic change on this pin is detected, a LED connected to PB4 pin is toggled due to the interrupt.

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(PCINT2_vect) {             
  PORTB ^= (1 << PB4);

int main(void) {
  // -------- Inits --------- //
  DDRB |= (1<<PB4);			//LED pin output config
  PORTD |= (1 << PD7);       // pullup on PCI pin PD7

  //Pin Change Interrupt Settings
  PCICR |= (1 << PCIE2);        // enable set pin-change interrupt for PD7 pin
  PCMSK2 |= (1 << PCINT23);     // set mask to look for PCINT23(PD7)
  sei();                        // set (global) interrupt enable bit

  while (1) {

  return 0;      

Let's break down the code and understand its functionality:

  1. #include <avr/io.h> and #include <avr/interrupt.h>: These are the header files for AVR input/output and interrupt handling, respectively. They provide the necessary functions, macros, and definitions for interacting with the AVR microcontroller.

  2. ISR(PCINT2_vect) { ... }: This is the Interrupt Service Routine (ISR) that will be executed when a Pin Change Interrupt is triggered. In this example, it toggles the state of PB4, which is connected to an LED, every time the state of the button connected to PD7 (PCINT23) changes.

  3. int main(void) { ... }: This is the main function of the program, where the microcontroller's operation is defined.

  4. DDRB |= (1<<PB4);: This sets PB4 as an output pin for the LED by setting the corresponding bit in the DDRB register.

  5. PORTD |= (1 << PD7);: This enables the internal pull-up resistor for PD7 (PCINT23), which is connected to the button, by setting the corresponding bit in the PORTD register.

  6. PCICR |= (1 << PCIE2);: This enables the Pin Change Interrupt for PCINT23 on PD7 by setting the corresponding bit in the PCICR register for PCIE2 (Pin Change Interrupt Enable 2).

  7. PCMSK2 |= (1 << PCINT23);: This sets the mask for PCINT23 on PD7 in the PCMSK2 register, which determines which pins to monitor for Pin Change Interrupts.

  8. sei();: This enables global interrupts by setting the Global Interrupt Enable (I) bit in the Status Register (SREG), allowing the ISR to be executed when a Pin Change Interrupt is triggered.

  9. while (1) { }: This is an infinite loop that keeps the microcontroller running indefinitely, allowing the Pin Change Interrupt to be detected and processed continuously.

  10. return 0;: This is the return statement of the main function, indicating the end of the program.

Finally in the ISR() code we have:

ISR(PCINT2_vect) {              /* Run every time button state changes */
  PORTB ^= (1 << PB4);

ISR stands for Interrupt Service Routine, and it's a function that gets executed automatically when an interrupt event occurs. In this case, the PCINT2_vect is the vector name for the Pin Change Interrupt that is triggered by changes on pins PCINT16 to PCINT23, as defined by the PCIE2 bit in the PCICR register.

The code inside the ISR() function is what gets executed when the interrupt is triggered. In this case, it toggles the state of PB4, which is connected to an LED, using the XOR (^) operator and the bit manipulation macro (1 << PB4). This means that every time the button state changes (either from LOW to HIGH or from HIGH to LOW), the LED on PB4 will be toggled, effectively making it blink.

Note that the code inside the ISR() function should be kept as short and efficient as possible, as interrupts are meant to be fast and should not introduce delays or heavy processing that could interfere with the normal operation of the microcontroller. In this example, the PORTB register is directly manipulated using bitwise XOR, which is a fast and efficient way to toggle a single bit without affecting the other bits in the register.

The following shows ATmega328 Pin change interrupt circuit diagram with push button and LED.

ATmega328 Pin change interrupt circuit diagram
Hence you have written the code and compiled you can then upload and test the code. If you don't know how to upload see the tutorial on How to Program ATmega328p using ATMEL Studio & AVR ISP MKII.


Pin Change Interrupts are highly efficient and can be used to quickly respond to changes in input signals with minimal overhead, making them ideal for applications where fast and responsive event detection is required. However, they do require careful handling of register configurations and proper debouncing techniques to avoid false triggering of interrupts.

Pin Change Interrupts in ATMega328 are widely used in various embedded systems applications, including robotics, sensors, input devices, and other applications where real-time event detection and response are critical. By understanding and utilizing the Pin Change Interrupts feature of ATMega328, you can enhance the performance and responsiveness of your embedded system projects.

References and Further Readings

[1] Programming ATmega328P ADC Interrupt

[2] Programming ATmega328P Input Capture with Interrupt

Post a Comment

Previous Post Next Post