Fernsteuerung für den omnidirektionalen Roboter - AZ-Delivery

Nel post di oggi vogliamo costruire un robot da esplorazione telecomandato con il kit Smart Car di Acebott. Il robot deve essere controllato con un joystick e mostrare l'ambiente circostante in ottica radar su un piccolo display.
Il sensore a ultrasuoni, già montato su un servomotore con un supporto in acrilico, viene utilizzato per scansionare l'ambiente circostante.

Il sistema di controllo utilizza lo shield joystick PS2 e la configurazione ESP32 D1 R32, che forse conoscete già grazie ai post sul blog dedicati al controllo del robot Bionic Spider.

Hardware

Se avete già apportato le modifiche al Joystick Shield PS2 in un blog precedente della serie Bionic Spider Controller, potete saltare il paragrafo seguente.

 

Per la realizzazione del progetto sono necessari:

ESP32 NodeMCU D1 R32

Schermo per joystick PS2

(opzionale) Batteria di blocco da 9V + Clip per batteria

e naturalmente uno assemblato Auto robot Acebott

 

Poiché gli ingressi analogici dei due assi del joystick sono collegati ai GPIO 2 e 4, che sono collegati all'ADC2, questi non possono essere utilizzati contemporaneamente al modulo WLAN. Tuttavia, le connessioni IO 34 e IO35 (ADC1) si trovano direttamente accanto a queste, il che significa che la funzionalità può essere facilmente stabilita tramite un ponticello.

Figura 1: Ponte di fili tra le connessioni

Collegare i collegamenti come indicato sopra.
Poiché il LED di bordo è collegato al GPIO 2, il pin deve essere rimosso in questo punto perché altrimenti il collegamento influenzerebbe il valore della tensione dell'asse X.

Lo stesso vale per il pin D12, che interferisce con il processo di avvio e quindi non è possibile avviare il sistema con lo shield inserito. Il pulsante centrale del joystick è collegato a questa connessione, il che significa che non può più essere utilizzato. Se si desidera comunque implementarlo, è necessario ricollegarlo a una connessione libera.

 

Figura 2: Scudo con modifiche

 

Lo schermo può essere semplicemente collegato al D1 R32. Assicuratevi che il piccolo interruttore nell'angolo sinistro sia impostato su 3v3 altrimenti l'ESP32 potrebbe essere danneggiato dalla tensione eccessiva di 5V.

 

 

Estensione del display

Poiché il controller dovrebbe visualizzare l'ambiente circostante il veicolo sotto forma di schermo radar, come si vede in molti film, è necessario un display.

Qui si consiglia il Display grafico OLED da 1,3" (SH1106). Questo non solo è un display grafico di grandi dimensioni e di facile lettura, ma ha anche la giusta disposizione per la striscia di prese del modulo HC05 sullo schermo (in alto a destra). Ciò significa che il display può essere inserito senza grandi modifiche.

È necessario rimuovere solo il resistore SMD superiore accanto al pulsante A, che è stato originariamente progettato per dimezzare la tensione sul pin RXD dell'HC05 in modo da non danneggiare il modulo quando funziona con il microcontrollore UNO.

 

Modifica dell'immagine

 

Poiché il microcontrollore utilizzato (ESP32) ha un livello logico di 3,3 V, la comunicazione con il display non funzionerebbe più con una tensione dimezzata.

 

Software

Per la comunicazione, i pacchetti di dati vengono scambiati utilizzando UDP. Con questo protocollo di comunicazione, i pacchetti vengono semplicemente inviati senza controllare se il pacchetto è stato ricevuto.

Sebbene questo renda lo scambio di dati molto veloce, è anche insicuro, in quanto non è possibile determinare se il destinatario sta effettivamente ricevendo i dati. La comunicazione tramite rete consente inoltre una maggiore portata rispetto a ESPnow o Bluetooth, in quanto la rete può coprire una vasta area con ripetitori e punti di accesso.
In linea di principio, i due dispositivi possono comunicare tra loro in qualsiasi luogo in cui sia presente la ricezione dello smartphone.

 

La libreria per la comunicazione UDP è già inclusa nell'ampio pacchetto di schede per l'ESP32.

 

Oltre alla libreria UDP e WiFi, sono necessarie altre librerie esterne, che devono essere installate.

U8g2

ArduinoJson

Questi possono essere scaricati da GitHub come file .zip tramite i seguenti link e scaricati nell'IDE Arduino sotto la voce

Sketch > include Libreria > Aggiungi libreria .zip ...

possono essere selezionati e installati.

 

Il ESP32Servo, ultrasuoni e veicolo Le librerie sono già incluse nel file .zip delle istruzioni. Sono già state installate anche nelle istruzioni per gli utenti avanzati.

 

Codice 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");
  }
}

Spiegazione:

All'avvio, l'ESP32 si connette alla WLAN, apre una porta UDP e inizializza i motori, il sensore a ultrasuoni e il servo. Nel ciclo principale, attende i comandi UDP in arrivo e li valuta come comandi di testo. A seconda del comando, i motori vengono controllati o vengono eseguite funzioni speciali.

Con il comando BTN_A, il servo ruota il sensore a ultrasuoni in passi, misura la distanza in ogni posizione e salva l'angolo e la distanza in un array JSON. Una volta completata la scansione, il JSON viene serializzato e rinviato al mittente via UDP, dopodiché il servo torna alla posizione centrale.

Dopo ogni azione eseguita, l'ESP32 invia il feedback "RDY" per segnalare che è pronto per il comando successivo.

 

È possibile utilizzare il codice qui scaricare.

Una volta caricato il programma sul microcontrollore con i dati di accesso WLAN corretti, verrà visualizzato l'indirizzo IP del dispositivo. Prendete nota di questo dato perché è richiesto dal controllore.

Codice controllore

#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, 
31);

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(
6463, 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);
}

Spiegazione:

Dopo l'avvio, l'ESP32 si connette alla WLAN, inizializza il display OLED, calibra il joystick sulla sua posizione centrale e imposta i GPIO necessari per i pulsanti. Gli assi del joystick vengono analizzati continuamente nel loop principale. Se superano un valore di soglia, viene determinata la direzione di marcia e inviata via UDP come comando (ad esempio, avanti, indietro, sinistra, destra) o l'arresto se il joystick è in posizione centrale. Inoltre, è possibile attivare comandi speciali utilizzando i tasti A-F. Per evitare trasmissioni multiple, ogni comando viene inviato nuovamente solo dopo la conferma o dopo che si è verificato un timeout.

 

Vengono analizzati anche i pacchetti UDP in arrivo. Se viene ricevuta la risposta "RDY", il ricevitore è pronto per il comando successivo. Se invece il pacchetto contiene un oggetto JSON con dati di misura, questo viene analizzato e visualizzato come una linea sull'OLED. L'angolo e la distanza dei valori misurati rappresentano linee che partono dal centro del display. Questo crea una semplice rappresentazione grafica dell'ambiente rilevato dall'altro ESP32.

 

Le linee vengono create con la funzione drawLineAngle(). Le coordinate finali della linea vengono calcolate utilizzando il seno e il coseno. Potete trovare una spiegazione dettagliata nel blog Orologio retrò con GC9A01A.

 

È possibile utilizzare il codice qui scaricare.

Caricare il programma con i dati di accesso corretti e l'indirizzo IP del ricevitore sul microcontrollore.


Conclusione

Se ora si preme il pulsante A sul controller, il display mostrerà l'ambiente circostante nei prossimi 60 cm sotto forma di linea grafica per ogni misurazione. Un pixel corrisponde a circa un centimetro. Se si desidera un raggio di visualizzazione più ampio, è possibile regolare il programma del controller di conseguenza, ma è necessario tenere presente la lunghezza massima di 60 pixel.

Naturalmente, questo progetto pone solo le basi per le vostre estensioni e modifiche. Oltre alla scansione dell'ambiente, è possibile aggiungere altre funzioni; i pulsanti non assegnati dello shield controller sono disponibili qui.

Divertitevi a ricostruire :)

Esp32Esp8266Projekte für anfänger

Lascia un commento

Tutti i commenti vengono moderati prima della pubblicazione