ESP32-WROVER Board

Hallo @all,

bin neu hier und versuche mich an einem Projekt auszutoben, wo ich mir vorgestellt habe, dieses mit einem ESP32-WROVER Board umzusetzen.

Das Projekt heißt bei uns „Aktion Banane" kann man drüber lachen :wink: aber ja....

Es ist eine echte Banane abgeformt worden, diese habe ich dann „holo“ nachgebaut als Silicon Form.
Im Innenleben sind zwei stellmotoren, die leichte Bewegungen auf das Silicon ausüben,
So weit so gut... Mechanisch gesehen;-9

Ich kann über den delay und den Winkel unterschiedliche Bewegungen simulieren.
So weit hat nun mein Anfänger Wissen gereicht...

Was ich nun gerne erweitern würde, sind zwei Sachen.
Einmal dass ich mich über das WLAN-Modul aufschalten kann um die Bewegung ändern zu können.
(Schneller und den Winkel)

Zugleich würde ich gerne über die Kamera sehen, ob sich im inneren noch alles Bewegt.
Das Ganze habe ich versucht, aus verschiedenen Informationen zusammen zu setzen, aber es funktioniert hinten und vorne nicht.

Ist aber eindeutig ein "User" Fehler :wink: Unwissenheit von mir.
Daher die Frage und Bitte, ob mir jemand dabei helfen könnte, das Ganze zu optimieren.

Schonmal herzlichen Dank für die Hilfestellung...

Willkommen im Forum,
ohne Sketsch ist das alles 'Banane', wir können nicht sehen was auf deinem Monitor drauf ist und was auf deinem Tisch sich bewegt, wie man ins Forum den Sketch reinstellt ist hier

Hallo und guten Abend.
Anbei der Code, von den zwei Stellmotoren,
die soweit funktionieren:

#include <ESP32Servo.h>
 
Servo myservo1;       // Servoobjekt-1
Servo myservo2;       // Servoobjekt-2
 
int pos1 = 0;     // vari1 zum Speichern der Pos
int pos2 = 0;     // vari1 zum Speichern der Pos
int laser = 2;    // AZS 2
 
void setup() 
{ 
  myservo1.attach(12);            // Servo1 an pin12
  myservo2.attach(15);            //Servo2 an pin15
  pinMode(laser, OUTPUT);  
  delay(500);
  digitalWrite(laser, HIGH);
} 
 
void loop() 
{ 
  for(pos1 = 0; pos1 < 90; pos1 += 2)      // Aktion Bewegung von 0° tbis 90°
  {                                                                // in einem Step von 2°
    myservo1.write(pos1);                 // Bewegung an die Pos1
    delay(30);                                     // warten 30ms bis position erreicht ist
    myservo2.write(pos1);               // Bewegung an die Pos1              
    delay(15);                                  // warten 15ms bis position erreicht ist
  } 
  for(pos2 = 90; pos2>=1; pos2-=10)     // Aktion Bewegung von 0° tbis 90°
  {                                                              // in einem Step von 10°
    myservo1.write(pos2);              // Bewegung an die Pos2
    delay(30);                                   // warten 30ms bis position erreicht ist
    myservo2.write(pos2);             // Bewegung an die Pos2
    delay(15);                                  // warten 15ms bis position erreicht ist
  } 
}

Jetzt würde ich gerne da dazu, die Kamera einbinden,
was mir aber nicht gelingt.
Sowie das WLAN Modul zum steuern der oben gezeigten Werte.
Mehr code hab ich aktuell leider nicht :-/

Soweit die News von der "Banane"
Dankeschön vorab...

Moin @klimper,

um tatsächlich unterstützen zu können, benötigen wir nähere Angaben zu der von Dir verwendeten Hardware; welches Board benutzt Du denn genau? Ist die Kamera dort integriert?

Wenn das geklärt ist, gibt es wohl noch ein wenig zum Thema "nicht-blockierende Funktionen" zu besprechen ... :wink:

Gruß
ec2021

Moin, Moin, es ist ein ESP32-WROVER Board von "FREENOVE" mit integrierter Kamera.
Anbei ein Bild zu klärung der genauen Situation:

Dann bin ich schon mal gespannt über die "nicht-blockierende Funktionen" zu diskutieren.
Besten Dank schon mal
und ein guten Start in den neuen Tag :wink:

Ok, dann findet man Anleitungen und Beispiele in den Sprachen C++ und Python unter dem Link:

https://github.com/Freenove/Freenove_ESP32_WROVER_Board

Das Video-Server-Beispiel ist dort unter

https://github.com/Freenove/Freenove_ESP32_WROVER_Board/blob/main/C/Sketches/Sketch_05.2_As_VideoWebServer/Sketch_05.2_As_VideoWebServer.ino

abgelegt. Versuche das Beispiel mal zum Laufen zu bringen. Das wäre der eine Schritt! Die Kamera ist allem Anschein nach Interrupt-getrieben eingebunden. Das heißt, dass sie in der loop() nicht weiter berücksichtigt werden muss, wenn sie einmal läuft.

Das Thema Nicht-blockierende Programmierung ist hier im Forum ein Klassiker ... Es geht dabei darum, den Prozessor möglichst nicht durch Wartezeiten auf "langsame" Vorgänge lahmzulegen, sondern seine Möglichkeit, schnell reagieren zu können, bestmöglich auszunutzen.

Hier mal ein Flussdiagramm zur Verdeutlichung:

Aufgabe 1 blockiert die loop() für 15 Sekunden, Aufgabe 2 für 25 Sekunden und Aufgabe 3 für 20 Sekunden. Wann - nach dem Start der loop() - kommt die Aufgabe 1 wieder dran? 60 Sekunden nach ihrem letzten Start; bzw. 45 Sekunden nachdem sie beendet wurde. Jede Aufgabe "blockiert" den Prozessor solange, wie sie selbst insgesamt benötigt.

Das kann so gewollt sein, ist aber in vielen Fällen ungewünscht. Das ist i.d.R. der Fall, wenn die Aufgaben voneinander unabhängig sind oder gerade voneinander abhängen: Wenn ein Robotfahrzeug z.B. per Ultraschall auf Hindernisse prüft, möchte man nicht erst viele Sekunden später bremsen ...

Hierfür bietet es sich an, die Aufgaben in Teilaufgaben zu zerlegen, die - gemessen an den übrigen Abläufen - schnell abgearbeitet werden können. Diese Teilabläufe werden reihum in passenden Zeitabständen bedient, bis sie erledigt sind:

  1. Jede (Teil-)Aufgabe belegt den Prozessor nur solange, wie unbedingt erforderlich.
  2. (Teil-)Aufgaben, deren Zeitintervall noch nicht abgelaufen ist, belasten den Prozessor nur wenige Zyklen (soviel wie für die Prüfung der abgelaufenen Zeit benötigt wird).

Das typische Beispiel nennt sich "Blink without Delay"; hier gibt es eine nette Erläuterung dazu:

https://forum.arduino.cc/t/blinkwithoutdelay-die-nachtwachtererklarung/407567

Gruß
ec2021

Hallo ec2021,

erstmal recht herzlichen Dank für die ganzen Infos.
Da hab ich mal ein Input zu verarbeiten... Halleluja..
Das Prinzip hab ich verstanden, doch ich sehe das noch nicht in meinem Programm...
Ich hau mir das aber alles mal rein...

Besten Dank mal….

Prima, es kommt immer darauf an, ob nur die gerade aktuelle Lösung von Bedeutung ist, oder es darum geht, den Umgang mit Mikroprozessoren zu erlernen ...

Wenn Du auch am Lernen interessiert bist, dann ist das auf jeden Fall interessant und hilfreich für zukünftige Erweiterungen und Projekte!

Gruß
ec2021

Ist beides...
Lernen und Projekt...
Lernen am Aktiven Projekt, bringt immer etwas mehr Druck :wink:
Denn man(n) sollte liefern....
Denke ich hab bestimmt noch die eine oder andere Frage.... :wink:
Kommt bestimmt...

Gruß...

Es gibt häufig mindestens so viele Lösungen, wie es Entwickler gibt, daher ist das Folgende auch nur eine von vielen Möglichkeiten, an die Aufgabe heranzugehen :wink:

