Zeitsteurung mit millis

Hallo,

ich möchte ein Ventil nit 2 Zeitvorgaben (Pausenzeit und Betriebszeit) ansteurn, die ich mittels Drehpotensiometer verändern kann. Dazu habe ich folgenden Sketch verfasst.
Die Sensorwerte werden erfasst aber der Relaispin ist immer auf HIGH. Kann den Fehler leider nicht finden.
Die auskommentierte Variante mit delay funktioniert zwar aber eigenartiger Weise nicht den Sensorwerten entsprechend sondern wesentlich langsamer.
Vielleicht kann wer helfen.

int PausenGeberPin = A0;
int BetriebszeitGeberPin = A1;
unsigned long PausenDauer = 0;
unsigned long BetriebsDauer = 0;
int RelaisPin = 13;
unsigned long VergangenePausenZeit=0;
unsigned long VergangeneBetriebsZeit=0;

void setup()
{
 Serial.begin(9600);
 pinMode(PausenGeberPin, INPUT);
 pinMode(BetriebszeitGeberPin, INPUT);
 pinMode(RelaisPin, OUTPUT);
}

void loop()
{
 PausenDauer = analogRead(PausenGeberPin)*1.0;
 BetriebsDauer = analogRead(BetriebszeitGeberPin)*1.0;
 Serial.print("PausenDauer: ");
 Serial.println(PausenDauer);
 Serial.print("BetriebsDauer: ");
 Serial.println(BetriebsDauer);
 Serial.println(VergangenePausenZeit);
 Serial.println(VergangeneBetriebsZeit);
 Serial.println("");

 

 if (millis() - VergangenePausenZeit > PausenDauer)
 {
   digitalWrite(RelaisPin, LOW);
   if (millis() - VergangeneBetriebsZeit > BetriebsDauer)
   {
       VergangenePausenZeit = millis();
       VergangeneBetriebsZeit = millis(); 
       digitalWrite(RelaisPin, HIGH);
    }
 }

 // delay(PausenDauer);
 //digitalWrite(RelaisPin, LOW);
 //delay(BetriebsDauer);
 //digitalWrite(RelaisPin, HIGH);
 
}

MfG

Gerald

  if (millis() - VergangenePausenZeit > PausenDauer)

{
    digitalWrite(RelaisPin, LOW);
    if (millis() - VergangeneBetriebsZeit > BetriebsDauer)
    {
        VergangenePausenZeit = millis();
        VergangeneBetriebsZeit = millis();
        digitalWrite(RelaisPin, HIGH);
    }
  }

Diese Verschachtlung dürfte ein Problem darstellen...

Ich könnte dir meine TimerLib anbieten...
Aber ...

Habs hinbekommen,

If-Verschachtelung entfernt und in der zweiten Abfage die Zeiten addiert.
Soll mal ein automatischer Kettenöler fürs Motorrad werden.

int PausenGeberPin = A0;
int BetriebszeitGeberPin = A1;
unsigned long PausenDauer = 0;
unsigned long BetriebsDauer = 0;
int RelaisPin = 13;
unsigned long VergangeneZeit=0;

void setup()
{
 Serial.begin(9600);
 pinMode(PausenGeberPin, INPUT);
 pinMode(BetriebszeitGeberPin, INPUT);
 pinMode(RelaisPin, OUTPUT);
}

void loop()
{
 PausenDauer = analogRead(PausenGeberPin)*1.0;
 BetriebsDauer = analogRead(BetriebszeitGeberPin)*1.0;
 Serial.print("PausenDauer: ");
 Serial.println(PausenDauer);
 Serial.print("BetriebsDauer: ");
 Serial.println(BetriebsDauer);

 if (millis() - VergangeneZeit > PausenDauer)
 {
   digitalWrite(RelaisPin, LOW);
 }

 if (millis() - VergangeneZeit > PausenDauer+BetriebsDauer)
 {
     VergangeneZeit = millis();
     digitalWrite(RelaisPin, HIGH);
  }
}

