Rotierender Garten | rotary garden | hydroponic wheel | Hydrokultur| hydroculture| infinity mirror | kinetic sculpture | art installation | GELÖST!

Hallo liebe community,

Ich arbeite gerade an meinem neuen Projekt und möchte Euch den Zwischenstand präsentieren.

Kurze Projektbeschreibung:
In einem rotierenden Garten wachsen Pflanzen in einem Zylinder und drehen sich um eine Lampe. Durch die stetige, langsame Rotation werden die Pflanzen bewässert. Ein Blick in einen rotierenden Garten wirkt als hätte man eine Fläche Land zu einem Zylinder umgebogen, die um eine künstliche Sonne kreist.

rotierender Garten 300dpi komprimiert

Bewässerung Prinzip komprimiert

In meinem Projekt soll ein rotierender Garten mit einem infinity- mirror kombiniert werden. Dafür soll der Zylinder mit einem Spiegel als Rückwand und einer Plexiglassscheibe als Vorderwand, die von Innen mit einer Spion- Spiegelfolie beklebt ist, versehen werden. Durch die Rückkopplung der beiden Spiegel soll die Illusion eines unendlichen Zylinders entstehen in dem sich Pflanzen um einen unendlichen Strahl aus Licht drehen.

Ich trage die Idee schon länger mit mir rum. Freundlicherweise hat mir nun ein anderer Maker, diesen Prototypen seines hydroponic wheels zur Verfügung gestellt, welches als Grundlage meines Projektes dienen soll.

wichtige Ansicht (2) komprimiert

Die Sprossen des Rades werden entfernt und durch die beiden spiegelnden Seitenwände ersetzt. Die Gewindestangen an denen die Pflanzencontainer aufgehängt werden, werden direkt an den Seitenwänden befestigt. Das wird dann ungefähr so aussehen:

wichtige Ansicht bearbeitet komprimiert

Die Anzahl der Gewindestangen + Pflanzencontainer wird verdoppelt, so dass auf einem Kreis acht Pflanzen Platz finden:

hydroponic wheel Ringform komprimiert

In der Tiefe wird der Zylinder aus drei Reihen Pflanzen bestehen, die sind leider nötig um eine gelungene Illusion zu schaffen:

drei Reihen von der Seite in 31,5 cm Tiefe komprimiert

drei Reihen komprimiert

Der rotierende Garten soll auf Augenhöhe positioniert werden, dafür werde ich eine Konstruktion aus Metall anfertigen:

hydroponik wheel komprimiert

Für gewöhnlich drehen sich rotierende Gärten mit 1 U/ Tag damit alle Pflanzen nur 1 x täglich bewässert werden . Weil mich die hypnotische Sogwirkung bei meinem Projekt besonders interessiert, werde ich die Drehzahl erhöhen müssen um sie für den Betrachter wahrnehmbar zu machen. Um ein Überwässern der Pflanzen zu vermeiden wird täglich nur bei einer Umdrehung des Rades die obere Nährstoffwanne vollgepumpt und fließt nach Abschluss der Bewässerungsumdrehung wieder zurück in den unteren Nährlösungstank.

Wasserfluss- Prinzip mit Aktoren komprimiert

Wasserflussprinzip:

  • Mit Hilfe eines Reedkontaktes werden die Umdrehungen des Rades gezählt, um gezielt die Bewässerungsumdrehung steuern zu können
  • Eine Pumpe befördert das Wasser in das obere Nährstoffbecken
  • Ein Schwimmerventil bewirkt das Ausschalten der Pumpe
  • Nach der Bewässerungsumdrehung wird das Magnetventil geöffnet und die Nährlösung fließt zurück in den unteren Nährstofftank

Hier ist der Aufbau der Steuerung:

Hier ist der Sketch für die Steuerung:

// Edit aus eigenem Programm zum Drehen des Rades: wenn ich die micros()- funktion in der zweiten if- Anweisung durch millis() ersetzte und die Zeit entsprechend kürze, funktioniert der Sketch, keine Ahnung warum das so ist

// Umdrehungen
//RTC- Modul
#include <Wire.h>    // Bibliothek für I2C-Kommunikation
#include <DS3231.h>  // Bibliothekt für das RTC- Modukl

DS3231 clock;
RTCDateTime dt;

// Definierungen zum Drehen des Rades
const byte stepPin = 9;
const byte dirPin = 8;

unsigned long ZeitmerkerusDelay = 0;
unsigned long ZeitmerkerDelaynachSchritt = 0;

bool stepPinStatus = false;  // stepPinstatus deklarieren und Ausgangszustand festlegen

// Definierungen zum Mitzählen der Umdrehungen
const byte REED_PIN = 2;             // Pin, an dem der Reed-Schalter angeschlossen ist
const byte DEBOUNCE_DELAY = 50;      // Entprellungszeit in Millisekunden
int reedState = HIGH;                // aktueller Zustand des Reed-Schalters // wird über den PullUp Widerstand immer HIGH gemacht
const byte lastReedState = HIGH;     // letzter Zustand des Reed-Schalters
int lastUmdrehungen = 0;             // zuletzt gezählte Umdrehungen
int Umdrehungen = 0;                 // gezählte Umdrehungen
unsigned long lastDebounceTime = 0;  // Zeitpunkt der letzten Entprellung

// Definierungen zur Bewaesserung
const byte SchwimmerschalterNaehrloesungBecken = 3;
const byte SchwimmerschalterNaehrloesungTank = 4;
bool BereitschaftBewaesserung = false;  // Merker für BereitschaftBewaesserung im Ausgangszustand auf LOW setzen
bool BewaesserungsUmdrehung = false;
bool PumpenStatus = false;
bool MagnetventilStatus = false;
bool NotabschaltungPumpe = false;

const byte Magnetventil = 5;
const byte Pumpe = 7;

// Definierungen zum Licht
const byte LichtPin = 6;
bool Licht = false;                   // Merker für Licht im Ausgangszustand auf LOW setzen
static uint32_t LichtZeitmerker = 0;  // "static" bedeutet, dass die Variable nur einmal deklariert wird und während der Programmausführung unverändert bleibt. "uint32_t" steht für "unsigned integer" und definiert eine Ganzzahlvariable ohne Vorzeichen mit einer Größe von 32 Bit.


void setup() {
  Serial.begin(9600);

  // Initialisierungen zum Drehen des Rades
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  digitalWrite(dirPin, HIGH);  // Motordrehrichtung im Uhrzeigersinn ( LOW wäre gegen den Uhrzeigersinn)

  // Intitialsierungen für Mitzaehlen der Umdrehungen
  pinMode(REED_PIN, INPUT_PULLUP);  // interneren Pullup- Widerstand des Pin 2 aktivieren

  // Initialsierungen für Bewaesserung
  pinMode(SchwimmerschalterNaehrloesungBecken, INPUT_PULLUP);  // Entprellen halte ich für unnötig
  pinMode(SchwimmerschalterNaehrloesungTank, INPUT_PULLUP);    // Entprellen halte ich für unnötig
  pinMode(Magnetventil, OUTPUT);
  pinMode(Pumpe, OUTPUT);

  // Initialisierungen für Licht
  pinMode(LichtPin, OUTPUT);


  // Initialisierungen für DS3231 RTC- Modul
  Serial.println("Initialize DS3231");
  ;
  clock.begin();

  // Deaktiviere Alarme und lösche Alarme für dieses Beispiel, da Alarme durch eine Batterie unterstützt werden.
  // Unter normalen Bedingungen sollten die Einstellungen nach dem Ausschalten des Stroms und Neustart des Mikrocontrollers zurückgesetzt werden.
  // d.h. im Klartext: Alles zurücksetzen am Anfang, weil das RTC durch eine Batterie unterstützt wird, wenn der Strom getrennt ist.
  clock.armAlarm1(false);
  clock.armAlarm2(false);
  clock.clearAlarm1();
  clock.clearAlarm2();

  // Manuell (Jahr, Monat, Tag, Stunde, Minute, Sekunde) // heißt das ich muss dem RTC ganz am Anfang den aktuellen Zeitpunkt manuell mitteilen?
  clock.setDateTime(2014, 4, 25, 0, 0, 0);


  // Setze Alarm für Licht- Jede 06h:0m:0s an jedem Tag
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  clock.setAlarm1(0, 6, 0, 0, DS3231_MATCH_H_M_S);

  // Setze Alarm für BereitschaftBewaesserung- Jede 8h:0m an jedem Tag // ich lasse, die Sekunden weg, weil ich davon ausgehe, dass ich das bei Alarm 2 muss...
  // setAlarm1(Date or Day, Hour, Minute, Mode, Armed = true)
  clock.setAlarm2(0, 8, 0, DS3231_MATCH_H_M);


  // Festlegen der Ausgangszustände der Aktoren
  digitalWrite(Magnetventil, LOW);
  digitalWrite(Pumpe, LOW);
  digitalWrite(LichtPin, LOW);
}

