Anleitung: Endlicher Automat mit millis()

Idee: Im Forum taucht immer wieder der Rat auf: „Ersetze dalay() durch millis().“ und „Das kann man mit einem Endlichen Automaten lösen.“ Hier ein Beispiel, das beides vereint.

Aufgabenstellung: Es sollte sich um eine allgemein bekannte Situation handeln, die mit einfachen Mitteln nachvollzogen werden kann. Meine Wahl fällt auf eine Ampelschaltung, weil sie jeder kennt. Asynchron dazu soll ein Blaulicht blitzen.

Material: Neben einem Arduino, ich verwende einen UNO, vier LEDs mit je einem Vorwiderstand.

Quellen: Viele nette Menschen, die ihr Wissen im Internet zur Verfügung stellen so wie in diesem Forum.

Sketch 1 mit delay()
Zunächst sehen wir uns den Sketch an, wie ihn jeder Anfänger schnell schreibt:

void setup() {
  // Definiert die Pins als Aus- oder Eingang
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
}

void loop() {
  // Ampelschaltung
  digitalWrite(2, HIGH);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  delay(3000);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, LOW);
  delay(1000);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, HIGH);
  delay(3000);
  digitalWrite(2, LOW);
  digitalWrite(3, HIGH);
  digitalWrite(4, LOW);
  delay(1000);
}

Sollte der Arduino nichts weiter machen, wären wir fertig. Die Funktion delay() unterbricht das Programm für eine definierte Zeit, während der keine anderen Aktionen möglich sind. Lange for- und while-Schleifen können genauso blockierend sein. Eine weitere zeitkritische Funktion wie ein Blaulicht oder eine Reaktion auf Taster ist auf diese Weise nicht zu realisieren. Dies geht nur, wenn loop() nicht blockiert wird.

Sketch 2 mit Konstanten
Ohne weiteren Kommentar kann man nur durch Verstehen des Codes erschließen, dass die rote LED an Pin 2 angeschlossen werden soll. Wollen wir die LED später mit einem anderen Anschluss verbinden, weil wir beispielsweise auf eine anderen Arduino umgestiegen sind, so müssten wir das ganze Programm durchsuchen, um die relevanten Stellen zu finden. Bei langen Programmen kann dies mühsam und fehlerträchtig sein. Daher werden Konstanten definiert, die mit ihrem Namen gleich auch ihre Bedeutung verraten.

// Ampel 1 Belegung der Ausgaenge
const byte RotPin = 2;
const byte GelbPin = 3;
const byte GruenPin = 4;
//
const boolean ein = HIGH;
const boolean aus = LOW;
const int ZEITROTPHASE = 3000;
const int ZEITGELBPHASE = 1000;

void setup() {
  // Definiert die Pins als Aus- oder Eingang
  pinMode(RotPin, OUTPUT);
  pinMode(GelbPin, OUTPUT);
  pinMode(GruenPin, OUTPUT);
}

void loop() {
  // Ampelschaltung
  digitalWrite(RotPin, HIGH);
  digitalWrite(GelbPin, LOW);
  digitalWrite(GruenPin, LOW);
  delay(ZEITROTPHASE);
  digitalWrite(RotPin, HIGH);
  digitalWrite(GelbPin, HIGH);
  digitalWrite(GruenPin, LOW);
  delay(ZEITGELBPHASE);
  digitalWrite(RotPin, LOW);
  digitalWrite(GelbPin, LOW);
  digitalWrite(GruenPin, HIGH);
  delay(ZEITROTPHASE);
  digitalWrite(RotPin, LOW);
  digitalWrite(GelbPin, HIGH);
  digitalWrite(GruenPin, LOW);
  delay(ZEITGELBPHASE);
}

Sketch 2 mit Konstanten
Ohne weiteren Kommentar kann man nur durch Verstehen des Codes erschließen, dass die rote LED an Pin 2 angeschlossen werden soll. Wollen wir die LED später mit einem anderen Anschluss verbinden, weil wir beispielsweise auf eine anderen Arduino umgestiegen sind, so müssten wir das ganze Programm durchsuchen, um die relevanten Stellen zu finden. Bei langen Programmen kann dies mühsam und fehlerträchtig sein. Daher werden Konstanten definiert, die mit ihrem Namen gleich auch ihre Bedeutung verraten.

