Problem mit einem Programm für einen 6 - 18 Timer

Hallo an die Gemeinschaft.
Ich möchte mit einem Arduino Pro Micro eine LED- Kette für 6 Stunden ein und für 18 Stunden ausschalten. Nach Ablauf der 24 Stunden soll die LED-Kette wieder an gehen.
Ich habe das folgende Prgramm geschrieben.
Die LED geht nach dem Start über den Taster an Pin 2 an, aber sie geht nicht wieder aus.

Kann mir jemand bei der Programmerstellung helfen.
Danke im Voraus.

//PROGRAMM ohne delay()
//Arduino Pro Micro

#include <avr/sleep.h>
#include <avr/wdt.h>

#define BUTTON_PIN 2    // Taster an Pin 2
#define OUTPUT_PIN 3    // Ausgang an Pin 3

volatile bool buttonPressed = false;
unsigned long previousMillis = 0; // Speichert die letzte Zeit
const unsigned long onDuration = 21600000; // 6 Stunden in Millisekunden
const unsigned long offDuration = 64800000; // 18 Stunden in Millisekunden
unsigned long durationCounter = 0; // Zähler für die Zeit

enum State {
    ON,
    OFF
};

State currentState = OFF;

void setup() {
    pinMode(OUTPUT_PIN, OUTPUT);      // Setze den Ausgangspin als Ausgang
    pinMode(BUTTON_PIN, INPUT_PULLUP); // Setze den Taster-Pin als Eingang mit Pull-Up-Widerstand

    // Interrupt für den Taster aktivieren
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}

void loop() {
    if (buttonPressed) {
        buttonPressed = false;
        currentState = ON;              // Setze den Zustand auf ON
        digitalWrite(OUTPUT_PIN, HIGH); // Schalte den Ausgang ein
        previousMillis = millis();       // Setze die Startzeit
    }

    if (currentState == ON) {
        durationCounter = millis() - previousMillis; // Berechne die vergangene Zeit

        if (durationCounter >= onDuration) {         // Wenn die Zeit für ON abgelaufen ist
            digitalWrite(OUTPUT_PIN, LOW);           // Schalte den Ausgang aus
            currentState = OFF;                       // Setze den Zustand auf OFF
            previousMillis = millis();                // Setze die Startzeit für OFF
        }
    } else if (currentState == OFF) {
        durationCounter = millis() - previousMillis; // Berechne die vergangene Zeit

        if (durationCounter >= offDuration) {        // Wenn die Zeit für OFF abgelaufen ist
            currentState = ON;                        // Setze den Zustand auf ON zurück
            digitalWrite(OUTPUT_PIN, HIGH);          // Schalte den Ausgang wieder ein
            previousMillis = millis();                // Setze die Startzeit erneut
        } else {
            enterSleepMode();                         // Gehe in den Sleep-Modus während der OFF-Zeit
        }
    }
}

void enterSleepMode() {
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // Setze Sleep-Modus auf Power Down
    sleep_enable();                         // Aktiviere Sleep-Modus

    sleep_cpu();                            // Gehe in den Sleep-Modus

    sleep_disable();                        // Deaktiviere Sleep-Modus nach dem Aufwachen

    wdt_reset();                            // WDT zurücksetzen, um einen Reset zu vermeiden
}

// Interrupt-Service-Routine für den Taster
void buttonISR() {
    buttonPressed = true;
}

Versuche es mal so:

//PROGRAMM ohne delay()
//Arduino Pro Micro

#define BUTTON_PIN 2    // Taster an Pin 2
#define OUTPUT_PIN 3    // Ausgang an Pin 3

volatile bool buttonPressed = false;
unsigned long previousMillis = 0; // Speichert die letzte Zeit
const unsigned long onDuration = 21600000; // 6 Stunden in Millisekunden
const unsigned long offDuration = 64800000; // 18 Stunden in Millisekunden
unsigned long durationCounter = 0; // Zähler für die Zeit

enum State {
    ON,
    OFF
};

State currentState = OFF;

