Millis - Ein- und Ausschaltverzögerung

Guten Tag,

bin gerade dabei eine Ein- und Ausschaltverzögerung zu programmieren. Bin noch blutiger Anfänger und versuche mich mit Google irgenwie ans Ziel zu hangeln. Leider bin ich an einem Punkt wo ich nicht mehr weiter komme und hoffe hier vielleicht etwas Hilfe zu bekommen.

Wenn der Sensor Strom bekommt (man könnte ihn auch einfach durch einen Schalter ersetzen, nur in der Realität ist es bei mir ein Stromsensor) soll mit einer Verzögerung von x Sekunden (im Beispiel 3 Sekunden) das Relais schalten und sobald der Sensor kein Strom mehr hat das Reläis mit einer Verzögerung von x Sekunden (im Beispiel 5 Sekunden) wieder ausschalten.

Der Code wie er unten steht funktionier auch genau so wie ich es möchte ABER

  1. Macht er das nicht zuverlässig, manchmal überspringt er die Einschaltverzögerung, warum?

  2. Würde ich den Code gerne mit 1-2 weiteren Sensoren und Relais erweitern, dies ist mir warum auch immer auch nicht gelungen. Am Ende hat immer gar nichts mehr funktioniert. :frowning:

int sensor = 4;
int relais = 5;

unsigned long lastMillis1;
unsigned long lastMillis2;

void setup() {

pinMode (sensor, INPUT_PULLUP);
pinMode (relais, OUTPUT);
lastMillis2 = millis();
lastMillis1 = millis();

digitalWrite(relais, HIGH);

}

void loop() {

if (digitalRead(sensor) == LOW){

 // Einschaltverzögerung

if ((millis() - lastMillis1) >= 3000) {
  digitalWrite(relais, LOW);
   lastMillis1 = millis();
  
  }
   }

if (!digitalRead(sensor)) {
    lastMillis2 = millis();
  }
   
if(digitalRead(sensor) == HIGH){

 //Ausschaltverzögerung 

if ((millis() - lastMillis2) >= 5000) {
  
  digitalWrite(relais, HIGH);
}
 }

}

Vielen vielen Dank schon mal für jede Antwort!

Ich wünsche euch allen frohe Osterfeiertage.

Viele Grüße
Frank

Ich könnte dir meine Libs anbieten, um solche Probleme zu erschlagen.

/**
 *    Ablaufdiagramm - Zeitdiagramm
 *    
 *        S1    _----------_____  Schalterstellung
 *        OUT1  _-------------__  Verzoegertes abschalten
 *        OUT2  ____-------_____  Verzoegertes einschalten
 *        Der Schalter S1 arbeitet invers und ist entprellt
 *       
 *
*/

#include <CombieTimer.h>
#include <CombiePin.h>
#include <CombieTypeMangling.h>

using namespace Combie::Pin;
using namespace Combie::Timer;
using namespace Combie::Millis;

TasterGND<2> S1; // Taster zwischen Pin und GND(invertierend)
OutputPin<3> OUT1;
OutputPin<4> OUT2;


EntprellTimer    entprell { 20_ms};  // Schalter entprellen
RisingEdgeTimer  ton      {500_ms};  // steigende Flanke wird verzoegert
FallingEdgeTimer toff     {5_sec};   // abfallende Flanke wird verzoegert
 
void setup(void) 
{
  S1.initPullup();
  OUT1.init();
  OUT2.init();
}

void loop(void) 
{
  bool schalter = entprell = S1; 
  OUT1 = toff = schalter;
  OUT2 = ton  = schalter;
}

Durch deinen Code blicke ich leider so auf Anhieb nicht durch.
Was wohl an den seltsamen Verschachtelungen liegt, und den Einrückungen.

CombieLib.zip (76.1 KB)

frankwg:
Der Code wie er unten steht funktionier auch genau so wie ich es möchte ABER

  1. Macht er das nicht zuverlässig, manchmal überspringt er die Einschaltverzögerung, warum?

  2. Würde ich den Code gerne mit 1-2 weiteren Sensoren und Relais erweitern, dies ist mir warum auch immer auch nicht gelungen. Am Ende hat immer gar nichts mehr funktioniert. :frowning:

