BlinkwithoutDelay - Die Nachtwächtererklärung

Ich wurden gebeten, die Erklärung des BlinkWithoutDelay hier nochmal reinzustellen.

Am Beispiel eines Wachmanns, der seine Runden dreht, wird in einfacher und eingängiger Weise erklärt, wie man ohne delay() zeitliche Abfolgen programmieren kann.

(an die Profis: es geht auch einfacher, es sind syntaktische Unsauberkeiten drin. Mein Augenmerk lag auf einer möglichst einfachen Darstellung für Menschen, die noch niemals im Leben was mit Programmiersprachen zu tun hatten)

Also: Wie funktioniert Blink Without Delay?
So kompliziert ist das gar nicht.

Gehen wir vom Blinken mal einen Schritt weg zu was ganz praktischem: Wir haben einen Nachtwächter / Wachmann, der immer seine Runden um ein Haus geht (das entspricht unserer Loop).
Zusätzlich erhält er die Aufgabe, jede Stunde für 15min eine Lampe einzuschalten. Die Lampe selbst sieht er nicht, aber es gibt einen Schalter am Haus.
Was macht er also:
Er schaltet das Licht ein, merkt sich die Zeit und dass die Lampe ein ist und macht weiter seine Runden. Bei jeder Runde schaut er auf die Uhr, ob die 15min schon um sind. Sind sie noch nicht um, geht er einfach weiter, wenn die um sind, schaltet er die Lampe aus und merkt sich, dass sie jetzt aus ist.
Nun schaut er bei jeder Runde auf die Uhr, um zu prüfen, ob die Stunde seit Einschalten schon um ist.
Ist sie um, schaltet er wieder ein, notiert sich Status der Lampe und Uhrzeit, und nimmt seine Runden wieder auf.

Genau so macht es der Arduino.
wenn wir die Schritte des Wachmanns in Arduinosprache übersetzen, schaut das in etwas so aus:

Zwischenablage04.jpg

wenn sich der Wachmann die Zeiten auf dem Zettel notiert, schreibt der Arduino einen Wert in eine Variable. millis() ist die Systemuhr des Arduino. Sie zählt einfach die vergangenen millisekunden seit Start des Arduino.

der zugehörige Code schaut dann so aus:

unsigned long LED_timestore;  // Variable Speicher für Systemzeit.
int LedStatus = LOW;
int LedPin = 13;



void setup() {
  pinMode(LedPin, OUTPUT);  // teilt dem Arduino mit, dass das ein Ausgang ist.
}

void loop() {
  if (LedStatus == LOW) {
    if (millis() - LED_timestore> 1000 ) {
      digitalWrite(LedPin, HIGH);
      LED_timestore = millis();
      LedStatus = HIGH;
    }
  } else {
    if (millis() - LED_timestore> 300) {
      digitalWrite(LedPin, LOW);
      LedStatus = LOW;
    }
  }
}

Wenn der Wachmann / Arduino mehrere Lampen schalten soll, braucht er einfach noch mehr Zettel / Variable und Schalter / Pins.

Geben wir Ihm doch noch eine Aufgabe dazu:
Erweiterung:
Ausserdem soll er alle 3h die Bewässerung für 10min einschalten.

Dann könnte das so aussehen:

In dieser Weise kann man (fast) beliebig viele Aktionen unabhängig voneinander ausführen lassen.
Man kann es übersichtlicher gestalten, in dem man die einzelnen Aktionen in Funktionen packt, oder flexibler, in dem man die Zeiten in Arrays steckt. Und, und, und...

Im Anhang Tabellen als Word-Datei (Excel geht leider nicht)

blink_without_delay_wachmann.doc (64 KB)

2 Likes

Das kann man aber auch allgemein besser schreiben, ich konnte es jetzt nicht testen..

unsigned long Zeit_Eben;  // Variable Speicher für Systemzeit.
int meineEinZeit=700;
int meineAusZeit=300;
int ledStatus = LOW;
int LedPin = 13;

void setup() {

  pinMode(LedPin, OUTPUT);  // teilt dem Arduino mit, dass das ein Ausgang ist.

}

void loop() {

  int Zeitfaktor=0;

  if (ledStatus == LOW)
    Zeitfaktor=meineAusZeit;
  else
    Zeitfaktor=meineEinZeit;


  if (Jetzt()-Zeitfaktor > Zeit_Eben ) {

    Zeit_Eben = Jetzt();
    ledStatus= !ledStatus; // Umkehrung Status
    digitalWrite(LedPin, ledStatus);

  }

}