void loop() {
  RadDrehen();
  UmdrehungenZaehlen();  // Umdrehungen mitzaehlen
  checkAlarms();         // Überprüfe Alarmeinstellungen
  licht();               // Licht an und ausschalten
  bewaesserung();
}

void RadDrehen() {
  if (micros() - ZeitmerkerDelaynachSchritt >= 950 && stepPinStatus == HIGH) {
    // Verzögerungszeit zwischen den Schritten, Minimalwert des jeweiligen Motors darf nicht unterschritten werden; wenn ich die Verzögerunszeit nach oben schalte, komme ich auf weniger Umdrehungen!// Minimalwert ist 950 Mikrosekunden für den Vollschrittmodus und den NEMA15-Motor.
    // Minimalwert ist 35 für den Sechzehntelschrittmodus; d.h. umso mehr Schritte, umso kürzer darf die Verzögerungspause sein
    // Eine Sekunde entspricht 1.000.000 Mikrosekunden und 1000 Millisekunden. delay(); wird z.B. immer in Millisekunden angegeben
    digitalWrite(stepPin, HIGH);  // Impuls für einen...
    ZeitmerkerusDelay = micros();
    stepPinStatus = false;
  }
  if (micros() - ZeitmerkerusDelay >= 1000000 && stepPinStatus == LOW) {
    digitalWrite(stepPin, LOW);  // ...Schritt nach vorne
    ZeitmerkerDelaynachSchritt = micros();
    stepPinStatus = true;
  }
}

void UmdrehungenZaehlen() {
  int reading = digitalRead(REED_PIN);  // Pin 2 wird ausgelesen und in der Variablen reading zwischengespeichert

  if (reading != lastReedState) {  // wenn der ausgelesene Wert sich vom letzten erkannten Wert unterscheidet...
    lastDebounceTime = millis();   // Zeitpunkt der letzten Entprellung wird 0 gesetzt
  }

  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {  // wenn die Entprellungszeit abgelaufen ist
    if (reading != reedState) {                          // und  der ausgelesene Wert sich immer noch vom letzten erkannten Wert unterscheidet
      reedState = reading;                               // aktualisieren den zuletzt erkannten Zustand auf den aktuellen Zustand des Reed- Schalters
      if (reedState == HIGH) {                           // (wenn bis hier hin alles zugetroffen ist) und der red Schalter HIGH ist
        Umdrehungen++;                                   // Implementiere Umdrehungen, d.h. addiere die Variable Umdrehungen mit +1
        Serial.print("Umdrehungen: ");
        Serial.println(Umdrehungen);  // Zeige die Umdrehungen auf dem seriellen Monitor an
      }
    }
  }
}

void checkAlarms() {
  dt = clock.getDateTime();                                 // liest das aktuelle Datum und die Uhrzeit vom RTC-Modul (Real Time Clock) aus und speichert sie in der Variable dt als Objekt der Klasse RTCDateTime.
  Serial.println(clock.dateFormat("d-m-Y H:i:s - l", dt));  // Ausgabe auf dem seriellen Monitor

  RTCAlarmTime a1;
  RTCAlarmTime a2;

  if (clock.isArmed1()) {
    a1 = clock.getAlarm1();

    Serial.print("Alarm1 wird ausgelöst ");
    switch (clock.getAlarmType1()) {
      case DS3231_MATCH_H_M_S:
        Serial.print("wenn Stunden, Minuten und Sekunden übereinstimmen: ");
        Serial.println(clock.dateFormat("__ H:i:s", a1));
        break;
      default:
        Serial.println("UNBEKANNTE REGEL");
        break;
    }
  } else {
    Serial.println("Alarm1 ist deaktiviert.");
  }

  if (clock.isArmed2()) {
    a2 = clock.getAlarm2();

    Serial.print("Alarm2 wird ausgelöst ");
    switch (clock.getAlarmType2()) {
      case DS3231_MATCH_H_M:
        Serial.print("wenn Stunden und Minuten übereinstimmen:");
        Serial.println(clock.dateFormat("__ H:i:s", a2));
        break;
      default:
        Serial.println("UNBEKANNTE REGEL");
        break;
    }
  } else {
    Serial.println("Alarm2 ist deaktiviert.");
  }
}

void licht() {
  if (clock.isAlarm1() && Licht == false) {  // wenn der Alarm 1 ausgelöst und das Licht ausgeschaltet ist
    digitalWrite(LichtPin, HIGH);
    Serial.println("Licht ist an");
    Licht = true;                // merken, dass das Licht an ist
    LichtZeitmerker = millis();  // Zeit merken, die das Licht an ist
  }

  if (Licht == true && millis() - LichtZeitmerker >= 57600000) {  // wenn das Licht angeschaltet ist und 16 h lang an war
    digitalWrite(LichtPin, LOW);                                  // Licht ausschalten
    Licht = false;                                                // merken, dass das Licht augeschaltet ist
  }
}

void bewaesserung() {
  // BereitschatfBewaesserung aktivieren
  if (clock.isAlarm2() && BereitschaftBewaesserung == false) {  //  wenn der Alarm 2 ausgelöst wurde und BereitschaftBewaessrung ausgeschaltet
    BereitschaftBewaesserung = true;                            // merken, dass die BereitschaftBewaesserung aktiviert ist
    Serial.println("BereitschaftBewaesserung aktiviert");
  }

  // Auffüllen des Nährlösungsbeckens
  if (BereitschaftBewaesserung == true && lastUmdrehungen != Umdrehungen && PumpenStatus == false && MagnetventilStatus == false) {  // wenn BereitschatBewaesserung aktiviert und eine neue Umdrehung gezählt wurde
    digitalWrite(Magnetventil, HIGH);
    MagnetventilStatus = true;           // Magnetventil schließen
    if (NotabschaltungPumpe == false) {  // die Pumpe geht nur an, wenn die Notabschaltung deaktiviert ist
      digitalWrite(Pumpe, HIGH);         // Pumpe einschalten
      PumpenStatus = true;               // merken, dass die Pumpe angeschaltet ist
    }
    BewaesserungsUmdrehung = true;  // merken, dass die BewaesserungsUmdrehung aktiviert ist
    lastUmdrehungen = Umdrehungen;
    Serial.println("Nährlösungsbecken auffüllen");
  }

  // Nährlösungsbecken aufgefüllt
  if (PumpenStatus == true && SchwimmerschalterNaehrloesungBecken == LOW) {  // wenn die Pumpe eingeschaltet ist und SchwimmerschalterNaehrloesungBecken auslöst
    digitalWrite(Pumpe, LOW);
    PumpenStatus = false;
    Serial.println("Nährlösungsbecken aufgefüllt");
  }

  // Nährlösungsbecken leeren und BereitschaftBewaesserung deaktiveren
  if (BewaesserungsUmdrehung == true && MagnetventilStatus == true && lastUmdrehungen != Umdrehungen) {  // wenn BewaesserungsUmdrehung aktiviert ist und eine neue Umdrehung gezählt wurde
    BewaesserungsUmdrehung = false;                                                                      // merken, dass die BewaesserungsUmdrehung beendet wurde
    lastUmdrehungen = Umdrehungen;
    BereitschaftBewaesserung = false;  // BereitschaftBewaesserung deaktivieren
    digitalWrite(Magnetventil, LOW);   // Magnetventil öffnen, sodass die Nährlösung abfließen kann
    MagnetventilStatus = false;        // merken, dass das Magnetventil aus ist

    Serial.println("Nährlösungsbecken leeren");
    Serial.println("BereitschaftBewaesserung deaktiviert");
  }

  //Notabschaltung Pumpe
  if (SchwimmerschalterNaehrloesungTank == HIGH) {  // wenn SchwimmerschalterNaehrloesungTank auslöst
    NotabschaltungPumpe = true;                     // verhindern, dass die Pumpe angeschaltet werden kann
    Serial.println("Notabschaltung Pumpe aktiviert");
  } else {  //
    NotabschaltungPumpe = false;
  }
}

Ich habe bisher (noch) keine konkreten Fragen aber würde mich sehr freuen wenn sich jemand den Sketch anschauen könnte um mir Fehler und Verbesserungsvorschläge mitzuteilen.

LG Leonardo

3 Likes

mit Abstand der grossartigse Projekt aus allen die ich hier gesehen hab. türlich werd reinschauen.