Herzlich willkommen im Club.

Du brauchst eine Variable, die den Umschaltzeitpunkt aufnimmt.
Du brauchst Statusvariablen die PinZustand und Schaltvorgang begleiten.

if (digitalRead(sensor) == LOW)

if (!digitalRead(sensor))

Das ist das selbe.
gewöhne Dir eine Schreibweise an. Ich empfehle letztere.

Dann kannst Du beides zusammenfassen.

Am besten baust du dir in einen Sketch in jede If-Anweisung auch ein entpsrechendes Serial.println("bin jetzt da"); ein damit du mitverfolgen kannst was dein Sketch macht.

Meiner Meinung hast du deine lastMillis1 und lastMillis2 durcheinander gewürfelt. Aus deiner Beschreibung ist mir noch nicht klar, wie das im Code stehen soll.

Beschreibe es dir selber noch mal in ganz kurzen Sätzen, inkl. der Variablen die du verwenden möchtest. Dann klappts auch mit der Übersetzung in Code

@TO: Noch einen Tipp: Benenne die Variablen so, dass Du auch in einigen Wochen noch weist, was sie bedeuten. Also anstelle lastMillis1 besser lastMillisAn usw.

Gruß Tommy

Vielen Dank schon mal für die Antworten! Wirklich toll! Leider komme ich trotzdem nicht weiter. :frowning:

Wollte die CombieTimer Library nutzen, leider ohne Erfolg. Mit diesem Code klappt es noch das die LED nach 5 Sekunden angeschaltet wird wenn der Arduino startet:

#include <CombieTimer.h>

Combie::Timer::SimpleTimer timer; // timer Instanz anlegen

int taster =2;
int led = 7;

void setup()
{
  pinMode(led,OUTPUT);
  pinMode(taster, INPUT_PULLUP);

  timer.start();
}

void loop()
{
  if(timer(5000)) // wenn abgelaufen
  { 
    digitalWrite(led, HIGH);
    timer.start();
  }
}

Sobald ich aber eine Schalterabfrage davor schreibe funktioniert der Timer nicht mehr, sah erst so leicht aus. Sehr schade.

#include <CombieTimer.h>

Combie::Timer::SimpleTimer timer; // timer Instanz anlegen

int taster =2;
int led = 7;

void setup()
{
  pinMode(led,OUTPUT);
  pinMode(taster, INPUT_PULLUP);

  timer.start();
}

void loop()
{

  if(digitalRead(taster)==LOW)
  {
    if(timer(5000)) // wenn abgelaufen
    { 
    digitalWrite(led, HIGH);
    timer.start();
    }
  }
    
}

Was mach ich falsch?

Edit:
Ich glaube das Problem ist, dass der Timer im Setup schon startet und bis ich auf den Taster drücke schon abgelaufen ist und die LED ohne Verzögerung angeschaltet wird. Kann das sein? Wenn ja wie starte ich den Timer wenn es auch Sinn macht? Mit timer.start(); nach der If Abfrage von dem Taster hat es nicht geklappt.

frankwg:
Vielen Dank schon mal für die Antworten! Wirklich toll! Leider komme ich trotzdem nicht weiter. :frowning:

Was mach ich falsch?

Denke Dir das logisch, was passieren muss.

const int Ausgabe_PIN = 13;
const int Einschalt_PIN = 2;

bool Einschalt_Status = LOW;
unsigned long start_ein = 0;

void setup() {
 // put your setup code here, to run once:
 pinMode(Ausgabe_PIN, OUTPUT);
 pinMode(Einschalt_PIN, INPUT_PULLUP);
}

void loop() {
 if (digitalRead(Einschalt_PIN) && (!Einschalt_Status))
 {
   Einschalt_Status = HIGH;
   start_ein = millis();
 }
 Einschalten();
 Ausschalten();
}

void Einschalten()
{
 const unsigned long Einschaltsperre = 9000;             // Wartezeit
 if ((millis() - start_ein > Einschaltsperre) && (Einschalt_Status))
 { // Bedingung  erfüllt
   digitalWrite(Ausgabe_PIN, HIGH);                          // PIN an
   Einschalt_Status = LOW;                                     // Status zurücksetzen
 }
}

