Fernsteuerung für den omnidirektionalen Roboter

Im heutigen Blogbeitrag wollen wir einen ferngesteuerten Erkundungsroboter aus dem Acebott Smart Car Kit bauen. Der Roboter soll über einen Joystick gesteuert werden und auf einem kleinen Display seine Umgebung in Radar-Optik darstellen.
Zum Scannen der Umgebung wird der Ultraschallsensor verwendet, welcher bereits mit einer Acrylhalterung auf einem Servomotor montiert ist.

Für die Steuerung wird der Aufbau aus PS2-Joystick Shield und ESP32 D1 R32 verwendet, welchen Sie vielleicht bereits aus den Blogbeiträgen zur Steuerung des Bionic Spider Roboters kennen.

Hardware

Falls Sie die Modifikationen am PS2 Joystick Shield bereits in einem vorherigen Blog aus der Reihe Bionic Spider Controller durchgeführt haben, können Sie den folgenden Absatz überspringen.

 

Für die realisierung des Projekts benötigen Sie:

ESP32 NodeMCU D1 R32

PS2 Joystick Shield

(optional) 9V Blockbatterie + Batterieclip

und natürlich ein Zusammengebautes Acebott Roboterauto

 

Da die Analogeingänge der zwei Joystick Achsen an den GPIO 2 und 4 anliegen, welche mit dem ADC2 verbunden sind, können diese nicht gleichzeitig mit dem WLAN Modul verwendet werden. Die Anschlüsse IO 34 und IO35 (ADC1) liegen aber direkt neben diesen, dadurch kann ganz einfach die Funktionsfähigkeit über eine Drahtbrücke hergestellt werden.

Abbildung 1: Drahtbrücke zwischen den Anschlüssen

Verbinden Sie die Anschlüsse wie oben abgebildet.
Da am GPIO 2 die On-Board LED angeschlossen ist, muss hier der Pin entfernt werden, da die Verbindung ansonsten den Wert der X-Achsen Spannung beeinflussen würde.

Das gleiche gilt für den Pin D12, da dieser den Boot Vorgang stört und somit kein boot mit aufgestecktem Shield möglich wäre. An diesem Anschluss liegt der mittlere Taster des Joysticks, dieser ist dadurch nicht mehr benutzbar. Falls sie diesen trotzdem implementieren wollen, muss dieser wieder mit einem freien Anschluss verbunden werden.

 

Abbildung 2: Shield mit Modifikationen

 

Das Shield kann einfach auf den D1 R32 gesteckt werden. Achten Sie darauf, dass der kleine Schalter in der linken Ecke auf 3v3 gestellt ist, da der ESP32 sonst durch die zu hohe Spannung von 5V beschädigt werden könnte.

 

 

Display Erweiterung

Da der Controller die Umgebung des Fahrzeugs in Form eines Radarbildschirms, wie man Ihn aus vielen Filmen kennt, darstellen soll, wird ein Display benötigt.

Hier empfiehlt sich das 1,3” OLED grafikdisplay (SH1106). Dieses ist nicht nur ein großes, gut leserliches Grafikdisplay, sondern hat auch das passende Layout für die Buchsenleiste des HC05 Moduls auf dem Shield (oben rechts). So kann das Display ohne große Modifikationen eingesteckt werden.

Lediglich der obere SMD Widerstand neben dem Button A muss entfernt werden, da dieser ursprünglich für das halbieren der Spannung am RXD Pin des HC05 konzipiert war, damit das Modul beim Betrieb mit dem UNO Mikrocontroller nicht beschädigt wird.

 

Bild Modifikation

 

Da der verwendete Mikrocontroller (ESP32) ein Logiklevel von 3.3V hat, würde mit der halbierten Spannung die Kommunikation mit dem Display nicht mehr funktionieren.

 

Software

Für die Kommunikation werden die Datenpakete mittels UDP ausgetauscht. Bei diesem Kommunikationsprotokoll werden die Pakete einfach gesendet, ohne nachzuvollziehen, ob das Paket empfangen wurde.

Dadurch ist der Datenaustausch zwar sehr schnell, aber auch unsicher, da nicht festgestellt wird, ob der Empfänger die Daten tatsächlich empfängt. Durch die Kommunikation über ein Netzwerk ist auch eine höhere Reichweite im Vergleich zu ESPnow oder Bluetooth möglich, da das Netz mit Repeatern und Accesspoints einen großen Bereich abdecken kann.
Grundsätzlich kann so an jeder Stelle, an der Sie mit Ihrem Smartphone empfang haben die beiden Geräte miteinander kommunizieren.

 