#include <Wire.h>    // Bibliothek für I2C-Kommunikation
#include <DS3231.h>  // Bibliothekt für das RTC- Modukl

DS3231 clock;
RTCDateTime dt;

// Definierungen zum Drehen des Rades
const byte REED_PIN = 2;             // Pin, an dem der Reed-Schalter angeschlossen ist
const byte SchwimmerschalterNaehrloesungBecken_Pin = 3;
const byte SchwimmerschalterNaehrloesungTank_Pin = 4;
const byte Magnetventil_Pin = 5;
const byte Licht_Pin = 6;
const byte Pumpe_Pin = 7;
const byte dir_Pin = 8;
const byte step_Pin = 9;

// Definierungen zum Mitzählen der Umdrehungen
unsigned int lastUmdrehungen = 0;             // zuletzt gezählte Umdrehungen
unsigned int Umdrehungen = 0;                 // gezählte Umdrehungen

// Definierungen zur Bewaesserung
bool BereitschaftBewaesserung = false;  // Merker für BereitschaftBewaesserung im Ausgangszustand auf LOW setzen
bool BewaesserungsUmdrehungErlaubt = false;
bool PumpeIstAn = false;
bool MagnetventilGeschlossen = false;
#define  MagnetventilOffnen (digitalWrite(Magnetventil_Pin, LOW))
#define  MagnetventilSchliessen (digitalWrite(Magnetventil_Pin, HIGH))
#define  TankIstVoll (digitalRead(SchwimmerschalterNaehrloesungTank_Pin)==LOW)
#define  BeckenIstVoll (digitalRead(SchwimmerschalterNaehrloesungBecken_Pin)==LOW)
#define  PumpeAusschalten (digitalWrite(Pumpe_Pin, LOW))
#define  PumpeStarten (digitalWrite(Pumpe_Pin, HIGH))


void setup() {
  Serial.begin(9600);

  // Intitialsierungen für Mitzaehlen der Umdrehungen
  pinMode(REED_PIN, INPUT_PULLUP);  // interneren Pullup- Widerstand des Pin 2 aktivieren
  pinMode(SchwimmerschalterNaehrloesungBecken_Pin, INPUT_PULLUP);  // Entprellen halte ich für unnötig
  pinMode(SchwimmerschalterNaehrloesungTank_Pin, INPUT_PULLUP);    // Entprellen halte ich für unnötig

  // Initialisierungen zum Drehen des Rades
  pinMode(step_Pin, OUTPUT);
  pinMode(dir_Pin, OUTPUT);

  // Initialsierungen für Bewaesserung
  pinMode(Magnetventil_Pin, OUTPUT);
  pinMode(Pumpe_Pin, OUTPUT);

  // Initialisierungen für Licht
  pinMode(Licht_Pin, OUTPUT);


  // Initialisierungen für DS3231 RTC- Modul
  Serial.println("Initialize DS3231");

  clock.begin();

  // Deaktiviere Alarme und lösche Alarme für dieses Beispiel, da Alarme durch eine Batterie unterstützt werden.
  // Unter normalen Bedingungen sollten die Einstellungen nach dem Ausschalten des Stroms und Neustart des Mikrocontrollers zurückgesetzt werden.
  // d.h. im Klartext: Alles zurücksetzen am Anfang, weil das RTC durch eine Batterie unterstützt wird, wenn der Strom getrennt ist.
  clock.armAlarm1(false);
  clock.armAlarm2(false);
  clock.clearAlarm1();
  clock.clearAlarm2();

  // Manuell (Jahr, Monat, Tag, Stunde, Minute, Sekunde) // heißt das ich muss dem RTC ganz am Anfang den aktuellen Zeitpunkt manuell mitteilen?
  //clock.setDateTime(2014, 4, 25, 0, 0, 0); //die aktuelle Zeit muss nicht jedes mal neu programmiert werden, denn mit 3V Zelle funzt RTC ununterbrochen


  // Setze Alarm für Licht- Jede 06h:0m:0s an jedem Tag
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  clock.setAlarm1(0, 6, 0, 0, DS3231_MATCH_H_M_S);

  // Setze Alarm für BereitschaftBewaesserung- Jede 8h:0m an jedem Tag // ich lasse, die Sekunden weg, weil ich davon ausgehe, dass ich das bei Alarm 2 muss...
  // setAlarm1(Date or Day, Hour, Minute, Mode, Armed = true)
  clock.setAlarm2(0, 8, 0, DS3231_MATCH_H_M);


  // Festlegen der Ausgangszustände der Aktoren
  MagnetventilOffnen;
  PumpeAusschalten;
  digitalWrite(Licht_Pin, LOW);
  digitalWrite(dir_Pin, HIGH);  // Motordrehrichtung im Uhrzeigersinn ( LOW wäre gegen den Uhrzeigersinn)
}

void loop() {
  RadDrehen();
  UmdrehungenZaehlen();  // Umdrehungen mitzaehlen
  checkAlarms();         // Überprüfe Alarmeinstellungen. Bist du dir wirklich sicher dass du jede Milisecunde prüfen musst ob Wecker gestellt ist?
  licht();               // Licht an und ausschalten
  bewaesserung();
}

void RadDrehen() {
  static unsigned long ZeitmerkerusDelay = 0;
  const unsigned int Pausierung = 500;
  if (millis() - ZeitmerkerusDelay >= Pausierung) {
    digitalWrite(step_Pin, !digitalRead(step_Pin));  // ...Schritt nach vorne
    ZeitmerkerusDelay += Pausierung;
  }
}

void UmdrehungenZaehlen() {
  static bool reedState = false;                // aktueller Zustand des Reed-Schalters // wird über den PullUp Widerstand immer HIGH gemacht
  static bool lastReedState = false;     // letzter Zustand des Reed-Schalters
  ;  // Pin 2 wird ausgelesen und in der Variablen reading zwischengespeichert
  if (digitalRead(REED_PIN) != lastReedState) {
    reedState = !reedState;                               // aktualisieren den zuletzt erkannten Zustand auf den aktuellen Zustand des Reed- Schalters
    if (reedState == HIGH) {                           // (wenn bis hier hin alles zugetroffen ist) und der red Schalter HIGH ist
      Umdrehungen++;
      Serial.print("Umdrehungen: ");
      Serial.println(Umdrehungen);  // Zeige die Umdrehungen auf dem seriellen Monitor an
    }
    delay(10);
  }
}

void checkAlarms() {
  dt = clock.getDateTime();                                 // liest das aktuelle Datum und die Uhrzeit vom RTC-Modul (Real Time Clock) aus und speichert sie in der Variable dt als Objekt der Klasse RTCDateTime.
  Serial.println(clock.dateFormat("d-m-Y H:i:s - l", dt));  // Ausgabe auf dem seriellen Monitor

  RTCAlarmTime a1;
  RTCAlarmTime a2;

  if (clock.isArmed1()) {
    a1 = clock.getAlarm1();

    Serial.print("Alarm1 wird ausgelöst ");
    switch (clock.getAlarmType1()) {
      case DS3231_MATCH_H_M_S:
        Serial.print("wenn Stunden, Minuten und Sekunden übereinstimmen: ");
        Serial.println(clock.dateFormat("__ H:i:s", a1));
        break;
      default:
        Serial.println("UNBEKANNTE REGEL");
        break;
    }
  } else Serial.println("Alarm1 ist deaktiviert.");

  if (clock.isArmed2()) {
    a2 = clock.getAlarm2();

    Serial.print("Alarm2 wird ausgelöst ");
    switch (clock.getAlarmType2()) {
      case DS3231_MATCH_H_M:
        Serial.print("wenn Stunden und Minuten übereinstimmen:");
        Serial.println(clock.dateFormat("__ H:i:s", a2));
        break;
      default:
        Serial.println("UNBEKANNTE REGEL");
        break;
    }
  } else Serial.println("Alarm2 ist deaktiviert.");
}

void licht() {
  static bool Licht = false;                   // Merker für Licht im Ausgangszustand auf LOW setzen
  static uint32_t LichtZeitmerker = 0;  // "static" bedeutet, dass die Variable nur einmal deklariert wird und während der Programmausführung unverändert bleibt(Nein. Das ist was anderes). "uint32_t" steht für "unsigned integer" und definiert eine Ganzzahlvariable ohne Vorzeichen mit einer Größe von 32 Bit.
  if (Licht) {
    if (millis() - LichtZeitmerker >= 57600000UL) { // wenn das Licht angeschaltet ist und 16 h lang an war

      digitalWrite(Licht_Pin, LOW);                                  // Licht ausschalten
      Licht = false;                                                // merken, dass das Licht augeschaltet ist
    }
  }
  else {
    if (clock.isAlarm1() ) { // wenn der Alarm 1 ausgelöst und das Licht ausgeschaltet ist
      digitalWrite(Licht_Pin, HIGH);
      Serial.println("Licht ist an");
      Licht = true;                // merken, dass das Licht an ist
      LichtZeitmerker = millis();  // Zeit merken, die das Licht an ist
    }
  }
}