void setup() {
    pinMode(OUTPUT_PIN, OUTPUT);      // Setze den Ausgangspin als Ausgang
    pinMode(BUTTON_PIN, INPUT_PULLUP); // Setze den Taster-Pin als Eingang mit Pull-Up-Widerstand

    // Interrupt für den Taster aktivieren
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}

void loop() {
	if (buttonPressed) {
		buttonPressed = false;
		currentState = ON;              // Setze den Zustand auf ON
		digitalWrite(OUTPUT_PIN, HIGH); // Schalte den Ausgang ein
		previousMillis = millis();       // Setze die Startzeit
	}

	durationCounter = millis() - previousMillis; // Berechne die vergangene Zeit

	if (currentState == ON) {        
	  if (durationCounter >= onDuration) {         // Wenn die Zeit für ON abgelaufen ist
		digitalWrite(OUTPUT_PIN, LOW);           // Schalte den Ausgang aus
		currentState = OFF;                       // Setze den Zustand auf OFF
		previousMillis = millis();                // Setze die Startzeit für OFF
	  }
	} else {
	  if (durationCounter >= offDuration) {        // Wenn die Zeit für OFF abgelaufen ist
		currentState = ON;                        // Setze den Zustand auf ON zurück
		digitalWrite(OUTPUT_PIN, HIGH);          // Schalte den Ausgang wieder ein
		previousMillis = millis();                // Setze die Startzeit erneut
	  } 
	}
}

// Interrupt-Service-Routine für den Taster
void buttonISR() {
    buttonPressed = true;
}

Ich würde empfehlen wollen, die Konstanten auch tatsächlich unsigned long zu machen:

const unsigned long onDuration = 21600000UL; // 6 Stunden in Millisekunden
const unsigned long offDuration = 64800000UL; // 18 Stunden in Millisekunden

Außerdem wird vermutlich wegen Ungenauigkeit der millis()-Millisekunde bald eine Zeitverschiebung eintreten.

Liest sich wie Terrarium, Aquarium oder dergleichen. Dann soll das vermutlich über eine längere Zeit laufen, wozu die Schwingungen eines µCs, auf denen die millis() basieren, leider zu ungenau sind.

Stattdessen gibt es temperaturkompensierte Uhrenmodule wie DS3231, die im Jahr ein paar Sekunden Abweichung haben. Noch genauer geht es mit der Zeit aus dem WWW oder von der Fritz!Box (Router).

Ich möchte es nur erwähnt haben, denn Arduinos können das :wink:

Es soll tatsächlich nur eine LED-Lichterkette angesteuert werden. Wobei es über eine Batterie gespeist wird und möglichst klein sein soll.
Wie groß wird die Zeitabweichung ca. pro 24 Stunden? Kann man das abschätzen.

Schwierig, da es sich um Fertigungstoleranzen und Umgebungsbedingungen wie die Temperatur handelt, die Einfluß nehmen.

Aber Du kannst es selbst testen, indem Du ein Programm schreibst, daß Dir jede Minute oder so die millis() rausschreibt. Dann vergleichst Du das mit der Uhrzeit, die der serielle Monitor mit anzeigen kann. Da sollte sich über den Tag eine Abweichung ergeben.

const uint32_t ZEITINTERVALL = 60000;

void loop()
{
  if ( !(millis() % ZEITINTERVALL) ) {
    Serial.println(millis());
    delay(58000);
  }
}

void setup() {
  Serial.begin(115200);
}

Ausgabe:

10:46:39.452 -> 60000
10:47:39.552 -> 120000
10:48:39.612 -> 180000
10:49:39.695 -> 240000
10:50:39.790 -> 300000
10:51:39.860 -> 360000

(860 ms - 452 ms) / 5 * 60 * 24 = 117.504 ms

Das wären dann 2 Minuten pro Tag bei meinem UNO bei Zimmertemperatur.

machs dir einfach, nimm eine Zeitschaltuhr.

Warum einfach, wenn es auch klein gehen könnte.
Betrieb, wie gesagt, über 2 Lipos, daher eigentlich an die Möglichkeit geglaubt, den Micro während der "OFF-Zeiten" vo Pin 3, in den Stromsparmodus zu schicken.
Programm soll natürlich ca 6 Wochen laufen.

