PINCHANGE Interrupt Example
* ein Zähler zählt das obere Nibble von PORTB laufend hoch (0,1...13,14,15,0,1,...)
* Bei Tastendruck PC0 wird ein Interrupt ausgelöst, der den Startwert
* des Zählers neu setzt. Dieser Startwert liegt am unteren Nibble von PORTB an.
Interruptsystem AVR (ATMEL328p)
Table of Contents
Interruptsystem AVR (ATMEL328p)
Interrupt = automatische Unterbrechung eines laufenden Programmes durch ein Gerät und Fortsetzung nachdem eine Interrupt-Behandlungs-Routine (ISR) abgearbeitet wurde, ohne dass das Hauptprogramm etwas davon merkt. Man kann allerdings über globale Variablen dem Hauptprogramm Informationen schicken.
Die ISR steht auf einer fest eingebauten Adresse (= Interrupt Vector Address) am Beginn des Programmspeichers.
Voraussetzungen, dass ein Interrupt ausgelöst wird:
1. Anfrage (Interrupt Request)
2. Erlaubnis (Interrupt Enable)
3. Priorität (interrupt Prio entweder über einen Controller konfigurierbar oder fix eingebaut)
4. Interruptsystem einschalten (sei(); //set interrupt flag )
Steuer-Register
am Beispiel eines externen Interrupts der durch eine Flanke am Pin PD2 des Prozessors ausgelöst wird:
Anfrage: FLAG Register (EIFR external int flag reg)
Erlaubnis: MASK Register (EIMSK ext int mask reg)
Priorität: eingebaut, kein Steuer-Register; je kleiner die Einsprung-Adresse, desto höher die Priorität
Interrupt-System einschalten: SREG Register (status reg) enthält das I-Flag, wenn es gesetzt ist sind alle Interrupts aktiviert
Stack
Der Stack ist ein logischer Speicher der am oberen Ende des internen SRAM angelegt wird. Er wird durch den Stack-Pointer verwaltet.
Der Stack-Pointer zeigt immer an das obere Ende des Stapels wo die nächsten Daten abgelegt bzw. geholt werden können. Nach dem Schreiben auf den Stapel wird die Adresse automatisch weitergestellt, so dass durch aufeinander folgende PUSH Befehle sehr einfach und schnell Daten auf den Stack gelegt werden können. Ein POP Befehl liest die Daten dann in umgekehrter Reihenfolge wieder aus.
Beim Programmstart muss der Stack-Pointer softwaremäßig auf das Speicherende gestellt werden (macht der C-Compiler automatisch).
Das Interrupt-System
-
Interrupt-Quellen: Extern, Timer, ADC, USART usw. (siehe IntVectorTable im Anhang)
-
jede I-Quelle braucht ein zugehöriges Enable-Flag
-
Request Flag wird durch das Gerät gesetzt; ob die Anfrage einen Interrupt auch auslöst hängt davon ab, ob dieser Interrupt enabled ist und ob die Priorität hoch genug ist
-
die Rücksprungadresse muss gesichert werden; wenn das Hauptprogramm unterbrochen wird, muss die Adresse des nächsten Befehls gerettet werde; dies erfolgt, indem die Adresse auf den Stack gerettet wird
-
Interrupt-Kontrollregister im IO Space
-
Global Interrupt Enable Bit (I-Flag) liegt im Status Register SREG
-
Die Interrupt-Service-Routine darf keine Veränderungen an den Registern hinterlassen, auch SREG wird nicht automatisch gesichert, vor dem Rücksprung muss der Status des Hauptprogramms wiederhergestellt werden (macht der C Compiler automatisch)
-
Mehrere Requests gleichzeitig: beim Aussprung aus ISR wird immer min. 1 Zyklus im Hauptprogramm abgearbeitet, bevor ein weiterer I ausgeführt wird;
C-Compiler
Der C-Compiler übernimmt das Retten der Register des Hauptprogramms
-
nach sei() wird noch ein Befehl abgearbeitet, bevor anstehende Interrupts verarbeitet werden, cli() wirkt schlagartig
-
Reaktionszeit auf einen Interrupt: Einsprung 4 Zyklen (Speichern der Rücksprungadresse, löschen des I Flags); Rücksprung auch 4 Zyklen
-
SREG:I wird automatisch resetiert, wenn in die ISR gesprungen wird; wird automatisch wieder gesetzt, wenn aus der ISR zurückgesprungen wird. Im folgenden Code:
ISR(TIMER2_OVF_vect){
cli();
}
… würde also das i Flag automatisch wieder gesetzt -
sei() innerhalb einer ISR → verschachtelte Interrupt-Aufrufe (jeder Interrupt darf die ISR unterbrechen); es gehen jedoch ein paar Zyklen verloren bis das sei() wirkt; braucht man es schneller, dann mit ISR(INT0_vect,ISR_NOBLOCK) {…}
Interrupt request flag löschen: eine 1 schreiben
Interrupt Vector Table (IVT)
-
Priorität geregelt nach der Anordnung innerhalb der IVT (Reset hat höchste Prio)
die Interrupts können an den Anfang des BOOT-Bereichs verschoben werden (IVSEL Interrupt-Vector-Select-Flag oder BOOTRST Boot-Reset-Fuse)
IVSEL … bewegt die IVT in Bootbereich, Reset-Adresse bleibt 0
BOOTRST … gesamte IVT (inkl. Reset) in Bootbereich
-
werden die Interrupts nicht benötigt, so kann der Speicherbereich der I-Vectortable für Programmcode genutzt werden
Der Request
-
Trigger durch Request-Flag
Hardware resetiert das Request Flag
Software kann das Request Flag auch löschen (Schreiben einer „1“)
Request ohne Enable bleibt bestehen, bis Enable aktiviert oder Software-Reset
stehen mehrere Requests an, entscheidet die eingebaute Priorität
Einsprung I-Vektor
-
Trigger ohne Request Flag
solange die Interrupt-Bedingung erfüllt ist, bleibt die Anfrage, verschwindet der Request vor einem Enable, dann wird kein Interrupt ausgelöst
z.B: level triggered external interrupt (EICRA: ISC01/ISC00=0/0 The low level Interrupt)
Externe Interrupts INT0, INT1
-
Modus: steigende Flanke, fallende Flanke, Pegel-Änderung und LOW Pegel löst aus
-
EICRA external int control register A (MODUS)
-
EIMSK – External Interrupt Mask Register (ENABLE)
-
EIFR – External Interrupt Flag Register (REQUEST)
-
LOW-Pegel Interrupt und Flankenerkennung-Interrupt arbeiten asynchron (wichtig für Aufwecken aus Sleep-Modi, weil dann evtl. kein Takt vorhanden ist)
Pin Change Interrupt
-
PCINT2 … (pin change interrupt 2); wird getriggert, wenn einer der Pins PCINT 23..16 aus der Pin-Gruppe 2 toggelt
-
PCINT1 … Pin-Gruppe 1; Pins PCINT 14..8
-
PCINT0 … Pin-Gruppe 2; Pins PCINT 7..0
-
Pin-Change Interrupts arbeiten asynchron, benötigen also keinen Takt, um aus einem Schlafmodus den uC aufzuwecken
-
PCICR (interrupt control reg); erlaubt eine der Pin-Gruppen
-
PCMSK2,PCMSK1,PCMSK0 (mask register) erlaubt die einzelnen Pins einer Pin-Gruppe
-
PCIFR – (Flag Register) enthält für jede Pin-Gruppe das Request-Flag
Software-Interrupt
-
"Interrupt" kann durch ein Programm ausgelöst werden; dies wird benutzt, um einen Unterprogrammaufruf in einem anderen Kontext (z.B. Betriebssystem, Taskwechsel) zu machen.
-
ist bei AVR nicht sinnvoll; aber ein externer Interrupt bzw. ein Pin-Change Interrupt wird auch ausgelöst, wenn die Pins als Outputs geschaltet sind und z.B: eine Flanke programmiert wird
Programmierung
Request
Einsprung in den I-Vector (Rücksprungadresse automatisch auf den Stack)
Start der ISR (Status retten, Register retten, abarbeiten, Register und Status zurückladen, reti)
Rücksprung aus der ISR (Rücksprungadresse automatisch vom Stack)
-
das Hauptprogramm darf von der Unterbrechung nichts mitbekommen, daher Status retten und wiederherstellen
-
in der ISR ist globales Intflag (SREG:I) automatisch gelöscht; wird nach dem Rücksprung wieder hergestellt
-
Regel: ISR so schnell wie möglich verlassen
-
Datenaustausch mit main() über globale Variable
-
Unterbrechung verhindern: I-Flag löschen (cli)
-
der C Compiler löscht beim Eintritt in die ISR das I-Flag und setzt es bei RETI wieder; das Status-Register und andere Register werden automatisch gesichert
-
globale Variable, die innerhalb der ISR verarbeitet werden, sollten als volatile gekennzeichnet werden. Der Compiler erkennt nicht, dass eine Variable innerhalb einer ISR verändert wird und würde sie daher u.U. wegoptimieren.
Arduino
Eine leere Arduino Anwendung verwendet bereits das Interrupt-System!
-
I-Flag ist gesetzt
-
Timer 0 läuft + Timer overflow Interrupt enabled
-
Timer 1 läuft, kein Interrupt
-
Timer 2 läuft, kein Interrupt
Achtung! Innerhalb einer Int-Service-Routine funktionieren delay(), micros() und millis() nicht richtig, da diese Funktionen interrupt-basiert sind.
delayMicroseconds() benutzt keine Zähler und wird deshalb normal funktionieren.
Beispiele
Bsp1
#include <Arduino.h>
void loop(void );
void isr_INT0();
volatile uint8_t num_of_interrupts;
void loop(void)
{ setup();
sei();
while(1)
{
Serial.println(num_of_interrupts, DEC);
delay(1000);
}
}
void isr_INT0(){
if (digitalRead(13)==HIGH)
digitalWrite(13,LOW);
else digitalWrite(13,HIGH);
num_of_interrupts++;
}
void setup(){
Serial.begin(57600);
pinMode(13,OUTPUT);
pinMode(2,INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INT0),isr_INT0,FALLING); //PIN 2
}
"Volatile" verhindert, dass die Variable beim Compilieren wegoptimiert wird; dies kann man überprüfen, wenn man das Schlüsselwort volatile entfernt und in der Perspektive "release" compiliert. Man kriegt keine Fehlermeldung, aber die Zählfunktion funktioniert nicht.
LIBC
Es wird ein Arduino-Projekt angelegt, um die Lib HardwareSerial zur Verfügung zu haben.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <HardwareSerial.h>
#include <util/delay.h>
void setup();
uint8_t num_of_interrupts;
int main(void)
{
setup();
sei();
while(1)
{
Serial.println(num_of_interrupts, DEC);
//_delay_ms(1000);
}
}
ISR(INT0_vect){
num_of_interrupts++;
}
void setup(){
Serial.begin(57600);
EIMSK |= _BV(INT0); //enable
EICRA |= _BV(ISC01); //falling edge
}
ISR(BADISR_vect) … wenn keine ISR vorhanden, dann springe hierher
ISR_NAKED … die Kontrolle über die ISR in der eigenen Hand (SREG und RETI())
ISR(INT0_vect, ISR_BLOCK) // das Gleiche, wie ISR(INT0_vect) d.h. während der Ausführung ist i-Flag gelöscht und wird beim Aussprung aus der ISR wieder gesetzt.
ISR_NOBLOCK .. an den Anfang der ISR wird vom Compiler sei() gesetzt, so dass die ISR unterbrochen werden kann.
ISR_ALIAS(INT1_vect, INT0_vect); //beide tun das Gleiche
EMPTY_INTERRUPT() aus dem Schlafmodus aufwecken, aber keine Funktionalität
Anhang
Interrupt Vector Table
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
Vector name |
Description |
ADC_vect |
ADC Conversion Complete |
ANALOG_COMP_0_vect |
Analog Comparator 0 |
ANALOG_COMP_1_vect |
Analog Comparator 1 |
ANALOG_COMP_2_vect |
Analog Comparator 2 |
ANALOG_COMP_vect |
Analog Comparator |
ANA_COMP_vect |
Analog Comparator |
CANIT_vect |
CAN Transfer Complete or Error |
EEPROM_READY_vect |
|
EE_RDY_vect |
EEPROM Ready |
EE_READY_vect |
EEPROM Ready |
EXT_INT0_vect |
External Interrupt Request 0 |
INT0_vect |
External Interrupt 0 |
INT1_vect |
External Interrupt Request 1 |
INT2_vect |
External Interrupt Request 2 |
INT3_vect |
External Interrupt Request 3 |
INT4_vect |
External Interrupt Request 4 |
INT5_vect |
External Interrupt Request 5 |
INT6_vect |
External Interrupt Request 6 |
INT7_vect |
External Interrupt Request 7 |
IO_PINS_vect |
External Interrupt Request 0 |
LCD_vect |
LCD Start of Frame |
LOWLEVEL_IO_PINS_vect |
Low-level Input on Port B |
OVRIT_vect |
CAN Timer Overrun |
PCINT0_vect |
Pin Change Interrupt Request 0 |
PCINT1_vect |
Pin Change Interrupt Request 1 |
PCINT2_vect |
Pin Change Interrupt Request 2 |
PCINT3_vect |
Pin Change Interrupt Request 3 |
PCINT_vect |
|
PSC0_CAPT_vect |
PSC0 Capture Event |
PSC0_EC_vect |
PSC0 End Cycle |
PSC1_CAPT_vect |
PSC1 Capture Event |
PSC1_EC_vect |
PSC1 End Cycle |
PSC2_CAPT_vect |
PSC2 Capture Event |
PSC2_EC_vect |
PSC2 End Cycle |
SPI_STC_vect |
Serial Transfer Complete |
SPM_RDY_vect |
Store Program Memory Ready |
SPM_READY_vect |
Store Program Memory Read |
TIM0_COMPA_vect |
Timer/Counter Compare Match A |
TIM0_COMPB_vect |
Timer/Counter Compare Match B |
TIM0_OVF_vect |
Timer/Counter0 Overflow |
TIM1_CAPT_vect |
Timer/Counter1 Capture Event |
TIM1_COMPA_vect |
Timer/Counter1 Compare Match A |
TIM1_COMPB_vect |
Timer/Counter1 Compare Match B |
TIM1_OVF_vect |
Timer/Counter1 Overflow |
TIMER0_CAPT_vect |
ADC Conversion Complete |
TIMER0_COMPA_vect |
TimerCounter0 Compare Match A |
TIMER0_COMPB_vect |
Timer Counter 0 Compare Match B |
TIMER0_COMP_A_vect |
Timer/Counter0 Compare Match A |
TIMER0_COMP_vect |
Timer/Counter0 Compare Match |
TIMER0_OVF0_vect |
Timer/Counter0 Overflow |
TIMER0_OVF_vect |
Timer/Counter0 Overflow |
TIMER1_CAPT1_vect |
Timer/Counter1 Capture Event |
TIMER1_CAPT_vect |
Timer/Counter Capture Event |
TIMER1_CMPA_vect |
Timer/Counter1 Compare Match 1A |
TIMER1_CMPB_vect |
Timer/Counter1 Compare Match 1B |
TIMER1_COMP1_vect |
Timer/Counter1 Compare Match |
TIMER1_COMPA_vect |
Timer/Counter1 Compare Match A |
TIMER1_COMPB_vect |
Timer/Counter1 Compare MatchB |
TIMER1_COMPC_vect |
Timer/Counter1 Compare Match C |
TIMER1_COMPD_vect |
Timer/Counter1 Compare Match D |
TIMER1_COMP_vect |
Timer/Counter1 Compare Match |
TIMER1_OVF1_vect |
Timer/Counter1 Overflow |
TIMER1_OVF_vect |
Timer/Counter1 Overflow |
TIMER2_COMPA_vect |
Timer/Counter2 Compare Match A |
TIMER2_COMPB_vect |
Timer/Counter2 Compare Match A |
TIMER2_COMP_vect |
Timer/Counter2 Compare Match |
TIMER2_OVF_vect |
Timer/Counter2 Overflow |
TIMER3_CAPT_vect |
Timer/Counter3 Capture Event |
TIMER3_COMPA_vect |
Timer/Counter3 Compare Match A |
TIMER3_COMPB_vect |
Timer/Counter3 Compare Match B |
TIMER3_COMPC_vect |
Timer/Counter3 Compare Match C |
TIMER3_OVF_vect |
Timer/Counter3 Overflow |
TIMER4_CAPT_vect |
Timer/Counter4 Capture Event |
TIMER4_COMPA_vect |
Timer/Counter4 Compare Match A |
TIMER4_COMPB_vect |
Timer/Counter4 Compare Match B |
TIMER4_COMPC_vect |
Timer/Counter4 Compare Match C |
TIMER4_OVF_vect |
Timer/Counter4 Overflow |
TIMER5_CAPT_vect |
Timer/Counter5 Capture Event |
TIMER5_COMPA_vect |
Timer/Counter5 Compare Match A |
TIMER5_COMPB_vect |
Timer/Counter5 Compare Match B |
TIMER5_COMPC_vect |
Timer/Counter5 Compare Match C |
TIMER5_OVF_vect |
Timer/Counter5 Overflow |
TWI_vect |
2-wire Serial Interface |
TXDONE_vect |
Transmission Done, Bit Timer Flag 2 Interrupt |
TXEMPTY_vect |
Transmit Buffer Empty, Bit Itmer Flag 0 Interrupt |
UART0_RX_vect |
UART0, Rx Complete |
UART0_TX_vect |
UART0, Tx Complete |
UART0_UDRE_vect |
UART0 Data Register Empty |
UART1_RX_vect |
UART1, Rx Complete |
UART1_TX_vect |
UART1, Tx Complete |
UART1_UDRE_vect |
UART1 Data Register Empty |
UART_RX_vect |
UART, Rx Complete |
UART_TX_vect |
UART, Tx Complete |
UART_UDRE_vect |
UART Data Register Empty |
USART0_RXC_vect |
USART0, Rx Complete |
USART0_RX_vect |
USART0, Rx Complete |
USART0_TXC_vect |
USART0, Tx Complete |
USART0_TX_vect |
USART0, Tx Complete |
USART0_UDRE_vect |
USART0 Data Register Empty |
USART1_RXC_vect |
USART1, Rx Complete |
USART1_RX_vect |
USART1, Rx Complete |
USART1_TXC_vect |
USART1, Tx Complete |
USART1_TX_vect |
USART1, Tx Complete |
USART1_UDRE_vect |
USART1, Data Register Empty |
USART2_RX_vect |
USART2, Rx Complete |
USART2_TX_vect |
USART2, Tx Complete |
USART2_UDRE_vect |
USART2 Data register Empty |
USART3_RX_vect |
USART3, Rx Complete |
USART3_TX_vect |
USART3, Tx Complete |
USART3_UDRE_vect |
USART3 Data register Empty |
USART_RXC_vect |
USART, Rx Complete |
USART_RX_vect |
USART, Rx Complete |
USART_TXC_vect |
USART, Tx Complete |
USART_TX_vect |
USART, Tx Complete |
USART_UDRE_vect |
USART Data Register Empty |
USI_OVERFLOW_vect |
USI Overflow |
USI_OVF_vect |
USI Overflow |
USI_START_vect |
USI Start Condition |
USI_STRT_vect |
USI Start |
USI_STR_vect |
USI START |
WATCHDOG_vect |
Watchdog Time-out |
WDT_OVERFLOW_vect |
Watchdog Timer Overflow |
WDT_vect |
Watchdog Timeout Interrupt |