Joystick-Fernsteuerung via Bluetooth

Bei der Durchführung des VHS-Ferienkurses „Robot Cars und Fernsteuerung programmieren“ habe ich als Durchführender diesmal wahrscheinlich mehr gelernt als die Teilnehmer.

Erstens: Nach den sehr guten Erfahrungen mit der IR-Fernsteuerung bei dem neuen Smart Car-Kit wollte ich diese preiswerte Lösung mit dem einfach zu handhabenden Motor Shield V1 realisieren. Während die Kiddies am Nachmittag frei hatten, hatte ich mit nicht vorhergesehenen Schwierigkeiten zu kämpfen. Ergebnis: Die IR-Fernsteuerung funktioniert (mit der Einschränkung Sonnenlicht) sehr schön mit den Motor Controllern L298N, L293D und Motor Shield V2, aber nicht mit dem Motor Shield V1. Und Motor Shield V1 funktioniert sehr schön mit Bluetooth HC-05, 433MHz HC-12 und 2,4 GHz nRF24L01. Aber Motor Shield V1 und IR vertragen sich nicht, sind nicht kompatibel. 

Zweitens: Fernsteuerung mit dem Bluetooth-Transceiver HC-05 ist ebenfalls eine praktikable und preiswerte Lösung, aber nur für diejenigen, die ein Android Smartphone besitzen. Wenn du ein iPhone hast, hast du ein iPhone. So oder so ähnlich hieß es vor einigen Jahren in der Werbung. Da ist sogar Bluetooth anders. Es gibt zwar eine Bluetooth-Arduino-App, aber das Koppeln mit dem HC-05 hat nicht geklappt. Und eine Teilnehmerin hatte gar kein Smartphone. Ergebnis: Eine Bluetooth-Fernsteuerung ohne Smartphone, am besten mit dem Joystick-Modul, muss entwickelt werden, denn auch nach mehrtägiger Recherche im Internet habe ich nichts Derartiges gefunden.

Das Robot Car mit dem Bluetooth Transceiver HC-05 hatten wir im Kurs schon zusammengebaut; es funktioniert sehr schön mit einem Android Smartphone und der App Arduino Bluetooth Controller. Bei der Suche nach einer eigenständigen Fernsteuerung stellte sich die Frage, ob wir einen zweiten AVR-Mikrocontroller mit einem zweiten HC-05 nehmen, oder einen Mikrocontroller, der eine Bluetooth-Schnittstelle von Haus aus mitbringt, also z.B. einen ESP32. Ich entscheide mich aus zwei Gründen für den Lolin32. Erstens genügt ein kleiner LiPo-Akku mit 3,7V, der über die µUSB-Schnittstelle aufgeladen werden kann und im Betrieb die Spannungsversorgung des Mikro Controllers sicherstellt. Und zweitens kommt der Lolin32 unverlötet; anstelle der Pins für die Benutzung auf einem Breadboard konnte ich stattdessen Federleisten einlöten, in die ich das leicht modifizierte Joystick-Modul einstecken konnte.

Benötigte Hardware

1

Mikro Controller Lolin 32

Alternativ beliebiger ESP32

1

Joystick-Modul

1

LiPo-Akku 3,7V 250mAh

1

Gehäuse ca. 80x50x26mm


Modifikation am Joystick-Modul

Alle auf dem Markt erhältlichen Joystick-Module haben eingelötete gewinkelte Pins wie auf dem folgenden Bild links. Damit kann man das Modul weder direkt in ein Breadboard stecken, noch wie von mir gedacht in die Federleisten an meinem Lolin32. Zum Glück muss man die fünfpolige Pinleiste nicht im Ganzen auslöten, sondern kann mit Hilfe der Ehefrau die Pins einzeln nach dem Erhitzen der Lötstelle herausziehen. Dann werden gerade Pins eingelötet (rechts im Bild).

Hier noch ein Bild des Lolin32 mit Federleisten und einem kleinen 3,7V LiPo-Akku. Das Joystick-Modul wird in die Pins 27 bis 32 gesteckt. Auf der Unterseite habe ich ein flaches Stück Schaumstoff mit Teppich-Klebeband befestigt. Pinout und eBook zum Lolin32 findet man auf der Produktseite.

Die Sketches

Nachdem man die Arduino IDE mit dem Link
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

unter Datei/Voreinstellungen in der Zeile „Zusätzliche Bordverwalter URL“ für alle ESP32 nutzbar gemacht und den Lolin32 ausgewählt hat, kann man auf eine Vielzahl von Programmbeispielen zugreifen:

Auch wenn alle Beschreibungen darauf hindeuten, dass die Sketches unter BluetoothSerial für die Verbindung zum Smartphone ausgelegt sind, habe ich durch Hinweise im Internet den kaum dokumentierten Sketch SerialToSerialBTM entdeckt und damit herumgetüftelt und tatsächlich kann man damit die Verbindung zwischen Lolin32 und einem AVR-Mikrocontroller mit HC-05 herstellen. Das korrespondierende AVR-Programm habe ich aus dem SoftwareSerialExample abgeleitet.

Das Prinzip ist ganz einfach: Was in der obersten Zeile des Seriellen Monitors eingegeben wird, wird über Bluetooth versendet; was über Bluetooth empfangen wird, wird postwendend im Seriellen Monitor ausgegeben. Nachdem ich die richtige MAC-Adresse des HC-05 im Sketch SerialToSerialBTM eingegeben hatte, hat das auf Anhieb geklappt.

Hier der Sketch für den Lolin32, fettgedruckt meine Änderungen, die Sie für Ihre Hardware entsprechend anpassen müssen. (Download Code)

 //This example code is in the Public Domain (or CC0 licensed, at your option.)
 //By Victor Tchistiak - 2019
 //
 //This example demostrates master mode bluetooth connection and pin
 //it creates a bridge between Serial and Classical Bluetooth (SPP)
 //this is an extention of the SerialToSerialBT example by Evandro Copercini - 2018
 //
 
 #include "BluetoothSerial.h"
 
 BluetoothSerial SerialBT;
 
 String MACadd = "98:D3:71:F5:E7:D0";       // MAC-Adresse des HC-05
 uint8_t address[6]  = {0x98, 0xD3, 0x71, 0xF5, 0xE7, 0xD0};
 //uint8_t address[6] = {0x00, 0x1D, 0xA5, 0x02, 0xC3, 0x22};
 String name = "HC-05-2";    // Name meines HC-05 Transceivers
 char *pin = "1234"; //<- standard pin would be provided by default
 bool connected;
 
 void setup() {
   Serial.begin(115200);
   //SerialBT.setPin(pin);
   SerialBT.begin("Lolin32_94:B9:7E:D9:52:C6", true);     //MAC address of LOLIN32
   //SerialBT.setPin(pin);
   Serial.println("The device started in master mode, make sure remote BT device is on!");
   
   // connect(address) is fast (upto 10 secs max), connect(name) is slow (upto 30 secs max) as it needs
   // to resolve name to address first, but it allows to connect to different devices with the same name.
   // Set CoreDebugLevel to Info to view devices bluetooth address and device names
   //connected = SerialBT.connect(name);
   connected = SerialBT.connect(address);
   
   if(connected) {
     Serial.println("Connected Succesfully!");
  } else {
     while(!SerialBT.connected(9600)) {
       Serial.println("Failed to connect. Make sure remote device is available and in range, then restart app.");
    }
  }
   // disconnect() may take upto 10 secs max
   if (SerialBT.disconnect()) {
     Serial.println("Disconnected Succesfully!");
  }
   // this would reconnect to the name(will use address, if resolved) or address used with connect(name/address).
   SerialBT.connect();
 }
 
 void loop() {
   if (Serial.available()) {
     int code = Serial.read();
     SerialBT.write(code);
  }
   if (SerialBT.available()) {
     Serial.write(SerialBT.read());
  }
   delay(20);
 }

Um die MAC-Adresse Ihres HC-05 herauszufinden, versetzen Sie das Modul durch Drücken des Tasters beim Einschalten in den AT-Kommando-Modus und geben Sie AT+ADDR? ein. Weitere Details finden Sie in unserem eBook auf der Produktseite.

Wichtig ist auch später für das Robot Car, dass der empfangende HC-05 bereits eingeschaltet sein muss, wenn dieser Sketch gestartet wird. Etwas verwirrend ist die Anzeige im Seriellen Monitor. Wenn alles funktioniert, erscheinen die Meldungen "Connected Succesfully!" und "Disconnected Succesfully!", jedoch wird ohne weitere Textausgabe „reconnected“. Also trotz der letzten Meldung vom Test steht die Verbindung und man kann mit der Eingabe und Übertragung beginnen.

Aber – die Zeichen werden einzeln als char übertragen. Ich benötige jedoch die Übertragung eines vierstelligen Codes als Integerzahl, damit ich damit meinen Motor Controller steuern kann.

Dieses Problem hatte ich jedoch schon bei meiner 433MHz-Fernsteuerung mit zwei HC-12 Transceivern gelöst und konnte so Code-Fragmente übernehmen. Das Zauberwort heißt parseInt().

Und übertragen möchte ich keine Eingabe im Seriellen Monitor, sondern ja den Code für die Motorsteuerung. Also muss der Code, der die Bluetooth-Verbindung herstellt, erweitert werden um die Abfrage der x- und y-Achse des Joysticks.