Du kannst dir mal anschauen, wie ich das bei meiner PWM gelöst hab.

  void runPwm() {
    unsigned long currentMillis = millis();
  
    if (nextMillis < currentMillis) {
      if (!pinActive && power > 0) {
        activatePin();
        nextMillis = currentMillis + calculateOnMillis();
      } else {
        deactivatePin();
        nextMillis = currentMillis + calculateOffMillis();
      }
    }
}

calculateOnMillis würde bei dir basierend auf dem BetriebszeitgeberPin die Einschaltzeit zurückgeben.
calculateOffMillis würde bei dir basierend auf dem PausenGeberPin die Pausenzeit zurückgeben.
power > 0 musst du noch rausschmeißen, das ist für dich uninteressant.

nextMillis = currentMillis + calculateOnMillis();

Tut mir leid, das sagen zu müssen: Das ist ein fataler Fehler!
Der wird dir recht pünktlich nach 49 Tagen Dauerlauf auf die Füße fallen.

Also:
Bitte du auch "BlinkWithoutDelay" lesen.
So oft, bis es verstanden ist, wieso da kein + auftauchen darf.

Wenn du 49 Tage am Stück Motorrad fährst, ohne auch nur einmal die Zündung aus zu machen, dann darfst du dir da auch gerne eine komplexere Lösung einfallen lassen.

Dass die Millis irgendwann überlaufen ist mir durchaus bewusst. Solange man keinen Dauerbetrieb plant (und das ist bei Pauli nicht der Fall), ist das absolut untragisch.

Dennoch ist es gut, dass du drauf hinweist. Nicht dass sich jemand meine Lösung für einen Dauerläufer abschaut :wink:

Besser: Nicht dass überhaupt einer auf die Idee kommt, diese "Lösung" einzusetzen.

Gruß Tommy

genau, lieber von Anfang an richtig machen, sonst droht später nur Kaos und man weiß nicht warum. Das einzigste Limit was es gibt betrifft die maximale Wartezeit von 49 Tagen, also das maximale einstellbare Intervall oder Pause oder wie auch immer man millis nutzen möchte. Der µC selbst kann ewig laufen trotz Werteüberlauf und macht alles richtig.

Es ist aber ineffizient, wenn ich bei jedem Durchlauf des Loops zuerst rechne und dann vergleiche.
1x rechnen reicht aus.

Wenn mir der Überlauf tatsächlich wichtig ist, dann kann ich das ja abfangen:

static unsigned long nextMillis = 0;
unsigned long currentMillis = millis();

if (nextMillis < currentMillis) {
  //hier der doublecheck
  unsigned long lastMillis = nextMillis - delay;
  if (currentMillis - lastMillis >= delay) {
    //jetzt bin ich mir sicher, dass auch nach überlauf mein delay vergangen ist
    nextMillis = currentMillis + delay;
  }
}

Üblicherweise sind unsere Delays gegenüber der Loop Geschwindigkeit ziemlich lang. Das heißt die Loop läuft einige tausend male durch, bevor das delay überhaupt erreicht ist. Oder anders gesagt: die erste Condition wird viel öfter false liefern, als true.
Somit muss der Chip viel seltener rechnen. Ja der Code erzeugt im Bereich des Überlaufs zusätzliche Last, entlastet den Chip aber die ganze übrige Zeit. Wenn man davon ausgeht, dass so ein Delay sich im Bereich von Sekunden bewegt, dann ist die doppelte Prüfung nur für wenige Sekunden oder Minuten überhaupt Zusatzlast und ansonsten ist es von der Performance her ein Vorteil.

Natürlich kann man die Performanceaspekte auch komplett ignorieren. Das entspricht aber nicht meinem eigenen Anspruch an Code.

/edit
mir fällt auf, dass man das noch geschickter formulieren kann, wenn der Speicherbedarf nicht relevant ist:

static unsigned long nextMillis = 0;
static unsigned long prevMillis = 0;

unsigned long currentMillis = millis();