Die Bibliothek für die UDP Kommunikation ist bereits im umfangreichen Boardpaket für den ESP32 enthalten.

 

Neben der UDP und WiFi Bibliothek werden noch weitere externe Libraries benötigt, welche noch von Ihnen installiert werden müssen.

U8g2

ArduinoJson

Diese können Sie über die folgenden Links von GitHub als .zip heruntergeladen werden und in der Arduino IDE unter

Sketch > include Library > Add .zip Library …

ausgewählt und installiert werden.

 

Die ESP32Servo, ultrasonic und vehicle Bibliotheken sind bereits in der .zip Datei der Anleitung enthalten. Diese sind auch schon in der Anleitung für Fortgeschrittene installiert worden.

 

Code smart car

#include <vehicle.h>
#
include <ultrasonic.h>
#
include <ESP32Servo.h>
#
include <ArduinoJson.h>
#
include <WiFi.h>
#
include <WiFiUdp.h>

ultrasonic myUltrasonic;
vehicle myCar;
Servo myServo;
JsonDocument doc;
WiFiUDP udp;

const char* ssid = "xxx";
const char* password = "xxx";
String cmd;
String msg;
int v = 255; //speed

void setup() {
 
Serial.begin(115200);
 
WiFi.begin(ssid, password);
 
while (WiFi.status() != WL_CONNECTED) {
   
delay(500);
   
Serial.print(".");
   
if(millis() > 30000) ESP.restart();
  }

  udp.
begin(4210);

 
Serial.println("ESP8266 ready (Reciever)");
 
Serial.print("IP:");
 
Serial.println(WiFi.localIP());
  myCar.Init();
//Initialize all motors
  myUltrasonic.Init(
13,14);
  myServo.
attach(25);//initialize
}

void loop() {
 
//Serial.print("IP:");
 
//Serial.println(WiFi.localIP());

 
int pSize = udp.parsePacket();
 
if (pSize) {
   
char buf[20];
   
int len = udp.read(buf, 20);
    buf[len] =
0;

    cmd =
String(buf);
   
Serial.println(cmd);
  }

 
if(cmd.length() > 0) {
   
if(cmd.indexOf("CMD_FWD") >= 0) {
     
Serial.println("FWD");
      myCar.Move(Forward, v);
    }
   
else if(cmd.indexOf("CMD_BWD") >= 0) {
     
Serial.println("BWD");
      myCar.Move(Backward, v);
    }
   
else if(cmd.indexOf("CMD_RGT") >= 0) {
     
Serial.println("RGT");
      myCar.Move(Move_Right, v);
    }
   
else if(cmd.indexOf("CMD_LFT") >= 0) {
     
Serial.println("LFT");
      myCar.Move(Move_Left, v);
    }
   
else if(cmd.indexOf("CMD_STOP") >= 0) {
     
Serial.println("Stop");
      myCar.Move(Stop,
0);
    }
   
else if(cmd.indexOf("BTN_A") >= 0) {
     
Serial.println("A");
     
int j = 0;
     
for(int i=30; i<=150; i+=5) {
        myServo.
write(i);
       
Serial.println(myUltrasonic.Ranging());
        doc[
"vals"][j]["angle"] = i + 180;
        doc[
"vals"][j]["dist"] = myUltrasonic.Ranging();
       
delay(150);
        j++;
      }
      doc[
"count"] = j;
      serializeJson(doc, msg);
     
Serial.println(msg);
      udp.
beginPacket(udp.remoteIP(), udp.remotePort());
      udp.
print(msg);
      udp.
endPacket();
      myServo.
write(90);
    }
   
else if(cmd.indexOf("BTN_B") >= 0) {
     
Serial.println("B");
    }
   
else if(cmd.indexOf("BTN_C") >= 0) {
     
Serial.println("C");
    }
   
else if(cmd.indexOf("BTN_D") >= 0) {
     
Serial.println("D");
    }
   
else if(cmd.indexOf("BTN_E") >= 0) {
     
Serial.println("E");
      myCar.Move(Clockwise,v);
    }
   
else if(cmd.indexOf("BTN_F") >= 0) {
     
Serial.println("F");
      myCar.Move(Contrarotate,v);
    }

    cmd =
"";
   
// send ready to controller to recieve new commands
    udp.
beginPacket(udp.remoteIP(), udp.remotePort());
    udp.
print("RDY");
    udp.
endPacket();
   
Serial.println("ACK gesendet");
  }
}

