Ein Automat ohne delay()

Inhaltsverzeichnis

IR-Auslöser für Pentax-Kameras

Akku-Ladekurve

Wir bauen uns eine Piepliothek!
Ein Automat ohne delay()

Pimp my Code!

Tipps + Tricks



Ein Automat ohne delay()

Es gibt zwei Fragestellungen, die im Arduinoforum immer wieder auftauchen:

  • Wie vermeide ich delay()?
  • Wie programmiere ich diesen oder jenen Ablauf?

Frage eins hat meistens damit zu tun, dass jemand mehrere Vorgänge gleichzeitig ablaufen lassen möchte. Mit delay() erzeugte Pausen sind hierfür Gift.

Die zweite Frage lässt sich oft mit dem Modell eines „endlichen Automaten“ (eine Art Strickmuster) beantworten.

Ein endlicher Automat heißt so, weil er eine endliche Zahl von Zuständen annehmen kann.

In Kurzform kann man die „Mechanik“ eines endlichen Automaten so beschreiben: Ein Vorgang wird in mehrere Schritte aufgeteilt, die benannt werden. Im weiteren Verlauf werden diese Schritte abgearbeitet, wobei am Ende jedes Schrittes zum nächsten „weitergeschaltet“ wird.

Anstatt für zeitliche Strukturen delay() zu verwenden, wird mit millis() gearbeitet. Die Funktion millis() gibt die Zahl der seit dem Einschalten des Arduinos vergangenen Millisekunden zurück. Wenn man sich den Startzeitpunkt eines Ablaufs merkt, kann man die Laufzeit mittels Differenzbildung errechnen.

Das Problematische an delay() ist, dass es den Prozessor blockiert. Mit der Abarbeitung weiterer Abläufe wird erst fortgefahren, wenn die mit delay() erzeugte Pause vorbei ist.

Der Schlüssel zum Verständnis, wie Abläufe ohne delay() programmiert werden können, besteht darin, die „Pausen-Denke“ zu ändern: Es sind keine Pausen mehr, die eingelegt werden müssen, sondern Zeitpunkte, zu denen etwas passieren soll.

Eine Frage, anhand der sich die Vermeidung von delay() und ein endlicher Automat erläutern lassen, lautet: Wie kann ich eine Blinksequenz so ablaufen lassen, dass nebenher weitere Dinge erledigt werden können?

Die hier beispielhaft realisierte Blinksequenz lässt sich am besten mit „blinkblink … blinkblink … blinkblink …“ beschreiben – also ein wiederholtes zweifaches Blinken, gefolgt von einer etwas längeren Pause.

Diese Blinksequenz kann man in vier Phasen gliedern:

  • LED an + Pause
  • LED aus + Pause
  • LED an + Pause
  • LED aus + lange Pause

Jede Phase bekommt einen „Namen“, den man in einer Aufzählungsvariable (enum) definiert. Die Dauer der Phasen ist in einer Feldvariable (array) festgelegt.

In loop() wird in einer switch-Anweisung der Code angesprungen, der mit der aktuellen Phase der Blinksequenz zusammenhängt. Da das Fortschreiten zur nächsten Phase im Code einer Phase passiert, entscheidet der Code einer Phase sozusagen selbst, wann zur nächsten Phase weitergeschaltet wird.

Der Einsatz von switch() ist nicht immer die beste oder verständlichste Konstruktion. Je nach Aufgabe kann ein if()-Konstrukt die bessere Wahl sein.

Wer schon ein bisschen programmieren kann, wird mit dem folgenden Code kaum Schwierigkeiten haben:

// Blinken ohne delay()

byte LEDPin=13; // Pin, an dem die LED haengt
enum Phase {ON1, OFF1, ON2, OFF2}; // "Namen" fuer die Phasen
int d[]={10, 200, 10, 1000}; // Dauer der Phasen in ms
Phase phase; // Enthaelt die aktuelle Phase
unsigned long int millisMem; // Merker fuer millis()

void setup()
{
  pinMode(LEDPin, OUTPUT);
  phase=ON1;
  millisMem=millis();
}

void loop()
{
  switch(phase)
  {
    case ON1:
              if(millis()-millisMem < d[0])
              { digitalWrite(LEDPin, HIGH); }
              else
              { phase=OFF1; }
              break;
    case OFF1:
              if(millis()-millisMem < d[0]+d[1])
              { digitalWrite(LEDPin, LOW); }
              else
              { phase=ON2; }
              break;
    case ON2:
              if(millis()-millisMem < d[0]+d[1]+d[2])
              { digitalWrite(LEDPin, HIGH); }
              else
              { phase=OFF2; }
              break;
    case OFF2:
              if(millis()-millisMem < d[0]+d[1]+d[2]+d[3])
              { digitalWrite(LEDPin, LOW); }
              else
              { phase=ON1; millisMem=millis(); }
              break;
  }

  // Hier weitere Dinge, die in loop() ablaufen sollen

}

Eine ordentliche switch-Anweisung hört eigentlich mit einem „default:“-Abschnitt auf. Der Übersichtlichkeit wegen habe ich diesen Abschnitt hier weggelassen.

Achtung: Da die Funktion millis() einen Wert zurückgibt, der 32 Bit breit ist, kann es je nachdem, wie man die Bedingung formuliert, nach rund 49 Tagen zu Überlaufproblemen kommen.
Ungünstig:
 if(millis() < millisMem + …)
Besser:
 if(millis() - millisMem > …)

Natürlich gibt es zum Modell des endlichen Automaten auch einen Artikel in der Wikipedia (siehe hier). Ich finde diesen Artikel allerdings ziemlich trocken und halte ihn für schwer nachvollziehbar.

Eine andere Erklärung, wie man zu einem endlichen Automaten kommt, befindet sich hier.
Und wie man delay() vermeiden kann, steht zum Beispiel auch hier.

Weiter zu: Pimp my Code!


Nach oben Inhaltsverzeichnis

Valid HTML 4.01 Transitional