if (nextMillis < currentMillis && currentMillis - lastMillis >= delay) {
  lastMillis = currentMillis;
  nextMillis = currentMillis + delay;
  }
}
void loop() {
	static unsigned long lastMillis = 0;
	const unsigned long interval = 1000;
		
	if(millis() - lastMillis > interval) {
	// ... mache etwas

	lastMillis = millis();
	}
}

Gibt eigentlich sinnvoll nur dieses. Wie Doc bereits geschriebene hat, wenn die Interval nicht > 49Tage entspricht, geht das ganze durchaus ohne viel zu schreiben.

Mit AVR entlasten hat das ganze wenig zu tun. Der rechnet den millis() Wert im Hintergrund sowie über die ISR mit. Du lässt dir mit millis() lediglich den aktuellen Wert zurückgeben.

Es geht doch nicht um millis() an sich.

Es geht darum, ob ich in jedem Durchlauf "millis() - lastMillis" rechne oder ob ich das nur dann tue, wenn ich tatsächlich in den Block reinkomme.

OT: Um wie viel "Entlastet" du den AVR wenn du das - lastMillis weglässt ...
wie nutzt du die "gewonnen" Performance?

Da sehe ich mehr Ineffizienz in deinem Webserver. Da könntest du so richtig viel rausholen, aber auch dazu gilt - was macht man dann mit dem gesparten. Wenn es für dich so funktioniert ist ja alles gut. Aber für eine Zeitabfrage empehle ich dein Konstrukt nicht weiter.

@TO: wie Combie schon schrieb:

Bitte du auch "BlinkWithoutDelay" lesen.

Bei Lust könnte das ja mal einer auswerten. Habe keinen Arduino oder Logik Analyser hier. Der Effekt wird so geschwinden gering sein, dass man damit keinerlei andere Aktion zusätzlich ausführen kann.

Ich finde, um Effizienz, kann es erst in zweiter Linie gehen.

§1 ist immer die zuverlässige Funktion

Die "Entlastung" ist garantiert marginal und wirkt sich wahrscheinlich nichtmal merklich aus. Dafür müsste man vermutlich tausend solcher Delays einbauen, damit das auffällt.
Allerdings habe ich mir angewöhnt prinzipiell unnötige Aufrufe bzw. Operationen zu vermeiden. Man weiß nicht immer, wie teuer ein Aufruf tatsächlich ist oder ob er nicht irgendwann mal teuer wird. Das gehört zum Job einfach dazu.

Was den Webserver angeht: Warum muss der effizient sein? Der Webserver ist für genau 30sec aktiv und wenn dann niemand conntected hat (was zu 99,9% der Fall ist), dann schaltet er sich komplett ab.

Mir ist es total wumpe, wie jemand sein Delay codet. Ich habe erläutert, warum ich es für richtig halte, die rechenoperation nicht als Teil der condition zu machen und da geht es mir ums Prinzip und nicht um das bischen, was diese Operation jetzt ausmachen würde.
Wenn man sich die unnötige Ausführung der Operationen angewöhnt, dann macht man das ggf. später auch mit wirklich teuren Operationen und dann wundert man sich, warum die Kiste nicht mehr läuft.
Ich hab das im Job oft genug erlebt, dass Code zwar funktioniert hat, wenn dann aber auf einmal die Datenmenge gestiegen ist, wurde es sehr schnell sehr langsam. Da waren die Augen dann schnell ganz groß.

Hallo,

ich sehe aber jetzt nicht wo deine Variante Rechenzeit einspart. Die Subtraktion muss immer ausgeführt werden bedingt durch die UND Verknüpfung. Im dümmsten Fall dauert das alles sogar länger. Meinste nicht auch? Wäre es ODER wäre nur erstmal die erste Prüfung interessant.