Erklärung:

Beim Start verbindet sich der ESP32 mit dem WLAN, öffnet einen UDP-Port und initialisiert Motoren, Ultraschallsensor und Servo. In der Hauptschleife wartet er auf eingehende UDP-Befehle und wertet diese als Textkommandos aus. Je nach Befehl werden die Motoren gesteuert oder Sonderfunktionen ausgeführt.

Beim Kommando BTN_A schwenkt der Servo den Ultraschallsensor schrittweise, misst bei jeder Position den Abstand und speichert Winkel und Distanz in einem JSON-Array. Nach Abschluss des Scans wird das JSON serialisiert und per UDP an den Absender zurückgesendet, anschließend fährt das Servo in die Mittelstellung zurück.

Nach jeder ausgeführten Aktion sendet der ESP32 die Rückmeldung „RDY“, um zu signalisieren, dass er bereit für den nächsten Befehl ist.

 

Den Code können Sie hier herunterladen.

Nachdem Sie das Programm mit korrekten WLAN-Zugangsdaten auf den Mikrocontroller geladen haben, wird Ihnen die IP-Adresse des Geräts angezeigt. Notieren Sie diese, da sie vom Controller benötigt wird.

Code controller

#include <math.h>
#
include <Arduino.h>
#
include <U8g2lib.h>
#
include <Wire.h>
#
include <WiFi.h>
#
include <esp_wifi.h>
#
include <WiFiUdp.h>
#
include <ArduinoJson.h>

#
define X 34
#
define Y 35

#
define A 26
#
define B 25
#
define C 17
#
define D 16
#
define E 27
#
define F 14

const char* ssid = "xxx";
const char* password = "xxx";
const char* rcvIP = "192.168.178.xxx";


int calibY, calibX;
bool status = true;
long sentTime;
String msg;

WiFiUDP udp;
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE,
3, 1);

JsonDocument doc;

void drawLineAngle(float angleDeg, float length) {
 
float angleRad = angleDeg * DEG_TO_RAD;

 
int x1 = 64 - cos(angleRad) * length;
 
int y1 = 63 + sin(angleRad) * length;

  u8g2.drawLine(
64, 63, x1, y1);
}

void setup() {
 
Serial.begin(115200);
 
delay(2000);

 
WiFi.begin(ssid, password);
 
while (WiFi.status() != WL_CONNECTED) {
   
delay(500);
   
Serial.print(".");
   
if(millis() > 7000) ESP.restart();
  }

  u8g2.
begin();
  u8g2.clearBuffer();

 
Serial.println("ESP32 ready (Controller)");

  calibX =
analogRead(X); //calibrating current Position to zero
  calibY =
analogRead(Y);

 
pinMode(A, INPUT_PULLUP);
 
pinMode(B, INPUT_PULLUP);
 
pinMode(C, INPUT_PULLUP);
 
pinMode(D, INPUT_PULLUP);
 
pinMode(E, INPUT_PULLUP);
 
pinMode(F, INPUT_PULLUP);
}