Sketch 3 mit Endlichem Automaten und millis()
Stell Dir vor, wir verabreden uns zu einem Treffen in fünf Minuten. Was passiert?

  1. Du schaust auf die Uhr, um die aktuelle Zeit zu ermitteln und merkst Dir diese als die Zeit, an der wir uns verabredet haben.
  2. Du wartest, bis die aktuelle Zeit auf Deiner Uhr abzüglich der Zeit, an der wir uns verabredet haben, mit den fünf Minuten übereinstimmt. Wir treffen uns.

Zu 1.: Zeit_der_Verabredung = aktuelle_Zeit

Zu 2.: Wenn aktuelle_Zeit abzüglich Zeit_der_Verabredung gleich fünf Minuten dann "Hallo!"

Deine Uhr zeigt die Sekunden, Minuten und Stunden seit Mitternacht an. millis() zeigt die Millisekunden seit Reset an. Für die Verabredung "in fünf Minuten" ist das aber nicht relevant, wann die Zeitzählung startet. Deine Uhr könnte Winterzeit zeigen, meine Sommerzeit oder auch vollkommen falsch gehen, wir würden uns dennoch in fünf Minuten treffen, weil es sich um eine relative Zeitangabe handelt.

Das jetzt in millis (Intervall = fünf Minuten):

Zu 1.: Zeit_der_Verabredung = millis();

Zu 2.: if (millis() - Zeit_der_Verabredung == Intervall) {Serial.println("Hallo");}

Für den (schlechten) Fall, Dein Programm sollte länger als eine Millisekunde für loop() benötigen, schreibt man besser:

Zu 2.: if (millis() - Zeit_der_Verabredung >= Intervall) {Serial.println("Hallo");}

Die Ampelphasen ROT, ROTGELB, GRUEN, GELB sind die Zustände des Endlichen Automaten. Die Variable zustand durchläuft alle Phasen, wobei durch Bedingungen, hier die Zeit, die Zustandsänderungen ausgelöst werden. Mittels enum werden die Zustände beginnend bei 0 fortlaufend durchnummeriert.

// Ampel Belegung der Ausgaenge
const byte RotPin = 2;
const byte GelbPin = 3;
const byte GruenPin = 4;
//
const boolean ein = HIGH;
const boolean aus = LOW;
//
const int ZEITROTPHASE = 3000;
const int ZEITGELBPHASE = 1000;
unsigned long ampelMillis;
unsigned long ampelIntervall;
//
enum ZUSTAENDE {ROT, ROTGELB, GRUEN, GELB};
byte zustand = ROT;

void setup() {
  // Definiert die Pins als Ausgang
  pinMode(RotPin, OUTPUT);
  pinMode(GelbPin, OUTPUT);
  pinMode(GruenPin, OUTPUT);
}

void loop() {
  // Ampelschaltung
  if (millis() - ampelMillis >= ampelIntervall) {
    switch (zustand) {
      case ROT:
        digitalWrite(RotPin, HIGH);
        digitalWrite(GelbPin, LOW);
        digitalWrite(GruenPin, LOW);
        zustand = ROTGELB;
        ampelMillis = millis();
        ampelIntervall = ZEITROTPHASE;
        break;
      case ROTGELB:
        digitalWrite(RotPin, HIGH);
        digitalWrite(GelbPin, HIGH);
        digitalWrite(GruenPin, LOW);
        zustand = GRUEN;
        ampelMillis = millis();
        ampelIntervall = ZEITGELBPHASE;
        break;
      case GRUEN:
        digitalWrite(RotPin, LOW);
        digitalWrite(GelbPin, LOW);
        digitalWrite(GruenPin, HIGH);
        zustand = GELB;
        ampelMillis = millis();
        ampelIntervall = ZEITROTPHASE;
        break;
      case GELB:
        digitalWrite(RotPin, LOW);
        digitalWrite(GelbPin, HIGH);
        digitalWrite(GruenPin, LOW);
        zustand = ROT;
        ampelMillis = millis();
        ampelIntervall = ZEITGELBPHASE;
        break;
    }
  }
}

Sketch 4 mit Blaulicht
Letztlich wird noch der Code für das Blaulicht ergänzt, der eine andere Variante der Verwendung von millis() zeigt.

