Arduino Kurs Stufe II – #9 – Multitasking, Verzögerungen mit millis()

 

Die Funktion delay (zur Eingabe von Verzögerungen) ist eines der ersten Dinge, die man lernt, wenn man Arduino kennenlernt. Ihr Betrieb kann jedoch eine Menge Probleme verursachen.

Zum Glück gibt es eine ausgefeiltere Lösung, die auf der Funktion millis basiert. Sie ermöglicht es dem Arduino, mehrere Aufgaben „gleichzeitig“ auszuführen.

Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>

Multitasking-Problem des Arduino

Das häufigste Beispiel, um das lernen mit dem Arduino zu beginnen, ist das Blinken einer LED. Meistens sieht es wie folgt aus:

				
					#define LED1pin 3

void setup() {
  //Pin, an dem die LED als Ausgang angeschlossen ist
  pinMode(LED1pin, OUTPUT);
}

void loop() {
  digitalWrite(LED1pin, HIGH); //Diode einschalten
  delay(1000); //warte 1000 ms
  digitalWrite(LED1pin, LOW); //Diode ausschalten
  delay(1000); //warte 1000 ms  
}
				
			

Der kurze Inhalt der Schleife loop() reicht aus, um die LED zum Blinken zu bringen. Hinweis: Der Übergang vom Einstellen des entsprechenden Zustands am Ausgang zu delay() ist augenblicklich, in der Animation unten dauert es einen „winzigen Moment“, bis sichtbar wird, was passiert:

Inhalt der Schleife loop() - Blinken einer Diode.

Viele kommen sofort auf die Idee, zwei LEDs (und zwar in unterschiedlichen Frequenzen) blinken zu lassen. So kommt man zu dem folgenden Programm:

				
					#define LED1pin 3
#define LED2pin 4

void setup() {
  //Pin, an dem die Diode als Ausgang angeschlossen ist
  pinMode(LED1pin, OUTPUT);
  pinMode(LED2pin, OUTPUT);
}

void loop() {
  digitalWrite(LED1pin, HIGH); //Diode einschalten
  delay(1000); //warte 1000 ms
  digitalWrite(LED1pin, LOW); //Diode ausschalten
  delay(1000); //warte 1000 ms  

  digitalWrite(LED2pin, HIGH); //Diode einschalten
  delay(500); //warte 500 ms
  digitalWrite(LED2pin, LOW); //Diode ausschalten
  delay(500); //warte 500 ms  
}
				
			

Die Funktionsweise dieses Codes weicht von dem ab, was die meisten erwarten. Es ist wichtig, sich daran zu erinnern, dass der Arduino alle Anweisungen der Schleife loop() Zeile für Zeile ausführt. Der Mikroprozessor kann immer nur eine Operation auf einmal ausführen.

Außerdem ist zu beachten, dass die Verzögerungsfunktion delay() das laufende Programm vollständig anhält!

Statt des erwarteten gleichzeitigen Blinkens der LEDs erhalten wir etwas Seltsames:

Die Funktionsweise des Programms, bei dem angeblich zwei LEDs (gleichzeitig) blinken sollten.

Auf den ersten Blick mag dies überraschen. Schließlich führen die Computer, die wir benutzen, eine Vielzahl verschiedener Operationen gleichzeitig aus (selbst solche mit einem Single-Core-Prozessor). Tatsächlich ist der Prozessor eines PCs in diesem Fall nicht besser als der Mikrocontroller eines Arduino. Auch er führt jeweils nur eine Operation aus. Die Multitasking-Natur von Computern besteht darin, dass sie sehr schnell zwischen verschiedenen Aufgaben wechseln (Tausende von Malen pro Sekunde). Aus menschlicher Sicht ist dies nicht zu bemerken.

Der Effekt ist vergleichbar mit dem Multiplexing von 7-Segment-Displays: Dort leuchtet immer nur eine Ziffer auf, der Rest ist auf die Trägheit unserer Augen zurückzuführen.

Wann kann delay() verwendet werden?

Die Funktion delay() ist äußerst einfach und praktisch. Leider blockiert jedes Auftreten der Funktion das gesamte Programm. Bei einfachen Beispielen ist das kein großes Problem. Bei großen Projekten ist es jedoch problematisch. Die Verwendung dieser Funktion kann zu Programmfehlern führen, die schwer zu erkennen sind (z. B. vorübergehende Probleme mit bestimmten Bibliotheken).