void Ausschalten()
{
}

Das Ausschalten schaffst Du damit.

Danach: Überlege, was passiert wenn Du ausschalten startest bevor einschalten + Einschaltverzögerung durchgelaufen sind, bzw. Du einschalten startest, bevor ausschalten durch ist.
Das wäre dann die gegenseitige Verriegelung.

const int Ausgabe_PIN = 13;

const int Einschalt_PIN = 2;

bool Einschalt_Status = LOW;
unsigned long start_ein = 0;

void setup() {
// put your setup code here, to run once:
pinMode(Ausgabe_PIN, OUTPUT);
pinMode(Einschalt_PIN, INPUT_PULLUP);
}

void loop() {
if (digitalRead(Einschalt_PIN) && (!Einschalt_Status))
{
  Einschalt_Status = HIGH;
  start_ein = millis();
}
Einschalten();
Ausschalten();
}

void Einschalten()
{
const unsigned long Einschaltsperre = 9000;             // Wartezeit
if ((millis() - start_ein > Einschaltsperre) && (Einschalt_Status))
{ // Bedingung  erfüllt
  digitalWrite(Ausgabe_PIN, HIGH);                          // PIN an
  Einschalt_Status = LOW;                                     // Status zurücksetzen
}
}

void Ausschalten()
{
}




Das Ausschalten schaffst Du damit.

Danach: Überlege, was passiert wenn Du ausschalten startest bevor einschalten + Einschaltverzögerung durchgelaufen sind, bzw. Du einschalten startest, bevor ausschalten durch ist.
Das wäre dann die gegenseitige Verriegelung.

Guten Morgen, vielen Dank für deine Antwort aber mit diesem Code funktioniert leider gar nichts. Bzw. wird die LED nach 9 Sekunden nachdem der Arduino gestartet wird eingeschalter. Mit einem Taster kann ich da gar nichts machen, nicht ein, und nicht aus schalten.

Ich würde glaube ich weiterhin gerne die CombieTimer Library nutzen, da mir diese am einfachsten erscheint.

Ich dachte ich komme mit einem kleinen Problem und mittlerweile stehe ich wieder am Anfang und ich hab so wenig Durchblick wie noch nie in diesem Projekt. Etwas deprimierend :frowning:

wenn du es nicht im Klartext hinschreiben kannst oder willst, was du benötigst, wirst du daraus auch keinen Code machen können.

Ich sehe keine Serial. print Ausgaben in deinen Codes in Post #5.

Ich glaube das Problem ist, dass

programmieren hat wenig mit glauben zu tun. Beginne zu analysieren. Serial.print können dir dabei helfen.

noiasca:
wenn du es nicht im Klartext hinschreiben kannst oder willst, was du benötigst, wirst du daraus auch keinen Code machen können.

Guck doch mal in meinen ersten Post, da steht es in Sätzen. Kann es sehr wohl klar formulieren was ich erreichen will nur leider klappt es mit der Programmierung nicht.

Einen habe ich auch noch :wink: - mit meinen MobaTools ( kannst Du über den Bibliotheksmanager installieren )

#include <MobaTools.h>
int sensor = 4;
int relais = 5;

MoToTimer verzoegerungAn;
MoToTimer verzoegerungAus;
bool sensorState;           // zur Flankenerkennung

void setup() {

  pinMode (sensor, INPUT_PULLUP);
  pinMode (relais, OUTPUT);

  digitalWrite(relais, HIGH);

}

void loop() {
  delay(20);  // nur zur Entprellung, kann bei prellfreiem Sensorsignal entfallen
  if ( !digitalRead(sensor) && !sensorState ) {
    sensorState = true;
    // Einschaltverzögerung starten
    verzoegerungAn.setTime( 3000 );
  }

  if (digitalRead(sensor) && sensorState) {
    //Ausschaltverzögerung
    sensorState = false;
    verzoegerungAus.setTime( 5000 );
  }

  if ( verzoegerungAn.expired() ) {
    //Einschaltverzoegerung abgelaufen
    digitalWrite(relais, LOW);
  }

  if ( verzoegerungAus.expired() ) {
    //Ausschaltverzoegerung abgelaufen
    digitalWrite(relais, HIGH);
  }

}