// Ampel Belegung der Ausgaenge
const byte RotPin = 2;
const byte GelbPin = 3;
const byte GruenPin = 4;
// Blaulicht Belegung der Ausgaenge
byte BlauPin = 5;
//
const boolean ein = HIGH;
const boolean aus = LOW;
//
const int ZEITROTPHASE = 3000;
const int ZEITGELBPHASE = 1000;
const int BLAUPHASE = 100;
unsigned long ampelMillis;
unsigned long ampelIntervall;
unsigned long blauMillis;
//
enum ZUSTAENDE {ROT, ROTGELB, GRUEN, GELB};
byte zustand = ROT;

void setup() {
  // Definiert die Pins als Ausgang
  pinMode(RotPin, OUTPUT);
  pinMode(GelbPin, OUTPUT);
  pinMode(GruenPin, OUTPUT);
  pinMode(BlauPin, OUTPUT);
}

void loop() {
  // Blaulicht
  if (millis() - blauMillis >= 750) {
    blauMillis = millis();
    digitalWrite(BlauPin, ein);
  }  else if (millis() - blauMillis >= 150) {
    digitalWrite(BlauPin, aus);
  }    else if (millis() - blauMillis >= 100) {
    digitalWrite(BlauPin, ein);
  }      else if (millis() - blauMillis >= 50) {
    digitalWrite(BlauPin, aus);
  }
  //
  // Ampelschaltung
  if (millis() - ampelMillis >= ampelIntervall) {
    switch (zustand) {
      case ROT:
        digitalWrite(RotPin, HIGH);
        digitalWrite(GelbPin, LOW);
        digitalWrite(GruenPin, LOW);
        zustand = ROTGELB;
        ampelMillis = millis();
        ampelIntervall = ZEITROTPHASE;
        break;
      case ROTGELB:
        digitalWrite(RotPin, HIGH);
        digitalWrite(GelbPin, HIGH);
        digitalWrite(GruenPin, LOW);
        zustand = GRUEN;
        ampelMillis = millis();
        ampelIntervall = ZEITGELBPHASE;
        break;
      case GRUEN:
        digitalWrite(RotPin, LOW);
        digitalWrite(GelbPin, LOW);
        digitalWrite(GruenPin, HIGH);
        zustand = GELB;
        ampelMillis = millis();
        ampelIntervall = ZEITROTPHASE;
        break;
      case GELB:
        digitalWrite(RotPin, LOW);
        digitalWrite(GelbPin, HIGH);
        digitalWrite(GruenPin, LOW);
        zustand = ROT;
        ampelMillis = millis();
        ampelIntervall = ZEITGELBPHASE;
        break;
    }
  }
}

Entsprechend der Aufgabenstellung sollen Ampel und Blaulicht unabhängig voneinander ausgeführt werden. Da loop() häufig durchlaufen wird, ist dies näherungsweise gewährleistet.

Eine anschauliche Erklärung, wie millis() verwendet werden.

Ich habe dies geschrieben aus Dank an die aktiven Forumsmitglieder und in der Hoffnung, es möge jemandem nutzen! :slight_smile:

Diejenigen, die meine Texte hier im Forum gründlich lesen und mich korrigieren, bitte ich, mit diesem Text ebenso zu verfahren.

Hallo,

Deine Mühen sind sehr löblich. Aber Du mußt dir bewußt sein, dass der Thread irgendwann nach hinten rutscht und verschwindet. Ich hab schon ähnliches durch. Du kannst den für dich fertig machen und bei Gelegenheit rausholen. Oder Du schreibst irgendwann ein Buch.

hi
das ist wirklich ein ganz tolles tutorial - leider steige ich trotzdem noch nicht ganz durch, aber ich werde es die nächste tage immer wieder lesen und mal nachbauen, bis ich es eben raffe,
eine frage habe ich, vielleicht hat jemand eine lösung:

ich habe einen einfachen staubsugerroboter programmiert, der in bahnen den raum durchfährt, ausschert, dreht und wieder die nächste bahn abfährt. nun muss man sich das so vorstellen:
er fährt parallel zur wand entlang und checkt, ahh ich bin in der rechten raumecke, also schere ich nun diagonal rückwärts im 45 gard winkel zur raummitte hin aus, drehe dann und fahre zurück.
um das zu realisieren habe ich die Grundbewegungen im Void angelegt, also vorwärtsfahren(); Auscheren (), Turn() etc.

die zeit wie lange er was macht also zB Rückwärts fährt habe ich über delay gelöst - und -WUNDER- stoße jetzt auf diesen artikel weil es eine millis lösung braucht, denn wenn er rückwärts ausschert reicht es nicht zu den motoren zu sagen: mach mal 800 millsec delay DENN DANN kann er nicht mehr auf den button reagieren der an der Rückseite angebracht ist. also muss ich das über millis lösen.