Die Funktion delay() kann nur dann verwendet werden, wenn man sich zu jeder Zeit bewusst ist, dass ihre Funktionsweise darin besteht, die Ausführung des gesamten Programms „einzufrieren“!

Was anstelle von delay()?

Es gibt eine Reihe von Bibliotheken, die es ermöglichen, „magische“ nicht-blockierende Verzögerungen einzuführen. Natürlich sollte man sich das Leben leichter machen und zu solchen vorgefertigten Bibliotheken greifen. Viele wissen jedoch nicht, wie diese Verzögerungen funktionieren, was zu weiteren, noch seltsameren Fehlern führt.

Deshalb ist es eine gute Idee, mit dem Umdenken zu beginnen und Programme auf eine andere Art zu schreiben. Daher ist es am besten, von vorne anzufangen, ohne vorgefertigte Bibliotheken….

Im Arduino eingebaute Stoppuhr - millis()!

Im Arduino finden wir die Funktion millis(). Ihre Funktionsweise lässt sich am besten mit einer Stoppuhr vergleichen, die beim Einschalten des Arduino startet. Diese Funktion gibt die Anzahl der Millisekunden zurück, die seit dem Einstecken des Boards verstrichen sind. Wir brauchen diese „Stoppuhr“ nicht zu starten, das macht der Arduino für uns. Sie funktioniert immer und in jedem Programm. Der Zählvorgang ist in die Hardware implementiert, also mit Hilfe von Zählern (Timern). Die Angaben von millis() können nicht versehentlich verfälscht werden.

Der Arduino hat eine eingebaute Stoppuhr!

Auch die Funktion delay() kann die interne Stoppuhr nicht anhalten, deren Ergebnis wir mit millis() ablesen können!

Die Verbindung der beiden genannten Funktionen (delay und millis) verfehlt das Ziel und kann einige Probleme verursachen, aber es ist gut zu wissen, dass es möglich ist – wir werden darauf zurückkommen.

Wie verwendet man millis()?

				
					unsigned long aktuelleZeit = millis();
				
			

Schauen wir uns nun an, wie diese Stoppuhr in der Praxis funktioniert. Wir können damit beginnen, die aktuelle Zeit über den UART an den Computer zu senden. Natürlich können wir sie nicht ohne Unterbrechungen senden, weil wir sonst den Datenpuffer verstopfen und alles zum Absturz bringen würden.

Wir wissen noch nicht, wie wir das Programm auf „geschickte Weise“ anhalten können, also benutzen wir ein letztes Mal das „unglückliche“ delay():

				
					unsigned long aktuelleZeit = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();
  //Senden an PC
  Serial.println(aktuelleZeit);

  // warte 1000 ms
  delay(1000);
}
				
			

Sobald das Programm auf dem Monitor der seriellen Schnittstelle gestartet ist, wird die Anzahl der Millisekunden, die seit dem Start des Arduino verstrichen sind, etwa im Sekundentakt angezeigt:

Der Effekt des Programms.

Ich habe nicht ohne Grund „etwa im Sekundentakt“ geschrieben, denn wie man sehen kann, gibt es leichte Unregelmäßigkeiten von +/- 1 Millisekunden. Dieses Experiment zeigt übrigens einen weiteren Nachteil von delay(): Das Zählen der Zeit ist mit dieser Methode nicht genau. Natürlich ändert 1 Millisekunde hier nicht viel. Würde die Schaltung jedoch die ganze Zeit laufen (z. B. als Uhr), wären die Abweichungen im Laufe der Zeit erheblich.

Fertige Sets für Forbot-Kurse
 Satz von Elementen   Garantierte Unterstützung   Versand in 24 Stunden

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).

Welche Einschränkung hat die Funktion millis()?

Wie man in der früheren Animation sehen konnte, wächst der zurückgegebene Wert schnell an. Irgendwann wird dieser Zähler überlaufen (seinen Höchstwert überschreiten und auf Null zurückkehren). Glücklicherweise tritt der Überlauf in diesem Fall erst nach 50 Tagen Betrieb auf.

Das bedeutet natürlich nicht, dass man den Arduino alle 50 Tage zurücksetzen sollte. Der Zähler wird überlaufen und wieder von vorne anfangen zu arbeiten. Wenn das Programm korrekt geschrieben ist, hat das Zurücksetzen des Zählers keinen Einfluss auf seinen Betrieb.

Darüber hinaus sollte man bedenken, dass Operationen mit so großen Zahlen (unsigned long) zu einigen Fehlern und logischen Abweichungen führen können. Vor allem, wenn wir versuchen, mathematische Operationen mit kleineren Variablen (z.B. vom Typ int) durchzuführen!

Eine bessere Variante der Zeitmessung

Wir wissen bereits, dass im Arduino eine genaue Stoppuhr eingebaut ist. Jetzt ist es an der Zeit, sie zu verwenden, um Verzögerungen zu erzeugen. Wir können die Stoppuhr nicht zurücksetzen, noch werden wir sie in irgendeiner Weise beeinflussen. Alles, was wir tun müssen, ist, die aktuelle Zeit in Bezug auf den Start des Arduino zu kennen.

Wie sollte die Zeitmessung für eine Sekunde aussehen?

  1. Wir prüfen die aktuelle Zeit und merken sie uns.
  2. In jedem Schleifendurchlauf überprüfen wir die aktuelle Zeit:
      1. Wenn der Unterschied zwischen der aktuellen Zeit und der zuvor gespeicherten Zeit weniger als 1 Sekunde beträgt, bedeutet dies, dass die gewünschte Zeit noch nicht verstrichen ist.
      2. Wenn der Unterschied zwischen der gespeicherten Zeit und der aktuellen Zeit 1 Sekunde beträgt, dann …. ist genau so viel Zeit vergangen!

Das Entscheidende an diesem Ansatz ist, dass das Programm ständig „im Kreis läuft“ und nirgendwo stehen bleibt. Es überprüft ständig, wie viel Zeit vergangen ist, und kann gleichzeitig andere Dinge tun.

Nun ist es an der Zeit, den obigen Algorithmus in einen Code zu übertragen:

				
					unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeit = 0;
unsigned long differenzZeit = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();
  differenzZeit = aktuelleZeit - gemerkteZeit;
  
  //Wenn die Differenz mehr als eine Sekunde beträgt
  if (differenzZeit >= 1000UL) {
    //Merke aktuelle Zeit
    gemerkteZeit = aktuelleZeit;
    //Senden an PC
    Serial.println(aktuelleZeit);
  }
}
				
			

Die Variablen, die die Zeit speichern, sind vom Typ unsigned long. Um Probleme beim Vergleich der Werte zu vermeiden, wurde die Anzahl der Millisekunden mit dem Zusatz UL (1000UL) geschrieben.
Dadurch wird sichergestellt, dass der Compiler 1000 als einen Wert vom Typ unsigned long behandelt.

Das Programm prüft die aktuelle Zeit, zählt die Differenz und wenn diese größer oder gleich 1000 ist, wissen wir, dass definitiv eine Sekunde vergangen ist. Zur Sicherheit wird in die Bedingung eine Ungleichheit anstelle des festen „== 1000“ gesetzt. Bei sehr komplexen Programmen kann es vorkommen, dass wir aus irgendeinem Grund die 1000 nicht perfekt treffen. Diese Ungleichheit stellt sicher, dass das Programm nicht „abbricht“ und wir bei der nächsten Gelegenheit in die Bedingung eintreten (z.B. 1001ms).

Wir wissen, dass eine Sekunde vergangen ist, wenn die Bedingung erfüllt ist. Deshalb merken wir uns gleich zu Beginn die aktuelle Zeit als die vorherige Zeit (in der die Bedingung erfüllt war). Wir zählen die nächste Sekunde ab dem neuen Wert. In diesem Fall ist die Bedingung erfüllt, wenn der Millisekundenzähler wieder 1000, 2000, 3000, 4000 usw. anzeigt.

Wie das Programm in der Praxis funktioniert, ist unten zu sehen:

Man sieht sofort den Vorteil gegenüber der Funktion delay(), hier wird die Zeitausgabe perfekt jede Sekunde aufgerufen!

Durchführung der zweiten Version des Programms in der Praxis.

Natürlich ist die Variable differenzZeit überflüssig (hier aus Gründen der Lesbarkeit verwendet). Man kann ebenso gut die Zeitdifferenz direkt in der Bedingung zählen:

				
					//Wenn die Differenz mehr als eine Sekunde beträgt