void bewaesserung() {
  // BereitschatfBewaesserung aktivieren
  if (clock.isAlarm2() && BereitschaftBewaesserung == false) {  //  wenn der Alarm 2 ausgelöst wurde und BereitschaftBewaessrung ausgeschaltet
    BereitschaftBewaesserung = true;                            // merken, dass die BereitschaftBewaesserung aktiviert ist
    Serial.println("BereitschaftBewaesserung aktiviert");
  }

  // Auffüllen des Nährlösungsbeckens
  if (BereitschaftBewaesserung == true && lastUmdrehungen != Umdrehungen && PumpeIstAn == false && MagnetventilGeschlossen == false) {  // wenn BereitschatBewaesserung aktiviert und eine neue Umdrehung gezählt wurde
    MagnetventilSchliessen;
    MagnetventilGeschlossen = true;           // Magnetventil schließen
    if (NaehrloesungTankIstVoll == false) {  // die Pumpe geht nur an, wenn die Notabschaltung deaktiviert ist
      PumpeStarten;         // Pumpe einschalten
      PumpeIstAn = true;               // merken, dass die Pumpe angeschaltet ist
    }
    BewaesserungsUmdrehungErlaubt = true;  // merken, dass die BewaesserungsUmdrehungErlaubt aktiviert ist
    lastUmdrehungen = Umdrehungen;
    Serial.println("Nährlösungsbecken auffüllen");
  }

  // Nährlösungsbecken aufgefüllt
  if (PumpeIstAn && SchwimmerschalterNaehrloesungBecken_Pin == LOW) {  // wenn die Pumpe eingeschaltet ist und SchwimmerschalterNaehrloesungBecken_Pin auslöst
    PumpeAusschalten;
    PumpeIstAn = false;
    Serial.println("Nährlösungsbecken aufgefüllt");
  }

  // Nährlösungsbecken leeren und BereitschaftBewaesserung deaktiveren
  if (BewaesserungsUmdrehungErlaubt && MagnetventilGeschlossen && lastUmdrehungen != Umdrehungen) {  // wenn BewaesserungsUmdrehungErlaubt aktiviert ist und eine neue Umdrehung gezählt wurde
    BewaesserungsUmdrehungErlaubt = false;                                                                      // merken, dass die BewaesserungsUmdrehungErlaubt beendet wurde
    lastUmdrehungen = Umdrehungen;
    BereitschaftBewaesserung = false;  // BereitschaftBewaesserung deaktivieren
    MagnetventilOffnen;   // Magnetventil öffnen, sodass die Nährlösung abfließen kann
    MagnetventilGeschlossen = false;        // merken, dass das Magnetventil offen ist
    Serial.println("Nährlösungsbecken leeren");
    Serial.println("BereitschaftBewaesserung deaktiviert");
  }
  if (NaehrloesungTankIstVoll) Serial.println("NaehrloesungTank Ist Voll. Pumpe weiss Bescheid");
}

Mein lieber Leonardo, Du bist immer für eine Überraschung gut!
:partying_face: :star_struck: :smiley:

Das DIng ist schon ne Hausnummer...
Ich sehe, Du nimmst Basilikum. Dazu - meine - Gärtnerdarstellung:
Basilikum ist ein Starkzehrer. Der muss unbedingt ne Menge Nähstoffe bekommen. Und er braucht viel Wasser. Er hat den Vorteil, das er auch in Wasser dauerhaft stehen könnte, ohne das die Wurzeln faulen.
Aber: Es gibt einen erheblichen Nachteil. Der ist empfindlich, was die Berührung angeht. Auch Blätter untereinander, wenn die reiben, tun ihm nicht gut.

Ich selbst hätte es eher mit Feldsalat probiert.
Der kann auch ganz dicht sein und kann ebenfalls mit nassem Untergrund auskommen.

Über den Code schau ich natürlich wieder.
Coole Sache. Diese.

Nachtrag: Schon jetzt gibts das Fleissbienchen für die Variablennamen und die Kommentare....

1 Like

LED licht braucht eigentlich keine Freilaufdiode.

Freilaufdioden braucht es für Verbraucher die mit Gleichstrom betrieben werden und eine induktive Last darstellen (Motore, Ventile, elektromagnetische Relais, Elektromagnete, mit anderen Worten alles was eine Spule eingebaut hat).

Grüße Uwe

Dann will ich mal nach kurzem draufschauen:
Ich muss wissen, welche DS3231-lib Du verwendest. Es gibt davon so viele (auch welche die komische Sachen machen...) und die eine, die ich hier noch hatte war es nicht.

Du verwendest an verschiedenen Stellen Statusvariablen, um Dir zu merken, welchen Zustand Du übergeben hast.
Die sind überflüssig.
Du kannst an Stelle der Variablen an jeder Stelle im Code den dazu gehörigen Pin auslesen.
Schönes Beispiel ist der StepPin.
Ich hab gleich mal die Funktion zum rotieren umgeschrieben:

void RadDrehen()
{
  // Verzögerungszeit zwischen den Schritten, Minimalwert des jeweiligen Motors darf nicht unterschritten werden; wenn ich die Verzögerunszeit nach oben schalte, komme ich auf weniger Umdrehungen!// Minimalwert ist 950 Mikrosekunden für den Vollschrittmodus und den NEMA15-Motor.
  // Minimalwert ist 35 für den Sechzehntelschrittmodus; d.h. umso mehr Schritte, umso kürzer darf die Verzögerungspause sein
  // Eine Sekunde entspricht 1.000.000 Mikrosekunden und 1000 Millisekunden. delay(); wird z.B. immer in Millisekunden angegeben
  static uint32_t lastStepTime = 0;
  switch (digitalRead(stepPin))
  {
    case HIGH:
      if (micros() - lastStepTime >= 1000000)
      {
        digitalWrite(stepPin, LOW);  // ...Schritt nach vorne
        lastStepTime = micros();
      }
      break;
    case LOW:
      if (micros() - lastStepTime >= 950)
      {
        digitalWrite(stepPin, HIGH);  // Impuls für einen...
        lastStepTime = micros();
      }
      break;
  }
}

Dann solltest Du die seriellen Ausgaben mit dem F-Makro versehen.

Die Variable lastReadState verändert sich nicht.
Wenn Du in der Funktion UmdrehungenZaehlen() nur die (erste) Flanke vom Reed erfassen willst, geht das einfacher.


void UmdrehungenZaehlen()
{
  static bool lastReadState = LOW;
  static uint32_t lastReadTime = 0;
  if (digitalRead(REED_PIN) == HIGH)  // Ausgelöst?
  {
    if ( lastReadState == false)      // Nicht gemerkt?
    {
      Umdrehungen++;                  // Implementiere Umdrehungen, d.h. addiere die Variable Umdrehungen mit +1
      Serial.print(F("Umdrehungen: "));
      Serial.println(Umdrehungen);    // Zeige die Umdrehungen auf dem seriellen Monitor an
      lastReadState = true;           // Status merken
    }
  }
  else if (millis() - lastReadTime > DEBOUNCE_DELAY) // Wenn nicht mehr ausgelöst UND debouncezeit abgelaufen
  {
    lastReadState = false;            // 
  }
}

Über den rest schau ich, wenn ich die richtige lib habe.

ja, genau, war auch meine Gedanke. die weitere:

  • man kann STEP alle halbe Sekunde umschalten, so ist eine Frequenz von 1Hz, so braucht man nicht arg achten ob Mindestgrenze für Step Impuls nicht unterschritten ist. und Drehgeschwindigkeit ist noch einfacher ein zu stellen;
  • die globale Variablen zu entsprechenden Funktionen verschoben, außer LichtZeitmerker ,etc;
  • auf Prällung wird nicht geachtet, weil die davon abhängige Prozesse sehr träge sind und werden sowieso nicht doppelt ausgeführt;
  • die Definitionen umgestaltet, einige Variable auf kleinere Typen geändert;
    ich glaub da war noch was...

egal, i mach neue Version, wo möglichst weniger "State" gespeichert wird.

#include <Wire.h>    // Bibliothek für I2C-Kommunikation
#include <DS3231.h>  // Bibliothekt für das RTC- Modukl

DS3231 clock;
RTCDateTime dt;

