Einsteigerfragen: Kann mein Code optimiert werden? Welches Board für Wifi?

Hallo zusammen,

vor ein paar Wochen hab ich einen Arduino Uno + Starterkit bekommen, da die meisten Projekte mal durch und Spass dran gefunden.

Als erste Anwendung hab ich mal mit einer Pflanzenbewässerung angefangen, scheint ja ein echter Klassiker zum Start zu sein, hab hier im Forum schon ein paar gute Threads dazu gelesen.

Ich will mal relativ einfach anzufangen und die Sache dann Schritt für Schritt ausauen. Dabei soll das nicht nur so irgendwie funktionieren, sondern ich mag auch gerade am Anfang darauf achten, dass ich einigermaßen brauchbaren Code produziere, über den man sich austauschen kann. Daher wäre mein erstes Anliegen mal über mein Werk drüber zu schauen:

// Konstantendeklaration
// Förderleistungen der Pumpen in den einzelnen Bereichen 0 bis 3 in ml/s
const int FL[] = {20, 20, 20, 20};

// Zeit bis Start erste Bewässerung in Stunden
const float ToFirst_h = 0.005; //18s zum Testen

//Belegung der Pins für die Pumpenrelais
const byte R[] = {2, 3, 4, 5};

// Variablendeklaration - sollen später im loop anpassbar sein

// auszubringende Wassermengen in den einzelnen Bereichen 0 bis 3 in ml
int Menge[] = {100, 75, 200, 125};

// Intervall in dem Wasser ausgebracht werden soll in Stunden
float Intervall_h;

// Kenner ob und wie viele Gießintervalle ausgelassen werden sollen pro Bereich
byte Skip[] = {0, 1, 2, 0};

// Fehlerkenner für Unterbrechung von loop Teilen und Anzeige
byte Failcode;

//globale Variablen für Code
unsigned long StartMillis; //Für Steuerung der Intervalle
unsigned long CurrentMillis;
unsigned long NextIntervall;
byte CurrentSkip[4]; // Zähler für die aktuelle Anzahl der Intervalle die bereits ausgelassen worden sind
unsigned long Pumpenlaufzeit[4];
unsigned long Pumpenlaufzeit_ges;
byte x; //Laufvariable für diverse For Schleifen

void setup()
{
  // Initialwerte für Bewässerung
  Serial.begin(9600);
  Failcode = 0; //Fehlercode 0 = kein Fehler
  Intervall_h = 0.01; //Bewässerung alle 36s für Test - später 24 oder so

  //Pins für die Relais setzen
  for (x = 0; x < 4; x++)
  {
    pinMode (R[x], OUTPUT);
    digitalWrite(R[x], HIGH); //Relais schalten bei LOW
  }

  StartMillis = millis(); // Startzeit setzen
  NextIntervall = ToFirst_h * 3600000; //Erstes Intervall setzen in Millis

  //Erste Skipwerte setzen
  for (x = 0; x < 4; x++)
  {
    CurrentSkip[x] = Skip[x];
  }
}

void loop()
{
  CurrentMillis = millis();
  if (CurrentMillis - StartMillis >= NextIntervall)
  {
    NextIntervall = Intervall_h * 3600000; //Nächstes Intervall setzen in Millis
    StartMillis = CurrentMillis; //Start zurücksetzen
    //Berechnung der Pumpenlaufzeiten
    Calc_Laufzeit();
    //Prüfung ob Gesamtlaufzeit Pumpen + Puffer unter Intervall liegt
    if (Pumpenlaufzeit_ges > NextIntervall)
    {
      Failcode = 1;
    }
    //Pumpen laufen lassen und Skipwerte aktualisieren
    if (Failcode == 0)
    {
      for (x = 0; x < 4; x++)
      {
        if (CurrentSkip[x] < 1)
        {
          digitalWrite (R[x], LOW);
          delay (Pumpenlaufzeit[x]);
          digitalWrite (R[x], HIGH);
          delay (250);
          CurrentSkip[x] = Skip[x];
        }
        else
        {
          CurrentSkip[x]--;
        }
      }
    }
  }
  Serial.print("Pumpenlaufzeit0: ");
  Serial.println(Pumpenlaufzeit[0]);
  Serial.print("Pumpenlaufzeit1: ");
  Serial.println(Pumpenlaufzeit[1]);
  Serial.print("Pumpenlaufzeit2: ");
  Serial.println(Pumpenlaufzeit[2]);
  Serial.print("Pumpenlaufzeit3: ");
  Serial.println(Pumpenlaufzeit[3]);
  Serial.print("NextIntervall: ");
  Serial.println(NextIntervall);
  Serial.print("Fehler: ");
  Serial.println(Failcode);
  delay(3000);
}

void Calc_Laufzeit()
{
  Pumpenlaufzeit_ges = 1000; //Zeit für die maximal 4x 250 ms zwischen den Relais
  for (x = 0; x < 4; x++)
  {
    if (CurrentSkip[x] < 1) //Wenn nicht geskipt wird, Berechnung aus Menge und Förderleistung
    {
      Pumpenlaufzeit[x] = Menge[x] / FL[x] * 1000;
    }
    else
    {
      Pumpenlaufzeit[x] = 0; // Wenn geskipt wird, dann keine Laufzeit
    }
    Pumpenlaufzeit_ges = Pumpenlaufzeit_ges + Pumpenlaufzeit[x];
  }
}