if (aktuelleZeit - gemerkteZeit >= 1000UL) {
				
			

Blinkende Diode ohne delay()

Nun ist es an der Zeit, die gewonnenen Informationen zum Blinken der Diode zu verwenden. Wir können die Zeit genau messen, so dass es nur noch darum geht, den Zustand der Diode im richtigen Moment zu ändern. Wir schließen die Diode an Pin Nummer 3 an und los geht’s!

In den Beispielen verwende ich zwei Dioden, um sie auf den Bildern besser sichtbar zu machen. Wenn du die Dioden aus dem Arduino-Kurs-Set (Stufe I) nicht hast, kannst du die RGB-Diode aus dem Stufe II-Set verwenden und jede Farbe unabhängig steuern.

Damit die Schaltung richtig funktioniert, müssen wir den aktuellen Zustand der Diode (ein/aus) speichern. Dann können wir in einer Bedingung, die jede Sekunde wahr ist, den Zustand der Diode in das Gegenteil ändern. Wir speichern diese Information in der Variable int zustandLED1 = LOW;. Wir können den Zustand der Diode später durch die folgende Operation verneinen: zustandLED1 = !zustandLED1;

LOW ist eine Konstante, die für 0 steht, so dass wir sie einer Variablen vom Typ int zuweisen können.

Der Code, der diese Aufgabe erfüllt, ist unten zu sehen:

				
					#define LED1 3
int zustandLED1 = LOW;

unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeitLED1 = 0;

void setup(){
  Serial.begin(9600);
  pinMode(LED1, OUTPUT);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();
  
  //Wenn die Differenz mehr als 1 Sekunde beträgt
  if ( aktuelleZeit - gemerkteZeitLED1 >= 1000UL) {
    //Merke die aktuelle Zeit
    gemerkteZeitLED1 = aktuelleZeit;
    //Ändere den Zustand der LED in den entgegengesetzten
    zustandLED1 = !zustandLED1;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED1, zustandLED1);
  }
}
				
			

Wenn er aktiviert ist, blinkt die LED wie im ersten Beispiel:

LED blinkt dank millis().

Die Verneinung des Zustands in der Form zustandLED1 = !zustandLED1; ist kurz und bequem, aber nicht unbedingt intuitiv für jeden (sie funktioniert nur aufgrund der korrekten Deklaration der Konstanten LOW und HIGH). Für mehr Sicherheit kann dieses Stück Code in dieser Form umgeschrieben werden:

				
					if (zustandLED1 == LOW) {
 zustandLED1 = HIGH
} else {
 zustandLED1 = LOW;
}
				
			

Multitasking auf dem Arduino - blinkende LEDs

Es ist an der Zeit, das obige Beispiel so zu erweitern, dass zwei LEDs unabhängig voneinander blinken. Erst dann wird der Vorteil dieser Lösung deutlich. Diesmal wollen wir, dass eine Diode ihren Zustand häufiger ändert als die andere:

Es wird weiterhin nur eine Schleife loop() geben, und alle Aufgaben werden Zeile für Zeile ausgeführt. Dieses Mal wird das Programm in der Lage sein, zwei LEDs unabhängig voneinander blinken zu lassen.

Zwei "parallele" Aufgaben in einer loop Schleife.

Wir brauchen zwei zusätzliche Variablen. Die erste wird Informationen darüber enthalten, „wann“ wir den Zustand der zweiten Diode zuletzt geändert haben. Die nächste wird Informationen über ihren Zustand (ein/aus) enthalten.

Der Rest des Programms läuft analog ab. Wir schließen die zweite Diode an Pin 4 an und los geht’s:

				
					#define LED1 3
#define LED2 4

int ZustandLED1 = LOW
int ZustandLED2 = LOW;

unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeitLED1 = 0; 
unsigned long gemerkteZeitLED2 = 0; 

void setup(){
  Serial.begin(9600);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();
  
  //Wenn die Differenz mehr als 1 Sekunde beträgt
  if (aktuelleZeit - gemerkteZeitLED1 >= 1000UL) 
{
    //Merke die aktuelle Zeit
    gemerkteZeitLED1 = aktuelleZeit;
    //Ändere den Zustand der Diode in den entgegengesetzten Zustand
    ZustandLED1 = !ZustandLED1;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED1, ZustandLED1);
  }

    //Wenn die Differenz mehr als 0,5 Sekunden beträgt
  if (aktuelleZeit - gemerkteZeitLED2 >= 500UL) {
    //Merke die aktuelle Zeit
    gemerkteZeitLED2 = aktuelleZeit;
    //Ändere den Zustand der LED in den entgegengesetzten Zustand
    ZustandLED2 = !ZustandLED2;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED2, ZustandLED2);
  }
}
				
			

Von nun an blinken die LEDs unabhängig voneinander! Eine ändert ihren Zustand jede Sekunde und die andere jede halbe Sekunde:

Um den Unterschied leichter zu erkennen, kann man eine Diode für eine Weile abdecken und dann die andere.

Zwei LEDs blinken unabhängig voneinander.

Die Veränderungen werden am besten sichtbar, wenn wir größere Unterschiede einstellen. Lass die erste LED jede Sekunde ihren Zustand ändern und die zweite alle 200 ms. Am besten ist es, zwei Variablen hinzuzufügen, z. B.: blinkLED1 und blinkLED2.

Wir deklarieren die neuen Variablen als unsigned long und vermeiden so mögliche Probleme beim Vergleich großer Werte. Natürlich muss man in diesem Fall nicht mehr das Kürzel UL an die Zahl anhängen.

				
					#define LED1 3
#define LED2 4

int ZustandLED1 = LOW;
int ZustandLED2 = LOW;

unsigned long blinkLED1 = 1000;
unsigned long blinkLED2 = 200;

unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeitLED1 = 0;
unsigned long gemerkteZeitLED2 = 0;

void setup(){
  Serial.begin(9600);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();
  
  //Wenn die Differenz größer ist als blinkLED1
  if (aktuelleZeit - gemerkteZeitLED1 >= blinkLED1) {
    //Merke die aktuelleZeit
    gemerkteZeitLED1 = aktuelleZeit;
    //Ändere den Zustand der LED in den entgegengesetzten Zustand
    ZustandLED1 = !ZustandLED1;
    //Setzen des neuen Zustands an der Diode
    digitalWrite(LED1, ZustandLED1);
  }

    //Wenn die Differenz größer ist als blinkLED2
  if (aktuelleZeit - gemerkteZeitLED2 >= blinkLED2) {
    //Merke die aktuelle Zeit
    gemerkteZeitLED2 = aktuelleZeit;
    //Ändere den Zustand der Diode in den entgegengesetzten Zustand
    ZustandLED2 = !ZustandLED2;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED2, ZustandLED2);
  }
}
				
			

Der Effekt ist jetzt viel deutlicher sichtbar:

Größerer Unterschied in der Blinkfrequenz.

Blinkende LEDs und eine Taste auf dem Arduino

Ein noch besserer Effekt wird erzielt, wenn wir eine Taste in das Programm einfügen. Wenn wir delay() verwenden, würde diese Funktion standardmäßig die Möglichkeit blockieren, Eingaben sofort zu überprüfen. Man müsste die Taste gedrückt halten, bis das Programm die Prüfzeile erreicht. Dies würde selten funktionieren, da delay() das Programm für einige Sekunden einfrieren würde.

Hier gibt es kein solches Problem, wir können einfach eine Bedingung hinzufügen, die sofort funktioniert. Zum Beispiel soll das Drücken der Taste (Pin 2) dazu führen, dass die LED1 ihren Zustand viel schneller ändert (alle 100 ms).

				
					#define LED1 3
#define LED2 4
#define TASTE 2

int ZustandLED1 = LOW;
int ZustandLED2 = LOW;

unsigned long blinkLED1 = 1000;
unsigned long blinkLED2 = 200;

unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeitLED1 = 0;
unsigned long gemerkteZeitLED2 = 0;

void setup(){
  Serial.begin(9600);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(TASTE, INPUT_PULLUP);
}

void loop(){
  // Abrufen der Anzahl der Millisekunden seit dem Start
  aktuelleZeit = millis();

  //Wenn die Taste gedrückt wird, blinkt sie schneller
  if (digitalRead(TASTE) == LOW) {
    blinkLED1 = 100;
  } else {
    blinkLED1 = 2000;
  }
  
  //Wenn die Differenz größer ist als blinkLED1
  if (aktuelleZeit - gemerkteZeitLED1 >= blinkLED1) {
    //Merke die aktuelle Zeit
    gemerkteZeitLED1 = aktuelleZeit;
    //Ändere den Zustand der Diode in den entgegengesetzten Zustand
    ZustandLED1 = !ZustandLED1;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED1, ZustandLED1);
  }

    //Wenn die Differenz größer ist als blinkLED2
  if (aktuelleZeit - gemerkteZeitLED2 >= blinkLED2) {
    //Merle die aktuelle Zeit
    gemerkteZeitLED2 = aktuelleZeit;
    //Ändere den Zustand der Diode in den entgegengesetzten Zustand
    ZustandLED2 = !ZustandLED2;
    //Einstellen des neuen Zustands an der Diode
    digitalWrite(LED2, ZustandLED2);
  }
}
				
			