void loop() {
 
int valX = analogRead(X) - calibX;
 
int valY = analogRead(Y) - calibY;
 
//Serial.println(valX);
 
//Serial.println(valY);

//no acknowledgement recieved after 10 sec. -> set marker to true; reenable sending
 
if((millis() - sentTime) > 10000) {
    status = true;
  }
 
if(status) {
   
//threshold values
   
if(valX < -50 || valY < -50 || valX > 50 || valY > 50) {
     
Serial.println("trig");
     
//choose dominant coordinate
     
if(abs(valX) > abs(valY)) {
       
if(valX < 0) {
         
Serial.println("LEFT");
          msg =
"CMD_LFT";
        }
       
else {
         
Serial.println("Right");
          msg =
"CMD_RGT";
        }
      }
     
else {
       
if(valY < 0) {
         
Serial.println("BWD");
          msg =
"CMD_BWD";
        }
       
else {
         
Serial.println("FWD");
          msg =
"CMD_FWD";
        }
      }
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
else {
      msg =
"CMD_STOP";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }

   
if(!digitalRead(A)) {
     
Serial.println("A");
      msg =
"BTN_A";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation +3000
    }
   
else if(!digitalRead(B)) {
     
Serial.println("B");
      msg =
"BTN_B";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
else if(!digitalRead(C)) {
     
Serial.println("C");
      msg =
"BTN_C";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
else if(!digitalRead(D)) {
     
Serial.println("D");
      msg =
"BTN_D";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
else if(!digitalRead(E)) {
     
Serial.println("E");
      msg =
"BTN_E";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
else if(!digitalRead(F)) {
     
Serial.println("F");
      msg =
"BTN_F";
      status = false;
//marking sent
      sentTime =
millis(); //store time for limitation
    }
   
    udp.
beginPacket(rcvIP, 4210);
    udp.
print(msg);
    udp.
endPacket();
    msg =
"";
  }
 
 
int pSize = udp.parsePacket();
 
if (pSize) {
   
char buf[pSize];
   
int len = udp.read(buf, pSize);
    buf[len] =
0; //termination

   
if (String(buf) == "RDY") {
      status = true;
     
Serial.println("ACK");
     
    }
   
else {
     
Serial.println(buf);
      deserializeJson(doc, buf);
     
float l;
      u8g2.clearBuffer();
     
for(int i = 0; i<doc["count"]; i++) {
        l = doc[
"vals"][i]["dist"];
       
if(l>60) l = 60;
        drawLineAngle(doc[
"vals"][i]["angle"] , l);
      }
      u8g2.sendBuffer();
    }
  }
 
delay(100);
}

Erklärung:

Nach dem Start verbindet sich der ESP32 mit dem WLAN, initialisiert das OLED-Display, kalibriert den Joystick auf seine Mittelstellung und setzt die benötigten GPIOs für die Taster. In der Hauptschleife werden kontinuierlich die Joystick-Achsen ausgewertet. Überschreiten sie einen Schwellwert, wird daraus eine Fahrtrichtung bestimmt und als Kommando (z. B. Vorwärts, Rückwärts, Links, Rechts), bzw. Stop wenn sich der Joystick in der Mittelposition befindet, per UDP gesendet. Zusätzlich können über die Tasten A–F spezielle Befehle ausgelöst werden. Um Mehrfachsendungen zu vermeiden, wird jedes Kommando erst dann erneut gesendet, wenn eine Bestätigung oder ein Timeout erfolgt ist.

 

Eingehende UDP-Pakete werden ebenfalls ausgewertet. Wird die Rückmeldung „RDY“ empfangen, signalisiert dies, dass der Empfänger bereit für den nächsten Befehl ist. Enthält das Paket stattdessen ein JSON-Objekt mit Messdaten, werden diese geparst und als Linienanzeige auf dem OLED dargestellt. Dabei repräsentieren Winkel und Distanz der Messwerte Linien, die vom Mittelpunkt des Displays ausgehen. So entsteht eine einfache grafische Darstellung der vom anderen ESP32 erfassten Umgebung.

 

Die Linien werden mit der drawLineAngle() Funktion erzeugt. Hier werden die Endkoordinaten der Linie mit dem Sinus und Cosinus berechnet. Eine detaillierte Erklärung finden Sie im Blog Retro Uhr mit GC9A01A.

 

Den Code können Sie hier herunterladen.

Laden Sie das Programm mit den korrekten Zugangsdaten und IP-Adresse des Empfängers auf den Mikrocontroller.

 

Fazit

Wenn Sie nun auf den Button A des Controllers drücken, wird Ihnen auf dem Display das Umfeld in den nächsten 60cm grafisch als Linie für jede Messung dargestellt. Hier entspricht ein Pixel etwa einem Zentimeter. Falls Sie einen weiteren Darstellungsradius wünschen, können Sie das Controller Programm entsprechend anpassen, wobei Sie aber die maximale Länge von 60 Pixeln beachten sollten.

Natürlich legt dieses Projekt nur die Grundlagen für Ihre eigenen Erweiterungen und Modifikationen. Neben dem Scannen der Umgebung können auch noch weitere Funktionen hinzugefügt werden, hier stehen Ihnen die unbelegten Buttons auf dem Controller Shield zur Verfügung.

Viel Spaß beim Nachbauen :)

Esp32Esp8266Projekte für anfänger

Kommentar hinterlassen

Alle Kommentare werden von einem Moderator vor der Veröffentlichung überprüft

Empfohlene Blogbeiträge

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery