Interrupts
Alle Dinge brauchen ihre Zeit und ihre Reihenfolge. Mikrocontroller stecken in der Waschmaschine, wie auch in modernen Automobilen. Messen, steuern, regeln und arbeiten in der Regel ihr Programm ab. In diesem Abschnitt bestimmen der Arduino und vor allem die Synthax seiner Entwicklungsumgebung das Geschehen und die Erläuterungen. Interrupts stellen Unterbrechungen des regulären Programmablaufs dar. Weil diese Unterbrechungen bestimmten Regeln unterworfen sein müssen zu denen auch die zu erreichende Minimierung der Unterbrechungsdauer gehört, fokussieren sich fortgeschrittene Beiträge zu diesem Thema auf hardwarenahe Formulierungen des Programmcodes. Wenn die folgenden Zeilen dennoch bei Programmcodes in C oder C++ bleiben, dann 1. zur Unterstützung des Verständnisses und 2. zur Verallgemeinerbarkeit der Aussagen und Codes, denn hardwarenahe Programmierung und unmittelbare Manipulation von Adressen gelten oft nur für den gewählten Prozessortyp. Eine Grundlage dieses Textes bildet der Text „Arduino Interrupt-Handling Teil1“ von Erik Bartmann [1].
Polling
Meist erledigt ein Mikrocontroller viele Aufgaben mehr oder weniger zugleich. Beim Programmen, die mit der Arduino-IDE geschrieben werden, finden sich diejenigen Aufgaben, die einmalig zu erledigen sind (z. B. Initialisierungen) in der Setup-Routine (void setup), ständig zu bearbeitende Aufgaben finden sich in der Loop-Schleife (void loop). Die „oder weniger“ Aussage des einführenden Satz erscheint berechtigt, weil alle Aufgaben in der Reihe ihrer Eintragung in die Loop-Schleife nacheinander bearbeitet werden und jede Arbeit nunmal ihre Zeit benötigt. Für die Steuerung des Schrittmotors werden Bits gesetzt und an die Digitalpins geschickt. Ein Lesen eines analogen Wertes von einem Analog-Pin erfordert die Aufbereitung des Wertes und dessen Weitergabe. Je nach Prozessorgeschwindigkeit, -Architektur und innerer Struktur, können von der Anforderung bis zur Verfügbarkeit eines Messwertes mehrere Taktzyklen vergehen. Im Beispiel des einfachen Blink-Programms wartet der Prozessor auf das Vergehen von Zeit bevor er eine weitere Aktion ausführt. Das Beispiel-Programm „BlinkWithoutDelay“ zeigte, dass zumindest die Wartezeit auch für weitere Aktionen nutzbar gemacht werden kann. Erfolgt die Abfrage innnerhalb einer Schleifenstruktur, nennt man die „Polling“. Die Abfrage von Temperatur und Luftfeuchtigkeit eines DHT22-Sensors kann sonsorbedingt nicht schneller als alle 2 Sekunden erfolgen. Aktoren benötigen für das Ausführen einer Aktion ebenfalls eine Mindestzeit. Ein Schrittmotor kann sich nicht beliebig schnell drehen, was der Geschwindigkeit (eigentlich Frequenz) der Änderung der an den Schrittmotortreiber gesendeten neuen Bitmuster Grenzen setzt.
Was nun, wenn das Ergebnis eines Sensors so wichtig ist, dass es bevorzugt werden muss? Ein Leckage-Sensor in der Waschmaschine, der Sensor für die Unfallerkennung beim Auto (Auslösen der richtigen Airbags) …. .
Sofortunterbrechung durch Interrupt
Die im vorigen Absatz gestellte Frage wird zur Aufgabe. Interrupts sind eine Antwort (dynamisch priorisierte Zeitscheiben können ebenfalls helfen). Das Kurztutorial „Arduino – Interrupts“ gibt eine sehr knappe Einführung in das Thema [2]. Tiefer sehr dicht an der verwendeten Hardware und ausführlich mit Beispielen führt Nick Gammon in das Thema ein [3]. Generell unterscheidet man zwischen Hardware Interrupts und Software Interrupts. Auslöser können Pegel oder Pegeländerungen digitaler Eingänge oder Timer sein. Die Anzahl einen Interrupt auslösender Vorgänge ist in jedem Fall begrenzt und ein Merkmal des betrachteten Systems. Beim Arduino UNO sind es zwei digitale Eingänge, die mit der Auslösung eines Interrupts verknüpfbar sind (DIO2 und DIO3). Notaus, Endschalterabfrage, Blockadeerkennung eines Aktors …. in konkreten Anwendungen addieren sich die gewünschten Möglichkeiten zur Erzeugung von Interrupts schnell über die zunächst vorgesehenen Möglichkeiten des Prozessors. Das Auslesen eines Drehgebers (rotatory encoder) ist eine Musteranwendung für den Einsatz von Interrupts.
Das folgende Video von Erik Bartmanns Demonstration zum Einsatz von Interrupts verdeutlicht das Problem, Aufgabe und Lösung
Youtubelink: https://www.youtube.com/watch?v=TpiDYoif0HY
In der ersten Version Schaltung des Lauflichts scheint die Reaktion auf die Betätigung des Tasters zu „klemmen“. Die durch Tasterbetätigung einzuschaltende grüne LED reagiert erst nach ausreichend langem Tastendruck beziehungsweise unmittelbar am Ende einer Delay-Zeit. Diese Erfahrung deckt sich mit der auf diesen Seiten formulierten Aufgabe, Tastendrücke während des Ablaufs der einfachen „Blink“-Demo zu zählen. Leider funktionieren Taster nicht immer im erwarteten Rahmen. Sie leifern mehr als einen Impulswechsel, bevor sich ein festes, neues Potenzial einstellt. Tasten können „prellen“ [4]. Die Ursache ist mechanisch bedingt und die Geschwindigkeit und Häufigkeit der Wechsel nicht genau vorherzusagen.
Die Erkennung von Interrupts erfolgt auf Basis Erkennung von ansteigenden Pegeln (rising), abfallenden Pegeln (falling) oder sich ändernden Pegeln (change) des zur Interruptauslösung verwendeten Digitalpins. Die Erkennung von Interrupts erfolgt sehr schnell (< µs). Prellt ein Taster am interruptfähigen Digitalpin, löst dies eine Serie von Interrupts aus. Mittels „Debouncing“ / Entprellen kann diesem Verhalten entgegengewirkt werden. Debouncing erfordert zusätzlichen Code oder zusätzliche Hardware (Kondensator 10 bis 100 nF). Um sich auf dem Problem der Interrupts widmen zu können, ohne die Ungewissheit möglicherweise falscher Ergebnisse durch prellende Taster fürchten zu müssen, steht die Überlegung im Raum, Taster zu verwenden, die bauartbedingt nicht prellen können.
Lichtschranken prellen nicht
Die 37 in 1 Sensorkits unterschiedlicher Hersteller enthalten einen Baustein, den sie zum Teil sehr unterschiedlich nennen. Für die folgenden Aufbauten kommt auf den Typ „KY-010 Photo Interrupter Module“ zum Einsatz eine Anleitung von annähernd 200 Seiten stellt die Firma Elegoo unter den „Electronic Component Kits“ -> „Elegoo 37 in 1 Sensor Modules Kit“ zur Verfügung [5].
Dieses Modul erzeugt einen „High“-Pegel, wenn ein Gegenstand zwischen LED und Fototransistor gelangt.
Fall 1: Wie der internen Beschaltung der Gabellichtschranke KY-010 zu entnehmen ist, wird der Fototransistor leitend, sobald Licht auf seine lichtsensible Basis fällt. Bei Beleuchtung des Fototransistors schwindet der Widerstand zwischen Collektor und Emitter des Fototransistors und das an DIO herausgeführte Potenzial des Spannungsteilers aus 4,7 kOhm-Widerstand und Fototransistor fällt gegen GND.
Fall 2: Wird die Basis des Fototransistors nicht beleuchtet, gibt es keinen Fotostrom mehr und der Widerstand zwischen Collector und Emitter steigt auf hohe Werte. Die größere Spannung fällt im Spannungsteiler am größeren Widerstand ab. Das an DIO herausgeführte Potenzial steigt gegen +5 V.
Material und Aufbau
- Arduino NANO oder Arduino UNO
- Steckbrett
- KY-010 Photo-Interrupter Module
- diverse Steckbrücken
- Digitalmultimeter
Gerne kann zur Erweiterung auch noch eine LED über einen 220 Ohm Widerstand an einen digitalen Ausgang gehängt werden. Die könnte dann anzeigen, ob die Anzahl der bisher gezählten Tastendrücken / Lichtunterbrechungen / Interrupts auf DIO2 gerade oder ungerade ist.
Das sähe dann so aus:
Die eingebaute LED auf Pin 13 dient als blinkende LED. Verwendung findet zunächst der Code aus der Blink-Demo, erweitert um eine Zählroutine, die ihr Ergebnis auf dem Seriellen Monitor ausgibt. Solange der Lichtweg in der Gabellichtschranke nicht unterbrochen ist, sollte der DIO-Pin der Gabellichtschranke ein „Low“-Potenzial ausgeben. Dies ist mit dem Digitalmultimeter zu verifizieren. Eine Unterbrechung des Lichtweges sollte ein 5 V-Potenzial ergeben. Jeweils gemessen zwischen dem auf der Abbildung linken Beinchen des Moduls (GND) und dem rechten Beinchen des Moduls (Signal). Eine Unterbrechung führt zu einem Anstieg der Flanke (RISING), eine Freigabe des Lichtweges demnach zu einem Abfall des Potenzials (FALLING).
Man kann sich zunächst auch mit dem Photo Interrupter Module in einem Vorversuch vertraut entsprechend machen. [6] Auch eine Anpassung des Codes aus der „Button“-Demo der Arduino-IDE ist einfach möglich. Der folgende Code gibt eine von vielen Möglichkeiten zur Umsetzung der Aufgabe.
// Interrupt-Beispiel mit KY-010-Photo Interrupter Module
// 06.06.2021 S. Schlote
int externeLED = 12; // Led zur Anzeige von Status der Lichtschranke
// oder Geradzahligkeit der Ereignisse etc.
int pimInputPin = 2; // interruptfähiger Eingangspin
volatile int pimStatus = LOW; // Status der Lichtbrücke
volatile unsigned long breakCounter = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(externeLED, OUTPUT);
pinMode(pimInputPin, INPUT);
attachInterrupt(digitalPinToInterrupt(pimInputPin), BreakCounterFunc, RISING);
}
void BreakCounterFunc() // ISR: Interrupt-Service-Routine
{
++breakCounter;
pimStatus = !pimStatus;
}
void loop() {
digitalWrite(externeLED, pimStatus);
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
Serial.println(breakCounter);
}
Abgesehen von der Formatierung erfüllt der Code die geplanten Aufgaben. Natürlich lässt sich durch Abfrage eines Status oder die Verwendung temporärer Variablen die Ausgabe auf den seriellen Monitor so optimieren, dass eine erneute Aufgabe der Zählvariable „breakCounter“ nur erfolgt, wenn sie sich geändert hat. Die Anzeige via LED, ob es sich um eine gerade Anzahl von Unterbrechungen oder eine ungerade Anzahl hat lässt sich durch Prüfung der Zählvariable bewerkstelligen. Nach der Art „if (breakCounter & 1) Serial.println(breakCounter)“. Es wird das letzte Bit der ganzzahligen Variablen geprüft. Ist es gesetzt, muss die Zahl wohl ungerade sein.
Der vorliegende Code demonstriert die Implementierung einer ISR (InterruptServiceRoutine). Die Funktion breakCounterFunc soll die laufenden Prozesse so kurz wie möglich unterbrechen. In ihr verwendete Variablen müssen bestimmten Vorgaben entsprechen, wenn sie von der ISR verändert und auch von nicht ISR-Funktionen verwendet werden soll. Sie werden als „volatile“ Variablen gekennzeichnet. Auf globale Variablen kann die ISR zugreifen, sollte diese jedoch nicht verändern, um Inkonsistenzen zu vermeiden [7].
Die Demonstration im Video zeigt keine Ausgaben von breakCounter auf dem seriellen Monitor. Diese Ausgaben entsprechen zu 100% dem erwarteten Verhalten. Das Verhalten der blauen LED lässt sich aus dem Code erklären.
Die Funktion attachInterrupt verknüpft innerhalb der Setup-Funktion den externen Interrupt mit im Fall des eintretenden Interrupt-Ereignisses auszuführenden Funktion. Sie legt ebenfalls fest, welcher Trigger-Modus dieses Ereignis qualifiziert. Mögliche Typen sind: „LOW“; „CHANGE“; „RISING“; „FALLING“. Für die Auswahl der Interrupts besteht einerseits die Möglichkeit der direkten Angabe der Interruptnummer. Diese entspricht beim Arduino UNO nicht der Pinnummer. Andererseits lässt sich die Nummer des Interrupts mithilfe der Funktion „digitalPinToInterrupt(Pinnummer)“ aus der Nummer des verwendeten DIO-Pins ermitteln. Dies ist der empfohlene Weg.
Zur Verifikation:
a) Welches Verhalten zeigt die Variable breakCounter im seriellen Monitor im Zusammenhang mit dem jeweils gewählten Trigger-Modus?
b) Das Zeitverhalten der externen LED ist unbefriedigend. Welche Gründe gibt es für dieses Verhalten und wie lässt sich das Verhalten optimieren?
c) Positionierende Maschinen verfügen neben dem „Not-Aus“ auch über Endschalter. Wie könnte eine Implementation einer Initialisierungsfahrt eines 3D-Druckers (die Verfahrwege gehen in die drei Raumrichtungen) so realisiert werden, dass die 3 Endschalter abgefragt, zu neuen Ausgangkoordinaten (Nullkoordinaten) werden und die Position der 3 Verfahrwege bei Unterbrechung durch Not-Aus auf dem seriellen Monitor angezeigt werden?
Quellen:
[1] „Die elektronische Welt mit dem Arduino entdecken“, Erik Bartman, 1. Auflage 2012; Freies Zusatzmaterial „#002 Interrupt-Handling Teil 1“
https://docplayer.org/37214141-Arduino-interrupt-handling-teil-1-seite-2.html
[2] https://www.tutorialspoint.com/arduino/arduino_interrupts.htm
[3] http://gammon.com.au/interrupts
[4] https://docplayer.org/110104236-Abbildung-4-4-1-ein-prellender-taster.html
[5] Code und Erläuterung zum Lichtschrankenmodul aus dem 37 Sensor-Kit
[6] https://arduinomodules.info/ky-010-photo-interrupter-module/
[7] https://www.arduino.cc/reference/de/language/variables/variable-scope-qualifiers/volatile/
[8] https://www.arduino.cc/reference/de/language/functions/external-interrupts/attachinterrupt/