Das System soll 4 Bereiche bewässern.

Der Grundablauf ist simpel:

  • warte einen definierten Zeitraum (24h, 12h oder sowas)
  • lass 4 Pumpen hintereinander eine definierte Wassermenge ausgeben
  • von vorn

Features:

  • Wassermengen pro Bereich können direkt eingegeben werden, die Ausgabe wird über Förderleistung in Laufzeit umgerechnet
  • Intervalle können geziehlt pro Bereich ausgelassen werden (also nur jeden 2. oder 3. Intervall gießen)
  • Erstes Intervall kann gesondert festgelegt werden (damit der Bewässerungszeitpunkt nicht gleich Startzeitpunkt sein muss)
  • Bisher wird ein Fehler geprüft und abgefangen - Überscheitet die Gesamtpumpenlaufzeit das Intervall (das würde uns wohl ins Chaos stürzen, sollt aber im Regelbetrieb kaum vorkommen)

Der Code macht soweit mal das was er soll, zumindest mal an 4 Test - Led, die ich über die Relais ansteuere momentan. Trotzdem wäre ich dran interessiert, ob hier noch was verbessert und vereinfacht werden kann. Passt diese Mischung aus Millis() und Delay() oder lieber alles mit Millis steuern? Der Serial.Print Block ist da auch noch nicht optimal, da sehe ich die aktuellen Laufzeiten erst, wenn die Pumpen schon gelaufen sind …

Weitere Schritte:

  • Das Intervall und die Wassermengen sollen online (Mobilphone) während der Laufzeit veränderbar sein
  • Einbindung von Sensoren für Feuchtigkeit, Temperatur, etc
  • Achja, die Hardware (Tank, Pumpen, Leitungen) muss ich irgendwann mal bauen :smiley:
  • Irgendwann wird das ganze wohl auf eine Pumpe und Ventile zur Steuerung der Bereiche umgebaut
  • Abfrage Füllstand Tank …

Für die Umsetzung der Steuerung aufs Handy ist mir Blynk ins Auge gestochen. Scheint so für den Anfang recht simpelzu sein. Nur ist hier die Frage, wie komm ich mit dem Uno Board ins WLAN, bzw. ist der Uno hier überhaupt das geeignete Board?

Besser scheint ja ein ESP8266 (also sowas:ESP8266) aber das hat auf den ersten Blick nur einen analog Eingang, was mir ungünstig erscheint, wenn ich da irgendwann Feuchtigkeits und Temperatursensoren anschließen will…? Ich hab da auch schon einiges hin und her recherchiert, aber das Feld ist groß und für Einsteiger recht unübersichtlich.

Bin also für Tips, Anregungen, gute Links und sonstigen Input dankbar.

Grüße Olli

Das du hier was fragen willst, ist uns klar. Aber wo dein Problem ist, eher nicht.

Somit ist es besser, wenn dein Titel auch deine Frage zeigt. Dann wird dein Problem besser gefunden und es gibt mehr Helfer.

Also mach das bitte gleich, das geht auch noch.

Hallo Dieter, alles klar, angepasst ...

olli_h: Hallo Dieter, alles klar, angepasst ...

Ok. Mit welchem Controller du startest, hängt auch von deinen Kenntnissen ab. Du kannst einen Uno für deine Sensoren verwenden und die Verbindung zum WLan mittels ESP8266 aufbauen. Beide Controller verbindest du per I2C.

Hallo Olli,

wenn WLAN mit dabei sein soll empfehle ich ESP32. Das ist der Nachfolger vom ESP8266. ziemlich viele IO-Pins RTC ist schon onboard, Bluetooth auch. Hat auch gleich noch FLASH-Speicher on board den man zum abspeichern von Daten benutzen kann. Für viele Analogeingänge würde man dann auch einen AD-Wandler-Chip dazu nehmen den man per I2C ansteuert.

https://circuits4you.com/2018/12/31/esp32-devkit-esp32-wroom-gpio-pinout/ ESP32-DevKit

oder https://az-delivery.de/products/esp32-developmentboard?_pos=2&_sid=c5d852b42&_ss=r

viele Grüße Stefan

olli_h:
vor ein paar Wochen hab ich einen Arduino Uno + Starterkit bekommen, da die meisten Projekte mal durch und Spass dran gefunden.

Dann willkommen zu Deinem neuen Hobby und hier im Forum!

olli_h:
Dabei soll das nicht nur so irgendwie funktionieren, sondern ich mag auch gerade am Anfang darauf achten, dass ich einigermaßen brauchbaren Code produziere, über den man sich austauschen kann. Daher wäre mein erstes Anliegen mal über mein Werk drüber zu schauen:

Du hast eine Menge Dinge richtig gemacht, die ich bei anderen Neulingen vermisse:

  • im Forum gelesen
  • Beschreibung, was das Programm tun soll
  • ein mit der IDE formatiertes Programm mit Kommentaren
  • Vorgehen Schritt für Schritt mit einer klaren Perspektive
  • freundliche Formulierungen

Dafür schon mal meine Anerkennung!

Nun, wie gewünscht, meine Anmerkungen:

1. Groß-Klein-Konventionen: Wenn der Compiler zufrieden ist, kannst Du Variablen, Konstanten und andere Objekte benennen, wie Du magst. Für den Austausch gibt es gewisse Konventionen, leider finde ich die Seite, die ich Dir verlinken wollte nicht wieder. Variablen klein, erster Buchstabe groß bei Klassen usw. Programmierrichtlinien

2. Typ:

const int FL[] = {20, 20, 20, 20};

Kann die Förderleistung negativ werden? Nein, also unsigned.

Wird die Leistung größer als 255? Wenn nein, dann uint8_t oder byte.

Der Typ int ist ungünstig, weil er auf 8-Bit AVRs 16 Bit lang ist, auf ESPs aber 32 Bit. Die Schreibweise int16_t ist unabhängig vom Prozessor und damit eindeutig. Anstelle unsigned long besser uint32_t.

Gleiche Überlegungen für die Wassermenge.

byte x; //Laufvariable für diverse For Schleifen

Das ist unüblich und könnte bei verschachtelten Schleifen auch zu Fehlern führen. Daher besser

for (byte x = 0; x < 4; x++)

3. Zeiten: Wenn Du mit delay arbeitest, ist in dieser Zeit der µC taub für Nachrichten, weshalb jede blockierende Programmierung nicht zulässig und auch nicht nötig ist. Das gilt auch für zeitraubende Schleifen.

4. Ausgaben: So schnell, wie loop sein sollte, kann niemand einer Ausgabe folgen, außerdem ist die Ausgabe blockierend. Daher sollte nur in Intervallen eine Anzeige von Werten erfolgen.

olli_h:
Nur ist hier die Frage, wie komm ich mit dem Uno Board ins WLAN, bzw. ist der Uno hier überhaupt das geeignete Board?

Mit Blynk habe ich mich noch nicht beschäftigt und daher für mich eine HTML-Seite entworfen, die mein Händi anzeigt. Die Seite steht im SPIFFS eines ESP32. Der ESP ist bei mir Access Point, kann sich aber auch am Router anmelden. Als Grundlage habe ich Esp32 Webserver Arduino Tab verwendet.

Siehe ESP32 Pinout Reference: Which GPIO pins should you use? Mit WiFi ist nur ADC1 zu verwenden!

Hallo zusammen,

vielen Dank für eure Antworten, das hat mich weiter gebracht!

Hab mal geschaut, was ich von den Tipps umgesetzt bekomme. Am schwersten ist es mir gefallen alle Pumpenlaufzeiten und deren Abhängigkeiten ohne delay hinzubekommen, aber jetzt läuft die Sache wieder (nachdem es zwischendrin mal wirklich merkwürdige Sachen gemacht hat). Ich hab ein bissel im Forum geschaut, da war so ein Beispiel von einem Wächter, der rumläuft und Lichtschalter an und aus macht … das hat geholfen :slight_smile:

Hier der überarbeitete Code:

// Konstantendeklaration
const byte Kreise = 4; // Anzahl der Kreise
// Förderleistungen der Pumpen in den einzelnen Kreisen 0 bis 3 in ml/s
const byte FL[] = {20, 20, 20, 20}; //Förderleisten bis 255 ml/s

// Zeit bis Start erste Bewässerung in Stunden
float ToFirst_h = 0.005; //18s zum Testen

//Belegung der Pins für die Pumpenrelais
const byte R[] = {2, 3, 4, 5};

// Variablendeklaration - sollen später im loop anpassbar sein

float Menge[] = {55, 25, 30, 50}; // Auszubringende Mengen in ml, hier wird float benötigt um die spätere Division durch FL hinzubekommen

uint32_t CurrentMillis; //Aktuell vergangene Zeit seit Start des Controllers
float Intervall_h; // Länge des Intervalls in dem Wasser ausgebracht werden soll in Stunden
uint32_t StartMillis; //Startpunkt des Intervalls bis zur Bewässerung
uint32_t NextIntervall; //Länge des nächsten Intervalls bis zur Bewässerung

uint32_t Pumpenlaufzeit[Kreise]; //Länge der Pumpenlaufzeiten pro Kreis
uint32_t Pumpenlaufzeit_ges; //Summe aller Pumpenlaufzeiten + Puffer

uint32_t StartMillis_Pumpe[] = {0, 0, 0, 0}; //Startpunkte der einzelnen Pumpen
bool Pumpenstatus[] = {HIGH, HIGH, HIGH, HIGH}; //Status ob Pumpe läuft oder nicht
byte Pumpenfolge = Kreise; // Zeigt welche Pumpe an der Reihe ist. Mit Setzen auf Kreise erst mal ungültig, damit am Anfang nix läuft.