kann ich das auch im void lösen und nicht im loop so dass bei der Funktion Rückwärts immer einen bestimmte zeit gemeint ist? geht das? oder muss das dann immer in den loop gebaut werden.

ich werde sicher noch eine zeit brauchen bis ich das beispiel ganz verstehe und das in meinem code einfügen kann und bin für jeden tipp dankbar.

buttonswitch.ino (4.79 KB)

tinaki:
hi
das ist wirklich ein ganz tolles tutorial

Danke, das freut mich :slight_smile:
Wenn Du zu dieser Anleitung einen Verbesserungsvorschlag hast, dann werde ich den gerne berücksichtigen. Dann wäre hier die richtige Stelle.

Die Frage zum Staubsauger fände ich in einem eigenen Thema besser aufgehoben. Über einen spezifischen Thementitel wirst Du auch von den richtigen Leuten gelesen, ggf. auch von mir. Denn die generelle Frage lautet: Wie wird delay() in einem Unterprogramm einer Funktion ersetzt. Möglicherweise muß die Programmstruktur Richtung switch (zustand) verändert werden. Spannend :slight_smile:

genau das war auch mein erster gedanke, ich hatte einen switch case gebastelt, der aber lief nur mit dem distance messer und nicht auch in verbindung mit den buttons- aber mann muss dazu sagen, ich bin totaler anfänger und dies ist mein erstes projekt. Genau das ist die frage: Wie wird delay() in einem Unterprogramm ersetzt ? Ein unterprogramm, welches man dann dann verschiedenen Bewegungen wie VORWÄRTS; TURN(); BACK; etc zuordnen kann, mit veränderbaren Zeiten (dauer)

  • ok also ich kann das ja neu posten ABER bevor ich jetzt was neues aufmache:
    Gibt es vielleicht noch mehr Leute da draußen die das Lesen?
    hat jemand eine idee?

buttonswitch.ino (4.79 KB)

Nehmen wir erst mal einen besseren Namen für ein Unterprogramm in der strukturierten Programmierung: Funktion

Funktionen können Rückgabewerte haben. Man kann eine Funktionen einen bool zurückgeben lassen. False wenn die Zeit noch nicht abgelaufen ist. True wenn sie abgelaufen ist. Dann ruft man die Funktion ständig auf und weiß in der aufrufenden Funktion ob die Zeit vorbei ist und etwas gemacht wurde.
Dann brauchst du noch etwas Logik damit der eigentliche Code nur einmal ausgeführt und sonst der "Delay" Teil.

Hier musst du auch lernen was "static" macht:
http://www.arduino.cc/en/Reference/Static
Lokale statische Variablen behalten ihren Wert von einem Funktionsaufruf zum nächsten.

Aber lies dich generell in das Thema endliche Automaten ein. Was du da machst ist eine Paradebeispiel dafür. Das kann man nämlich auch besser lösen und die Verzögerung in einen extra Zustand, d.h. eine eigene Funktion auslagern. Dann wechselt man Funktion1 -> Verzögerung -> Funktion2

super! ich verstehe: ich mache die ganze zeit eine anfrage, darunter fällt auch die zeit und die buttons sind auch dabei.
static versteh ich noch nicht ganz kommt viellicht noch.

aber was meinst du damit:
"Aber lies dich generell in das Thema endliche Automaten ein. Was du da machst ist eine Paradebeispiel dafür. Das kann man nämlich auch besser lösen und die Verzögerung in einen extra Zustand, d.h. eine eigene Funktion auslagern. Dann wechselt man Funktion1 -> Verzögerung -> Funktion2"

gibt es dafür ein beispiel? 1000 dank!!

tinaki:
gibt es dafür ein beispiel?

Schau mal an in welchem Thread du bist. Ist im ersten Post erklärt.

Das ist leicht anders als was du willst. Da wird jeder Zustand zeitgesteuert. Aber ist auch kein Problem nur einen Zustand WARTEN zu haben und nur in diesem den Zustandsübergang per Zeit zu machen. Aber das Grundgerüst ist das gleiche.

Endliche Automaten kann man auch auf andere Arten realisieren. Aber switch/case ist am einfachsten und reicht völlig aus wenn sich die Zustände in Grenzen halten

