Go Down

Topic: Einsteigerfragen: Kann mein Code optimiert werden? Welches Board für Wifi?  (Read 505 times) previous topic - next topic

olli_h

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:

Code: [Select]
// 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 :D
- 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

HotSystems

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.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)


HotSystems

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.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

noiasca

Quote
Daher wäre mein erstes Anliegen mal über mein Werk drüber zu schauen:
löse die delay() Blockaden ab. Meide lange delays. Nutze das System von "BlinkWithoutDelay" Beispiel.

Quote
aber das hat auf den ersten Blick nur einen analog Eingang,
das stimmt, aber du kannst weitere Analogsensoren an den NodeMCU einfach mittels I2C AnalogDigital Wandler dranhängen.
Damit kommst du dann auf "wesentlich mehr" als dir ein Uno zur Verfügung stellen kann.



how to react on postings:
- post helped: provide your final sketch, say thank you & give karma.
- post not understood: Ask as long as you understand the post
- post is off topic (or you think it is): Stay to your topic. Ask again.
- else: Ask again.

StefanL38

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

agmue

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!

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:
Code: [Select]
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.

Code: [Select]
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

Code: [Select]
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.

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!

Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

olli_h

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  :)

Hier der überarbeitete Code:
Code: [Select]
// 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


combie

Quote
Kann mein Code optimiert werden?
Ich sehe bei dir einige Arrays, welche gleich lang sein MÜSSEN.

Quote
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:

Code: [Select]


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


Man sollte keine Dummheit zweimal begehen, die Auswahl ist schließlich groß genug.
Quelle: Jean-Paul Sartre

HotSystems

Quote
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.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

combie

Man sollte keine Dummheit zweimal begehen, die Auswahl ist schließlich groß genug.
Quelle: Jean-Paul Sartre

HotSystems

Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

olli_h

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?  :smiley-confuse:

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

HotSystems

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.
Gruß Dieter

I2C = weniger ist mehr: weniger Kabel, mehr Probleme. 8)

agmue

Die Struktur bildenden Maßnahmen werde ich einleiten.
Das Beispiel Wieder bewässerung hast Du sicherlich schon gefunden.

Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

Go Up