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.
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.
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:
Die Anzahl der Gewindestangen + Pflanzencontainer wird verdoppelt, so dass auf einem Kreis acht Pflanzen Platz finden:
In der Tiefe wird der Zylinder aus drei Reihen Pflanzen bestehen, die sind leider nötig um eine gelungene Illusion zu schaffen:
Der rotierende Garten soll auf Augenhöhe positioniert werden, dafür werde ich eine Konstruktion aus Metall anfertigen:
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.
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