Du musst dir aber noch überlegen, was passieren soll, wenn sich der Status des Sensors während der Laufzeit der Verzögerung ändert.
Evtl. kommst Du auch mit einem MoToTimer aus, da ja Einschalt- und Ausschaltzeit nie gleichzetig läuft. Dann musst Du über ein Flag oder die Pinzustände beim Ablauf des Timers unterscheiden ob es Ein- oder Ausschalten ist.

Guck doch mal in meinen ersten Post

Ein Block über 4 Zeilen mit einem einzigen Satzzeichen ist keine Beschreibung die ein Controller versteht.

aber ok, wenn du nicht willst, dann mach ich beim meinen eigenen Sachen weiter.

abschließender Tipp, recherchiere was eine State Machine macht.
ich sehe da sowas wie enum {IDLE, ONDELAY, ON, OFFDELAY};

frankwg:
Guten Morgen, vielen Dank für deine Antwort aber mit diesem Code funktioniert leider gar nichts. Bzw. wird die LED nach 9 Sekunden nachdem der Arduino gestartet wird eingeschalter. Mit einem Taster kann ich da gar nichts machen, nicht ein, und nicht aus schalten.

Eine Bedingung versaut, weil ich nicht copy und paste gemacht habe und im wackligen Vehicel mir was ausgedacht habe um Dir unter die Arme zu greifen.

Richtig: if ( ! digitalRead(Einschalt_PIN) && (!Einschalt_Status))

Ich würde glaube ich weiterhin gerne die CombieTimer Library nutzen, da mir diese am einfachsten erscheint.

Ich dachte ich komme mit einem kleinen Problem und mittlerweile stehe ich wieder am Anfang und ich hab so wenig Durchblick wie noch nie in diesem Projekt. Etwas deprimierend :frowning:

Wo ist denn das Problem?
Wenn Du die Logik die Du brauchst oder die dahinter steckt nicht für dich selbst erörters und verstehst, kommt Dir eine lib sicher entgegen, aber beim nächsten Mal stehst Du vor genau dem selben Problem.

Du hast meinen Codeschnipsel ja auch nicht verstanden.

my_xy_projekt:
Wenn Du die Logik die Du brauchst oder die dahinter steckt nicht für dich selbst erörters und verstehst, kommt Dir eine lib sicher entgegen, aber beim nächsten Mal stehst Du vor genau dem selben Problem.

... und nimmst dann wieder genau dieselbe Lib :wink:

Das ganze Arduino-System ist doch auf Libs und vordefinierten Funktionen aufgebaut, und nur in den wenigsten Fällen dürfte man die Internas der verwendeten Lib kennen und/oder verstehen. Was ist bei Zeitfunktionen plötzlich so schlimm an Libs?

MicroBahner:
Einen habe ich auch noch :wink: - mit meinen MobaTools ( kannst Du über den Bibliotheksmanager installieren )

#include <MobaTools.h>

int sensor = 4;
int relais = 5;

MoToTimer verzoegerungAn;
MoToTimer verzoegerungAus;
bool sensorState;           // zur Flankenerkennung

void setup() {

pinMode (sensor, INPUT_PULLUP);
 pinMode (relais, OUTPUT);

digitalWrite(relais, HIGH);

}

void loop() {
 delay(20);  // nur zur Entprellung, kann bei prellfreiem Sensorsignal entfallen
 if ( !digitalRead(sensor) && !sensorState ) {
   sensorState = true;
   // Einschaltverzögerung starten
   verzoegerungAn.setTime( 3000 );
 }

if (digitalRead(sensor) && sensorState) {
   //Ausschaltverzögerung
   sensorState = false;
   verzoegerungAus.setTime( 5000 );
 }

if ( verzoegerungAn.expired() ) {
   //Einschaltverzoegerung abgelaufen
   digitalWrite(relais, LOW);
 }

if ( verzoegerungAus.expired() ) {
   //Ausschaltverzoegerung abgelaufen
   digitalWrite(relais, HIGH);
 }

}