Das fehlt komplett in Deinem Code.
Eigentlich ist das was für eine Schrittkette, die einmal ausgelöst wird.

Lesestoff: Sleep Modes and Power Management

Die Genauigkeit hängt davon ab, ob der Arduino einen Quarz oder einen Resonator hat.

Die Genauigkeit ( maximaler Fehler) eines Resonators ist ca 0,5%. Das sind pro Tag (86400 Sekunden) 432 Sekunden oder 7,2 Minuten

Die Genauigkeit eines Quarzes ist typischerweise 100 ppm. Das sind pro Tag 8,64 Sekunden.

Grüße Uwe

Es wäre schön, die Ursache herauszufinden. (Auf den ersten Blick sehe ich kein Problem, jedenfalls bis zum ersten Ausschalten)
Vermutlich fängst du dir durch Störung/falsche Verdrahtung öfters mal ein Interrupt ein, der die LED immer wieder neu startet.
Hast du das ganze mal mit kürzerer Zeit getestet?
In den if(buttonPressed) Zweig eine Testausgabe eingebaut?

Ich habs mal nur umgestellt:

//PROGRAMM ohne delay()
//Arduino Pro Micro

#define BUTTON_PIN 2    // Taster an Pin 2
#define OUTPUT_PIN 3    // Ausgang an Pin 3

volatile bool buttonPressed = false;
unsigned long previousMillis = 0; // Speichert die letzte Zeit
const unsigned long onDuration = 21600000; // 6 Stunden in Millisekunden
const unsigned long offDuration = 64800000; // 18 Stunden in Millisekunden
unsigned long durationCounter = 0; // Zähler für die Zeit

enum State
{
  ON,
  OFF
};

State currentState = OFF;

void setup()
{
  pinMode(OUTPUT_PIN, OUTPUT);      // Setze den Ausgangspin als Ausgang
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Setze den Taster-Pin als Eingang mit Pull-Up-Widerstand
  // Interrupt für den Taster aktivieren
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}

void loop()
{
  durationCounter = millis() - previousMillis; // Berechne die vergangene Zeit
  if (digitalRead(OUTPUT_PIN) == HIGH)
  {
    if (durationCounter >= onDuration)           // Wenn die Zeit für ON abgelaufen ist
    {
      digitalWrite(OUTPUT_PIN, LOW);           // Schalte den Ausgang aus
      previousMillis = millis();                // Setze die Startzeit für OFF
    }
  }
  else   // Wenn AUS
  {
    if (durationCounter >= offDuration)          // Wenn die Zeit für OFF abgelaufen ist
    {
      digitalWrite(OUTPUT_PIN, HIGH);          // Schalte den Ausgang wieder ein
      previousMillis = millis();                // Setze die Startzeit erneut
    }
    if (buttonPressed)   // Prüfung ob ausgelöst
    {
      buttonPressed = false;
      digitalWrite(OUTPUT_PIN, HIGH); // Schalte den Ausgang ein
      previousMillis = millis();       // Setze die Startzeit
    }
  }
}

// Interrupt-Service-Routine für den Taster
void buttonISR()
{
  buttonPressed = true;
}

Damit löst der Button nur aus, wenn die LED auch wirklich aus ist.

Jetzt oute ich mich mal (ungewollt)
Ich hab noch nie was mit dem Watchdog gemacht.

Bitte schaut jemand drüber und korrigiert mich.
Der Wachtdog erzeugt einen Hardwarereset falls innerhalb der programmierten Zeit (max 4 Sekunden ) kein Zurücksetzen des Watchdog erfolgt. Das zurücksetzen erfolgt durch die (vorhandene) Funktion wdt_reset(); welche aber an ganz der falschen Stelle sitzt. Die richtige Stelle müßte ohne IF-Kondition im loop() sein. Es fehlt die Angabe des watchdogsintervall im Setup.

setup() {
 wdt_disable();
  ... Restlicher Code im setup()
 wdt_enable(WDTO_2S);
}

Darum hat der watchdog nicht die erwartete Funktion und könnte sogar den Arduino in einer Watchdogschleife halten.