Anbei mal ein Lösungsansatz, der mehrere Anteile aufzeigt:

  • blockierungsfreie Ansteuerung von Servos über millis() (Zeitsteuerung)
  • Verwendung von Strukturen, um die zu einem Objekt gehörenden Daten übersichtlich zusammenzufassen
  • Verwendung der Übergabe der Adresse(!) einer Strukturvariablen (dazu dient das & bei "servoType &S", damit man eine Funktion nur einmal schreiben, aber für mehrere Objekte/Strukturen parallel nutzen kann, ohne dass sich die Variablen in die Quere kommen
  • Verwendung einer State Machine, um Abläufe sauber zu strukturieren

Hier der Sketch:

#include <ESP32Servo.h>


struct servoType {
   int actPos;
   int stepUp;
   int stepDown;
   unsigned long lastMove;
   unsigned long waitTime;
   boolean GoUp;
   Servo servo;
};
 
void Move(servoType &s, int targetPos){
  unsigned long currentMillis = millis();
   if (currentMillis - s.lastMove > s.waitTime){
      if (s.actPos < targetPos){
          s.lastMove = currentMillis;
          s.actPos += s.stepUp;
          if (s.actPos >= targetPos){
            s.actPos = targetPos; 
            s.GoUp = false;
          } 
          s.servo.write(s.actPos);
      } else 
        if (s.actPos > targetPos){
          s.lastMove = currentMillis;
          s.actPos -= s.stepDown;
          if (s.actPos <= targetPos){ 
            s.actPos = targetPos;
            s.GoUp = true;
           }
          s.servo.write(s.actPos);
        }
   }
}


enum States {WAIT_UP, WAIT_DW,GO_UP, GO_DOWN};

const int UpPos   = 90;
const int DownPos =  0;


servoType myservo1 = {0,2,10,0,45, true};       // 1. Servo
servoType myservo2 = {0,2,10,0,45, true};;       // 2. Servo
int laser = 2;    // AZS 2
int state = GO_UP;

void StateMachine(){
  static unsigned long StartWait = 0;
  static int WaitTime = 0;
   switch(state) {
     case WAIT_UP : if(millis()-StartWait > WaitTime) state = GO_UP;
                    break;
     case WAIT_DW : if(millis()-StartWait > WaitTime) state = GO_DOWN;
                    break;
     case GO_UP   : Move(myservo1,UpPos);
                    Move(myservo2,UpPos);
                    if (myservo2.actPos >= UpPos) {
                        state = WAIT_DW;
                        WaitTime = 1000;
                        StartWait = millis();
                    }    
                    break;
     case GO_DOWN : Move(myservo1,DownPos);
                    Move(myservo2,DownPos);
                    if (myservo2.actPos <= DownPos) {
                        state = WAIT_UP;
                        WaitTime = 2000;
                        StartWait = millis();
                    }
                    break;
     default      : break;               
   }

}


void setup() 
{ 
  myservo1.servo.attach(12);            // Servo1 an pin12
  myservo2.servo.attach(15);            //Servo2 an pin15
  myservo1.servo.write(0);
  myservo2.servo.write(0);
  state = GO_UP;
  pinMode(laser, OUTPUT);  
  delay(500);
  digitalWrite(laser, HIGH);
} 
 

void loop() 
{ 
  StateMachine();
}

Du kannst ihn bei Wokwi ausprobieren:

https://wokwi.com/projects/358727682612531201

Ein grundsätzlicher Unterschied zum Ablauf in Deinem Sketch besteht allerdings; die neuen Position myservo2 werden bei Deinem Sketch mit einem Versatz von 30 ms zum Setzen von myservo1 gesetzt. Bei diesem Sketch oben ohne wesentlichen Versatz. Wenn das eine Rolle spielt, muss man das Setzen von myservo2 mit myservo1 synchronisieren ... :wink:

P.S.: In der State Machine sind zwischen den beiden Bewegungsrichtungen der Servos Wartezeiten eingebaut, um das Prinzip zu verdeutlichen. Die kann man herausnehmen oder die Zeiten anpassen. An besten ein wenig damit herumspielen...

Gruß
ec2021

Hoi "ec2021"
erstmal herzlichen Dank, dass Du Dir das mit dem Servo nochmal angeschaut hast.
Das sieht nun ganz anders aus, wie bei mir.
Alter Verwalter :-/ Brrrrrr
Bin da im Moment gerade etwas raus.....
Schau ich mir gerne genauer an...

Dankeschön :wink:

Ist sicher erstmal ziemlich schwer zu verdauen, es lohnt sich aber, sich da durch zubeißen...

Daher vielleicht zunächst die o.a. Links lesen.

Gruß
ec2021

Aber so was von...
Bin etwas verwirrt :slightly_frowning_face:
Aber ich beise mal

Gruß und Danke,

Das ist schon so, wenn man sich erstmals und mit wenigen oder keinen Vorkenntnissen in die Welt der Mikroprozessoren "hineinwagt". Wenn Du etwas mehr willst, als nur vorhandenen Code zusammenzubasteln, bis das gewünschte Ergebnis mehr oder weniger herauskommt, braucht es einen gewissen Zeit- (und Denk-)aufwand :wink: Wenn man dann stecken bleibt, kann i.d.R. dieses Forum weiterhelfen.

Was sich für den Anfang lohnt, ist es, sich diese Pdf

https://www.arduinoforum.de/referenz.php

herunterzuladen und ganz in Ruhe von vorne nach hinten durchzulesen und Stück für Stück auch auszuprobieren! Lernerfolg stellt sich dann ein, wenn man den Ablauf eines Sketches noch nachvollziehen kann. Wer gleich mit einem komplexen Programm beginnt, hat es wesentlich schwerer Fehler und Missverständnisse aufzudecken, besonders dann, wenn sich mehrere Fehler addieren oder gegenseitig verdecken ...

Hier mal ein einfacher Sketch zum Nachvollziehen der Anwendung von millis():

/*
  Ein intensiv dokumentierte Einführungssketch in die Verwendung von millis() zum zeitgesteuerten Schalten
  einer LED
von ec2021 für https://forum.arduino.cc/t/esp32-wrover-board/1099241/15

  Ziel: Die Schaltung und der Sketch sollen es ermöglichen, eine LED zeitgesteuert blinken zu lassen, 
        ohne die Abläufe im Hauptprogramm (der loop()) zu blockieren
        
        Ein- und Ausschaltzeit sind identisch


  Hardware:
    Arduino UNO                kann aber auch ein weitgehend beliebiges anderes Board sein
                              ggf. wäre dann der Digitalausgang für die Led-Ansteuerung anzupassen

    Led                       Farbe beliebig, für das Beispiel wurde grün gewählt

    Vorwiderstand             Dient der Strombegrenzung am Arduino-Pin, da dort i.d.R. maximal 40 mA
                              verfügbar sind. Kann entfallen bei Led mit integrierten Vorwiderstand
*/



const byte LEDGRUEN = 6;       // Der Anschlusspin der grünen LED wird als const byte definiert,
                               // damit der Compiler einen Fehler auswirft, wenn versucht wird, diesen Wert
                               // im Programm zu ändern. Das wäre fatal, da dann alle Aktionen, die den Pin
                               // LEDGRUEN ansteuern sollten, dann auf irgendeinen anderen Pin wirken würden.
                               //
                               // Das muss man nicht so machen, aber es vermeidet im Falle "menschlichen Versagens"
                               // bei größeren, weniger übersichtlichen Sketchen mühsame Fehlersuche ...

void setup() {                 // Im sogenannten setup() werden diejenigen Funktionen eingetragen,
                               // die bei jedem Neustart des Prozessors einmal erfolgen müssen,
                               // damit eine definierte Ausgangslage für die dann folgenden Abläufe 
                               // vorliegt; i.d.R. werden hier die Modi der Ein-/Ausgabe-Pins gesetzt,
                               // die serielle Schnittstelle, Wifi- oder Grafik/LCD-Boards initialisiert 
                               // (soweit vorhanden und benötigt)
                               //
  pinMode(LEDGRUEN,OUTPUT);    // Hier wird der mit LEDGRUEN bezeichnete Pin als Output gesetzt
}

                               // Die folgenden Variablen werden in diesem Fall nur in der loop() benötigt.
                               // Daher kann ihre Deklaration auch nach setup() erfolgen. Werden sie bereits
                               // im setup() verwendet, müssen diese Zeilen nach oben vor setup() verschoben
                               // werden. 
                               //
unsigned long currentMillis;  // Diese Variable dient der Speicherung des Wertes der Funktion millis() 
                              // Die Funktion millis() zählt die Millisekunden seit dem Start des
                              // Prozessors; allerdings steckt dahinter keine Echtzeituhr, sondern tatsächlich
                              // nur ein Zähler, dessen Wert jede Tausendstel Sekunde um 1 erhöht wird.
                              //                              
unsigned long zuletztGruen = 0; // Diese Variable dient der Speicherung eines bestimmten Zeitpunkt;
                              // die Variable zuletztGruen wird definiert mit dem Wert 0 (Null) initialisiert.
                              // Damit wird sichergestellt, dass bei der allerersten Verwendung 
                              // die Differenz "currentMillis - zuletztGruen" gleich dem Werte von currentMillis
                              // ist.  
                              // 
int WarteZeit = 1000;         // Hier wird die Zeit, nach der jeweils geschaltet werden soll, vorgegeben.
                              // Wäre sie immer fest, würde man hier auch ein const davor schreiben können,
                              // aber vielleicht möchte man diesen Wert ja auch einmal programmgesteuert
                              // ändern können ...                     

void loop() {
    currentMillis = millis();                        // Hier wird die aktuelle Zeit seit Einschalten des 
                                                     // zwischengespeichert.
    if (currentMillis - zuletztGruen >= WarteZeit){  // Es wird geprüft, ob seit dem letzten Schaltvorgang
                                                     // der zum Zeitpunkt zuletztGruen stattfand, mindestens
                                                     // die WarteZeit vergangen ist.
                                                     //
                                                     // Solange das nicht der Fall ist, kann sich der
                                                     // Prozessor um andere Dinge kümmern ... sobald wir
                                                     // in der loop() Weiteres eingebaut haben
                                                     // hier dreht er solange (allerdings ziemlich schnell)
                                                     // "Däumchen" ... Aber das kann sich in Zukunft ja noch
                                                     // ändern.
                                                     //
      digitalWrite(LEDGRUEN,!digitalRead(LEDGRUEN)); // Mit digitalRead(LEDGRUEN) wird ermittelt, ob der Pin
                                                     // momentan auf LOW oder HIGH liegt. Das Ausrufezeichen
                                                     // vor dem digitalRead() invertiert das Ergebnis; aus
                                                     // LOW wird alse HIGH, aus HIGH wird LOW.
                                                     // digitalWrite(LEDGRUEN,...) schalten damit den Pin 
                                                     // von LOW auf HIGH oder umgekehrt, und damit
                                                     // die LED  im Wechsel ein und aus.
                                                     //
      zuletztGruen = currentMillis;                  // Hier halten wir den Zeitpunkt dieses Umschaltens fest
                                                     // indem wir den in currentMillis gespeicherten Wert
                                                     // der Variablen zuletztGruen zuweisen.
    }                                                
                                                     // hier endet die "Runde" durch loop() und es 
                                                     // geht oben mit "currentMillis = millis();" weiter
}

image

siehe hier https://wokwi.com/projects/358810974590930945

Gerne mal ausprobieren, nachvollziehen und z.B. um eine oder zwei LED erweitern ...

Gruß
ec2021

Besten Dank!
Das *.pdf ist ja Mega... Hab mal kurz rein geschaut.
Da hab ich ja was zum lesen :wink:
Versuch Parallel an meiner "Herausforderung" zu arbeiten :wink:
Im Moment, für mich unlösbar....
Aber ich beiße mal weiter...

Danke!

Da die Kamera im Videoserver-Betrieb per Interrupt zu arbeiten scheint, kannst Du vermutlich sogar Deinen Ursprungssketch verwenden. Der ist nur schwer zu erweitern, wenn Du z.B. noch Tastenabfragen integrieren möchtest ...

Hast Du schon mal die drei Files von

https://github.com/Freenove/Freenove_ESP32_WROVER_Board/tree/main/C/Sketches/Sketch_05.2_As_VideoWebServer

in ein Verzeichnis mit dem Namen "...\Sketch_05.2_As_VideoWebServer\" kopiert, das .ino-File geladen und dann zu kompilieren versucht?

Das sollte dann weitgehend so aussehen:

Vor dem Kompilieren musst Du noch im ino-File Deine WLAN-Daten eingeben

const char* ssid     = "********";   //input your wifi name
const char* password = "********";   //input your wifi passwords

und dann das Programm auf Deinen ESP32 laden. Wenn alles klappt, sollte auf der seriellen Schnittstelle eine Ausgabe der Art:

Camera Ready! Use http://xxx.xxx.xxx.xxx to connect

zu lesen sein. Damit sollte man dann die Videobilder vom ESP32 in einem Browser auf PC, Tablet oder Smartphone ansehen können, Voraussetzung: Das Empfangsgerät muss im gleichen WLAN-Netzwerk eingeloggt sein ...

Bei mir kompiliert das Ganze problemlos, kann es aber nicht testen, da ich dieses Board nicht besitze ...

Gruß
ec2021