Da haben wir es – einfaches Multitasking in der Praxis! Wir lassen die LEDs unabhängig voneinander blinken und reagieren sofort, wenn eine Taste gedrückt wird. Das ist natürlich nur ein Beispiel. Anstatt den Zustand der LEDs zu ändern, kann dort auch etwas ganz anderes erscheinen.

Unabhängiges Blinken der LED + Reaktion auf den Eingang.

Intelligente Beleuchtung

Ursprünglich sollte es in diesem Artikel um etwas ganz anderes gehen (Hausautomatisierung), daher werde ich dieses Thema am Ende als „sehr lockeres“ Beispiel anführen.

Immer mehr Menschen interessieren sich für das Thema „Smart Home“. Automatisch hochfahrende Jalousien, Fernsteuerung von Geräten und Beleuchtung, Fernüberwachung der Temperatur. Auf dem Markt gibt es viele Fertiglösungen für die Hausautomatisierung. Leider haben die meisten dieser Systeme eine unerwünschte Eigenschaft gemeinsam – einen hohen Preis.

Diesmal befassen wir uns mit dem Thema „intelligente Beleuchtung“, um eine Treppe zu beleuchten. Unter den Lesern von Forbot gibt es auch junge Elektronikstudenten. Deshalb werde ich nicht auf die Steuerung von normaler Beleuchtung (230V) eingehen – wir werden uns mit einem sicheren Beispiel beschäftigen. Wer sich für aufwändigere Lösungen interessiert, kann die Schaltung sicher schon selbst einstellen (mit Relais).

Beispiel für eine einfache Automatisierung mit Arduino

Wenn wir abends die Treppe hinuntergehen, schalten wir immer das Licht an. Das Gleiche gilt, wenn wir durch einen dunklen Korridor gehen. Dies ist eine ideale Aufgabe, um sie zu automatisieren! Nehmen wir dieses Mal folgende Situation an: Direkt neben der Eingangstür befindet sich eine Treppe, an der sich unten und oben jeweils ein kleiner Flur befindet.

Nehmen wir an, dass wir das Licht aktivieren wollen, wenn eine Bewegung erkannt wird. Wenn der PIR-Sensor jemanden in der Nähe der Treppe bemerkt, schaltet er das Licht für 180 Sekunden ein oder bis die Tür geöffnet wird.

Ein Beispiel für einen Raum, der es wert ist, automatisiert zu werden.

Die Wahl der richtigen Beleuchtung

Wie ich bereits erwähnt habe, überlassen wir den Spaß mit 230V den erfahreneren Anwendern. Die Steuerung der normalen Beleuchtung erfolgt meist mit Relais.

Eine zweite, viel sicherere Möglichkeit sind die neuerdings beliebten LED-Streifen. Die beliebtesten Module dieser Art können sicher mit einer niedrigeren Spannung (z. B. 12 V) betrieben werden. Sie können über ein Relais oder einen Transistor (MOSFET) an den Arduino angeschlossen werden. Informationen zur Steuerung von Peripheriegeräten über einen MOSFET wurden in Teil 3 des Kurses beschrieben.

Man sollte bedenken, dass das Relais die Möglichkeit der PWM-Steuerung (Steuerung der Helligkeit der LED-Leisten) ausschließt. Außerdem ist bei jedem Ein- und Ausschalten der Beleuchtung das Schaltgeräusch des Relais zu hören.

Eine dritte Möglichkeit ist die Verwendung programmierbarer Dioden, wie z. B. der WS2812, die wir im zweiten Teil des Kurses behandelt haben. Diese Lösung ist teurer als normale Dioden, ermöglicht aber interessantere Effekte.

Beispiel für ein RGB-Diodenmodul.

Zu Testzwecken (ich habe diese Option gewählt) kann man auch eine normale Leuchtdiode anschließen, um eine Beleuchtung zu simulieren. Ich habe die Diode an Pin Nummer 4 angeschlossen.