Du musst dir aber noch überlegen, was passieren soll, wenn sich der Status des Sensors während der Laufzeit der Verzögerung ändert.
Evtl. kommst Du auch mit einem MoToTimer aus, da ja Einschalt- und Ausschaltzeit nie gleichzetig läuft. Dann musst Du über ein Flag oder die Pinzustände beim Ablauf des Timers unterscheiden ob es Ein- oder Ausschalten ist.

Vielen vielen Dank MicroBahner! Mit einer kleinen Anpassung funktioniert dein Code bis jetzt ohne Fehler. Hatte die Hoffnung schon fast aufgegeben. :slight_smile: Heute Abend werde ich noch versuchen das Ganze noch etwas zu erweitern.

my_xy_projekt, auch dir vielen Dank für deine Mühe, leider bin ich wirklich nicht so fit im programmieren. Versuche aber auch deinen Code nachvollziehen zu können.

Was ist bei Zeitfunktionen plötzlich so schlimm an Libs?

gar nichts ist schlimm drann. Libs sind bequem, laden aber dazu ein, sich zu wenig Gedanken zu machen. Zeitsteuerungen braucht man immer wieder und ob dann eine Lib wieder genau das macht was man erwartet, steht auf einem anderen Blatt.

Mit Libs kommen halt immer wieder Sachen im Hintergrund mit, die nicht sofort erkennbar sind. Der Ersteller einer Lib hat klar eine andere Sicht drauf. Wenn du eine Lib erstellst, weist du auswendig was sie macht. Als Anwender dümpelt man da oft im dunkeln oder muss erst recht wieder im Source der Lib nachsehen. Erging mir jetzt z.B. bei deiner .expired(). liefert die einen Status und nur einmal true? Ok, nachsehen, liefert nur einmal true zurück, sonst gäbe es ja die .running(). Dieses else active = 0, erster Blick eigenartig... aaah, der nimmt da eine Bitmap, spart ein extra Variable, gscheit. Wie gesagt, macht ja vieles Sinn, aber ob das alles einfacher für den Anwender wird, bin ich mir nicht sicher.

Anyway. Hier wäre eine Variante wie ich das klassisch inCode machen würde

/*
  by noiasca
*/
enum {IDLE, ONDELAY, ON, OFFDELAY};
class OnOffDelay                    // a class for an on off delay
{
  private:
    const byte buttonPin;                    // GPIO für den Button gegen 5V
    const byte outputPin;                    // GPIO für die LED die nachläuft
    uint32_t previousMillis = 0;             // Zeitstempel, wann die LED das letzte mal eingeschaltet wurde
    byte state = IDLE;

  public:
    OnOffDelay(byte buttonPin, byte outputPin) :  // ein Constructor für unsere Klasse mit Initialisierungsliste
      buttonPin(buttonPin),
      outputPin(outputPin)
    {}

    byte onDelay = 3;                         // Delay nach Button On in Sekunden
    byte offDelay = 5;                        // Nachlaufzeit in Sekunden

    void begin() {                            // die Klasse bekommt eine begin Methode, hier kommt alles rein, was hardware-mäßig zu unserem Objekt gehört und im setup() war
      pinMode(buttonPin, INPUT_PULLUP);
      pinMode(outputPin, OUTPUT);
      digitalWrite(outputPin, HIGH);
    }

    void tick() {                             // was die Klasse laufend machen soll
      switch (state)
      {
        case IDLE:
          if (digitalRead(buttonPin) == LOW)                     // Signal im IDLE checken
          {
            previousMillis = millis();
            state = ONDELAY;
          }
          break;
        case ONDELAY:                                             // Verzögerungszeit
          if (millis() - previousMillis > onDelay * 1000UL)
          {
            digitalWrite(outputPin, HIGH);
            previousMillis = millis();
            state = ON;
          }
          break;
        case ON:
          if (digitalRead(buttonPin) == HIGH)                      // Signal im ON checken
          {
            previousMillis = millis();
            state = OFFDELAY;
          }
          break;
        case OFFDELAY:
          if (millis() - previousMillis > offDelay * 1000UL)
          {
            digitalWrite(outputPin, LOW);
            previousMillis = millis();
            state = IDLE;
          }
          break;
      }
    }
};