// Definierungen zum Drehen des Rades
const byte REED_PIN = 2;             // Pin, an dem der Reed-Schalter angeschlossen ist
const byte SchwimmerschalterNaehrloesungBecken_Pin = 3;
const byte SchwimmerschalterNaehrloesungTank_Pin = 4;
const byte Magnetventil_Pin = 5;
const byte Licht_Pin = 6;
const byte Pumpe_Pin = 7;
const byte dir_Pin = 8;
const byte step_Pin = 9;

// Definierungen zum Mitzählen der Umdrehungen
unsigned int lastUmdrehungen = 0;             // zuletzt gezählte Umdrehungen
unsigned int Umdrehungen = 0;                 // gezählte Umdrehungen

#define  PumpeIstAn (digitalRead(Pumpe_Pin))
#define  MagnetventilGeschlossen (digitalRead(Magnetventil_Pin))
#define  MagnetventilOffnen (digitalWrite(Magnetventil_Pin, LOW))
#define  MagnetventilSchliessen (digitalWrite(Magnetventil_Pin, HIGH))
#define  TankIstVoll (!digitalRead(SchwimmerschalterNaehrloesungTank_Pin))
#define  BeckenIstVoll (!digitalRead(SchwimmerschalterNaehrloesungBecken_Pin))
#define  PumpeAusschalten (digitalWrite(Pumpe_Pin, LOW))
#define  PumpeStarten (digitalWrite(Pumpe_Pin, HIGH))
#define  Licht (digitalRead(Licht_Pin))

void setup() {
  Serial.begin(9600);

  // Intitialsierungen für Mitzaehlen der Umdrehungen
  pinMode(REED_PIN, INPUT_PULLUP);  // interneren Pullup- Widerstand des Pin 2 aktivieren
  pinMode(SchwimmerschalterNaehrloesungBecken_Pin, INPUT_PULLUP);  // Entprellen halte ich für unnötig
  pinMode(SchwimmerschalterNaehrloesungTank_Pin, INPUT_PULLUP);    // Entprellen halte ich für unnötig

  // Initialisierungen zum Drehen des Rades
  pinMode(step_Pin, OUTPUT);
  pinMode(dir_Pin, OUTPUT);

  // Initialsierungen für Bewaesserung
  pinMode(Magnetventil_Pin, OUTPUT);
  pinMode(Pumpe_Pin, OUTPUT);

  // Initialisierungen für Licht
  pinMode(Licht_Pin, OUTPUT);

  // Initialisierungen für DS3231 RTC- Modul
  Serial.println("Initialize DS3231");

  clock.begin();

  // Deaktiviere Alarme und lösche Alarme für dieses Beispiel, da Alarme durch eine Batterie unterstützt werden.
  // Unter normalen Bedingungen sollten die Einstellungen nach dem Ausschalten des Stroms und Neustart des Mikrocontrollers zurückgesetzt werden.
  // d.h. im Klartext: Alles zurücksetzen am Anfang, weil das RTC durch eine Batterie unterstützt wird, wenn der Strom getrennt ist.
  clock.armAlarm1(false);
  clock.armAlarm2(false);
  clock.clearAlarm1();
  clock.clearAlarm2();

  // Manuell (Jahr, Monat, Tag, Stunde, Minute, Sekunde) // heißt das ich muss dem RTC ganz am Anfang den aktuellen Zeitpunkt manuell mitteilen?
  //clock.setDateTime(2014, 4, 25, 0, 0, 0); //die aktuelle Zeit muss nicht jedes mal neu programmiert werden, denn mit 3V Zelle funzt RTC ununterbrochen


  // Setze Alarm für Licht- Jede 06h:0m:0s an jedem Tag
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  clock.setAlarm1(0, 6, 0, 0, DS3231_MATCH_H_M_S);

  // Setze Alarm für BereitschaftBewaesserung- Jede 8h:0m an jedem Tag // ich lasse, die Sekunden weg, weil ich davon ausgehe, dass ich das bei Alarm 2 muss...
  // setAlarm1(Date or Day, Hour, Minute, Mode, Armed = true)
  clock.setAlarm2(0, 8, 0, DS3231_MATCH_H_M);


  // Festlegen der Ausgangszustände der Aktoren
  MagnetventilOffnen;
  PumpeAusschalten;
  digitalWrite(Licht_Pin, LOW);
  digitalWrite(dir_Pin, HIGH);  // Motordrehrichtung im Uhrzeigersinn ( LOW wäre gegen den Uhrzeigersinn)
}

void loop() {
  RadDrehen();
  UmdrehungenZaehlen();  // Umdrehungen mitzaehlen
  checkAlarms();         // Überprüfe Alarmeinstellungen. Bist du dir wirklich sicher dass du jede Milisecunde prüfen musst ob Wecker gestellt ist?
  licht();               // Licht an und ausschalten
  bewaesserung();
}

void RadDrehen() {
  static unsigned long ZeitmerkerusDelay = 0;
  const unsigned int Pausierung = 500;
  if (millis() - ZeitmerkerusDelay >= Pausierung) {
    digitalWrite(step_Pin, !digitalRead(step_Pin));  // ...Schritt nach vorne
    ZeitmerkerusDelay += Pausierung;
  }
}

void UmdrehungenZaehlen() {
  static bool reedState = false;                // aktueller Zustand des Reed-Schalters // wird über den PullUp Widerstand immer HIGH gemacht
  static bool lastReedState = false;     // letzter Zustand des Reed-Schalters
  reedState =digitalRead(REED_PIN);  // Pin 2 wird ausgelesen und in der Variablen reading zwischengespeichert
  if (reedState != lastReedState) {
    lastReedState = reedState;                               // aktualisieren den zuletzt erkannten Zustand auf den aktuellen Zustand des Reed- Schalters
    if (reedState == HIGH) {                           // (wenn bis hier hin alles zugetroffen ist) und der red Schalter HIGH ist
      Umdrehungen++;
      Serial.print("Umdrehungen: ");
      Serial.println(Umdrehungen);  // Zeige die Umdrehungen auf dem seriellen Monitor an
    }
    delay(20);
  }
}

void checkAlarms() {
  dt = clock.getDateTime();                                 // liest das aktuelle Datum und die Uhrzeit vom RTC-Modul (Real Time Clock) aus und speichert sie in der Variable dt als Objekt der Klasse RTCDateTime.
  Serial.println(clock.dateFormat("d-m-Y H:i:s - l", dt));  // Ausgabe auf dem seriellen Monitor

  RTCAlarmTime a1;
  RTCAlarmTime a2;

  if (clock.isArmed1()) {
    a1 = clock.getAlarm1();

    Serial.print("Alarm1 wird ausgelöst ");
    switch (clock.getAlarmType1()) {
      case DS3231_MATCH_H_M_S:
        Serial.print("wenn Stunden, Minuten und Sekunden übereinstimmen: ");
        Serial.println(clock.dateFormat("__ H:i:s", a1));
        break;
    }
  } else Serial.println("Alarm1 ist deaktiviert.");
  if (clock.isArmed2()) {
    a2 = clock.getAlarm2();
    Serial.print("Alarm2 wird ausgelöst ");
    switch (clock.getAlarmType2()) {
      case DS3231_MATCH_H_M:
        Serial.print("wenn Stunden und Minuten übereinstimmen:");
        Serial.println(clock.dateFormat("__ H:i:s", a2));
        break;
    }
  } else Serial.println("Alarm2 ist deaktiviert.");
}

void licht() {
  static uint32_t LichtZeitmerker = 0;  // "static" bedeutet, dass die Variable nur einmal deklariert wird und während der Programmausführung unverändert bleibt(Nein. Das ist was anderes). "uint32_t" steht für "unsigned integer" und definiert eine Ganzzahlvariable ohne Vorzeichen mit einer Größe von 32 Bit.
  if (Licht) {
    if (millis() - LichtZeitmerker >= 57600000UL)digitalWrite(Licht_Pin, LOW); // wenn das Licht angeschaltet ist und 16 h lang an war -- Licht ausschalten
   else {
    if (clock.isAlarm1() ) { // wenn der Alarm 1 ausgelöst und das Licht ausgeschaltet ist
      digitalWrite(Licht_Pin, HIGH);
      Serial.println("Licht ist an");
      LichtZeitmerker = millis();  // Zeit merken, die das Licht an ist
    }
  }
}
}
void bewaesserung() {
  // Auffüllen des Nährlösungsbeckens
  if (clock.isAlarm2() && lastUmdrehungen != Umdrehungen && PumpeIstAn == false && MagnetventilGeschlossen == false) {  // wenn BereitschatBewaesserung aktiviert und eine neue Umdrehung gezählt wurde
    MagnetventilSchliessen;
    if (NaehrloesungTankIstVoll == false)PumpeStarten;
    lastUmdrehungen = Umdrehungen;
    Serial.println("Nährlösungsbecken auffüllen");
  }

  // Nährlösungsbecken aufgefüllt
  if (PumpeIstAn && SchwimmerschalterNaehrloesungBecken_Pin == LOW) {  // wenn die Pumpe eingeschaltet ist und SchwimmerschalterNaehrloesungBecken_Pin auslöst
    PumpeAusschalten;
    Serial.println("Nährlösungsbecken aufgefüllt");
  }

  // Nährlösungsbecken leeren
  if ( MagnetventilGeschlossen && lastUmdrehungen != Umdrehungen) {  // wenn BewaesserungsUmdrehungErlaubt aktiviert ist und eine neue Umdrehung gezählt wurde
    lastUmdrehungen = Umdrehungen;
    MagnetventilOffnen;   // Magnetventil öffnen, sodass die Nährlösung abfließen kann
    Serial.println("Nährlösungsbecken leeren");
  }
  if (NaehrloesungTankIstVoll) Serial.println("NaehrloesungTank Ist Voll. Pumpe weiss Bescheid");
}