Zweite Frage.
Im Sleepmodus wird millis überhaupt weitergezählt?

Das ist aber kein Problem, weil das Programm, so wie vom To geschrieben nach einem Tastendruck ( Tasten sollte man nicht mittels Interrupt abfragen) zuerst 6 Stunden an ist und dann 18 Stunden Aus um dann in sleep / watchdog Modus zu fallen.

Ich frage mich ob der Watchdog überhaupt so funktioneirt da ansonsten der Arduino in diesem Sketch in einem Dauerresetoszillation gehalten würde.

Grüße Uwe

Nach Ablauf erfolgt ein Reset.

Alternativ: (einstellbar)
Erster Ablauf löst einen Interrupt aus.
Zweiter Ablauf einen Reset.

Nein.

Hier ist eine einfache Variante um einen ATMega368 per Watchdog im Sleepmodus zu steuern. Der Watchdog wird auf den Interruptmodus eingestellt und die Periode auf die längst mögliche Spanne von 8 Sekunden.

Im vorliegenden Beispiel wird etwas in loop() gemacht und anschließend in einer Schleife der Sleepmodus angeschaltet. Mit jedem Aufwachen nach 8 Sekunden wird die Schleife einmal durchlaufen und der Controller so lange gleich wieder schlafen gelegt, bis die gewünschte Schlafenszeit (x * 8 Sekunden) abgelaufen ist. Daraufhin wird die loop Funktion erneut aufgerufen.

Resettet wird da nichts.

#include <Arduino.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <util/atomic.h>

constexpr byte WATCHDOG_TIME {8};   // in Sekunden
constexpr uint32_t SLEEP_ZEIT {32};

// watchdog ISR
ISR(WDT_vect) {}   // Nichts tun, wird aber für das Aufwachen benötigt

void enableWatchdog() {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    wdt_reset();
    WDTCSR |= bit(WDCE) | bit(WDE);   // Setze WDCE um WDE setzen/ändern zu können
    // WDTCSR = bit(WDIE) | bit(WDP3);               // 4s / interrupt, kein System Reset
    WDTCSR = bit(WDIE) | bit(WDP3) | bit(WDP0);   // 8s / interrupt, kein System Reset
  }
}

// Lege den Microcontroller schlafen
void enterSleep(void) {
  wdt_reset();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // EDIT: SLEEP_MODE_PWR_DOWN - niedrigster Stromverbrauch.
  sleep_enable();
  sleep_mode();                          // Starte Schlafmodus
  // Nach dem Aufwachen, wird das Programm genau hier weiterlaufen
  sleep_disable();   // Erste Aktion muss das Auschalten des Schlafmodus sein.
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start...");
  power_adc_disable();
  enableWatchdog();
}

void loop() {
  Serial.println("LEDs anschalten");
  delay(2000);
  Serial.println("LEDs ausschalten... Gute N8");
  delay(20);

  // In den Schlafmodus wechseln
  // 32 / 8 = 4 mal aufwachen und gleich wieder schlafen -> 32 Sekunden Schlafzeit
  for (size_t i = 0; i < (SLEEP_ZEIT / WATCHDOG_TIME); ++i) {
    enterSleep();
  }
}

@Kai-R: Macht es etwas, wenn die loop länger als 8 sek. braucht? Stört da der WDT? Ich denke nicht.

Ne, das kann ruhig länger dauern. Z.b. 6 Stunden.

Wenn man die loop() anpasst:

void loop() {
  Serial.println("LEDs anschalten");
  for (size_t i = 0; i < 6; ++i) {
    delay(10000);
    Serial.print(". ");
  }
  Serial.println("\nLEDs ausschalten... Gute N8");
  delay(20);

  // In den Schlafmodus wechseln
  // 32 / 8 = 4 mal aufwachen und gleich wieder schlafen -> 32 Sekunden Schlafzeit
  for (size_t i = 0; i < (SLEEP_ZEIT / WATCHDOG_TIME); ++i) {
    enterSleep();
  }
}

Kann man es ausprobieren...

Im konkreten Code Schnippsel nicht,
Aber sonst musst du schon den Watchdog neu triggern wenn etwas lange dauern kann.