OnOffDelay myRelay[] {
  { 4, 5},   // first objekt,  index 0: buttonPin, outputPin
//  { A1, 13},   // second objekt, index 1: buttonPin, outputPin
//  { A2, 12},   // third objekt,  index 2: buttonPin, outputPin
//  { A3, 11},   // 4th objekt,    index 3: buttonPin, outputPin
};

void setup() {
  for (auto & i : myRelay)  // range based for iterates through array of objects and "i" gets a reference onto the object
    i.begin();

//  myRelay[0].onDelay = 4;   // set a different interval for this group
//  myRelay[0].offDelay = 6;
}

void loop() {
  for (auto & i : myRelay)
   i.tick();
}
  • skaliert einfach wenn mehrere Sensoren/Buttons benötigt werden, je Sensor/Button EINE Zeile auskommentieren.
  • Spielt aktuell vieleicht wenig Rolle, aber verbraucht für 1 Sensor/1Button nur 1354 Bytes Programmspeicherplatzes. 18 Byte Globale am Uno

Wenn Du schon eine Klasse draus machst, solltest Du die Zeiten einstellbar und nicht hartverdrahtet im Code machen. Die enum brauchst Du auch nicht in jeder Instanz nochmal.

Gruß Tommy

das enum kauf ich, danke. Die Zeiten sind eh public.

Instanzvariablen public = pfui. Das verstößt gegen die Regeln der Kapselung.

Gruß Tommy

Einen habe ich auch noch - rein prozedural:

const byte sensor = 4;
const byte relais = 5;

const uint32_t EINSCHALTVERZOEGERUNG = 3000, AUSSCHALTVERZOEGERUNG = 5000;
uint32_t jetzt, vorhin, intervall;
enum {WARTEN, VERZOEGERUNG_EIN, EIN, VERZOEGERUNG_AUS};
byte schritt = WARTEN;

void setup() {
  pinMode (sensor, INPUT_PULLUP);
  digitalWrite(relais, HIGH);
  pinMode (relais, OUTPUT);
}

void loop() {
  jetzt = millis();
  switch (schritt) {
    case WARTEN:
      if ( !digitalRead(sensor) ) {
        vorhin = jetzt;
        intervall = EINSCHALTVERZOEGERUNG;
        schritt = VERZOEGERUNG_EIN;
      }
      break;
    case VERZOEGERUNG_EIN:
      if (jetzt - vorhin >= intervall) {
        digitalWrite(relais, LOW);
        schritt = EIN;
      }
      break;
    case EIN:
      if ( digitalRead(sensor) ) {
        vorhin = jetzt;
        intervall = AUSSCHALTVERZOEGERUNG;
        schritt = VERZOEGERUNG_AUS;
      }
      break;
    case VERZOEGERUNG_AUS:
      if (jetzt - vorhin >= intervall) {
        digitalWrite(relais, HIGH);
        schritt = WARTEN;
      }
      break;
    default:
      schritt = WARTEN;
  }
}

Meinung an den TO: combie spielt mindestens eine Klasse zu hoch für Dich, wundere Dich also nicht, wenn die Verwendung der Bibliothek CombieTimer im Frust endet.

Die MobaTools haben in der Anwendung das übliche Niveau guter Arduino-Bibliotheken, weshalb eine Beschäftigung damit sicherlich lohnt.

Eine solch "einfache" Aufgabe sollte man aber auch ohne entsprechende Bibliotheken gelöst bekommen, um das Verständnis für die Problematik quasi paralleler Abläufe zu schärfen.

Die Anwendung einer Schrittkette, hier mit switch/case realisiert, löst viele Aufgabenstellungen auf einfache und zugleich elegante Weise. Auch deren Verständnis gehört daher für mich zum unbedingten Handwerkszeug.


MicroBahner:
Einen habe ich auch noch :wink: - mit meinen MobaTools

Bei dieser Reihenfolge sollen die Relais nicht klappern:

  digitalWrite(relais, HIGH);
  pinMode (relais, OUTPUT);

Frohe Festtage, die uns sicherlich in Erinnerung bleiben werden!

1 Like