Arduino – Pin Change Interrupt

Standardmäßig stellt der Arduino UNO mit der Funktion „attachInterrupt()“ nur 2 Interrupt-Pins (Pin 2 und 3) zu Verfügung. Durch entsprechende Registerprogrammierung kann jedoch durch jeden Ein-/Ausgangspin ein „Pin Change Interrupt“ ausgelöst werden.

Wie der Name „Pin Change Interrupt“ bereits sagt, wird bei jeder Zustandsänderung eines Eingangssignals, also sowohl bei steigender als auch bei fallender Flanke ein Interrupt ausgelöst. Die Auswertung, welche Flanke den Interrupt ausgelöst hat, bzw. falls mehr als ein Pin Change Interrupt pro Port programmiert wurde, welcher Pin einen Interrupt ausgelöst hat, muss in der „Interrupt-Serviceroutine“ erfolgen.

Verwendete Register:
PCICR = Pin Change Interrupt Register
PCMSKx = Pin Change Mask Register x (0 / 1 / 2)
SREG = Statusregister

Vorgehensweise:
1. Alle Interrupts sperren
2. Port für Interrupt freischalten
3. Pin(s) für Pin Change Interrupt freigeben
4. Alle Interrupts freigeben
5. Interrupt-Serviceroutine

1. Alle Interrupts sperren
Während der Manipulation der Interruptregister sollte kein Interrupt ausgelöst werden. Durch löschen des Global Interrupt Enable Bits (I) im Status Register (SREG) werden alle Interrupts gesperrt

Bit 7 6 5 4 3 2 1 0
I T H S V N Z C
SREG – Status Register
// Löschen des Global Interrupt Enable Bits (I) im Status Register (SREG)
   cli(); // alle Interrupts sperren

// Setzen des Global Interrupt Enable Bits (I) im Status Register (SREG)
   // sei(); // alle Interrupts zulassen
   // oder
   SREG |= 0x80; // alle Interrupts zulassen

 2. Port für Interrupt freischalten

Im Pin Change Interrupt Control Register (PCICR) wird festgelegt welcher Ports für einen Pin Change Interrupt zugelassen werden. Den Ports sind bestimmte Pins zugeordnet.

Bit 7 6 5 4 3 2 1 0
 PCICR PCIE2 PCIE1 PCIE0
Pin D0 – D7 A0 – A5 D8 – D13
Port D C B
PCICR – Pin Change Interrupt Control Register

In Abhängigkeit für welche Pins ein Interrupt ausgelöst werden soll, ist das entsprechende Pin Change Interrupt Enable Bit (PCIEx) zu setzen.

// Setzen des PCIE2-Bit im Pin Change Interrupt Control Register (PCICR)
   PCICR |= (1 << PCIE2);

// Zurücksetzen / löschen des PCIE2-Bit im Pin Change Interrupt Control Register (PCICR)

// Abfragen

3. Pin(s) für Pin Change Interrupt freigeben
Durch setzen des Pin Change Enable Mask Bit (PCINTx) im entsprechenden Pin Change Enable Mask Register (PCMSKx) wird die Auslösung eines Interrupt ermöglicht.

Bit 7 6 5 4 3 2 1 0
 PCMSK0 PCINT7 PCINT6 PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0
PIN D13 D12 D11 D10 D9 D8
Port B
PCMSK0 – Pin Change Mask Register 0
Bit 7 6 5 4 3 2 1 0
 PCMSK1  PCINT14 PCINT13 PCINT12 PCINT11 PCINT10 PCINT9 PCINT8
PIN A5 A4 A3 A2 A1 A0
Port C
PCMSK1 – Pin Change Mask Register 1
Bit 7 6 5 4 3 2 1 0
 PCMSK2 PCINT23 PCINT22 PCINT21 PCINT20 PCINT19 PCINT18 PCINT17 PCINT16
PIN D7 D6 D5 D4 D3 D2 D1 D0
Port D
PCMSK2 – Pin Change Mask Register 2
//Setzen des Pin Change Enable Mask Bit 18 (PCINT18)  ==> Digital-Pin D2
  PCMSK2 |= (1 << PCINT18);

// Zurücksetzen / löschen

// Abfragen

4. Alle Interrupts freigeben
Durch setzen des Global Interrupt Enable Bits (I) im Status Register (SREG) werden alle Interrupts freigegeben. Siehe 1. Alle Interrupts sperren.

5. Interrupt Serviceroutine
Wird ein Pin Change Interrupt ausgelöst, verzweigt das Programm in die entsprechende Serviceroutine. Die Namen der Interrupt-Serviceroutinen sind vom System fest vorgegeben und können nicht ohne weiteres verändert werden.

Pin Change Interrupt PCMSK0 -> Serviceroutine ISR(PCINT0_vect)
Pin Change Interrupt PCMSK1 -> Serviceroutine ISR(PCINT1_vect)
Pin Change Interrupt PCMSK2 -> Serviceroutine ISR(PCINT2_vect)

ISR = Interrupt Service Routine

Beispeilcode
Als Beispiel soll der Pin Change Interrupt am Digital-Pin D2 ausgelöst werden:

void setup(){
  //Löschen des Global Interrupt Enable Bits (I) im Status Register (SREG)
  cli();

  //Setzen des PCIE2-Bit im Pin Change Interrupt Control Register (PCICR)
  PCICR |= (1 << PCIE2);

  //Setzen des Pin Change Enable Mask Bit 18 (PCINT18)  ==> Digital-Pin 2
  PCMSK2 |= (1 << PCINT18);

  //Setzen des Global Interrupt Enable Bits (I) im Status Register (SREG)
  SREG |= 0x80; // gleichwertig mit "sei();"
}

...

//Aufruf der Interrupt Serviceroutine
ISR(PCINT2_vect){
  //Programmcode der Service-Routine
}


Quellen:
arduino-projekte.webnode.at
ATmega328/P Datenblatt