Hier der erweiterte Code für das Ermitteln des Codes mithilfe der Funktion map(). Erläuterungen folgen im Anschluss. (Download Code)

 #include "BluetoothSerial.h"
 
 BluetoothSerial SerialBT;
 
 String MACadd = "98:D3:71:F5:E7:D0";
 uint8_t address[6]  = {0x98, 0xD3, 0x71, 0xF5, 0xE7, 0xD0};
 //uint8_t address[6] = {0x00, 0x1D, 0xA5, 0x02, 0xC3, 0x22};
 String name = "HC-05-2";
 char *pin = "1234"; //<- standard pin would be provided by default
 bool connected;
 
 void setup() {
   Serial.begin(115200);
   pinMode(32,OUTPUT);
   pinMode(33,OUTPUT);
   pinMode(27,INPUT_PULLUP);
   digitalWrite(32,LOW);
   digitalWrite(33,HIGH);
   SerialBT.begin("Lolin32_94:B9:7E:D9:52:C6", true); //Name of device
   Serial.println("The device started in master mode, make sure remote BT device is on!");
   connected = SerialBT.connect(address);
   
   if(connected) {
     Serial.println("Connected Succesfully!");
  } else {
     while(!SerialBT.connected(9600)) {
       Serial.println("Failed to connect. Make sure remote device is available and in range, then restart app.");
    }
  }
   // disconnect() may take upto 10 secs max
   if (SerialBT.disconnect()) {
     Serial.println("Disconnected Succesfully!");
  }
   // this would reconnect to the name(will use address, if resolved) or address used with connect(name/address).
   SerialBT.connect();
 }
 
 void loop() {
   int y = map(analogRead(25)+100, 0, 4095, 1,9);
   int x = map((analogRead(26))+100, 0, 4095, 1,9);
   Serial.print("analogRead(25) = ");
   Serial.println(analogRead(25));  
   Serial.print("analogRead(26) = ");
   Serial.println(analogRead(26));  
   int code = 1000*y+100*x;
   Serial.println(code);  
   SerialBT.println(code);
   delay(250);
 }

Das Joystick-Modul benötigt eine Spannungsversorgung, die über die Pins 32 (-) und 33 (+) erfolgt. Die angrenzenden Pins 25 und 26 liefern die analogen Werte des Joysticks als 12-bit-Zahlen, also Wertebereich 0 bis 4095. Pin 27 ist als INPUT_PULLUP für den Joystick-Button definiert, wird von mir jedoch nicht verwendet. Wie gesagt, mit der map()-Funktion werden die Werte von 0 bis 4095 auf den Bereich 1 bis 9 reduziert. Da der Wert für die Mittelposition bei mir nicht bei 2047, sondern nur bei ca. 1900 lag, habe ich den Wert von analogRead um 100 erhöht. Entscheidend ist, dass die Mittelposition jeweils den „ge-map-ten“ Wert 5 liefert; der Code für den Ruhezustand lautet 5500.