byte Skip[] = {0, 1, 0, 2}; // Kenner ob und wie viele Gießintervalle ausgelassen werden sollen pro Bereich
byte CurrentSkip[Kreise]; // Zähler für die aktuelle Anzahl der Intervalle die bereits ausgelassen worden sind

byte Failcode; // Fehlerkenner für Unterbrechung von loop Teilen und Anzeige

uint32_t Monitorintervall = 3000; //Intervall für Ausgabe auf dem Seriellen Monitor
uint32_t StartMonitor = 0;

void setup()
{
  // Initialwerte für Bewässerung
  Serial.begin(9600);
  Failcode = 0; //Fehlercode 0 = kein Fehler
  Intervall_h = 0.005; //Bewässerung alle 18s für Test - später 24 oder 12

  for (byte x = 0; x < Kreise; x++)
  {
    pinMode (R[x], OUTPUT); //Pins für die Relais setzen
    digitalWrite(R[x], HIGH); //Relais schalten bei LOW, daher Grundzustand HIGH
    CurrentSkip[x] = Skip[x]; //Erste Skipwerte setzen
  }
  StartMillis = millis(); // Startzeit setzen
  NextIntervall = ToFirst_h * 3600000; //Erstes Intervall setzen in Millis
}

void loop()
{
  CurrentMillis = millis();
  
  // Bewässerungsintervall
  if (CurrentMillis - StartMillis >= NextIntervall)
  {
    NextIntervall = Intervall_h * 3600000; //Nächstes Intervall setzen in Millis
    StartMillis = CurrentMillis; //Start zurücksetzen
    Calc_Laufzeit(); //Berechnung der Pumpenlaufzeiten
    //Prüfung ob Gesamtlaufzeit Pumpen + Puffer unter Intervall liegt
    if (Pumpenlaufzeit_ges > NextIntervall)
    {
      Failcode = 1;
    }
    if (Failcode == 0)
    {
      Pumpenfolge = 0; // Setzt die Pumpenfolge auf den ersten gültigen Wert und startet damit die Pumpenschleife
    }
  }
  
  // Pumpenaktivierung
  for (byte x = 0; x < Kreise; x++)
  {
    if ((Pumpenstatus[x] == HIGH) && (Pumpenfolge == x)) //Wenn die Pumpe aus ist und an der Reihe
    {
      digitalWrite (R[x], LOW); // Pumpe einschalten
      Pumpenstatus[x] = LOW; // Pumpenstatus anpassen
      StartMillis_Pumpe[x] = CurrentMillis; //Startpumkt der Pumpe setzen
    }
    else if ((Pumpenstatus[x] == LOW) && (CurrentMillis - StartMillis_Pumpe[x] >= Pumpenlaufzeit[x]) && (Pumpenfolge == x)) //Wenn die Pumpe an ist, die Laufzeit erreicht und an der Reihe
    {
      digitalWrite (R[x], HIGH); // Pumpe ausschalten
      Pumpenstatus[x] = HIGH; // Pumpenstatus anpassen
      Pumpenfolge = x + 1; // Aktiviert die nächste Pumpe und bleibt nach der letzten Pumpe auf ungültigem Wert stehen
    }
  }
  if (CurrentMillis - StartMonitor >= Monitorintervall) //Ausgabe im Intervall
  {
    Serial.print("Pumpenlaufzeit0: ");
    Serial.println(Pumpenlaufzeit[0]);
    Serial.print("Pumpenlaufzeit1: ");
    Serial.println(Pumpenlaufzeit[1]);
    Serial.print("Pumpenlaufzeit2: ");
    Serial.println(Pumpenlaufzeit[2]);
    Serial.print("Pumpenlaufzeit3: ");
    Serial.println(Pumpenlaufzeit[3]);
    Serial.print("NextIntervall: ");
    Serial.println(NextIntervall);
    Serial.print("Fehler: ");
    Serial.println(Failcode);
    StartMonitor = CurrentMillis;
  }
}

void Calc_Laufzeit()
{
  Pumpenlaufzeit_ges = 2000; //kleiner Puffer
  for (byte x = 0; x < Kreise; x++)
  {
    if (CurrentSkip[x] < 1) //Wenn nicht geskipt wird, Berechnung aus Menge und Förderleistung
    {
      Pumpenlaufzeit[x] = Menge[x] / FL[x] * 1000;
      CurrentSkip[x] = Skip[x]; //Rücksetzung der Skipvariable auf Ausganswert
    }
    else
    {
      Pumpenlaufzeit[x] = 0; // Wenn geskipt wird, dann keine Laufzeit
      CurrentSkip[x]--; //Runterzählen der Skipvariable bis 0
    }
    Pumpenlaufzeit_ges = Pumpenlaufzeit_ges + Pumpenlaufzeit[x];
  }
}

Der macht wieder was er soll, aber von ideal sicherlich noch recht entfernt…

Die Empfehlung zum ESP 32 klingt interessant. Damit werde ich es wohl mal probieren. Zwischenzeitlich hab ich auch gelernt, dass ein analoger Eingang für mehrere Sensoren kein großes Problem ist.

Für weitere Tipps und Anregungen bin ich immer offen …

Grüssle Olli

Kann mein Code optimiert werden?

Ich sehe bei dir einige Arrays, welche gleich lang sein MÜSSEN.

Für weitere Tipps und Anregungen bin ich immer offen …

Das vorher genannte, ist eigentlich ein dringendlicher Anlass eine Struktur bildende Maßnahme einzuläuten.
Also nicht die Daten einer Pumpe über einige Arrays verstreut, sondern zusammengefasst, was zusammen gehört.
Und die Pumpen dann selber in ein Array stopfen

Hier mal ein Strickmuster:

struct Pumpe
{
  const int FL;  // Förderleistungen der Pumpen in den einzelnen Bereichen 0 bis 3 in ml/s
  const byte R;  // Belegung der Pins für die Pumpenrelais
  int Menge;     // auszubringende Wassermengen in den einzelnen Bereichen 0 bis 3 in ml
  byte Skip;     // Kenner ob und wie viele Gießintervalle ausgelassen werden sollen pro Bereich
  
  void init()
  {
    digitalWrite(R, HIGH); //Relais schalten bei LOW
    pinMode(R, OUTPUT);
  }
  
  void run()
  {
     // hier tue das, was deine Pumpe tun muss
  }
};


Pumpe pumpen[] {
                           {20,2,100,0},
                           {20,3, 75,1},
                           {20,4,200,2},
                           {20,5,125,0},
                         };




void setup() 
{
  Serial.begin(9600);
 
  for(Pumpe &p:pumpen) p.init();
}

void loop() 
{
  for(Pumpe &p:pumpen) p.run();
}

Todo:
Run mit Fleisch füllen
Auf class umbauen
Ein Konstruktor wird sinnvoll sein

Zwischenzeitlich hab ich auch gelernt, dass ein analoger Eingang für mehrere Sensoren kein großes Problem ist.

Da hast du sicher etwas falsch verstanden.

HotSystems: Da hast du sicher etwas falsch verstanden.

evtl. meint ihm I2C über A4 A5

combie: evtl. meint ihm I2C über A4 A5

Jo, da kann es sein.

Hi zusammen,

zum Thema mehrere Sensoren an einem Analog Eingang hab ich einmal diese Schaltung gefunden:

Anleitung Schaltung. Fand ich soweit ganz einleuchtend.

Und diese Bauteile: Multiplexer Klingt auch recht simpel, oder täusche ich mich da? :confused:

Die Struktur bildenden Maßnahmen werde ich einleiten. Aber ich fürchte da brauche ich ein bissel Zeit für. Der Rest der Woche ist ziemlich verplant .... ::)

Grüssle Olli

Ahh...ok, mittels Multiplexer, das ist ja auch was anderes und sicher keine simple Sache. Aber ok, so geht das natürlich, mit dem entsprechenden Sketch.

olli_h:
Die Struktur bildenden Maßnahmen werde ich einleiten.

Das Beispiel Wieder bewässerung hast Du sicherlich schon gefunden.

In #6 habe ich einen Link zu Programmierrichtlinien ergänzt.

Als Einzelkämpfer ist das egal, in der Gruppe macht eine Richtlinie Sinn.

In #7 hast Du schon (wieder) eine Menge richtig gemacht, dennoch Anmerkungen von mir:

1. Typ:

float Menge[] = {55, 25, 30, 50};

Wegen der Deutlichkeit würde ich {55.0, 25.0, 30.0, 50.0} schreiben.

2. Zahl der Elemente:

const byte FL[Kreise] = {20, 20, 20, 20};

Der Zugriff auf nicht vorhandene Elemente eines Feldes ist ein häufiger Fehler vom Typ “Programm schmiert manchmal ab”. Leider wird das zur Laufzeit vom Programm nicht erkannt. Mit der zusätzlichen Angabe der Anzahl der Elemente kann man dieses Problem zumindest manchmal frühzeitig erkennen.

3. Schleife hin zu loop öffnen: (ungetestet)

 // Pumpenaktivierung
  static byte x = 0;
  if (Pumpenfolge < Kreise)
  {
    if ((Pumpenstatus[x] == HIGH) && (Pumpenfolge == x)) //Wenn die Pumpe aus ist und an der Reihe
    {
      digitalWrite (R[x], LOW); // Pumpe einschalten
      Pumpenstatus[x] = LOW; // Pumpenstatus anpassen
      StartMillis_Pumpe[x] = CurrentMillis; //Startpumkt der Pumpe setzen
    }
    else if ((Pumpenstatus[x] == LOW) && (CurrentMillis - StartMillis_Pumpe[x] >= Pumpenlaufzeit[x]) && (Pumpenfolge == x)) //Wenn die Pumpe an ist, die Laufzeit erreicht und an der Reihe
    {
      digitalWrite (R[x], HIGH); // Pumpe ausschalten
      Pumpenstatus[x] = HIGH; // Pumpenstatus anpassen
      Pumpenfolge = x + 1; // Aktiviert die nächste Pumpe und bleibt nach der letzten Pumpe auf ungültigem Wert stehen
    }
  }
  x = (1 + x) % Kreise;