unsigned long Jetzt()
{
  return millis();
}

oder so

unsigned long Zeit_Eben;  // Variable Speicher für Systemzeit.
int meineEinZeit=700;
int meineAusZeit=300;
int LedStatus = LOW;
int LedPin = 13;

void setup() {
  pinMode(LedPin, OUTPUT);  // teilt dem Arduino mit, dass das ein Ausgang ist.
  }

void loop() {
  if (LedStatus == LOW) {
  
    if (Jetzt()-meineAusZeit > Zeit_Eben ) {
      digitalWrite(LedPin, HIGH);
      Zeit_Eben = Jetzt();
      LedStatus = HIGH;
    }
 
  } else {
    
 if (Jetzt()-meineEinZeit > Zeit_Eben) {
      digitalWrite(LedPin, LOW);
      Zeit_Eben = Jetzt();
      LedStatus = LOW;
    
 }
}
}

unsigned long Jetzt()
{
return millis();
}

Für das Verständnis ist es allerdings besser zu verstehen.. weil man eine ohnehin schwierige Numemr nicht noch im Kopf umkehren muss.

Magst wohl recht haben.. aber da muss ein Programm erstmal hinkommen.

Dann erkläre es doch gleich richtig!

Womöglich wird die Millis() Variable nach 50Tagen oder wann auch immer überlaufen und auf Null resettet, wenn man kurz vorm Überlauf die 300 oder 700 draufrechnet gibts einen Rechenfehler.

Also macht man das mit Minus.

#include <TaskMacro.h>
// Die  TaskMacros sind hier zu finden:
// https://forum.arduino.cc/index.php?topic=415229.0


const bool AUS = false;
const bool EIN = true;

// Pin def
const byte licht  = 13;
const byte wasser = 12;


void tueWasser()
{
  taskBegin();
  taskPause(3L*60L*60L*1000L); // 3H warten
  while(1)
  { 
   digitalWrite(wasser,EIN);
   taskPause(10L*60L*1000L); // 10 Minuten warten
   digitalWrite(wasser,AUS);
   taskPause((3L*60L*60L-10L*60L)*1000L); // Warten bis 3H voll 
  }
  taskEnd();
}

void tueLicht()
{
  taskBegin();
  taskPause(60L*60L*1000L); // Stunde warten
  while(1)
  { 
   digitalWrite(licht,EIN);
   taskPause(15L*60L*1000L); // 1/4 Stunde warten
   digitalWrite(licht,AUS);
   taskPause(45L*60L*1000L); // 3/4 Stunde warten
  }
  taskEnd();
}

void nachtwaechter()
{
  tueLicht();
  tueWasser();
}

void setup() 
{
  pinMode(licht,OUTPUT);  
  digitalWrite(licht,AUS); 
  
  pinMode(wasser,OUTPUT); 
  digitalWrite(wasser,AUS); 
}

void loop() 
{
  nachtwaechter();
}

Das ist eine Umsetzung des Nachtwächters mit Hilfe der TaskMacros.

PS:
Schuppeste, meine Kritik an deinem Code habe ich gelöscht, da sie jetzt hinfällig ist.

1 Like

Schuppeste:
Das kann man aber auch allgemein besser schreiben, ich konnte es jetzt nicht testen..

ja, ich weiß.

Deshalb hatte ich auch das geschrieben:

guntherb:
(an die Profis: es geht auch einfacher, es sind syntaktische Unsauberkeiten drin. Mein Augenmerk lag auf einer möglichst einfachen Darstellung für Menschen, die noch niemals im Leben was mit Programmiersprachen zu tun hatten)

Aber tob dich ruhig weiter aus wenns deinem Wohlbefinden dient.

1 Like

guntherb:
ja, ich weiß.

Deshalb hatte ich auch das geschrieben: Aber tob dich ruhig weiter aus wenns deinem Wohlbefinden dient.

Hallo Gunther,

deine Erklärung (auch mit dem DocToGo) ist sehr ausführlich und verständlich.
Das es nicht um den Sketch geht, sondern um eine verständliche Beschreibung hat wohl nicht jeder mitbekommen.

Alle Kritiker müssen das erst mal besser machen. Also mach weiter so.

Vielen Dank.

P.S.
Die Lösung von combie finde ich allerdings gut gelungen und hoffe das es für Anfänger verständlich ist.

Ich finde Deine Anleitung sehr anschaulich, weshalb ich sie bei meiner Anleitung Ein Endlicher Automat entsteht mit verlinkt habe.