ich? bitte PM

Wofür soll die Löscherei gut sein?

Gruß Tommy

Vielen Dank schonmal fürs Drüberschauen!!! :slight_smile: Ich war am Wochenende leider unterwegs aber werde mich morgen ausführlich zurückmelden! LG Leonardo

Hallo LeonardoFiando

Hier kommt ein Link gedacht als Ideenspender.

@my_xy_projekt:

Das DIng ist schon ne Hausnummer...

Heißt das, Dir gefällt das Projekt oder Du hälst die Ausführung für sehr kompliziert? :smiley:
Ich freue mich, dass Du Gärtner bist und Kompetenz in dem Bereich hast, das erklärt auch warum Du mich schon so sehr bei meinem Projekt "endless summer" bereichern konntest :slight_smile: !

Aber: Es gibt einen erheblichen Nachteil. Der ist empfindlich, was die Berührung angeht. Auch Blätter untereinander, wenn die reiben, tun ihm nicht gut.

Okay, Du hast mich überzeugt! :slight_smile: Basilikum wird nicht kultiviert, er sollte vor allem zum Testen des infinity mirror- Effektes dienen. Beim Basilikum schrecken mich auch die relativ hohen Temperautren ab, die er zum Wachsen benötigt: "Bei Temperaturen unter 10°C hört das Wachstum normalerweise auf, und Kälteperioden können das Basilikum sogar abtöten." (Quelle: ChatGPT)

Der rotierende Garten wird eine künstlerische Arbeit. Mich interessiert das Bild, das entsteht. Dafür ist die Wahl der Pflanze nicht besonders wichtig. Am liebsten wäre mir die einfachste Lösung. Ich könnte mir sogar vorstellen Plastikpflanzen zu nutzen, wenn diese optisch überzeugen. Obwohl diese keinen Geruch im Ausstellungsraum erzeugen, den ich eventuell doch ganz gerne hätte.

Wenn es echte Pflanzen werden:
Ich brauche 24 Pflanzen einer Sorte. Es muss eine Pflanze sein die einfach zu kultivieren ist, der die Umdrehungen im Zylinder nicht schaden und die leicht verfügbar ist. Am allerliebsten wäre mir eine, die bereits in Steinwollwürfeln erhältlich ist. In großen Gärtnereien werden z. B. Tomaten in Steinwollwürfeln kultiviert. Am liebsten wäre es mir ich könnte aus einem Großbetrieb wie diesem hier: Home | Wittenberg Gemüse GmbH (wittenberg-gemuese.de) bei Bedarf 24 kleine Pflanzen beziehen und sie vor einer Ausstellung in den Zylinder setzen. Andernfalls könnte ich mir auch vorstellen eine Mutterpflanze zu halten und bei Bedarf 24 Stecklinge zu schneiden, die dann in den Zylinder gesetzt werden.

Ich selbst hätte es eher mit Feldsalat probiert.

Das kann ich mir auch vorstellen, auch wenn ich nicht denke, dass nasser Untergrund ein Problem darstellen wird :wink: .

Hier ist eine Liste kultivierbarer Pflanzen in einem rotierenden Garten, mit der ich mich nochmal auseinandersetzen werde: What can you grow with an Omega Garden? | Omega Garden

Falls Dir ein neuer Top- Favorit einfällt, würde ich ich mich sehr über Deine Meinung freuen! :slight_smile:

Und jetzt zum Sketch:
@kolaha und my_xy_projekt: Danke, dass Ihr drüber geschaut habt! :slight_smile:

@my_xy_projekt: Ich verwende diese library: GitHub - jarzebski/Arduino-DS3231: DS3231 Real-Time-Clock und habe mich an dem Beispiel: DS3231_alarm orientiert.

@kolaha:

  • man kann STEP alle halbe Sekunde umschalten, so ist eine Frequenz von 1Hz, so braucht man nicht arg achten ob Mindestgrenze für Step Impuls nicht unterschritten ist. und Drehgeschwindigkeit ist noch einfacher ein zu stellen;

Der Schrittmotor läuft mit einem an Deinem orientierten Test- Sketch in der Tat genauso. :slight_smile: Am liebsten würde ich den Motor aber später mit Mikroschritt ansteuern, falls er dann noch genug Drehmoment liefert (in der Regel ca. 30 % weniger bei Mikroschritt) und dafür scheint mir void RadDrehen() von my_xy_projekt besser geeignet.

Ich habe alle Eure Verbesserungsvorschläge in den Sketch integriert, von my_xy_projekt void RadDrehen() und void UmdrehungenZaehlen () und von kolaha alles Andere:

// Edit aus eigenem Programm zum Drehen des Rades: wenn ich die micros()- funktion in der zweiten if- Anweisung durch millis() ersetzte und die Zeit entsprechend kürze, funktioniert der Sketch, keine Ahnung warum das so ist

//Definierungen für das RTC- Modul zum Auslösen der Alarme ( für Licht und Bewaesserung)
#include <Wire.h>    // Bibliothek für I2C-Kommunikation
#include <DS3231.h>  // Bibliothekt für das RTC- Modukl

DS3231 clock;
RTCDateTime dt;

// Definierungen zum Drehen des Rades
const byte dir_Pin = 8;
const byte step_Pin = 9;

// Definierungen zum Mitzählen der Umdrehungen
const byte REED_Pin = 2;  // Pin, an dem der Reed-Schalter angeschlossen ist
unsigned int lastUmdrehungen = 0;
unsigned int Umdrehungen = 0;  // gezählte Umdrehungen

// Definierungen zur Bewaesserung
const byte SchwimmerschalterNaehrloesungBecken_Pin = 3;
const byte SchwimmerschalterNaehrloesungTank_Pin = 4;

// Durch die Definition des Makros von z.B. "PumpeIstAn" wird der aktuelle Status der Pumpe abgefragt, der durch digitalRead(Pumpe_Pin) ermittelt wird. Wenn PumpeIstAn gleich false ist, bedeutet das, dass die Pumpe ausgeschaltet ist. Wenn PumpeIstAn gleich true ist, bedeutet das, dass die Pumpe angeschaltet ist
// ein Makro ist eine Abkürzung für einen Code-Block oder eine Code-Zeile
#define PumpeIstAn (digitalRead(Pumpe_Pin))
#define MagnetventilGeschlossen (digitalRead(Magnetventil_Pin))
#define MagnetventilOffnen (digitalWrite(Magnetventil_Pin, LOW))
#define MagnetventilSchliessen (digitalWrite(Magnetventil_Pin, HIGH))
#define NaehrloesungTankIstVoll (!digitalRead(SchwimmerschalterNaehrloesungTank_Pin))  // Notabschaltung // wenn Schwimmerventil LOW ist der Tank voll, wenn es HIGH ist, ist der Tank leer
#define BeckenIstVoll (!digitalRead(SchwimmerschalterNaehrloesungBecken_Pin))
#define PumpeAusschalten (digitalWrite(Pumpe_Pin, LOW))
#define PumpeStarten (digitalWrite(Pumpe_Pin, HIGH))

const byte Magnetventil_Pin = 5;
const byte Pumpe_Pin = 7;

// Definierungen zum Licht
const byte Licht_Pin = 6;
#define Licht (digitalRead(Licht_Pin))