Hiho und guten Abend, vielen Dank für die Wegweiser und “Hausaufgaben”, hab mich mal mit den structs befasst und den code entsprechend umgebaut:

const byte Kreise = 4; // Anzahl der Kreise, Achtung bei Änderung auch die Arrays mit anpassen!

//Strukturdefinition
struct Pumpenkreis_st
{
  const byte Nr; //Pumpennummer
  const byte FL; //Förderleistung des Pumpenkreises bis 255 ml/s
  const byte R; //Belegung der Pins für die Pumpenrelais
  float Menge; // Auszubringende Mengen in ml, hier wird float benötigt um die spätere Division durch FL hinzubekommen
  byte Skip; // Kenner ob und wie viele Gießintervalle ausgelassen werden sollen pro Bereich
  uint32_t StartMillis_Pumpe; //Startpunkte der einzelnen Pumpen
  bool Pumpenstatus; //Status ob Pumpe läuft oder nicht
  uint32_t Pumpenlaufzeit; //Länge der Pumpenlaufzeiten pro Kreis
  byte CurrentSkip; // Zähler für die aktuelle Anzahl der Intervalle die bereits ausgelassen worden sind
  byte Folge;

  void init() //Initialisierung
  {
    pinMode (R, OUTPUT); //Pins für die Relais setzen
    digitalWrite (R, HIGH); //Relais schlaten bei LOW, daher Grundzustand HIGH
    CurrentSkip = Skip; //erste Skipwerte setzen
    Folge = Kreise; //Pumpenfolge auf ungültig setzen
  }

  uint32_t calc() //Berechnet die Pumpenlaufzeiten
  {
    if (CurrentSkip < 1) //Wenn nicht geskipt wird, Berechnung aus Menge und Förderleistung
    {
      Pumpenlaufzeit = Menge / FL * 1000;
      CurrentSkip = Skip; //Rücksetzung der Skipvariable auf Ausgangswert
    }
    else
    {
      Pumpenlaufzeit = 0; // Wenn geskipt wird, dann keine Laufzeit
      CurrentSkip--; //Runterzählen der Skipvariable bis 0
    }
    return Pumpenlaufzeit;
  }
  byte start(uint32_t Now, byte Folge) //Lässt die Pumpen laufen, braucht die aktuelle Zeit und die Pumpenfolge als Parameter
  {
    if ((Pumpenstatus == HIGH) && (Folge == Nr)) //Wenn die Pumpe aus ist und an der Reihe
    {
      digitalWrite (R, LOW); // Pumpe einschalten
      Pumpenstatus = LOW; // Pumpenstatus anpassen
      StartMillis_Pumpe = Now; //Startpumkt der Pumpe setzen
    }
    else if ((Pumpenstatus == LOW) && (Now - StartMillis_Pumpe >= Pumpenlaufzeit) && (Folge == Nr)) //Wenn die Pumpe an ist, die Laufzeit erreicht und an der Reihe
    {
      digitalWrite (R, HIGH); // Pumpe ausschalten
      Pumpenstatus = HIGH; // Pumpenstatus anpassen
      Folge++; // Aktiviert die nächste Pumpe und bleibt nach der letzten Pumpe auf ungültigem Wert stehen
    }
    return Folge;
  }

  void ausgabe() //  Gibt Daten auf dem Serial Monitor aus
  {
    Serial.print("Pumpenlaufzeit ");
    Serial.print(Nr);
    Serial.print(": ");
    Serial.println(Pumpenlaufzeit);
  }

};

//Array für Pumpenkreise deklarieren und befüllen
Pumpenkreis_st Pumpenkreis [Kreise]
{ //Nr, FL, R, Menge, Skip, StartMillis_Pumpe, Pumpenstatus, Pumpenlaufzeit, CurrentSkip
  {0,  20, 2, 55.0,  0,    0,                 HIGH,         0,               0           },
  {1,  20, 3, 25.0,  1,    0,                 HIGH,         0,               0           },
  {2,  20, 4, 30.0,  0,    0,                 HIGH,         0,               0           },
  {3,  20, 5, 50.0,  2,    0,                 HIGH,         0,               0           },
};

float ToFirst_h = 0.005; //Zeit bis Start erste Bewässerung in Stunden, 18s zum Testen

uint32_t CurrentMillis; //Aktuell vergangene Zeit seit Start des Controllers
float Intervall_h; // Länge des Intervalls in dem Wasser ausgebracht werden soll in Stunden
uint32_t StartMillis; //Startpunkt des Intervalls bis zur Bewässerung
uint32_t NextIntervall; //Länge des nächsten Intervalls bis zur Bewässerung

uint32_t Pumpenlaufzeit_ges; //Summe aller Pumpenlaufzeiten + Puffer
byte Pumpenfolge = Kreise; // Zeigt welche Pumpe an der Reihe ist. Mit Setzen auf Kreise erst mal ungültig, damit am Anfang nix läuft.
byte Failcode; // Fehlerkenner für Unterbrechung von loop Teilen und Anzeige