Deine Anmerkung habe ich gelesen, dennoch fände ich diese Zeilen nicht gar so verwirrend:

bool LedStatus = LOW;
const int LedPin = 13;

guntherb:
Ich wurden gebeten, die Erklärung des BlinkWithoutDelay hier nochmal reinzustellen.

Am Beispiel eines Wachmanns, der seine Runden dreht, wird in einfacher und eingängiger Weise erklärt, wie man ohne delay() zeitliche Abfolgen programmieren kann.
Im Anhang Tabellen als Word-Datei (Excel geht leider nicht)

Vielen Dank für dieses geniale Beispiel.

Gruß Tommy

@guntherb

1h Wachmann entspricht 1sek Arduino
Erweiterung:
Ausserdem soll er alle 3h die Bewässerung für 10min einschalten.

Auf die Uhr sehen, ob die 3h schon rum ist. Wenn ja: if (millis() - Water_timestore > 1000){

müsste es nicht heissen:

  if (millis() -  Water_timestore > 3000){

ja, sollte es, da hast du recht.

Hallo,

ich weiß auch nicht warum man sich über eine gute Erklärung künstlich aufregt.
Zudem der Variablenüberlauf hier gar keine Rolle spielt.

Es ging ja gar nicht um den Überlauf (der ist korrekt), sondern dass ich 1000 anstelle 3000 geschrieben hatte. Ich verstehe es auch nicht als aufregen, sondern als Hinweis auf einen Fehler.

Ich werde es bei Gelegenheit korrigieren.

Ich verstehe es auch nicht als aufregen, sondern als Hinweis auf einen Fehler.

dito/ebenso/konform

Dieses Tutorial soll Anfängern auf die Sprünge helfen, und nicht verwirren.

combie:
dito/ebenso/konform

Dieses Tutorial soll Anfängern auf die Sprünge helfen, und nicht verwirren.

Deshalb hatte ich auch die Frage gestellt. Nicht dass sich Anfänger wundern wieso die "1000" einmal 1 Stunde, und einmal 3 Stunden bedeuted.

Es sollte keinesfalls eine Kritik an der genialen Erklärung sein.

Hallo,

will ja nicht nörgeln, aber ich lese Threads immer noch komplett. Darauf bezog sich meine Aussage. Ich sehe weder ein Überlaufproblem noch eine angebliche, in einen Fehler führende, Addition.

Dann erkläre es doch gleich richtig!

Womöglich wird die Millis() Variable nach 50Tagen oder wann auch immer überlaufen und auf Null resettet, wenn man kurz vorm Überlauf die 300 oder 700 draufrechnet gibts einen SystemFehler.

Also macht man das mit Minus.

Halllo und Grüss Euch erstmal.
Vielen Dank für die ausführliche Erklärung!

Ich habe da eine Frage :
Ich habe eine Uhr gebaut.

Kann ich den "Wachmann" auch für das refreshen eines Displays (Nokia5110) (Displayclear) verwenden und die Sekunden Punkte einer Uhr darüber blinken lassen? Es müsste doch möglich sein, die Ausgabe vom LED Pin umzuschreiben auf eine Funktion die dann Aufgerufen wird? Oder wie ich es gerne machen würde zwei Funktionen.

Bisher habe ich das mit delay gemacht ... keine gute lösung

Vielen Dank!

Carsten

ja liese es sich, vorausgesetzt das die Abarbeitung binnen einer Sekunde erfolgt, ansonsten hast du ein unregelmäßiges Blinken.

Es gibt ja noch eine weitere lösung
Z.b. per Interrupt - die sind aber sehr schwer zu programmieren .. oder?

carsten72:
Halllo und Grüss Euch erstmal.
Vielen Dank für die ausführliche Erklärung!

Ich habe da eine Frage :
Ich habe eine Uhr gebaut.

Kann ich den "Wachmann" auch für das refreshen eines Displays (Nokia5110) (Displayclear) verwenden und die Sekunden Punkte einer Uhr darüber blinken lassen? Es müsste doch möglich sein, die Ausgabe vom LED Pin umzuschreiben auf eine Funktion die dann Aufgerufen wird? Oder wie ich es gerne machen würde zwei Funktionen.

Bisher habe ich das mit delay gemacht ... keine gute lösung

Vielen Dank!

Carsten

Klar geht das. Anstelle der Led erfolgt Deine Ausgabe.

Gruß Tommy