Nun zur Empfängerseite, Robot Car mit AVR-Mikrocontroller, Motor Shield V1 und HC-05. (Download Code)

 /* Sample Code for Robot Car with Motor Shield V1 and BT receiver HC-05, as of 20220515
   based on Adafruit Motor shield V2 library, copyright Adafruit Industries LLC
   this code is public domain, enjoy!
   modified for Funduino
   Pins
   BT VCC to Arduino 5V out.
   BT GND to GND
   Arduino A1=15 (SS RX) - BT TX no need voltage divider
   Arduino A2=16 (SS TX) - BT RX through a voltage divider (5v to 3.3v)
 */
 
 #include <AFMotor.h>
 AF_DCMotor motor1(2);
 AF_DCMotor motor2(3);
 
 #include <SoftwareSerial.h>
 // initialize HC-05
 SoftwareSerial BTSerial(17, 18); // RX, TX über Kreuz an TX, RX(voltage divider)
 int x = 0;
 int y = 0;
 int left = 0;
 int right = 0;
 int code = 5500;
 int codeRead = 5500;
 int speedL = 0;
 float factor = 1.8;          // Correction for speedLevel 255/100 * 6V/VBatt
 
 void setup() {
   Serial.begin(9600); // set up Serial Monitor at 9600 bps
   Serial.println("Motor sketch!");
   BTSerial.begin(9600); // set up transmission speed for HC-05
   Serial.println("SoftwareSerial initialized!");
 }   // end setup
 
 void loop() {
   if (BTSerial.available() > 1) {
     //read serial input and convert to integer (-32,768 to 32,767)
     codeRead = BTSerial.parseInt();
     Serial.print("code received: ");
     Serial.println(codeRead);
     if (codeRead<=9999) {
       if (code != codeRead) {
         code = codeRead;
 //       Serial.print("code: ");
 //       Serial.println(code);
      }
    }
     else {
 //     Serial.print("wrong code received: ");
       code = 5500;
    }
  }
 // else {
 //   Serial.println("Nothing received");
 //   code = 5500;
 // }
   BTSerial.flush();//clear the serial buffer for unwanted inputs
   Serial.print("code: ");
   Serial.println(code);
   motor();    
   delay(200);   //little delay for better serial communication and to avoid bouncing
 
 }   // end loop
 
 void motor() {
   int speedLevel[9] = { -100, -80, -60, -40, 0, 40, 60, 80, 100};
   y = int(code / 1000);
   x = int((code - 1000 * y) / 100);
   speedL = speedLevel[y - 1];
   Serial.print("code = ");
   Serial.print(code);
   Serial.print(" y = ");
   Serial.print(y);
   Serial.print(" x = ");
   Serial.print(x);
   Serial.print(" speedL = ");
   Serial.println(speedL);
 
   //Korrektur der Fahrtstufen für Kurvenfahrt
   if (x == 1) {
     right = speedL + 40;
     left = speedL - 40;
  }
   else if (x == 2) {
     right = speedL + 20;
     left = speedL - 20;
  }
   else if (x == 3) {
     right = speedL + 10;
     left = speedL - 10;
  }
   else if (x == 4) {
     right = speedL + 5;
     left = speedL - 5;
  }
   else if (x == 6) {
     right = speedL - 5;
     left = speedL + 5;
  }
   else if (x == 7) {
     right = speedL - 10;
     left = speedL + 10;
  }
   else if (x == 8) {
     right = speedL - 20;
     left = speedL + 20;
  }
   else if (x == 9) {
     right = speedL - 40;
     left = speedL + 40;
  }
   else {
     right = speedL;
     left = speedL;
  }
 
   //Eingabe der Fahrtstufen für "left" und "right"
   Serial.print("left = ");
   Serial.print(left);
   Serial.print(" right = ");
   Serial.println(right);
 
   if (left < 40 & left > -40) {
     motor1.run(RELEASE);
  }
   if (right < 40 & right > -40) {
     motor2.run(RELEASE);
  }
   if (left >= 40) {
     if (left > 100) left = 100;
     motor1.run(FORWARD);
     motor1.setSpeed(left * factor);
  }
   if (right >= 40) {
     if (right > 100) right = 100;
     motor2.run(FORWARD);
     motor2.setSpeed(right * factor);
  }
   if (left <= -40) {
     if (left < -100) left = -100;
     motor1.run(BACKWARD);
     left = -left;
     motor1.setSpeed(left * factor);
  }
   if (right <= -40) {
     if (right < -100) right = -100;
     motor2.run(BACKWARD);
     right = -right;
     motor2.setSpeed(right * factor);
  }
 }   // end motor

An der selbst-definierten Funktion motor() habe ich keine Veränderungen vornehmen müssen. Diese übernimmt den Wert der globalen Variablen code und setzt diesen in Fahrtstufen um.

Die wesentlichen Codezeilen für den Bluetooth-Empfang sind in der void loop().  Diese Codezeilen habe ich aus einem früheren Projekt mit dem HC-12 übernommen, aber es gibt im Aufbau einen Unterschied. Da die meisten digitalen Pins durch den Motor Controller V1 bereits belegt sind, bieten sich die Pins A0 bis A5 (=D14 bis D19) für die SoftwareSerial-Schnittstelle an. Allerdings gibt es zwei wesentliche Unterschiede zum HC-12, den wir direkt an die analogen Pins anschließen konnten: Erstens liefern die Pins nicht genügend Strom für die Stromversorgung des HC-05 und zweitens verträgt der Transceiver am RX-Pin nur 3,3 V. 

Dieses Problem kann man mit Breadboard und Kabeln sowie einem Spannungsteiler lösen, oder mit einem kleinen selbstgelöteten Adapter auf einer Lochrasterplatine. Hier Schaltung und Bilder für den Anschluss des HC-05 am Motor Shield V1.

Auf dem Motor Shield habe ich einige Federleisten für den sichere Halt und die notwendigen Verbindungen eingelötet:

Wie aus der Beschriftung des HC-05 hervorgeht, liegt die Versorgungsspannung zwar zwischen 3 und 5V, jedoch die UART Pins arbeiteten mit 3,3V; wir benötigen einen Spannungsteiler, z.B. 1kΩ und 2,2kΩ für den RX-Anschluss. Hier der Schaltplan:

Hier die Bilder von Vor- und Rückseite meines Adapters:

Hier das Robot Car mit HC-05:

Und die Teile für die Fernsteuerung:

Und zuletzt das Bild der fertigen Bluetooth-Fernsteuerung in einem Universalgehäuse von ca. 80x50x26mm-. Vielleicht kann man dies auch mit einem 3D-Drucker selbst erstellen.

Viel Spaß beim Nachbauen. 

Esp-32Projekte für anfänger

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert