Description
As you might know, in most of the Arduino boards we have very limited hardware interrupts and as an example in Arduino UNO we only have 2 hardware interrupts, So what do we do when we need more interrupts? Just follow this complete tutorial to Find out.
To solve this problem we are going to use software interrupts, for e.g. in Atmega 328p we have software pin change interrupt on all the pins, the interrupts only detect the pin state change, they do not detect FALLING, RISING, LOW, and HIGH trigger and these interrupts are of slightly lower priority compared to hardware interrupts but it shouldn't worry us as it affects only in the extreme cases.
Explanation
For demonstration we'll consider example of Arduino UNO with Atmega 328p having DIP package, and the pin mapping of Arduino board is not exactly same as that of atmega 328p chip so while working we may need to refer datasheet of the Atmega 328p.
Arduino Uno has three different ports for input output pins and these are port B C and D each are of 8-bits and can be configured for pin change interrupts. So each pin on Port B will be labeled from PB0-PB7 and its in the same way for Port C and D.
Step1: Enabling/Disabling PCINT
We can activate or deactivate pin change interrupt for a group of pins through the PCICR (pin change interrupt control register) as seen in Table 1 below. The functions of these PCIE (Pin change interrupt enable) pins are as follows:
PCIE0: controls the group of pins for PCINT0 to PCINT7 and those correspond to the digital pins of the Arduino D8 to D13
PCIE1: controls pins from PCINT8 to PCINT14 and those correspond to the analog pins of the Arduino A0 to A5.
PCIE2: controls pins from PCINT16 to PCINT23 and those correspond to the digital pins of the Arduino D0 to D7.
Setting these PCIE bits 1 enables the set of pins corresponding to that particular PCIE bit. e.g. In the example below bits 2 and 0 are enabled.
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
PCIE2 | PCIE1 | PCIE0 |
Table 1
Step2: Enabling/Disabling a specific pin or set of pins
Once the PCINT register is set, we have to turn on/off each of the separate pins and that process is known as unmasking/masking respectively. we have three different registers for masking of three different ports these are PCMSK0, PCMSK1 and PCMSK2 these are called pin change mask and if we set a bit to one in this particular register that particular corresponding pin will trigger an interrupt, and if we set it to zero that particular pin won't trigger an interrupt.
PCMSK0
BIT | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
PCINT | PCINT7 | PCINT6 | PCINT5 | PCINT4 | PCINT3 | PCINT2 | PCINT1 | PCINT0 |
Arduino Pin | - | - | D13 | D12 | D11 | D10 | D9 | D8 |
PCMSK1
BIT | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
PCINT | - | PCINT14 | PCINT13 | PCINT12 | PCINT11 | PCINT10 | PCINT9 | PCINT8 |
Arduino Pin | - | Reset | A5 | A4 | A3 | A2 | A1 | A0 |
PCMSK2
BIT | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
PCINT | PCINT23 | PCINT22 | PCINT21 | PCINT20 | PCINT19 | PCINT18 | PCINT17 | PCINT16 |
Arduino Pin | D7 | D6 | D5 | D4 | D3 | D2 | D1 |
Table 2
In the example below we have enabled pins from D2-D9 to trigger an interrupt.
Step3: Clearing the interrupt flag
It is very important to use cli(); i.e clear interrupt function before Beginning with the software interrupts and using sei(); i.e set interrupt function after completing the setting of software interrupts, as seen in the program below.
Step4: Defining the ISR
Now we need to define an ISR (interrupt service routine), this is used whenever a pin change will trigger a software interrupt and the program control will go into the ISR and for the pin change interrupts the 3 ISR's present are mention below:
• ISR (PCINT0_vect) for pins D8 to D13
• ISR (PCINT1_vect) for pins A0 to A5
• ISR (PCINT2_vect) for pins D0 to D7
In the example below we have used PCINT0_vect and PCINT2_vect since we are going to use pins D2-D9 to trigger an interrupt.
Note: During execution of ISR millis and micros are not updated so keep the ISR as small as possible to minimize the possible errors.
For more details you can visit the video by ELECTRONOOBS.
//Using Arduino pin change interrupts for detecting button presses on arduino pins D2-D9
#include <avr/interrupt.h>
bool D2_state = HIGH;
bool D3_state = HIGH;
bool D4_state = HIGH;
bool D5_state = HIGH;
bool D6_state = HIGH;
bool D7_state = HIGH;
bool D8_state = HIGH;
bool D9_state = HIGH;
bool flag = HIGH;
void setup() {
for (int i=2; i<10; i++){
pinMode(i,INPUT_PULLUP);
}
cli();
PCICR |= 0b00000101; //Bit2 = 1, Bit0 = 1 -> "PCIE2 & PCIE0" enabeled (PCINT16 to PCINT23) & (PCINT0 to PCINT7)
PCMSK2 |= 0b11111100; //Bit2,3,4,5,6,7 = 1 -> "PCINT18,19,20,21,22,23" enabled -> D2,3,4,5,6,7, will trigger interrupt
PCMSK0 |= 0b00000011; //Bit8,9 = 1 -> "PCINT0,1" enabled -> D8,9 will trigger interrupt
sei();
Serial.begin(9600);
}
void loop() {
if(D2_state == LOW){Serial.println("Pin 2 pressed"); D2_state = HIGH;}
if(D3_state == LOW){while(digitalRead(3)==LOW){Serial.println("Pin 3 pressed");} D3_state = HIGH;}
if(D4_state == LOW){Serial.println("Pin 4 pressed"); D4_state = HIGH;}
if(D5_state == LOW){Serial.println("Pin 5 pressed"); D5_state = HIGH;}
if(D6_state == LOW){Serial.println("Pin 6 pressed"); D6_state = HIGH;}
if(D7_state == LOW){Serial.println("Pin 7 pressed"); D7_state = HIGH;}
if(D8_state == LOW){Serial.println("Pin 8 pressed"); D8_state = HIGH;}
if(D9_state == LOW){Serial.println("Pin 9 pressed"); D9_state = HIGH;}
}
ISR (PCINT0_vect)
{
if(digitalRead(8)==LOW){D8_state = LOW;}
if(digitalRead(9)==LOW){D9_state = LOW;}
}
ISR (PCINT2_vect)
{
if(digitalRead(2)==LOW){D2_state = LOW;}
if(digitalRead(3)==LOW){D3_state = LOW;}
if(digitalRead(4)==LOW){D4_state = LOW;}
if(digitalRead(5)==LOW){D5_state = LOW;}
if(digitalRead(6)==LOW){D6_state = LOW;}
if(digitalRead(7)==LOW){D7_state = LOW;}
}
Please do share if you have any interesting ideas to modify the code and the applications where you used this.Thank you😉Adityapratap Singh