if (nextMillis < currentMillis && currentMillis - lastMillis >= delay) {

Ich kenne nur die klassische Variante wie von sschulteworker gezeigt oder die andere die jurs irgendwann mal eingeführt hatte.

static unsigned long last_millis = 0;
const unsigned int interval = 2000;

if (millis() - last_millis < interval) return; // Zeit noch nicht erreicht, Funktion abbrechen
last_millis += interval;                       // addiere Wartezeit

Allen, auch deiner, ist gemeinsam das einmal gerechnet werden muss.

Edit:
deine erste Version ist laut meiner Meinung nach auch nicht Überlauf sicher. Deswegen auch der sinnfreie Doublecheck der dann immer zum tragen kommt und damit auch immer Rechenzeit kostet. Man gewinnt nichts. Man gewinnt nur komplizierteren Code der fehlerträchtig ist.

if (nextMillis < currentMillis) {

Ich wollte es wissen: ich hab mal den BlinkWithoutDelay um zwei Varianten erweitert.

V1 mit Subtraktion wie es viele hier machen würden
V2 mit einer Zielzeiterreichung

was besseres als die Micros davor und danach zu messen ist mir nicht eingefallen.

/*
  Blink without Delay

  Based on discussion in: http://forum.arduino.cc/index.php?topic=524248.msg3618724#new
*/

const int ledPin =  LED_BUILTIN;// the number of the LED pin
uint8_t ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated

// constants won't change:
const long interval = 1000;           // interval at which to blink (milliseconds)

uint32_t v1previousMillis = 0;
uint32_t v1before = 0;
uint32_t v1in = 0;
uint32_t v1ready = 0;
uint32_t v1total = 0;

uint32_t v2nextMillis = interval;    // uint32_t v2previousMillis = 0;   // same usage, but first nextMillis is 1 second
uint32_t v2before = 0;
uint32_t v2in = 0;
uint32_t v2ready = 0;
uint32_t v2total = 0;



void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  // here is where you'd put code that needs to be running all the time.

  unsigned long currentMillis = millis();

  v1before = micros();
  if (currentMillis - v1previousMillis > interval) {
    v1in = micros();
    // save the last time

    v1previousMillis = currentMillis;

    // do what you want to do
    v1total = v1in - v1before;

    // Statistic
    Serial.print(F("V1: "));Serial.print(v1before);
    Serial.print(F(" to "));Serial.print(v1in);
    Serial.print(F(" =  "));Serial.print(v1total);Serial.println(F(" micros"));
  }

  v2before = micros();
  if (v2nextMillis < currentMillis) {                       // Never use this example in production systems
    v2in = micros();
    // calculate the next time 

    v2nextMillis = currentMillis + interval;

    // do what you want to do
    v2total = v2in - v2before;

    // Statistic
    Serial.print(F("V2: "));Serial.print(v2before);
    Serial.print(F(" to "));Serial.print(v2in);
    Serial.print(F(" =  "));Serial.print(v2total);Serial.println(F(" micros"));
  }


  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

V1/V2 sollten das gleiche machen.

Ergibt auf einem UNO Clone im Serial

V1: 1001480 to 1001484 =  4 micros
V2: 1002408 to 1002412 =  4 micros
V1: 2002960 to 2002964 =  4 micros
V2: 2003888 to 2003892 =  4 micros
V1: 3003400 to 3003404 =  4 micros
V2: 3004328 to 3004336 =  8 micros
V1: 4004872 to 4004876 =  4 micros
V2: 4005800 to 4005804 =  4 micros
V1: 5005328 to 5005332 =  4 micros
V2: 5006256 to 5006260 =  4 micros
V1: 6006800 to 6006804 =  4 micros
V2: 6007728 to 6007732 =  4 micros
V1: 7007248 to 7007252 =  4 micros
V2: 7008176 to 7008184 =  8 micros
V1: 8008720 to 8008724 =  4 micros
V2: 8009648 to 8009652 =  4 micros
V1: 9009160 to 9009164 =  4 micros
V2: 9010092 to 9010096 =  4 micros
V1: 10010632 to 10010636 =  4 micros
V2: 10011656 to 10011660 =  4 micros
V1: 11011084 to 11011092 =  8 micros
V2: 11012112 to 11012116 =  4 micros
V1: 12012564 to 12012568 =  4 micros
V2: 12013588 to 12013592 =  4 micros
V1: 13014028 to 13014032 =  4 micros
V2: 13015056 to 13015060 =  4 micros
V1: 14015500 to 14015504 =  4 micros
V2: 14016528 to 14016532 =  4 micros
V1: 15016976 to 15016984 =  8 micros
V2: 15018004 to 15018008 =  4 micros
V1: 16017424 to 16017432 =  8 micros
V2: 16018448 to 16018452 =  4 micros
V1: 17018896 to 17018904 =  8 micros
V2: 17019920 to 17019928 =  8 micros
V1: 18019340 to 18019344 =  4 micros
V2: 18020368 to 18020372 =  4 micros

V1 (mit Subtraktion) ist fast immer so "schnell" wie die V2.
Ausreißer gibt es nach beiden Seiten.

Einer der alten kann sicher erklären warum das so ist. Mir reicht die Beobachtung.
Ich bleib bei V1.

Zur Zeitersparnis:

if (nextMillis < currentMillis && currentMillis - lastMillis >= delay) {

nextMillis < currentMillis ist nur dann true, wenn die nächste Schaltzeit erreicht ist (also wenn das Delay abgelaufen ist). Also ist es die meiste Zeit false.
Wenn der erste Ausdruck schon false ist, dann wird der zweite Ausdruck gar nicht ausgeführt.

Man kann sich && und || auch so erklären:
&&

if(a) {
 if(b) {
  doIt();
 }
}

wenn der äußere Block false liefert, kommt man in den inneren Block gar nicht rein. Das zweite Statement wird nur ausgeführt, wenn das erste true ist.

||

if(a) {
  doIt();
} else if(b) {
  doIt();
}

wenn der erste Block true liefert, kommt man in den zweiten Block nicht mehr rein. Das zweite Statement wird nur ausgeführt, wenn das erste false ist.

Zum Test von noiasca:
Du betrachtest einen viel zu kleinen Bereich. Du musst das 1000x am Stück machen und dann die Zeit anschauen.

int repeat = 1000;
int i = 0;
v1before = micros();
while(i<repeat) {
  if (currentMillis - v1previousMillis > interval) {
  }
  i++;
}
v1after = micros();


i = 0;
v2before = micros();
while(i<repeat) {
  if (v2nextMillis < currentMillis) {
  }
  i++;
}
v2after = micros();

So kannst du dann beliebig skalieren. Auf 10000 erhöhen oder sonst was, um zu sehen, wie es sich auswirkt. Irgendwann sollte man mit der Zeit in einen messbaren Bereich kommen (4 Micros ist nicht messbar).
Was man natürlich mit drin hat, ist die Operation auf dem Zähler i. Das lässt sich aber nicht vermeiden, wenn man nicht 1000x das if statement hinschreiben will.

Hallo,

&& das stimmt, da hatte ich einen Denkfehler. Zum anderen, gut, kann man machen, ist aber noch nicht komplett effizient. Wenn man jetzt nicht hunderte verschiedene Intervalle hat, dann lässt man vom Timer im Intervall ein Flag setzen und fragt nur das Flag im Hauptprogramm ab. Wenn wahr führt man aus und setzt das Flag zurück. Dabei wird nichts mehr gerechnet.

Mir ist es total wumpe, wie jemand sein Delay codet. Ich habe erläutert, warum ich es für richtig halte, die rechenoperation nicht als Teil der condition zu machen und da geht es mir ums Prinzip und nicht um das bischen, was diese Operation jetzt ausmachen würde.

Dein Code ist fehlerhaft!

nextMillis = millis() + interval;
Führt zu einem Überlauf.
Und wenn das geschieht, liefern zeitlich nachfolgende Abfragen falsche Ergebnisse.

Wenn du das für dich tolerieren möchtest, ok.
Aber dann akzeptiere auch bitte, dass wir es hier, mit noch größerer Intensität, verhindern möchten, dass Andere das falsche lernen.

Allerdings habe ich mir angewöhnt prinzipiell unnötige Aufrufe bzw. Operationen zu vermeiden.

Das unterstütze ich!
Aber auch nur soweit, wie es nicht auf kaputten/fehlerhaften Code beruht.