void setup() {
  Serial.begin(9600);

  // Initialisierungen zum Drehen des Rades
  pinMode(step_Pin, OUTPUT);
  pinMode(dir_Pin, OUTPUT);

  // Intitialsierungen für Mitzaehlen der Umdrehungen
  pinMode(REED_Pin, INPUT_PULLUP);  // interneren Pullup- Widerstand des Pin 2 aktivieren

  // Initialsierungen für Bewaesserung
  pinMode(SchwimmerschalterNaehrloesungBecken_Pin, INPUT_PULLUP);  // Entprellen halte ich für unnötig
  pinMode(SchwimmerschalterNaehrloesungTank_Pin, INPUT_PULLUP);    // Entprellen halte ich für unnötig
  pinMode(Magnetventil_Pin, OUTPUT);
  pinMode(Pumpe_Pin, OUTPUT);

  // Initialisierungen für Licht
  pinMode(Licht_Pin, OUTPUT);


  // Initialisierungen für DS3231 RTC- Modul
  Serial.println(F("Initialize DS3231"));
  ;
  clock.begin();

  // Deaktiviere Alarme und lösche Alarme für dieses Beispiel, da Alarme durch eine Batterie unterstützt werden.
  // Unter normalen Bedingungen sollten die Einstellungen nach dem Ausschalten des Stroms und Neustart des Mikrocontrollers zurückgesetzt werden.
  // d.h. im Klartext: Alles zurücksetzen am Anfang, weil das RTC durch eine Batterie unterstützt wird, wenn der Strom getrennt ist.
  clock.armAlarm1(false);
  clock.armAlarm2(false);
  clock.clearAlarm1();
  clock.clearAlarm2();

  // Manuell (Jahr, Monat, Tag, Stunde, Minute, Sekunde) // heißt das ich muss dem RTC ganz am Anfang den aktuellen Zeitpunkt manuell mitteilen?
  clock.setDateTime(2014, 4, 25, 0, 0, 0);


  // Setze Alarm für Licht- Jede 06h:0m:0s an jedem Tag
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  clock.setAlarm1(0, 6, 0, 0, DS3231_MATCH_H_M_S);

  // Setze Alarm für BereitschaftBewaesserung- Jede 8h:0m an jedem Tag // ich lasse, die Sekunden weg, weil ich davon ausgehe, dass ich das bei Alarm 2 muss...
  // setAlarm1(Date or Day, Hour, Minute, Mode, Armed = true)
  clock.setAlarm2(0, 8, 0, DS3231_MATCH_H_M);


  // Festlegen der Ausgangszustände der Aktoren
  MagnetventilOffnen;
  PumpeAusschalten;
  digitalWrite(Licht_Pin, LOW);
  digitalWrite(dir_Pin, HIGH);  // Motordrehrichtung im Uhrzeigersinn ( LOW wäre gegen den Uhrzeigersinn)
}

void loop() {
  RadDrehen();
  UmdrehungenZaehlen();  // Umdrehungen mitzaehlen
  checkAlarms();         // Überprüfe Alarmeinstellungen
  licht();               // Licht an und ausschalten
  bewaesserung();
}

void RadDrehen() {
  // Verzögerungszeit zwischen den Schritten, Minimalwert des jeweiligen Motors darf nicht unterschritten werden; wenn ich die Verzögerunszeit nach oben schalte, komme ich auf weniger Umdrehungen!// Minimalwert ist 950 Mikrosekunden für den Vollschrittmodus und den NEMA15-Motor.
  // Minimalwert ist 35 für den Sechzehntelschrittmodus; d.h. umso mehr Schritte, umso kürzer darf die Verzögerungspause sein
  // Eine Sekunde entspricht 1.000.000 Mikrosekunden und 1000 Millisekunden. delay(); wird z.B. immer in Millisekunden angegeben
  static uint32_t lastStepTime = 0;
  switch (digitalRead(step_Pin)) {
    case HIGH:
      if (micros() - lastStepTime >= 1000000) {
        digitalWrite(step_Pin, LOW);  // ...Schritt nach vorne
        lastStepTime = micros();
      }
      break;
    case LOW:
      if (micros() - lastStepTime >= 950) {
        digitalWrite(step_Pin, HIGH);  // Impuls für einen...
        lastStepTime = micros();
      }
      break;
  }
}

void UmdrehungenZaehlen() {
  static bool lastReadState = LOW;
  static uint32_t lastReadTime = 0;
  static byte DEBOUNCE_DELAY = 50;    // Entprellungszeit in Millisekunden
  if (digitalRead(REED_PIN) == HIGH)  // Ausgelöst?
  {
    if (lastReadState == false)  // Nicht gemerkt?
    {
      Umdrehungen++;  // Implementiere Umdrehungen, d.h. addiere die Variable Umdrehungen mit +1
      Serial.print(F("Umdrehungen: "));
      Serial.println(F(Umdrehungen));  // Zeige die Umdrehungen auf dem seriellen Monitor an
      lastReadState = true;         // Status merken
    }
  } else if (millis() - lastReadTime > DEBOUNCE_DELAY)  // Wenn nicht mehr ausgelöst UND debouncezeit abgelaufen
  {
    lastReadState = false;  //
  }
}

void checkAlarms() {
  dt = clock.getDateTime();                                    // liest das aktuelle Datum und die Uhrzeit vom RTC-Modul (Real Time Clock) aus und speichert sie in der Variable dt als Objekt der Klasse RTCDateTime.
  Serial.println(F(clock.dateFormat("d-m-Y H:i:s - l", dt)));  // Ausgabe auf dem seriellen Monitor

  RTCAlarmTime a1;
  RTCAlarmTime a2;

  if (clock.isArmed1()) {
    a1 = clock.getAlarm1();

    Serial.print(F("Alarm1 wird ausgelöst "));
    switch (clock.getAlarmType1()) {
      case DS3231_MATCH_H_M_S:
        Serial.print(F("wenn Stunden, Minuten und Sekunden übereinstimmen: "));
        Serial.println(F(clock.dateFormat("__ H:i:s", a1)));
        break;
      default:
        Serial.println(F("UNBEKANNTE REGEL"));
        break;
    }
  } else {
    Serial.println(F("Alarm1 ist deaktiviert."));
  }

  if (clock.isArmed2()) {
    a2 = clock.getAlarm2();

    Serial.print(F("Alarm2 wird ausgelöst "));
    switch (clock.getAlarmType2()) {
      case DS3231_MATCH_H_M:
        Serial.print(F("wenn Stunden und Minuten übereinstimmen:"));
        Serial.println(F(clock.dateFormat("__ H:i:s", a2)));
        break;
      default:
        Serial.println(F("UNBEKANNTE REGEL"));
        break;
    }
  } else {
    Serial.println(F("Alarm2 ist deaktiviert."));
  }
}