Sensoren

Nun ist es an der Zeit, die Sensoren auszuwählen. Offensichtlich werden wir Bewegungen mit einem PIR-Sensor (Pin Nummer 5) erkennen. Und für die Türüberwachung ist ein Reedschalter (Pin Nummer 3) am besten geeignet. Alle diese Sensoren wurden in dem Artikel über die Erstellung einer einfachen Alarmzentrale beschrieben.

Wir montieren den PIR-Sensor oben im Flur und den Reedschalter an der Tür:

In der Praxis sah meine Testplattform folgendermaßen aus:

Die Simulation des beschriebenen Falls.

Programm unter Verwendung von millis()

Bevor wir die Übungen in diesem Abschnitt durchführt haben, würden wir das Licht für 180 Sekunden mit delay() einschalten. Wenn eine Bewegung erkannt wird, wird das Licht und delay(180) eingeschaltet, und während dieser Zeit wird die Schaltung „eingefroren“. Sie würde weder auf den Reed-Schalter noch auf andere Sensoren reagieren….

Natürlich wäre es in dem beschriebenen Fall möglich, einen Reedschalter unter die Unterbrechung zu schalten. Bei mehreren Sensoren wäre dies jedoch nicht möglich!

Es ist also an der Zeit, den heute erlernten Mechanismus zu nutzen. Das Programm soll wie folgt funktionieren: Wenn eine Bewegung erkannt wird, wird das Licht für 180 Sekunden aktiviert. Nach dieser Zeit geht das Licht aus (wenn es keine weitere Bewegung gibt) oder wenn jemand den Raum verlässt, d.h. die Tür öffnet.
Eine der vielen Möglichkeiten zur Umsetzung dieses Beispiels ist die folgende:

				
					#define BELEUCHTUNG 4
#define REEDSCHALTER 3
#define PIR 5

unsigned long aktuelleZeit = 0;
unsigned long gemerkteZeitBeleuchtung = 0;

void setup(){
 Serial.begin(9600);
 pinMode(BELEUCHTUNG, OUTPUT);
 pinMode(REEDSCHALTER, INPUT_PULLUP); //Reedschalter als Eingang
 pinMode(PIR, INPUT); //PIR als Eingang
}

void loop(){
//Abrufen der Anzahl der Millisekunden seit dem Start
 aktuelleZeit = millis();

 //Wenn Tür offen
 if (digitalRead(REEDSCHALTER) == HIGH) {
 digitalWrite(BELEUCHTUNG, LOW); //Beleuchtung ausschalten
 } else if (digitalRead(PIR) == HIGH) {
 //Wenn Tür geschlossen und Bewegung erkannt wird
 
gemerkteZeitBeleuchtung = aktuelleZeit; //Zeit merken
 digitalWrite(BELEUCHTUNG, HIGH); //Beleuchtung einschalten
 } 

 //Wenn Licht für eine bestimmte Zeit eingeschaltet ist, schalte das Licht aus
 if (aktuelleZeit - gemerkteZeitBeleuchtung >= 180000UL) {
 digitalWrite(BELEUCHTUNG, LOW);
 }
}
				
			

Natürlich hat die Grundidee dieses Beispiels einige Nachteile, und die Schaltung müsste erweitert werden, um sie für das tägliche Leben nutzbar zu machen. Zumindest wäre es sinnvoll, einen Fotowiderstand hinzuzufügen, damit das Licht nur bei Dunkelheit eingeschaltet wird. Die weitere Entwicklung der Schaltung überlasse ich interessierten Bastlern.

Zusammenfassung

Das letzte Beispiel war sehr einfach, zeigt aber eine praktische Anwendung für die millis-Funktion, die die Hauptrolle in diesem Teil spielte. Ich hoffe, dass von nun an niemand mehr Probleme damit hat, dass die Delay-Funktion das ganze Programm blockiert.

Ich möchte euch ermutigen, eure eigenen Tests zu machen! Es lohnt sich, die hier beschriebenen Programme zu erweitern. Füge weitere LEDs, Tasten und andere Sensoren hinzu. Wenn alles klar ist, kann man getrost zu Bibliotheken übergehen, die solche Verzögerungen „magisch“ von selbst ausführen.

Bestellen Sie ein Set mit Elementen und beginnen Sie mit dem Lernen in der Praxis! Hier gehts zum Shop >>

Nach oben scrollen