Beispiele von mir zum Thema Endlicher Automat mit millis() und Schalterzustand:
fade millis
LED zeiversetzt schalten
Arduino UNO, 2Schalter, 1LED

@agmue
Ich hab mich auch mal an so etwas versucht: Projektvorstellung: simpleThreads auf dem Arduino mit Hilfe von Makros - Deutsch - Arduino Forum
In der neusten LCDMenuLib ist es auch enthalten.

Aber solange es die Delay Funktion in der Arduino Umgebung gibt wird dieses als erste Wahl bevorzugt.

Diesen Code in einer Lib zu verstecken wäre aber auch nicht fair :grin:

#undef delay()
#define delay(x)

An agmue

Herzlichen dank suchte schon eine kleine Ewigkeit genau das.
weil das delay ein biserl verteufelt ist.

mfg. Kapitano

@Jomelo: Gegenüber dem, was Du machst, spiele ich nur in der Kreisklasse 8)

@Kapitano: Danke, freue mich :slight_smile:

@agmue:
Es ist echt prima, dass du hier so eine tolle Anleitung gegen dieses schon oft und zu Recht verwünschte "delay(x)" geschrieben hast.

Ich kann mich noch gut an meine Anfänge gegen Ende 2014 erinnern, als ich mich selbst auf ein Ampelprogramm für eine Modelleisenbahn gestürzt habe. Dabei hat mir das delay(x) fast Finger und Ohren gleichzeitig gebrochen... damals habe ich händeringend nach Lösungen gesucht. Diese habe ich dann Mitte 2015 auch mit "millis()" gefunden und setze delay(x) heute nur noch bei kleinen Dreizeilern ein, wenn ich "mal eben" schnell etwas testen möchte. Oder innerhalb "void setup()" für eine bewusste Verzögerung nach einer Art "Splash-Screen" fürs LCD-Display.

Wäre es für deine Anleitung nicht eine geeignete Ergänzung, wenn man deine Ampel jetzt mit einer Anforderung für Fußgänger erweitert? Hier fast vor der Haustür beobachte ich nämlich täglich an einer Kreuzung, wie die Ampeln brav den Verkehr regeln. Wenn aber nun jemand über die Straße möchte drückt er einen Knopf, die Ampel zeigt blinkend "Warten" an, und in der nächsten Rot-Phase für die Autos darf der Fußgänger dann losspurten. Das wäre in meinen Augen doch eine ideale Erweiterung - für dein Tutorial - und auch für die Modelleisenbahn - oder ähnliche Gebiete?!

LG, Rudi

agmue:
@Jomelo: Gegenüber dem, was Du machst, spiele ich nur in der Kreisklasse 8)

@Kapitano: Danke, freue mich :slight_smile:

@agmue

Bitte kannst du mir einen tipp geben wo ich #include <SM.h> diese Datei herunterladen kann suche schon stunden im netz.
bin ja noch in den Kinderschuhen und muss alles ausprobieren und lernen. (Arduino ca. 4 Monate und absolut keine Vorkenntnisse).
danke im voraus
mfg. Kapitano

Ich verstehe manchmal einfach nicht warum so viele Probleme mit Delay haben.
Eventuell muss man doch nur mal darüber nachdenken was ein Funktionsname
bedeutet.

Wenn ich dem Prozessor sage "Mach mal Pause" dann macht der das und ich kann doch nicht erwarten
das er was anderes tut, oder ?

Auf jeden Fall ein nettes Tutorial.

Ulli

beeblebrox:
Ich verstehe manchmal einfach nicht warum so viele Probleme mit Delay haben.

Die meisten Probleme entstehen, weil es delay überhaupt gibt und der Umstieg auf millis dann so schwer fällt.

Kapitano:
Bitte kannst du mir einen tipp geben wo ich #include <SM.h> diese Datei herunterladen kann suche schon stunden im netz.

Dann scheinst Du diese Seite übersehen zu haben: "Code removed by author". Zwei Vorschläge:

  • Du schickst eine persönliche Meldung an jurs, der hat die Bibliothek möglicherweise noch auf seiner Festplatte.
  • Du machst Dir einen endlichen Automaten (SM -> finite state machine -> FSM) selbst. Ich mag die Variante mit switch/case, die auch für Anfänger geeignet ist :slight_smile:

RudiDL5:
Wäre es für deine Anleitung nicht eine geeignete Ergänzung, wenn man deine Ampel jetzt mit einer Anforderung für Fußgänger erweitert?

Danke für Dein Lob! Es kribbelt mir bei dem Gedanken an die Ampel schon in den Fingern. Nur tue ich mich mit der verständlichen Beschreibung schwer. Ich könnte mir auch vorstellen, ich mache es prozedural und Du mit OOP.

Deinen Vorschlag werde ich überdenken!

RudiDL5:
Wenn aber nun jemand über die Straße möchte drückt er einen Knopf, die Ampel zeigt blinkend "Warten" an, und in der nächsten Rot-Phase für die Autos darf der Fußgänger dann losspurten. Das wäre in meinen Augen doch eine ideale Erweiterung - für dein Tutorial - und auch für die Modelleisenbahn - oder ähnliche Gebiete?!

Bei einer normalen Kreuzung laufen die Fußgänger einfach mit der Fahrzeugen mit, da bedarf es keiner Anforderungstaste. Wie funktioniert das bei Deinem Vorbild?

@agmue

Danke für deine rasche Antwort.
irgendwann werde auch ich so ein Genie werden wie @agmue

nochmals herzliches Danke
mfg. Kapitano

@beeblebrox:

Ich verstehe manchmal einfach nicht warum so viele Probleme mit Delay haben.

Da sprichst du ein wahres Wort gelassen aus... Aber so lange von Schulen oder sogar bekannten Tools-Herstellern immer wieder "mal eben" auf das bequeme delay(x) zurückgegriffen wird, ist diese "Seuche" nicht wirklich auszurotten (habe mehrere PDF hier, in denen das vehement eingesetzt wird...).

Ja... aber...

Ich denke, delay(x) hat auch etwas Gutes! Meiner Meinung nach... Vermutlich hat der Arduino nur deshalb so viel Popularität erworben, weil delay(), pinMode(), digitalWrite() usw. enthalten sind. Ohne diese Funktionen wären wahrscheinlich mit Abstand nie so viele Exemplare verkauft worden. Und der Eine oder Andere, der "wirklich" in die Materie einsteigen will, wird sich über kurz oder lang eh von delay() verabschieden und mit millis() flirten. Spätestens bei so eine Ampelmaschine wie von @agmu...

@agmue:

Danke für Dein Lob!

Nix zu danken, war mir eine Ehre :wink:

Es kribbelt mir bei dem Gedanken an die Ampel schon in den Fingern

Öhm, dir kribbelt es in den Fingern? Komisch, mir auch... aber gewaltig. Das wäre für 'ne Modelleisenbahn wirklich etwas, was man gut einsetzen kann.

da bedarf es keiner Anforderungstaste

Mag sicherlich sein. Aber hier am Ort sind im Abstand von etwa 1000m sogar zwei Fußgänger-Ampeln an einer Bundesstraße, die ausschließlich nur auf Anforderung umschalten. Sogar asynchron... So etwas in der Art stelle ich mir vor.

Die Ampel von "damals" (Ende 2014) war auch noch hust mit delay(x) gelöst. Das hatte ich mit "switch" und "Array" gelöst, welches im Sekunden-Takt abgearbeitet wurde und die entsprechenden Zustände schalteten... Das Programm ist (leider) einem defekten Lappy zum Opfer gefallen. Aber egal, das war auch nicht der große Bringer.

Und da wir schon dabei sind - was hälst du davon, "unsere" Ampel so zu bauen, dass sie auf Knopfdruck in den Nachtmodus schaltet? Als so dass "nachts" nur noch ein gelbes Warnlicht blinkt. Erst bei Tag geht es wieder (auf Anforderung zunächst) in den Tagesmodus.

Willst du noch einen Joke hören? :smiley: Und am Schluß jibbet dann 2 Knöpfchen, die das Blaulicht von Feuerwehr UND Polizei (natürlich vollkommen asynchron und unabhängig) für eine "gewisse" Zeit einschaltet. Ist das Fahrzeug vorbei, ist auch das Blinken futsch...

Ich denke, DAS wäre doch mal eine Herausforderung?! :wink:
Und würde sich auf einer Modellbahn sicherlicht gut darstellen.

LG, Rudi

Da ich hier seit Tagen mit 'ner hartnäckigen Erkältung herumhänge und keine Lust auf Outdoor-Aktivitäten habe dachte ich mir, den von @agmue nett gemeinten Vorschlag umzusetzen und sein "Ampel-Programm gegen delay()" tatsächlich in OOP umzusetzen. Es ging einfacher als ursprünglich gedacht. Der nachfolgende Sketch zeigt meine Version seines obigen Programmes. Darin enthalten sind auch meine Ideen zur Ergänzung "Fußgänger-Anforderung", "Blaulicht auf Knopfdruck" und "Umschaltung Tag-/Nachtmodus". Der Sketch sieht ein wenig anders aus, als man es von üblichen Arduino-Sketches vielleicht gewöhnt ist, aber das Progi funktioniert wunderbar:

// ----------------------------------------------------------------------------------
// Ampel 1.0 ... für Arduino UNO      
// Mit Bibliothek "MobaTools.h", eigens für dieses Projekt erstellt

#include <MobaTools.h>   

// Parameter "Taster"
const byte tMod     =   2;        //  Taste "Modus"   (Tag-/Nacht)
const byte tPoli    =   3;        //  Taste "Polizei" (Kurzfristiger Blinker)
const byte tUeber   =   4;        //  Taste "Überweg" (Anforderung Fußgänger)

// Parameter "Ampel"
const byte aRot     =   5;        //  LED-Pin 
const byte aGelb    =   6;        //  "
const byte aGruen   =   7;        //  "
const byte aModus   =   8;        //  " (Signal Anforderung Tag-/Nacht-Modus)

const byte fFuss    =   9;        //  LED-Pins (Signal Anforderung f. Fussgänger)
const byte fWeiss   =  10;        //  " (Signal "Warten")
const byte fRot     =  11;        //  "
const byte fGruen   =  12;        //  "

const word zRot     =  30;        //  Sekunden
const word zRotgelb =   2;        //  "
const word zGruen   =  20;        //  "
const word zGelb    =   3;        //  "

// Parameter "Blaulicht"
const byte bPin     =  13;        //  Blaue LED
const word mEin     =  15;        //  Millis EIN
const word mAus     = 150;        //  Millis AUS

const byte wHol     =   2;        //  Wiederholungen
const word mZyk     = 555;        //  Millis Gesamt-Zyklus (Ein + Aus) * Wiederholung inkl. Warten
const word gAnz     =  25;        //  Anzahl kompletter Gesamt-Zyklen

// Instanzen
Taster          Modus(tMod);      //  "Nachtmodus" (Down-Aktiv)
Taster          Polizei(tPoli);   //  "Blaulicht"  (Down-Aktiv)
Taster          Ueberweg(tUeber); //  "Überweg"    (Down-Aktiv)

Warnblinker     Blaulicht( bPin, mEin, mAus, wHol, mZyk, gAnz );

Ampelschaltung  Ampel( aRot,   aGelb,        aGruen, aModus,  \
                       zRot,   zRotgelb,     zGruen, zGelb,   \
                       fWeiss, fRot, fGruen, fFuss            );  

void setup()
{
  Modus.begin();                  //  Buttons / Taster
  Polizei.begin();                //  "
  Ueberweg.begin();               //  "
  
  Ampel.begin();                  //  Ampelschaltung  
  Blaulicht.begin();              //  Blaulicht  
}

void loop()
{
  // --- Permanente Überwachung, Status-Updates etc. ---------------------
  //
  Blaulicht.Run();                //  Abarbeitung Blaulicht, nur bei Anforderung
  Ampel.Run();                    //  Abarbeitung Ampelschaltung, permanent
  
  // --- Aktionen auf Anforderung ----------------------------------------
  //
  if( Modus.KeyDown() )           //  Anforderung Tag-/Nachtmodus
    Ampel.Nachtmodus();           //  Bleibt dann bis neue Anforderung 

  if( Ueberweg.KeyDown() )        //  Anforderung Überweg 
    Ampel.Fussgaenger();          //  Wird in der Rotphase freigegeben
      
  if( Polizei.KeyDown() )         //  Anforderung Polizeieinsatz
    Blaulicht.Start();            //  Blinkt eine Weile, stoppt selbstständig       
}

Wer das Programm testen möchte muß die anhängende Library installieren und einen Blick auf die beigefügte Schaltung werfen. Es ist alles m.E. einigermaßen ausführlich kommentiert. Vielleicht hätte man es auch weniger umständlich programmieren können - aber es erfüllt seinen Zweck und zeigt, wie man mit "ohne delay()" und mit OOP coole Dinge zaubern kann.

LG, Rudi

MobaTools.zip (5.76 KB)