void licht() {
  static uint32_t LichtZeitmerker = 0;  // "static" bedeutet, dass die Variable nur einmal deklariert wird und während der Programmausführung unverändert bleibt(Nein. Das ist was anderes). "uint32_t" steht für "unsigned integer" und definiert eine Ganzzahlvariable ohne Vorzeichen mit einer Größe von 32 Bit.
  if (Licht) {
    if (millis() - LichtZeitmerker >= 57600000UL) digitalWrite(Licht_Pin, LOW);
    {  // wenn das Licht angeschaltet ist und 16 h lang an war -- Licht ausschalten
    }
    else {
      if (clock.isAlarm1()) {  // wenn der Alarm 1 ausgelöst und das Licht ausgeschaltet ist
        digitalWrite(Licht_Pin, HIGH);
        Serial.println(F("Licht ist an"));
        LichtZeitmerker = millis();  // Zeit merken, die das Licht an ist
      }
    }
  }

  void bewaesserung() {
    // Auffüllen des Nährlösungsbeckens
    if (clock.isAlarm2() && lastUmdrehungen != Umdrehungen && PumpeIstAn == false && MagnetventilGeschlossen == false) {  // wenn BereitschatBewaesserung aktiviert und eine neue Umdrehung gezählt wurde
      MagnetventilSchliessen;
      if (NaehrloesungTankIstVoll == false) PumpeStarten;  // Notabschaltung // nur wenn der Tank voll ist Pumpe starten
      lastUmdrehungen = Umdrehungen;
      Serial.println(F("Nährlösungsbecken auffüllen"));
    }

    // Nährlösungsbecken aufgefüllt
    if (PumpeIstAn && SchwimmerschalterNaehrloesungBecken_Pin == LOW) {  // wenn die Pumpe eingeschaltet ist und SchwimmerschalterNaehrloesungBecken_Pin auslöst
      PumpeAusschalten;
      Serial.println(F("Nährlösungsbecken aufgefüllt"));
    }

    // Nährlösungsbecken leeren
    if (MagnetventilGeschlossen && lastUmdrehungen != Umdrehungen) {  // wenn BewaesserungsUmdrehungErlaubt aktiviert ist und eine neue Umdrehung gezählt wurde
      lastUmdrehungen = Umdrehungen;
      MagnetventilOffnen;  // Magnetventil öffnen, sodass die Nährlösung abfließen kann
      Serial.println(F("Nährlösungsbecken leeren"));
    }
    if (NaehrloesungTankIstVoll) {
      Serial.println(F("NaehrloesungTank Ist Voll. Pumpe weiss Bescheid"));      
    }
  }

Leider kommt es beim Kompilieren des Sketches zu folgendem Fehler und ich checke nicht wo der Fehler liegt :'D :

F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino: In function 'void loop()':
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:99:3: error: 'bewaesserung' was not declared in this scope
   bewaesserung();
   ^~~~~~~~~~~~
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino: In function 'void UmdrehungenZaehlen()':
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:127:19: error: 'REED_PIN' was not declared in this scope
   if (digitalRead(REED_PIN) == HIGH)  // Ausgelöst?
                   ^~~~~~~~
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:127:19: note: suggested alternative: 'REED_Pin'
   if (digitalRead(REED_PIN) == HIGH)  // Ausgelöst?
                   ^~~~~~~~
                   REED_Pin
In file included from C:\Users\lonsk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino/Arduino.h:28:0,
                 from C:\Users\lonsk\AppData\Local\Temp\arduino\sketches\E6A5492DB6FD5B0BBB2AD1C34692B42D\sketch\hydroponic_wheel_sketch.ino.cpp:1:
C:\Users\lonsk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino/WString.h:38:74: error: initializer fails to determine size of '__c'
 #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
                                                                          ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:133:22: note: in expansion of macro 'F'
       Serial.println(F(Umdrehungen));  // Zeige die Umdrehungen auf dem seriellen Monitor an
                      ^
C:\Users\lonsk\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino/WString.h:38:74: error: array must be initialized with a brace-enclosed initializer
 #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
                                                                          ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:133:22: note: in expansion of macro 'F'
       Serial.println(F(Umdrehungen));  // Zeige die Umdrehungen auf dem seriellen Monitor an
                      ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino: In function 'void checkAlarms()':
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:144:36: error: initializer fails to determine size of '__c'
   Serial.println(F(clock.dateFormat("d-m-Y H:i:s - l", dt)));  // Ausgabe auf dem seriellen Monitor
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:144:18: note: in expansion of macro 'F'
   Serial.println(F(clock.dateFormat("d-m-Y H:i:s - l", dt)));  // Ausgabe auf dem seriellen Monitor
                  ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:144:36: error: array must be initialized with a brace-enclosed initializer
   Serial.println(F(clock.dateFormat("d-m-Y H:i:s - l", dt)));  // Ausgabe auf dem seriellen Monitor
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:144:18: note: in expansion of macro 'F'
   Serial.println(F(clock.dateFormat("d-m-Y H:i:s - l", dt)));  // Ausgabe auf dem seriellen Monitor
                  ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:156:42: error: initializer fails to determine size of '__c'
         Serial.println(F(clock.dateFormat("__ H:i:s", a1)));
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:156:24: note: in expansion of macro 'F'
         Serial.println(F(clock.dateFormat("__ H:i:s", a1)));
                        ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:156:42: error: array must be initialized with a brace-enclosed initializer
         Serial.println(F(clock.dateFormat("__ H:i:s", a1)));
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:156:24: note: in expansion of macro 'F'
         Serial.println(F(clock.dateFormat("__ H:i:s", a1)));
                        ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:173:42: error: initializer fails to determine size of '__c'
         Serial.println(F(clock.dateFormat("__ H:i:s", a2)));
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:173:24: note: in expansion of macro 'F'
         Serial.println(F(clock.dateFormat("__ H:i:s", a2)));
                        ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:173:42: error: array must be initialized with a brace-enclosed initializer
         Serial.println(F(clock.dateFormat("__ H:i:s", a2)));
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:173:24: note: in expansion of macro 'F'
         Serial.println(F(clock.dateFormat("__ H:i:s", a2)));
                        ^
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino: In function 'void licht()':
F:\Arduino- und Forum\Sketches\hydroponic_wheel_sketch\hydroponic_wheel_sketch.ino:190:5: error: expected '}' before 'else'
     else {
     ^~~~

exit status 1

Compilation error: 'bewaesserung' was not declared in this scope

tut mir Leid ich habe keine Ahnung wie "meine Art" dem im Weg steht.
wenn du Geschwindigkeit erhöhen willst - verkleinerst du den Wert.
wenn Mikroschritte eingebaut dann teilst du den Wert durch Microsteping Größe.

Okay, dann kann ich das ja auch getrost so übernehmen, ist das erste Mal dass ich mit einem Schrittmotor hantiere ;). Ich bin Dir auf jeden Fall extrem dankbar für deinen optimierten Sketch :slight_smile: !

Wenn Du STRG-T drückst, dann merkst Du, das irgendwo eine Klammer falsch ist, denn

das Wort void darf nicht eingerückt sein.

Die passende Stelle ist hier:

Da ist in einer Zeile die Bedingung und eine Handlung. Die öffnende Klammer kommt erst später in der Zeile mit dem Kommentar.

Allerdings empfehle ich auf die #define zu verzichten.
Zum einen sind sie überflüssig, zum anderen sind Sie fehlerträchtig. Der Compiler bekommt das vorgesetzt und kann nicht prüfen, was da vorher passiert ist.

Und irgendwas kollidiert da auch mit dem FlashStringHelper. (Muss Dir nix sagen - es reicht es erstmal nur zu wissen :slight_smile: ) Ich bekomme das nicht kompiliert. Mal sehen, ob ich mir da was passendes zu einfallen lassen kann. Das sind eigentlich klassische get-Funktionen.

Ok. Wir haben schon die anderen Projekte zum laufen bekommen (Ich bin noch immer von dem Rasen fasziniert... :wink: - das hier wird auch funktionieren.
Lib hab ich geholt - Auf den Rest schau ich.
Man liest sich.

Ich werde die Seitenwand des rotierenden Gartens, die mit der Spiegel- Spion- Folie beklebt wird, aus Plexiglas auslasern lassen. Ich habe dafür eine OpenSCAD - Datei angefertigt. Leider verzweifele ich gerade dabei den Abstand zwischen zwei Bohrlöchern zu ermitteln (zwischen einem Bohrloch der Reihe1 und einem Bohrloch der Reihe 2, die achsengespiegelt sind). Dieser Abstand soll 102 mm betragen. Gibt es hier jemanden, der sich mit OpenSCAD auskennt und mir helfen könnte? Es kann keine große Sache sein, ich will wirklich nur den Abstand der Bohrlöcher wissen. Ich habe jetzt schon sehr viel ausprobiert und schaffe es einfach nicht.

ja, so einen kenne ich, zumindest scheint mir dass ich mich kenne.
du hast Abstand 10° zu einander und 291mm vom Zentrum gemacht, richtig?
dann es ist 50.7mm.

ändere 5 auf 10.0936

Danke kolaha, es stimmt! Mit einem STL- Reader / dem Autodesk Viewer in dem Falle ist es mir jetzt auch gelungen es abzumessen. Der 44,7, die hier von Lochgrenze zu Lochgrenze gemessen wurden, müssen noch 2 x 3 mm addiert werden ( Durchmesser/ Loch = 6 mm) dann komme ich hier auch auf die 50, 7! Es wundert mich aber ein wenig, dass Autodesk 50, 7 m angibt :smiley: .
Aber jetzt würde mich noch interessieren, wie Du es herausgefunden hast! Mit OpenSCAD?

Nu bin ich nicht @kolaha, habe auch kein cadProgramm, aber das Betriebssystem hat einen Taschenrechner.
Ich hab das mal ausführlich gemacht:
Du willst auf einem Kreis, dessen Umfang Du nicht direkt kennst Bohrungen anbringen, die alle im gleichen Abstand von 10° sein sollen.
Umfang = 2 * PI * Durchmesser zum Quadrat
Umfang = 2 * 3.1415926 * (291*2)^2 Hinweis: 291 ist der Radius daher *2!
Umfang = 2 * 3.1415926 * 582 ^ 2
Umfang = 2 * 3.1415926 * 338724
Umfang = 2128265.6236848

Abstand 10°
Vollumfang = 360°
Anzahl = 360/10 = 36
Abstand = Umfang / Anzahl = 2128265.6236848 / 36
Abstand = 59118,4895468
-> Dabei handelt es sich um den Abstand von Bohrung Mittelpunkt bis Bohrung Mittelpunkt!

Mathe, internet

welchen Teil misst du, mit 5° oder 10.0936° ? du wolltest 102mm, freust dich aber über 50,7