The ATMega328 microcontroller, commonly found in Arduino boards, offers the powerful feature of Pin Change Interrupts. These interrupts allow you to detect changes in the state of a pin and execute an interrupt service routine (ISR) in response. Pin Change Interrupts enable quick responsiveness to input signal changes, such as button presses or sensor readings, without the need for continuous polling in your code. All pins of the ATmega328p, except for power pins, can be configured to use Pin Change Interrupts. In this guide, we will demonstrate how to use Pin Change Interrupts by monitoring a specific pin and turning on an LED when the input changes.
Pin Change Interrupts in the 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 pins in the group, the corresponding interrupt flag(s) is set, and the associated ISR is executed.
To utilize Pin Change Interrupts in the ATMega328, you need to configure the relevant registers and register bits. This typically involves setting the interrupt mask register (PCMSKx) to specify which pins you want to monitor within the group, and enabling the Pin Change Interrupts in the Pin Change Interrupt Control Register (PCICR). Optionally, there is also a pin change interrupt flag register that is automatically set if interrupts are enabled. Additionally, you need to implement the ISR function in your code, which will be executed when the interrupt is triggered. It's important to note that configuring Pin Change Interrupts differs slightly from programming external interrupts on the ATMega328P, mainly due to the required bit masking and the lower priority of pin change interrupts compared to external hardware interrupts.
The following registers are associated with Pin Change Interrupts:
PCICR (Pin Change Interrupt Control Register)
PCIFR (Pin Change Interrupt Flag Register)
PCMSK2 (Pin Change Mask Register 2)
PCMSK1 (Pin Change Mask Register 1)
PCMSK0 (Pin Change Mask Register 0)
These registers can be classified into three types: control register, flag register, and mask register. While the flag register is typically not required since interrupts are automatically triggered, the control and mask registers need to be configured. The mask register is necessary because Pin Change Interrupts are grouped into banks of ports. In the case of the ATmega328, there are three banks for pin change interrupts corresponding to ports B, C, and D.
The following steps outline how to enable pin change interrupt on a specific pin:
- Select the pin for the pin change interrupt, e.g., PD7.
- Enable pin-change interrupt on PD7. To achieve this, determine the bank where PD7 is located, which in this case is port D. Port D's pin change interrupt belongs to the PCINT2 bank. Consequently, the PCIE2 bit in the PCICR register must be set. In the program code, you can accomplish this using the following line:
PCICR |= (1 << PCIE2);
- Mask the PD7 pin. Since PD7 belongs to PCINT2, you need to mask the PCINT23 bit in the PCMSK2 register. In the program code, this can be achieved as follows:
PCMSK2 |= (1 << PCINT23);
- Enable global interrupts.
sei();
- Write the ISR.
ISR(PCINT2_vect) {
// ISR code here
}
- Implement the desired task within the ISR. For example, to toggle an LED connected to pin PB4 whenever the ISR is invoked, you can write the following program code inside the ISR:
ISR(PCINT2_vect) {
PORTB ^= (1 << PB4);
}
The complete code is below:
#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;
}
The code within the ISR (Interrupt Service Routine) will be executed every time there is a change in the state of the pin. In this case, it toggles the state of PB4 by using the bitwise XOR operator (^) and the bit manipulation macro (1 << PB4). This means that whenever the button state changes (from LOW to HIGH or HIGH to LOW), the LED connected to PB4 will be toggled, creating a blinking effect.
It's important to keep the code inside the ISR function as short and efficient as possible. 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 code directly manipulates the PORTB register using bitwise XOR, which is a quick and efficient way to toggle a single bit without affecting other bits in the register.
Once you have written and compiled the code, you can upload it to your ATmega328 microcontroller and test its functionality. If you're unsure about the upload process, you can refer to tutorials on how to Program ATmega328p using ATMEL Studio & AVR ISP MKII.
In conclusion, Pin Change Interrupts are highly efficient and useful for quickly responding to changes in input signals with minimal overhead. They are commonly employed in various embedded systems applications, including robotics, sensors, and input devices, where real-time event detection and response are crucial. By understanding and utilizing the Pin Change Interrupts feature of the ATMega328 microcontroller, you can enhance the performance and responsiveness of your embedded system projects.
For further references and additional information, you can explore topics such as Programming ATmega328P ADC Interrupt and Programming ATmega328P Input Capture with Interrupt.