uint32_t Monitorintervall = 3000; //Intervall für Ausgabe auf dem Seriellen Monitor
uint32_t StartMonitor = 0;

void setup()
{
  // Initialwerte für Bewässerung
  Serial.begin(9600);
  Failcode = 0; //Fehlercode 0 = kein Fehler
  Intervall_h = 0.005; //Bewässerung alle 18s für Test - später 24 oder 12

  for (Pumpenkreis_st &Pk : Pumpenkreis) Pk.init(); //Pumpenkreis_st ist die Struktur, Pumpenkreis das Array vom Typ Pumpenkreis_st, Pk ist die einzelne Variable im Array Pumpenkreis vom Typ Pumpenkreis_stt, init ist die Methode die innerhalb Pumpenkreis_st aufgerufen wird

  StartMillis = millis(); // Startzeit setzen
  NextIntervall = ToFirst_h * 3600000; //Erstes Intervall setzen in Millis
}

void loop()
{
  CurrentMillis = millis();

  // Bewässerungsintervall
  if (CurrentMillis - StartMillis >= NextIntervall)
  {
    NextIntervall = Intervall_h * 3600000; //Nächstes Intervall setzen in Millis
    StartMillis = CurrentMillis; //Start zurücksetzen
    Pumpenlaufzeit_ges = 2000; //Puffer und Reset der Gesamtlaufzeit
    for (Pumpenkreis_st &Pk : Pumpenkreis) Pumpenlaufzeit_ges = Pumpenlaufzeit_ges + Pk.calc(); //Aufruf Struktur über for in Kurzform wie im Setup kommentiert, Methode calc gibt hier die einzelnen Pumpenlaufzeiten zurück, Summierung in Gesamtlaufzeit
    if (Pumpenlaufzeit_ges > NextIntervall) Failcode = 1; //Prüfung ob Gesamtlaufzeit Pumpen + Puffer unter Intervall liegt
    if (Failcode == 0) Pumpenfolge = 0; // Setzt die Pumpenfolge auf den ersten gültigen Wert und startet damit die Pumpenschleife
  }

  // Pumpenaktivierung
  for (Pumpenkreis_st &Pk : Pumpenkreis) Pumpenfolge = Pk.start(CurrentMillis, Pumpenfolge); //Aufruf wie oben, Methode start bekommt aktuelle Zeit und Pumpenfolge übergeben, gibt die neue Pumpenfolge zurück

  if (CurrentMillis - StartMonitor >= Monitorintervall) //Ausgabe im Intervall
  {
    for (Pumpenkreis_st &Pk : Pumpenkreis) Pk.ausgabe(); //Ausgabe einiger Strukturwerte
    Serial.print("Gesamtlaufzeit der Pumpen: ");
    Serial.println(Pumpenlaufzeit_ges);
    Serial.print("NextIntervall: ");
    Serial.println(NextIntervall);
    Serial.print("Fehler: ");
    Serial.println(Failcode);
    Serial.print("Pumpenfolge: ");
    Serial.println(Pumpenfolge);
    StartMonitor = CurrentMillis;
  }
}

Macht auch wieder das was er soll, Kritik wie immer erwünscht :slight_smile:

Der Umbau in class steht noch an.

Ich bin mir nicht ganz sicher, wie man das am besten mit den Variablen macht. Hab irgendwie ein ungutes Gefühl aus einer struct/class Methode globale Variablen zu ändern, obwohl das hier und da einiges vereinfachen würde. Aber eigentlich sollte ja so eine class problemlos in andere codes übertragbar sein und ob sie da die gleichen globalen Variablen findet ist ja eher fraglich, also hab ich mal versucht alles zu übergeben. Hab allerdings auch irgendwo gelesen, dass Variablen per Referenz übergeben werden können, muss ich mir mal anschauen.

Die ESP 32 sind heute auch schon gekommen … das steht dann auch noch an, vom Uno da drauf umzuziehen …

Grüssle Olli

Ich bin mir nicht ganz sicher, wie man das am besten mit den Variablen macht. Hab irgendwie ein ungutes Gefühl aus einer struct/class Methode globale Variablen zu ändern, obwohl das hier und da einiges vereinfachen würde. Aber eigentlich sollte ja so eine class problemlos in andere codes übertragbar sein und ob sie da die gleichen globalen Variablen findet ist ja eher fraglich, also hab ich mal versucht alles zu übergeben. Hab allerdings auch irgendwo gelesen, dass Variablen per Referenz übergeben werden können, muss ich mir mal anschauen.

Ja, ich nenne Funktionen, welche auf globale Variablen zugreifen, oder statische Variablen enthalten Wegwerf Funktionen. Oder auch wegwerf Klassen, wenn es darin geschieht.

Weil eben nicht/schlecht wieder verwendbar.

