Beim Schreiben komplexer Programme kommt man leicht in eine Situation, in der die Schaltung nicht sofort reagiert, zum Beispiel wenn eine Taste gedrückt wird. In diesem Fall ist es am besten, Unterbrechungen zu verwenden, die in diesem Artikel beschrieben werden, damit jeder die Idee hinter ihrer Verwendung versteht.
Zunächst aber ein paar Worte zu den aus der Alarmtechnik bekannten Sensoren: Reed-Schalter und Bewegungsmelder.
Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>
Alarmsensoren: Reed-Schalter
Der erste, sehr einfache Alarmsensor ist der Reed-Schalter. Er wird an Türen und Fenstern verwendet. Dieser besteht aus zwei Teilen: einer entsprechend geformten Platte (in einer Glasröhre) und einem Magneten, der an der Tür/Fenster angebracht ist.
Wenn der Magnet in die Nähe des Röhrchens gebracht wird, berühren sich die inneren Platten und es fließt Strom von der einen zur anderen Sensorleitung. Vom Arduino-Standpunkt aus gesehen ist die Situation also analog zur Verwendung gewöhnlicher Tasten.
In der Praxis werden Reedschalter für Alarmanwendungen in Gehäusen verkauft, die eine einfache Montage ermöglichen – ein solcher Sensor wird den Sets von Forbot beigefügt. Diese werden wie folgt montiert (Magnet auf dem Fenster und Reedschalter auf dem Rahmen). Schon eine minimale Kippbewegung des Fensters unterbricht den Stromkreis und löst den Alarm aus:
Der Reedschalter in der Praxis
Nun ist es an der Zeit, das Verhalten des Sensors in der Praxis zu testen. Zu diesem Zweck schließen wir eine einfache Testschaltung an. Eine handelsübliche RGB-Diode wird als Indikator für den Alarmstatus dienen. Wenn das Fenster/die Tür geschlossen ist, sollte die Diode grün leuchten, ansonsten rot.
Fertige Sets für Forbot-Kurse
Die Komponenten für die Übungen aus dem Arduino-Kurs (Stufe 2) sind als fertige Sets erhältlich! Darin enthalten sind programmierbare Dioden, analoge und digitale Thermometer, 7-Segment-Anzeigen und ein Bewegungssensor (PIR).
Liste der Verbindungen:
- LED_R über 1k-Widerstand, an Pin 10,
- LED_G über 1k-Widerstand, an Pin 11,
- LED_B über 1k-Widerstand, an Pin 12,
- gemeinsame LED-Kathode an Masse,
- ein Reedschalter-Pin an Pin 0,
- zweite Leitung des Reed-Schalters an Masse.
Der Reed-Schalter ist kein polares Element, es spielt keine Rolle,
welche der Leitungen wir mit Masse und welche mit dem Arduino verbinden!
Der Anschluss auf der Platine kann wie auf dem Bild unten aussehen. Der Einfachheit halber habe ich den Reed-Schalter mit der M3-Schraube aus dem Set und dem fertigen Loch im Sockel an das Sperrholz geschraubt.
Das äußerst einfache Funktionsprinzip des Sensors ermöglicht ein ebenso einfaches Programm. Neben dem absoluten Minimum (Pin-Konfiguration) kümmern wir uns um die Überprüfung des Zustandes des Sensors:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define REEDSCHALTER 0
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
digitalWrite(LED_R, LOW); //Diode ausgeschaltet
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}
void loop() {
if (digitalRead(REEDSCHALTER) == LOW) { //Wenn Sensor kurzgeschlossen
digitalWrite(LED_R, LOW); //Zustand OK - grüne Diode
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //Zustand ALARM - rote Diode
digitalWrite(LED_G, LOW);
}
}
Das Ergebnis ist ein einfacher Alarm, der auf den Zustand des Reed-Schalters reagiert:
Wie in der Abbildung oben zu sehen ist, müssen die beiden Teile des Reed-Schalters nicht ganz genau aneinander liegen. Ein gewisser Spielraum ist zulässig, was die Montage an Fenstern/Türen erheblich erleichtert.
Hausaufgabe 4.1
Verwende einen Summer ohne Generator (wie im vorherigen Teil des Kurses besprochen) und schreibe ein Programm, das einen akustischen Alarm auslöst, sobald der Reed-Schalter betätigt wird. Sein erneuter Kontakt (z. B. durch Schließen der Tür) sollte das akustische Signal nicht deaktivieren.
Alarmsensoren: Bewegungserkennung, PIR
Mit PIR-Bewegungsmeldern (engl. Passive Infra Red) assoziieren die meisten von uns Alarmanlagen. Aus der Sicht der Person, die einen handelsüblichen Sensor verwendet, handelt es sich um ein einfaches Gerät, das den Zustand seines Ausgangs entsprechend ändert, wenn eine Bewegung erkannt wird.
Wir werden hier nicht auf das genaue Prinzip ihrer Funktionsweise eingehen. Es ist jedoch wichtig zu wissen, dass die einfachsten dieser Sensoren auf der Messung der Infrarotstrahlung (d. h. der Temperatur) der Umgebung beruhen. Daher wird z. B. auch ein warmer Luftzug als Bewegung gewertet. Etwas „intelligentere“ Lösungen kosten dementsprechend mehr.
Interessant ist auch, wie der Sensor selbst aussieht, der sich in der Regel hinter einer speziellen Linse (im Inneren des Sensorgehäuses) befindet:
HC-SR501 - der beliebte PIR-Sensor
Für Hobbyprojekte greifen Entwickler am ehesten zu einem sehr preiswerten PIR-Sensormodul mit der Bezeichnung HC-SR501. Dieses werden wir uns jetzt ansehen. Die wichtigsten Informationen zu diesem Modul:
- Zulässige Versorgungsspannung: 5-20 V,
- Stromverbrauch: ab 40 uA in Standby, bis zu 150 uA,
- Ausgangssignal: 0 / 3,3 V,
- einstellbare Reichweite: 3-7 m,
- Erfassungswinkel: 120º.
Wie man sehen kann, können wir diesen Sensor direkt vom Arduino versorgen, da er bereits ab 5 V korrekt funktioniert. Außerdem zieht er relativ wenig Strom, so dass er den Betrieb der restlichen Schaltung nicht beeinträchtigt (wie es zum Beispiel bei der Verwendung eines Servos der Fall war). Er kann auch problemlos in batteriebetriebenen Konstruktionen verwendet werden.
Damit der Sensor beim Aufbau leichter eingesetzt werden kann, liefert der Hersteller das Ganze ohne Gehäuse. Natürlich liegt dem Set eine passende Kunststofflinse bei, ohne die der Betrieb des Sensors nicht möglich wäre.
Von der Unterseite her sind 3 interessante Komponenten zu sehen:
- Potentiometer (Tx) zur Einstellung der Dauer des High-Zustands, wenn ein Objekt erkannt wird.
- Potentiometer (Sx) zum Einstellen der Empfindlichkeit des Sensors.
- Anschluss – Stromversorgung und Ausgangssignal.
Das HC-SR501-Modul ist praktisch sofort nach dem Auspacken einsatzbereit. Standardmäßig arbeitet das gesamte Gerät im Retriggering-Modus. Dieser Modus bedeutet, dass bei der Erkennung einer Bewegung ein High-Zustand am Ausgang erscheint und für eine bestimmte Zeit (Tx) beibehalten wird, und jedes Mal, wenn erneut eine Bewegung erkannt wird, wird die Zeit (Tx) wieder von Null heruntergezählt.
Vereinfacht ausgedrückt: Am Ausgang liegt ein High-Zustand an, solange der Sensor
eine Bewegung + die am Tx-Potentiometer eingestellte Zeit erkennt.
In der Betriebsart non-retriggering erhält der Ausgang nur einmal einen High-Zustand, danach geht der Sensorausgang auf Low – unabhängig von der Erkennung weiterer laufender Bewegungen.
Die Modi werden mit Hilfe von Jumpern geändert, die auf der Sensorplatine als
L und H gekennzeichnet sind. In diesem Artikel verwenden wir den Standardmodus (H).
Die Funktionsweise des Sensors in dem von uns gewählten Modus (Retriggering) ähnelt der Funktionsweise des zuvor beschriebenen Reed-Schalters. Ein hoher Zustand am Arduino-Eingang zeigt an, dass eine Bewegung erkannt wurde. Mit dem Tx-Potentiometer kann zusätzlich eingestellt werden, wie lange der High-Status nach einer einzelnen Bewegungserkennung anhalten soll.
Wir können diesen Wert von etwa 5 Sekunden bis zu 3 Minuten einstellen.
Der Einstellbereich der Zeit und die Reichweite des Sensors variieren bei jedem Gerät geringfügig. Es ist ratsam, das eigene Modul zu testen, um sicher zu gehen.
HC-SR501 in der Praxis
Zunächst werden wir den Bewegungssensor in ähnlicher Weise wie den Reed-Schalter verwenden. Der Anschluss der RGB-Diode bleibt unverändert. Ich habe lediglich den PIR-Sensor hinzugefügt, indem ich ihn wie folgt angeschlossen habe:
- Vcc → 5V auf der Kontaktplatte,
- OUT → Pin Nr. 2 auf dem Arduino,
- GND → GND auf der Kontaktplatte.
Trotz der verlöteten Anschlüsse (Goldpins) wird der Sensor nicht direkt in die Kontaktplatte gesteckt. Dies kann auf verschiedene Arten gelöst werden. Ich habe eine Kombination aus männlich-männlich und weiblich-weibliche Anschlüssen verwendet – wie auf dem Bild unten zu sehen. Auf diese Weise habe ich einen weiblichen Anschluss in das Modul und den anderen (männlichen) Anschluss direkt in die Kontaktplatte gesteckt. Beide Arten von Kabeln sind in den Forbot-Sets für den Arduino Stufe II-Kurs enthalten.
Die gesamte Schaltung sah schließlich wie folgt aus:
Zunächst braucht man sich nicht mit den Potentiometern für die Einstellung der Impulszeit und der Reichweite zu befassen. Dreh sie einfach in die äußerste Position, wie in der Abbildung unten gezeigt. Dann ist es am einfachsten, den Effekt der Schaltung zu beobachten.
Wenn man die Potentiometer unterschiedlich einstellt, kann es anfangs viel schwieriger sein, die Funktionsweise der Schaltung zu verstehen (vor allem, wenn man einen zu hohen Wert für Tx einstellt).
Das Programm funktioniert auf ähnliche Weise wie das vorherige:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define REEDSCHALTER 0
#define PIR 2
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
pinMode(PIR, INPUT); //PIR als Eingang
digitalWrite(LED_R, LOW); //Diode ausgeschaltet
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}
void loop() {
if (digitalRead(PIR) == LOW) { //Wenn Bewegung erkannt wird
digitalWrite(LED_R, LOW); //Zustand OK - grüne Diode
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //Zustand ALARM rote Diode
digitalWrite(LED_G, LOW);
}
}
Wenn eine Bewegung erkannt wird, wechselt die Diode von grün auf rot und schaltet nach einer Weile wieder auf grün. Ich empfehle euch, die Reichweite des Sensors zu testen, da sie ziemlich groß ist!
Es ist wichtig zu bedenken, dass PIR-Sensoren mit ihrer Reichweite einen großen Bereich abdecken. Ihre Auflösung erlaubt es ihnen nicht, z.B. „einen winkenden Finger in einer Zimmerecke“ zu erkennen. Vielmehr sollten sie in Situationen eingesetzt werden, in denen es notwendig ist, auf das Vorbeigehen einer Person usw. zu reagieren.
Einzelne PIR-Sensoren erlauben es nicht, den Ort zu lokalisieren, an dem eine Bewegung erkannt wird.
Hausaufgabe 4.2
Schreibe ein Programm, das eine kurze Melodie spielt, wenn eine Bewegung erkannt wird. Ein solches Gerät kann z. B. an der Tür angebracht werden, um eine neue Person zu melden, die den Raum betritt.
Unterbrechungen auf dem Arduino
Die Alarmsensoren selbst erfordern keine neuen Programmierkenntnisse. Sie können sehr einfach in Ihren Projekten verwendet werden. Da der Hardwareteil so einfach ist, können wir uns in aller Ruhe mit Unterbrechungen beim Arduino beschäftigen, was ein völlig neues Thema ist.
Zunächst eine Anmerkung für diejenigen, die mit dem Thema vertraut sind. Ich bin mir bewusst, dass Unterbrechungen ein viel komplexeres Thema sind. In der folgenden Beschreibung habe ich jedoch versucht, einige der Informationen zu verallgemeinern und nur diejenigen einzuführen, die für den Arduino relevant sind. Ich möchte die Leser nicht mit Theorie überschwemmen, die sie am Anfang sowieso nicht verwenden werden.
Was sind externe Unterbrechungen?
Unterbrechungen sind ein Mechanismus, der es ermöglicht, unter dem Einfluss eines externen Signals den Teil des Programms, der gerade ausgeführt wird, sofort zu unterbrechen und auf eine bestimmte Funktion umzuschalten. So muss z. B. ein Alarm registriert werden, wenn der Reedschalter ausgelöst wird, egal was das Programm gerade tut.
Dies ist eine Aufgabe, die wir als zeitkritisch für das gesamte Gerät bezeichnen würden.
Es scheint, dass eine einfache Schleife, die den Zustand des Eingangs überprüft, ausreicht. Wir können es uns jedoch nicht immer leisten, das Programm nur mit der Überprüfung des Sensors zu beschäftigen. Manchmal ist eine Kommunikation mit dem Computer (UART) oder die Steuerung anderer Teile des Systems erforderlich, was alles wertvolle Zeit in Anspruch nimmt, in der wir ein Signal des Alarmsensors verpassen könnten.
Unterbrechungen im täglichen Leben
Um das Problem zu verdeutlichen, möchte ich ein Beispiel aus dem Leben nennen. Du erwartest ein wichtiges Paket, so wichtig, dass du absichtlich zu Hause bleibst und auf den Kurier wartest. Wie in solchen Situationen üblich, ist die Gegensprechanlage gerade ausgefallen und der Kurier hat Ihre Nummer nicht. Gleichzeitig würde man gerne etwas anderes zu Hause machen – zum Beispiel eine Suppe kochen.
Version 1 (ohne Unterbrechungen). Im Laufe des Tages musst du regelmäßig zum Fenster gehen, um zu sehen, ob der Kurier vielleicht schon angekommen ist(?). Du schneidest eine Karotte, gibst sie in die Suppe und schaust aus dem Fenster. Du schneidest eine weitere Karotte und gehst wieder zum Fenster. Du musst nicht nur beide Aufgaben unterbrechen, sondern, was noch schlimmer ist, der Kurier könnte gerade ankommen, während du das Gemüse schneidest.
Der Kurier hat keine Zeit, also wird er nach einer Weile wieder gehen,
und du wirst nicht einmal merken, dass du seine Ankunft bereits verpasst hast!
Version 2 (mit Unterbrechungen). Diesmal hast du nicht die Absicht, ab und zu zum Fenster zu gehen. Deshalb lächelst du deine ältere Nachbarin an, der den ganzen Tag aus dem Fenster schaut (wahrscheinlich hat jeder einen) ….
Du bittest sie, in dem Moment, in dem sie den Kurier sieht, durch die Wand zu klopfen. Von da an widmest du dich ganz dem Schneiden der Karotten. Den ganzen Tag in aller Ruhe, ohne ans Fenster zu gehen. Plötzlich hörst du ein Klopfen an der Wand – für dich ist das ein sehr wichtiges Signal – es gibt „etwas“, das genau in diesem Moment erledigt werden muss. Der Kurier ist vorgefahren und wartet, diese Aufgabe ist zeitkritisch.
Deshalb unterbrichst du, ohne zu zögern, die aktuelle Aufgabe
(sogar mitten im Schneiden) und holst das Paket ab.
Wenn du es abholst und in die Wohnung zurückkehrst, kannst du die Suppe weiter kochen, wo du aufgehört hast. Dank der Hilfe eines Nachbarn, der deine Arbeit unterbrochen hat, konntest du dich anderen Aufgaben widmen und, was wichtig ist, du hast die Ankunft des Kuriers nicht verpasst.
Unterbrechungen in der Welt der Mikrocontroller
Die obige Beschreibung, ist absichtlich von der Programmierung losgelöst, aber beachte, dass:
- Kochen der Suppe → regulärer Programmbetrieb (z. B. Statusmeldung, Kommunikation mit dem Computer, Steuerung von Komponenten).
- Ankunft des Kuriers → zeitkritische Aufgabe, z. B. Erkennung eines Alarms durch einen PIR-Sensor oder Betätigung einer Taste durch den Benutzer. Alles, was eine „hier und jetzt“-Reaktion erfordert. Niemand möchte, dass ein Gerät einen Tastendruck erst nach 5 Sekunden bemerkt.
- Nachbarin → Mechanismus zur Verarbeitung von Unterbrechungen.
- Klopfen an der Wand → Meldung der Unterbrechung.
- Empfang eines Pakets → Bearbeitung der Unterbrechung.
Damit haben wir den Unterbrechungsmechanismus ausgearbeitet!
Ich hoffe, es ist nun klar, in welchen Situationen dies hilfreich ist und welche Vorteile es bringt. Die Vorteile von Unterbrechungen:
- Wir haben einen „Helfer“ der sich im Hintergrund um die Überwachung des Eingangs (Sensor) kümmert.
- Dieser Helfer arbeitet parallel zu dem Mikrocontroller, der das Programm ausführt.
- Wir müssen keine Zeit damit „verschwenden“, den Sensor häufig zu überprüfen.
- Durch die Verwendung einer Unterbrechung wird sichergestellt, dass das Ereignis genau dann behandelt wird, wenn es benötigt wird. Selbst wenn es notwendig ist, den Teil des Programms zu unterbrechen, der gerade ausgeführt wird.
Wann ist eine Unterbrechung in der Praxis sinnvoll?
Versuchen wir nun, in der Praxis am Arduino zu zeigen, wann eine Unterbrechung erforderlich sein könnte. Gehen wir zurück zu dem früheren Beispiel, bei dem die Farbe der LED vom Zustand des Reed-Schalters abhing:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define REEDSCHALTER 0
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
digitalWrite(LED_R, LOW); //Diode ausgeschaltet
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}
void loop() {
if (digitalRead(REEDSCHALTER) == LOW) { //Wenn Sensor kurzgeschlossen
digitalWrite(LED_R, LOW); //Zustand OK - grüne Diode
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //Zustand ALARM - rote Diode
digitalWrite(LED_G, LOW);
}
}
Die Hauptschleife des Programms ist kurz und ihre einzige Aufgabe besteht darin, den Zustand des Reed-Schalters zu überprüfen. Es ist unmöglich, dass ein solches Programm das Öffnen des Reedschalters nicht erkennt. Eine einfache Änderung des Programms reicht jedoch aus, um die Situation völlig anders aussehen zu lassen…
Erweitern wir das Programm so, dass die Schleife relativ lange dauert. Wir können einen solchen Effekt erreichen, indem wir eine Steuerung hinzufügen – das Blinken der im Arduino eingebauten Diode (Pin 13) soll den Betrieb des „Alarms“ signalisieren:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define LED_SYG 13
#define REEDSCHALTER 0
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(LED_SYG, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
digitalWrite(LED_R, LOW); //Diode ausgeschaltet
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
digitalWrite(LED_SYG , LOW);
}
void loop() {
if (digitalRead(REEDSCHALTER) == LOW) { //Wenn Sensor kurzgeschlossen
digitalWrite(LED_R, LOW); //Zustand OK - grüne Diode
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //Zustand ALARM - rote Diode
digitalWrite(LED_G, LOW);
}
digitalWrite(LED_SYG, HIGH); //Diode blinkt
delay(2000);
digitalWrite(LED_SYG, LOW);
delay(2000);
}
Mal sehen, wie sich das Programm jetzt verhält! Der Alarm funktioniert nicht mehr gut. Das Ganze reagiert unterschiedlich auf Sensoränderungen – manchmal sofort, manchmal erst nach 4 Sekunden und manchmal gar nicht.
Bei einer Alarmanlage darf so etwas nicht passieren. Während das Auslösen des Alarms nach ein paar Sekunden noch akzeptabel ist, disqualifiziert das völlige Fehlen einer Reaktion eine solche Lösung. Wenn ein Dieb schnell durch ein Fenster käme, hätte unser System nicht einmal Zeit, es zu bemerken.
Wir können es uns nicht leisten, dass eine kritische Aufgabe
(„Alarmmeldung“) nicht erledigt wird.
Es ist an der Zeit, endlich eine Unterbrechung einzuführen und das Programm zu verbessern!
Unterbrechungen im Arduino
Zunächst muss in der Funktion setup() eine Unterbrechung deklariert werden. Wir werden den recht langen Befehl attachInterrupt verwenden, dessen Syntax wie folgt lautet:
attachInterrupt(digitalPinToInterrupt(PIN), FUNKTION, REAKTION_AUF);
Das Thema ist recht komplex, daher habe ich jedes Argument in einem eigenen Unterabschnitt ausführlicher erörtert.
Unterbrechungs Pin - digitalPinToInterrupt(PIN)
Das erste Argument der Funktion, die eine Unterbrechung deklariert, ist die Angabe des Pins, der als externe Unterbrechung verwendet werden soll. Unterbrechungen sind ein in Mikrocontroller eingebauter Mechanismus – es handelt sich nicht nur um ein Programmierverfahren.
Aus diesem Grund können wir nur bestimmte Pins verwenden.
Dies gilt zum Beispiel auch für UART, das immer auf den Pins 0 und 1 liegt.
Im Fall von UNO können wir nur Interrupts an den Pins 2 und 3 behandeln. Andere Boards behandeln Unterbrechungen in anderen Mengen an anderen Pins. Warum geben wir die Pin-Nummer in den Argumenten der Funktion attachInterrupt über eine andere Funktion, digitalPinToInterrupt, an?
Die Pins und ihre Nummerierung sind eine Sache, die nachfolgenden Unterbrechungen und ihre Nummerierung eine andere. Meiner Meinung nach ist eine genaue Kenntnis dieses Themas in diesem Stadium nicht erforderlich. Schaut euch einfach die Tabelle an:
Wie man sieht, liegt die mit int.0 gekennzeichnete Unterbrechung auf dem Arduino UNO-Board auf Pin 2, aber im Falle des Arduino Leonardo liegt int.0 auf Pin 3. Ich werde nicht darauf eingehen, warum das so ist.
Hier kommt die Funktion digitalPinToInterrupt ins Spiel, die, damit wir uns die Unterbrechungsnummern nicht merken müssen, die Nummer auf der Grundlage des von uns angegebenen Pins und des im Compiler ausgewählten Arduino-Board-Typs selbständig zuordnet.
Dadurch wird sichergestellt, dass ein auf dem UNO geschriebener Code auch beim Neukompilieren auf Leonardo usw. funktioniert. Ohne die besagte Funktion würde das Programm fehlerhaft (oder gar nicht) laufen.
Im Falle des Arduino UNO haben wir zwei Möglichkeiten:
attachInterrupt(digitalPinToInterrupt(2), FUNKTION, REAKTION_AUF); //Unterbrechung an Pin 2
attachInterrupt(digitalPinToInterrupt(3), FUNKTION, REAKTION_AUF); //Unterbrechung an Pin 3
In einer Unterbrechung aufgerufene Funktion
Wenn der Mikrocontroller Informationen über eine externe Unterbrechung erhält, unterbricht er die aktuelle Aufgabe und führt den Code aus, der in der von uns geschriebenen Funktion enthalten ist. Ihr Name ist das zweite Argument von attachInterrupt().
Es handelt sich um eine gewöhnliche Funktion, wie wir sie im Arduino-Kurs der Stufe I schon oft geschrieben haben. Sie kann jedoch keine Argumente annehmen oder zurückgeben. Wenn zum Beispiel eine Unterbrechung von Pin 2 die Variable Alarm auf 1 setzen würde, würde dies wie folgt geschehen:
attachInterrupt(digitalPinToInterrupt(2), einstellenAlarm, REACTION_AUF); //Unterbrechung an Pin 2
[...]
void einstellenAlarm {
Alarm = 1;
}
Man sollte sich merken, dass eine gute Funktion, die in einer Unterbrechung aufgerufen wird,
eine Funktion ist, die nur eine kurze Zeit zur Ausführung benötigt!
Kein Warten, keine schwierigen Berechnungen, keine Bedingungen, die auf andere Signale warten, usw. Ich werde jetzt nicht mehr darüber schreiben, da es in der Praxis viel einfacher sein wird, es zu verstehen.
Wann ist eine Unterbrechung zu melden (REACTION_AUF)?
Wir wissen bereits, welcher Pin als externe Unterbrechung fungieren soll und welcher Code ausgeführt werden soll. Als letzten Parameter der Funktion attachInterrupt müssen wir angeben, „worauf“ der Unterbrechungsmechanismus reagieren soll. Wir können wählen zwischen:
- LOW – Aufruf der Unterbrechung, wenn am Eingang ein Low-Zustand anliegt.
- CHANGE – Aufruf, wenn sich der Wert am Pin ändert (von High zu Low und umgekehrt).
- RISING – Aufruf, wenn sich der Wert von einem Low- zu einem High-Zustand ändert.
- FALLING – Aufruf, wenn sich der Wert von High zu Low ändert.
Die Möglichkeit, Unterbrechungen aufzurufen, wenn ein steigender oder fallender Hügel auftritt, macht es in vielen Situationen einfacher, Programme zu schreiben!
Mach dir keine Sorgen, wenn das für dich im Moment nicht selbstverständlich ist!
Es ist am besten, es in der Praxis zu verstehen.
Arduino-Unterbrechungen in der Praxis
Es ist Zeit für das erste Programm, das die Unterbrechung verwenden wird. Wir beginnen mit der Überprüfung des Zustands des PIR-Sensors, da dieser bereits mit dem Pin verbunden ist, der die Unterbrechung verarbeitet.
Das erste Programm, das die Unterbrechung verwendet, könnte wie folgt aussehen:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define REEDSCHALTER 0
#define PIR 2
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
pinMode(PIR, INPUT); //PIR als Eingang
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
attachInterrupt(digitalPinToInterrupt(PIR), Alarm, RISING); // Unterbrechung als Reaktion auf steigenden Hügel
}
void loop() {
//Nichts passiert
}
void alarm() {
digitalWrite(LED_R, HIGH); //Zustand ALARM - rote Diode
digitalWrite(LED_G, LOW);
}
Auf den ersten Blick sollte dieser Code nichts bewirken, da es keine Anweisung in der Hauptschleife loop() gibt. Man muss nur das Programm laden, um sich vom Gegenteil zu überzeugen. Wenn eine Änderung des logischen Zustands des Sensorausgangs (von Low zu High) erkannt wird, leuchtet die rote Diode auf.
Wir haben eine durch einen steigenden Hügel ausgelöste Unterbrechung (RISING) verwendet, weil wir an einer Situation interessiert sind, in der der Sensorausgang seinen Zustand von Low 0 V auf den Zustand High 3,3 V ändert, d. h. er steigt an.
Dies ist insofern interessant, da wir den Zustand des Sensors nirgendwo „direkt“ in der Software überprüfen! Das übernimmt für uns der für Unterbrechungen zuständige Hardware-Mechanismus, der auf eine Zustandsänderung des Sensors reagiert.
Es ist zu beachten, dass die Unterbrechung bei Erkennung einer Bewegung nur einmal gemeldet wird – nämlich dann, wenn der Sensor eine Bewegung feststellt und das Signal an seinem Ausgang von Low auf High wechselt.
Außerdem ist die Funktion dieses Mechanismus unempfindlich gegenüber Dingen, die in der Hauptschleife platziert sind. Zum Beispiel können wir dort eine blinkende Diode (angeschlossen an Pin 13) hinzufügen, die normalerweise eine verzögerte Reaktion auf das Sensorsignal verursachen würde:
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define LED_SYG 13
#define REEDSCHALTER 0
#define PIR 2
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(LED_SYG, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
pinMode(PIR, INPUT); //PIR als Eingang
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
attachInterrupt(digitalPinToInterrupt(PIR), Alarm, RISING); // Unterbrechung auf steigenden Hügel reagierend
}
void loop() {
digitalWrite(LED_SYG, HIGH);
delay(2000);
digitalWrite(LED_SYG, LOW);
delay(2000);
}
void alarm() {
digitalWrite(LED_R, HIGH); //Zustand ALARM - rote Diode
digitalWrite(LED_G, LOW);
}
Hausaufgabe 4.3
Schreibe ein Programm, das mit Hilfe von Unterbrechungen einen Alarm auslöst (Aufleuchten der roten LED), und zwar nicht, sobald der Sensor eine Bewegung erkennt, sondern erst, wenn die Bewegung aufhört.
Übermittlung von Informationen "von" der Unterbrechungsfunktion
Wie bereits erwähnt, kann die für die Bearbeitung der Unterbrechung zuständige Funktion keinen Wert über den Standard Mechanismus return zurückgeben. Dies kann natürlich „umgangen“ werden, indem man z.B. globale Variablen verwendet. Man muss jedoch sicherstellen, dass alle globalen Variablen, die innerhalb der Unterbrechungsfunktion verwendet werden, mit einem speziellen Präfix – volatile – deklariert werden.
Durch die Verwendung des Präfixes „volatile“ wird sichergestellt, dass der Compiler nicht versucht, diese Variable zu optimieren, wodurch viele schwer zu erkennende Fehler vermieden werden. Wäre die Optimierung aktiviert, würde das Programm möglicherweise „nicht bemerken“, dass der Wert der Variablen in einer Unterbrechung geändert wurde.
Versuchen wir, ein Programm zu erstellen, das zählt, wie oft der PIR-Sensor eine Bewegung erkannt hat, und die Farbe der RGB-LED davon abhängig macht. Dazu erhöhen wir einfach den Wert der Variablen, die die Anzahl der steigenden Hügel in der entsprechenden Unterbrechung zählen wird.
#define LED_R 10
#define LED_G 11
#define LED_B 12
#define LED_13 13
#define REEDSCHALTER 0
#define PIR 2
volatile int wievielMal = 0;
void setup() {
pinMode(LED_R, OUTPUT); //Einzelne Dioden-Steuerpins als Ausgänge
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
pinMode(PIR, INPUT); //PIR als Eingang
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
attachInterrupt(digitalPinToInterrupt(PIR), alarm, RISING); // Unterbrechung als Reaktion auf steigenden Hügel
}
void loop() {
if (wievielMal < 3) { // Akzeptabel - grüne Farbe
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
} else if (wievielMal < 6) { //Beginnt gefährlich zu werden - blaue Farbe
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, HIGH);
} else { //Alarm - mehr als 6 mal Bewegung erkannt - rote Farbe
digitalWrite(LED_R, HIGH);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}
}
void alarm() { //Unterbrechung
wievielMal++; //Es wurde ein weiterer Alarm erkannt
}
Die Funktionsweise des Programms in der Praxis ist wie erwartet. Die Farbe der LED ändert sich mit steigender Zahl und zeigt damit an, dass der PIR-Sensor eine Bewegung erkannt hat.
Hausaufgabe 4.4
Schreibe ein Programm, das mit Hilfe von Unterbrechungen den Zustand einer LED jedes Mal in den entgegengesetzten Zustand ändert, wenn der PIR-Sensor eine Bewegung erkennt. Das heißt: erste Erkennung – leuchtet, zweite Erkennung leuchtet nicht, usw.
Was darf eine Funktion aus einer Unterbrechung nicht enthalten?
Die Verwendung von Unterbrechungen kann uns bei vielen Projekten das Leben erleichtern. Es ist jedoch genauso leicht, in eine Falle zu tappen. Eine unsachgemäße Verwendung von Unterbrechungen kann dazu führen, dass sich das Programm sehr seltsam verhält und es schwierig wird, die Ursache zu finden.
Daher sollte man beim Schreiben von Funktionen, die durch Unterbrechungen aufgerufen werden, Folgendes beachten:
- Es dürfen auf keinen Fall Verzögerungen (delay, delayMicroseconds, etc.) in sie eingebaut werden.
- Führe in ihnen keine Operationen durch, die viel Zeit in Anspruch nehmen.
- Operationen, die innerhalb einer Unterbrechung ausgeführt werden, sollten extrem kurz sein. Wie oben – eine Variable inkrementieren und das war’s. Wir kümmern uns nicht einmal mehr um das Einschalten der entsprechenden LED. Das können wir ganz normal in einer Schleife machen – das einzig zeitkritische war die Tatsache, dass der PIR-Sensor eine Bewegung registriert hat.
Zum Glück für den Arduino funktioniert die delay()-Funktion nicht einmal, wenn wir sie in eine Unterbrechungsfunktion einbauen. Die Funktion delayMicroseconds() funktioniert jedoch einwandfrei, aber wir sollten sie nicht verwenden, um künstlich längere Verzögerungen im Programm hervorzurufen.
Wenn eine Funktion, die in einer Unterbrechung ausgeführt wird, sehr lange dauert, wirkt sich dies auf den Betrieb des gesamten Programms aus. Dies kann unter anderem zu künstlich verlängerten Verzögerungen führen, die durch delay() Funktionen erzeugt werden.
Warum ist das wichtig? Wenn eine Funktion aus einer Unterbrechung z.B. 5 Sekunden für die Ausführung benötigen würde (was viel zu lang ist), führt jedes Auftreten der Unterbrechung zu einer solchen Verzögerung im gesamten Programm.
Denk daran, dass der ganze Vorteil des fraglichen Mechanismus darin besteht, dass der Mikrocontroller die laufende Operation unterbricht, um die Funktion der Unterbrechung auszuführen. Wenn wir den Zustand der Diode in der Hauptschleife jede Sekunde ändern würden, würde jeder Unterbrechungsaufruf die Diode für weitere 5 Sekunden einfrieren.
Das heißt, der folgende Code wäre in der Schleife sichtbar, und die LED würde unterschiedlich blinken – manchmal würde sich ihr Zustand alle 1 Sekunde ändern, manchmal alle 6 Sekunden (delay(1000) + Unterbrechungszeit):
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
Beim Schreiben längerer, komplexer Programme wäre es sehr, sehr schwierig, die Ursache eines solchen Fehlers zu finden!
Natürlich kann man von den oben genannten Regeln abweichen, aber nur, wenn man mit dem Thema Unterbrechungen vertraut ist und genau weiß, was nach solchen Änderungen passieren wird. In 99,99% der Fälle, wenn du gerade mit dem Programmieren anfängst und davon überzeugt bist, dass du eine Verzögerung in einer Unterbrechung einführen musst, liegst du jedoch falsch – das Problem kann sicherlich auch anders gelöst werden.
Wann sollte man Unterbrechungen einsetzen?
- Wenn die Reaktion auf ein externes Signal zeitkritisch ist (z. B. Alarmerkennung).
- Wenn das Programm sehr komplex ist (viele Funktionen, Schleifen, Verzögerungen) und wir zudem ständig reagieren müssen, z. B. auf Tasten/Sensoren.
Wann sollte man keine Unterbrechungen einsetzen?
- Wenn das Programm relativ kurz ist oder keine Verzögerungen aufweist, so dass wir in der Lage sind, alle Eingaben laufend zu überprüfen.
- Wenn die Erfassung der Informationen von den Eingängen/Sensoren nicht zeitkritisch ist.
Natürlich sind alle Bemerkungen über das „Einsparen von Unterbrechungen“ sinnvoll, wenn wir einen umfangreichen Code schreiben. Wenn die Aufgabe, die wir erfüllen wollen, einfach ist und die Verwendung einer Unterbrechung unsere Arbeit erleichtern kann, dann lohnt es sich, sie einzusetzen.
Der wichtigste Punkt ist, dass man nicht zu sehr darauf aus ist, alles mit Unterbrechungen durchzuführen, weil man sich dann leicht eine Menge Probleme bereiten kann.
Zusammenfassung
Die Verwendung von Unterbrechungen im Arduino ist nicht schwierig, es sind nur ein paar Zeilen Code mehr. Schwierig ist es jedoch, es richtig und bewusst zu tun. Ich empfehle, mit Interrupts herumzuspielen und zu experimentieren. Es ist besonders wichtig zu verstehen, wie sich die Auslösung von Unterbrechungen durch Hügel (ansteigend/absteigend) von der Auslösung durch Zustände unterscheidet. Ich werde versuchen, in weiteren Artikeln mehr Informationen über Unterbrechungen in der Praxis zu geben.
Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>