Millis statt delay funktioniert so nicht

Hallo Forumsteilnehmer.
Bei meiner Frage geht es weniger darum das delay durch millis zu ersetzen, sondern um die Problematik drum herum, die das Funktionieren verhindert.
So wie ich es sehe kann das SO nicht funktionieren, die Frage lautet also wie ich es umbaue das es funktionieren kann.

Ich steige dabei in eine if-Schleife ein mit folgender Bedingung und Ausführung:

if ((zaehler == 0) && (tem > t1))  // zaehler = 0; fenster geschlossen
    {
      //seqstrt = millis();
      myMCP.setPinX(0, A, OUTPUT, HIGH);  //LED 2, rot, Bank_A; on;    
      digitalWrite(relaisPinA2, HIGH);  //Relaisboard Spannungsversorgung EIN

      //seqstrt = millis();
    Serial.print(seqstrt);
    Serial.println("  if_initialisierung");
    
        if (seqstrt <= seqstrt + Fenster1 + FeOpen) 
      {
      digitalWrite(relaisPin2, HIGH);  //Fenster1 (D2_AUF & D3_ZU)
      //delay(3500);
      Serial.print(seqstrt);
      Serial.println("  if_Schleife");
      }
      digitalWrite(relaisPin2, LOW);  //Fenster1 AUF_STOP
    Serial.print(seqstrt);
    Serial.println("  else_schleife");

nachdem 5 mal ein anderer Motor angesteuert wird, folgt am Ende der Schleife ein:
zaehler++;

Diesen Zähler benutze ich um auf dem Display einen Status auszugeben.
Mit der delay-option funktioniert das ganze zwar, aber delay ist doof.
Ergo dachte ich mir ich ersetze den delay-teil mit einer millis-funktion, nachdem die Schleife aber nur EINMAL durchlaufen wird (da am ende ja der zaehler für den Status um eins erhöht wird), funktioniert millis natürlich nicht in dem gewünschtem Maße.

Für millis brauche ich eine Scheife und die gibt es nur im Loop, nur wie lager ich das Ganze so aus, das der Pin seine Zeit (HIGH) ist?

Wenn wer eine Idee hat und sich die Mühe machen will was dazu zu schreiben fände ich das toll.
Beste Grüße,
Betty

Es gibt keine if schleifen.
Das was du suchst ist eine schrittkette.

Erstens:
Die Addition ist böse. Sie führt irgendwann zu einem Überlauf der nicht kompensiert wird.

Zweitens;
Eine Addition führt immer zur vergrößerung eines Wertes.
Außer beim unsigned Überlauf.
Also kann der Ausdruck nur kurz vor dem millis() Überlauf wahr werden.

Mehr/Genauer kann ich nicht erkennen, weil der Code bis zur untestbarkeit verstümmelt ist.

Moin Betty,

wie schon geposted, beinhaltet Dein Sketch-Auszug leider nicht genügend Informationen, um den Gesamtzusammenhang erkennen zu können. Die Zeile

if (seqstrt <= seqstrt + Fenster1 + FeOpen)

verheißt aber schon nichts Gutes ... :wink:

Der übliche millis()-Ersatz für delay() basiert auf dem Vergleich der Differenz zwischen

  • Jetztzeit (millis()) und zwischengespeichertem
  • Startwert (im Beispiel "seqStart") mit einem
  • Zeitintervall (im Beispiel "sequenceDuration"):
  if (millis() - seqStart >= sequenceDuration) {

wobei genau diese Vorgehensweise eben auch Überläufe des millis()-Zählers korrekt behandelt (siehe u.a. hier https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/)

Da Du anscheinend eine Art "Maschine" programmierst, könnte Dich der Ansatz "finite state machine" (auf Deutsch "endlicher Automat") interessieren.

siehe https://de.wikipedia.org/wiki/Endlicher_Automat

Hier ein einfaches Beispiel mit einigen Kommentaren:

/*
  Forum: https://forum.arduino.cc/t/millis-statt-delay-funktioniert-so-nicht/1381778
  Wokwi: https://wokwi.com/projects/431040999760784385

  A very simple state machine that demonstrates the use of
    enums for states
    millis() instead of delays to control sequence flows

  A sequence is started by Serial input 's' or 'S'.
  The sequence ends after  "sequenceDuration" [ms]

  ec2021
  2025/05/15

*/

constexpr unsigned long sequenceDuration = 3500; // [ms]
constexpr unsigned long printInterval    = 333;  // [ms]

enum States {IDLE, RUNNING};
States state = IDLE;
unsigned long seqStart = 0;
unsigned long lastTimeHere = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("Input s or S");
}

void loop() {
  verySimpleStateMachine();
}

boolean startSequence() {
  if (Serial.available()) {
    char c = Serial.read();
    if (toupper(c) == 'S') {
      return true;
    }
  }
  return false;
}

void verySimpleStateMachine() {
  switch (state) {
    case IDLE:
      if (startSequence()) {
        // This is performed when a sequence shall start
        // We store the start time
        seqStart = millis();
        // Do something that shall be done once everytime
        // a sequence starts (here we only print something)
        Serial.print("Start - Running - ");
        // and now change the state to RUNNING
        state = RUNNING;
      }
      break;
    case RUNNING:
      if (millis() - lastTimeHere >= printInterval) {
        // This is performed every printInterval while state == RUNNING
        // We store the time when this if clause was entered for the next loop()
        lastTimeHere = millis();
        // We could do e.g. read sensor values or control a stepper or ...just print something
        Serial.print('.');
      }
      if (millis() - seqStart >= sequenceDuration) {
        // This is performed when sequenceDuration is expired
        // We could stop a motor, switch an led on or off, or just print something
        Serial.println(" - Stop");
        Serial.println("Input s or S");
        // As the sequenceDuration has expired and we have done our "final work" we go back to IDLE
        state = IDLE;
      }
      break;
  }
}

Zum Ausprobieren: https://wokwi.com/projects/431040999760784385

Möglicherweise lässt sich Deine Anwendung hierdurch übersichtlicher gestalten, einfacher pflegen und von blockierenden delays befreien?!

Ansonsten müsstest Du schon etwas mehr posten, vermutlich den gesamten Sketch plus einer Erläuterung, was Du damit erreichen willst ...

Viel Erfolg!
ec2021

Jo, weis ich das mein if-fall im günstigsten Fall "unkonventionell" ist.
Gedacht ist das so, das bei Eintreten der Bedingung
if ((zaehler == 0) && (tem > t1) (der zähler ist für eine statusanzeige, tem eine Temperatur)
ein Motor mit Spannung versorgt wird, 3500 millisekunden lang.
Nach 3500 millisekunden, soll nach einer Pause (ist derzeit nicht nötig, daher auch nicht definiert) der nächste (2.te) Motor angesteuert werden , damit die ganze Konstruktion Strommäßig überschaubar bleibt.
Hier mal der "originalteil" der Motoransteuerung in der zweiten Sequenz

// Teilöffnung 2 Anfang
  if ((zaehler == 1) && (tem > 24))  //zaehler = 1; fenster 33% von 90 grad geöffnet
  {
    
    myMCP.setPinX(0, A, OUTPUT, HIGH);  //LED 2, rot, Bank_A; on;
      digitalWrite(relaisPinA2, HIGH);  //Relaisboard Spannungsversorgung EIN
      //Fenster1
      digitalWrite(relaisPin2, HIGH);  //Fenster1 (D2_AUF & D3_ZU)
       delay(3500);
      digitalWrite(relaisPin2, LOW);  //Fenster1 AUF_STOP
    //Serial.print("  Fenster 1 2/3 GEÖFFNET");
    //Serial.println("     ");
      //Fenster2
      digitalWrite(relaisPin4, HIGH);  //Fenster2 (D4_AUF & D3_ZU)
       delay(3500);
      digitalWrite(relaisPin4, LOW);  //Fenster2 AUF_STOP
      //Fenster3
      digitalWrite(relaisPin6, HIGH);  //Fesnter3 (D6_AUF & D7_ZU)
       delay(3500);
      digitalWrite(relaisPin6, LOW);   //Fenster3 AUF_STOP
      //Fenster4
      digitalWrite(relaisPin8, HIGH);  //Fenster4 (D8_AUF & D9_ZU)
       delay(3500);
      digitalWrite(relaisPin8, LOW);    //Fenster4 AUF_STOP
      //Fenster5
      digitalWrite(relaisPinA0, HIGH);  //Fenster5 (A6_AUF & A3_ZU)
       delay(3500);
      digitalWrite(relaisPinA0, LOW);    //Fesnter5 AUF_STOP
    //Serial.print("  Fenster 5 2/3 GEÖFFNET");
    //Serial.println("     ");
      digitalWrite(relaisPinA2, LOW);    //Relaisboard Spannungsversorgung AUS
    myMCP.setPinX(0, A, OUTPUT, LOW);  //LED 2, rot, Bank_A; on;
    zaehler++;
  }

Den Forumsbedingungen nach darf ich noch keine Anhänge/Dateien Hochladen oder so lange Post erstellen, daher der in meinen Augen relevante teil den ich umbauen möchte.

Gepostet habe ich die IF-bedingung, mit dem delay den ich ersetzen will, was so nicht funktioniert, wie ich bereits gesehen habe.
Daher suche eine Lösung für mein Problem und habe schon ein paar Stichworte bekommen um mich weiter zu orientieren damit ich eine Vorstellung von einer Lösung bekomme.
Morgen schaue ich mir deinen Vorschlag mal genauer an, klingt jedenfalls vielversprechend, danke.

Dort ist die vereinfachte Form eines endlichen Automaten sinnvoll.
Eine Schrittkette.

Wenn man seine Software von Anfang an nicht-blockierend ausführt, hat das erhebliche Vorteile.

Du kannst z.B. den Teil, der Aktionen auslöst oder stoppt, getrennt von der Ausführung halten. In Deinem Sketch kannst Du die Sequenz der Relais-Ein/Ausschaltung wunderbar in einer State Machine abbilden und zusätzlich die immer gleichen Folgen

      digitalWrite(relaisPinX, HIGH);  
      delay(3500);
      digitalWrite(relaisPinX, LOW);    

sogar über ein Array der Pins:

constexpr byte relaisPin2 { 2 };
constexpr byte relaisPin4 { 4 };
constexpr byte relaisPin6 { 6 };
constexpr byte relaisPin8 { 8 };
constexpr byte relaisPinA0 { A0 };
constexpr byte pinArray[] = { relaisPin2, relaisPin4, relaisPin6, relaisPin8, relaisPinA0 };
constexpr int noOfPins = sizeof(pinArray) / sizeof(pinArray[0]);

constexpr unsigned long switchDelay = 3500;
unsigned long lastSwitchTime = 0;
int index = 0;

void setup() {
  Serial.begin(115200);
  for (auto p : pinArray) {
    pinMode(p, OUTPUT);
    digitalWrite(p, LOW);
  }
}

void loop() {
  switchRelais();
}

void switchRelais() {
  if (millis() - lastSwitchTime >= switchDelay) {
    lastSwitchTime = millis();
    byte state = !digitalRead(pinArray[index]);
    change(index, state);
    if (!state) {
      index++;
      if (index >= noOfPins) {
        index = 0;
      }
      change(index, HIGH);
    }
  }
}

void change(int idx, byte aState){
    digitalWrite(pinArray[idx], aState);
    Serial.print("Switch pin ");
    Serial.print(pinArray[idx]);
    Serial.print(" to ");
    Serial.println(aState ? "On" : "Off");
}

Wie auch immer, viel Spaß und Erfolg ...
ec2021

void checkActive()
{
  // Teilöffnung 2 Anfang
  if ((zaehler == 1) && (tem > 24))  //zaehler = 1; fenster 33% von 90 grad geöffnet
  {
    if (status == warten)
    { status = schrittStart; }
  }
}

void schrittkette()
{
  switch (status)
  {
    case warten:
      break;

    case schrittStart:
      myMCP.setPinX(0, A, OUTPUT, HIGH);  //LED 2, rot, Bank_A; on;
      digitalWrite(relaisPinA2, HIGH);  //Relaisboard Spannungsversorgung EIN
      //Fenster1
      digitalWrite(relaisPin2, HIGH);  //Fenster1 (D2_AUF & D3_ZU)
      startZeit = millis();
      status = actionOne;
      break;

    case actioneOne:
      if (millis() - startZeit > 3500)
      {
        digitalWrite(relaisPin2, LOW);  //Fenster1 AUF_STOP
        //Serial.print("  Fenster 1 2/3 GEÖFFNET");
        //Serial.println("     ");
        //Fenster2
        digitalWrite(relaisPin4, HIGH);  //Fenster2 (D4_AUF & D3_ZU)
        startZeit = millis();
        status = actionTwo;
      }

      break;

    case actionTwo:
      if (millis() - startZeit > 3500)
      {
        digitalWrite(relaisPin4, LOW);  //Fenster2 AUF_STOP
        //Fenster3
        digitalWrite(relaisPin6, HIGH);  //Fesnter3 (D6_AUF & D7_ZU)
        startZeit = millis();
        status = actionThree;
      }

      break;

    case actionThree:
      if (millis() - startZeit > 3500)
      {
        digitalWrite(relaisPin6, LOW);   //Fenster3 AUF_STOP
        //Fenster4
        digitalWrite(relaisPin8, HIGH);  //Fenster4 (D8_AUF & D9_ZU)
        startZeit = millis();
        status = actionFour;
      }

      break;

    case actionFour:
      if (millis() - startZeit > 3500)
      {
        digitalWrite(relaisPin8, LOW);    //Fenster4 AUF_STOP
        //Fenster5
        digitalWrite(relaisPinA0, HIGH);  //Fenster5 (A6_AUF & A3_ZU)
        startZeit = millis();
        status = actionFive;
      }

      break;

    case actionFive:
      if (millis() - startZeit > 3500)
      {
        digitalWrite(relaisPinA0, LOW);    //Fesnter5 AUF_STOP
        //Serial.print("  Fenster 5 2/3 GEÖFFNET");
        //Serial.println("     ");
        digitalWrite(relaisPinA2, LOW);    //Relaisboard Spannungsversorgung AUS
        myMCP.setPinX(0, A, OUTPUT, LOW);  //LED 2, rot, Bank_A; on;
        zaehler++;
        status = warten;
      }

      break;
  }
}

das hier

boolean startSequence() {
  if (Serial.available()) {
    char c = Serial.read();
    if (toupper(c) == 'S') {
      return true;
    }
  }
  return false;
}

da verstehe ich den Sinn nicht wirklich. Wird da gecheckt ob ein Serial-"Kanal" vorhanden ist um die Eingabe "s" oder "S" zu erkennen?

Der Rest ist recht verständlich, in meinem Sketch benutze ich Case auch für die Anzeige des Menüs, das man da auch if-Fälle unterbringen kann, die man durch den Loop immer wieder aufruft war mir neu ... coole Sache

void updateAnzeige() 
{
  switch (menu) 
  {  //variable menu kann unterschiedliche werte (integer) annehmen

    case -1:
      menu = 0;
      break;

    case 0:
    oledFill(&ssoled, 0, 1);
      //oledWriteString(&ssoled, 0, 0, 0, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 1, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 2, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 3, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 4, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 5, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 6, (char*)"                 ", FONT_NORMAL, 0, 1);
      // oledWriteString(&ssoled, 0, 0, 7, (char*)"                 ", FONT_NORMAL, 0, 1);
      break;

    case 1:  //fall mit variabler menu = 1;
      oledFill(&ssoled, 0, 1);
      char buffer[3];
      snprintf(buffer, sizeof(buffer), "%d", (byte)zaehler);
      oledWriteString(&ssoled, 0, 94, 5, (char*)buffer, FONT_SMALL, 0, 1);
      oledWriteString(&ssoled, 0, 2, 5, (char*)"   Fenster-Stat", FONT_SMALL, 0, 1);
      oledWriteString(&ssoled, 0, 20, 2, (char*)" Automatik ", FONT_NORMAL, 1, 1);
      oledWriteString(&ssoled, 0, 35, 3, (char*)" Modus ", FONT_NORMAL, 1, 1);
      oledWriteString(&ssoled, 0, 0, 4, (char*)"               ", FONT_NORMAL, 0, 1);
      oledWriteString(&ssoled, 0, 0, 6, (char*)"   Manueller   ", FONT_NORMAL, 0, 1);
      oledWriteString(&ssoled, 0, 0, 7, (char*)"     Modus     ", FONT_NORMAL, 0, 1);
      break;

@my_xy_projekt & @ec2021

Danke für eure Hilfe, Hinweise und Beispiele, ich denke das mir das weiterhelfen wird. jetzt geht es daran das umzusetzen.
Das spannende am Programmieren ist die "Uferlosigkeit" der Möglichkeiten, allerdings gibt es auch mit jeder neuen Erfahrung Möglichkeiten Balken auf Wasser zu bauen.
Eure Hinweise helfen mir auch ein Nebenschauplatz unter Kontrolle zu bringen, da der Motor 3 gleiche Sequenzen hat zum öffnen und 3 zum Schließen, kann ich das ganze nun auf eine zum Öffnen (die ich 3 mal durchlaufen kann) und eine zum Schließen (incl 3 maligem Durchlauf) reduzieren.
Puh, drückt im Oberstübchen, macht allerdings auch Spaß.
DANKE.

available() liefert die Anzahl angekommener aber noch nicht gelesener Zeichen.
Also true falls was zum Lesen da ist.

Das ist insgesamt eine Funktion, die im Beispiel zum Starten der Sequenz dient ....

Serial.begin() im Setup() initialisiert eine serielle Schnittstelle zum Datenaustausch mit z.B. dem seriellen Monitor der Arduino IDE

Serial.available() prüft, ob im Empfangspuffer Zeichen zum Auslesen vorhanden sind.

Wenn das der Fall ist wird genau ein Zeichen mit Serial read() dem Puffer entnommen und in der Variablen c gespeichert.

Die Funktion toupper() wandelt dieses Zeichen - soweit es sich um einen Buchstaben handelt, in einen Großbuchstaben um. Entspricht das so gewandelte Zeichen einem 'S' , gibt die Funktion true. sonst in allen anderen Fällen false zurück.

boolean startSequence() {
  if (Serial.available()) {
    char c = Serial.read();
    if (toupper(c) == 'S') {
      return true;
    }
  }
  return false;
}

Der Einsatz von Serial ist bei nahezu jeder Entwicklung im Arduino- Umfeld extrem hilfreich zur Fehlersuche, Ausgabe von Messdaten und der Eingabe von Kommandos.

Eigentlich ist es das Erste was man mit dem berühmten "Hello World" Programm kennenlernt....