Beispiel: Du hast eine Methode ausgabe() in der Klasse/Struktur erfunden. Gute Idee! Ich peppe die mal jetzt mal etwas auf, so dass sie die globale Variable Serial zur Ausgabe übergeben bekommt. Das ist dann schon mal eine Abhängigkeit weniger. Auch hast du jetzt die Möglichkeit deine Objekte z.B. in Dateien zu drucken. Eben alles, was Print implementiert, nimmt deine Klasse.

class Pumpenkreis_st : public Printable
{
  private:
   const int kreis;

  public: 
    Pumpenkreis_st(const int kreis):kreis(kreis){}

    virtual size_t printTo(Print & print) const override
    {
      size_t len = 0;
      len += print.print("Kreis ");
      len += print.print(kreis);
      return len;
    }

};

Pumpenkreis_st Pumpenkreis[] {1,2,3,4};


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

void loop()
{
  for (Pumpenkreis_st &Pk : Pumpenkreis) Serial.println(Pk); //Ausgabe einiger Strukturwerte
}

olli_h: ...

Ich bin gerade eben erst in diesen Thread gestolpert und habe Deinen Code nur sehr oberflächlich quergelesen. Aufgefallen sind mir dabei zwei Dinge:

  • Ich finde Codes außerordentlich lästig, wenn sie zu breit sind und ich horizontal scrollen muss. Da ich meine Programme zum Lesen und Korrigieren gerne ausdrucke und auf dem Balkon lese, sind 80 Zeichen Breite mein Maximum. Notfalls verteile ich Kommentare halt auf mehrere Zeilen. Siehe z.B. hier, drittes Code-Schnipselchen.
  • Den Körper der for-Schleife solltest Du der besseren Lesbarkeit/Verständlichkeit wegen unbedingt klammern. Ich meine das for() nach dem Kommentar '// Pumpenaktivierung'

Das nur mal auf die Schnelle mein Senf dazu.

Gruß

Gregor

Auch zwei Senfkörnchen von mir: Als fauler Mensch mag ich kreis(X_kreis), da ich mir keine zwei Namen ausdenken muß. Zur Verdeutlichung, daß es sich aber um unterschiedliche Dinge handelt, meine Lernversion:

class Pumpenkreis_st : public Printable
{
  private:
    const int kreis;

  public:
    Pumpenkreis_st(const int X_kreis): kreis(X_kreis) {}

    virtual size_t printTo(Print & print) const override
    {
      size_t len = 0;
      len += print.print("Kreis ");
      len += print.print(kreis);
      return len;
    }

};

Pumpenkreis_st Pumpenkreis[] {1, 2, 3, 4};


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

void loop()
{
  for (Pumpenkreis_st &Pk : Pumpenkreis) Serial.println(Pk); //Ausgabe einiger Strukturwerte
}

X_kreis enthält die Werte {1, 2, 3, 4}, womit die Konstante kreis, bei einer Variablen als Anfangswert, gefüllt wird. Die Methode verwendet dann die Konstante kreis.

Variablen, die für alle Elemente den gleichen Anfangswert bekommen, können ihren Wert auch vom Konstruktor erhalten:

//Strukturdefinition
class Pumpenkreis_st
{
    const byte Nr; //Pumpennummer
    const byte FL; //Förderleistung des Pumpenkreises bis 255 ml/s
    const byte R; //Belegung der Pins für die Pumpenrelais
    float Menge; // Auszubringende Mengen in ml, hier wird float benötigt um die spätere Division durch FL hinzubekommen
    byte Skip; // Kenner ob und wie viele Gießintervalle ausgelassen werden sollen pro Bereich
    uint32_t StartMillis_Pumpe; //Startpunkte der einzelnen Pumpen
    bool Pumpenstatus; //Status ob Pumpe läuft oder nicht
    uint32_t Pumpenlaufzeit; //Länge der Pumpenlaufzeiten pro Kreis
    byte CurrentSkip; // Zähler für die aktuelle Anzahl der Intervalle die bereits ausgelassen worden sind
    byte Folge;

  public:
    Pumpenkreis_st(const byte Nr, const byte FL, const byte R, float Menge, byte Skip):
      Nr(Nr), FL(FL), R(R), Menge(Menge), Skip(Skip), StartMillis_Pumpe(0), Pumpenstatus(HIGH), Pumpenlaufzeit(0), CurrentSkip(0) {}

...

//Array für Pumpenkreise deklarieren und befüllen
Pumpenkreis_st Pumpenkreis [Kreise]
{ //Nr, FL, R, Menge, Skip, StartMillis_Pumpe, Pumpenstatus, Pumpenlaufzeit, CurrentSkip
  {0,  20, 2, 55.0,  0},
  {1,  20, 3, 25.0,  1},
  {2,  20, 4, 30.0,  0},
  {3,  20, 5, 50.0,  2}
};
...

Das Wesentliche an combie’s Beispiel ist die Ermunterung, eigene Klassen als Erweiterung von Printable zu definieren, um Printable::printTo zu überladen.

Setzt natürlich voraus, dass klar ist, was bei
MeineKlasse x = …;
Serial.println(x);
ausgegeben werden sollte.

Wenn man schon dabei ist, extra eine Funktion/Methode ausgabe() zu erfinden …

Karma +