ESP32-Projekt: Roboterarm mit Game Controller steuern

In diesem Tutorial wird gezeigt, wie der bereits vorgestellte Roboterarm nun mit einem Game Controller gesteuert werden kann. da dort die Grundlagen der Servosteuerung und den Aufbau soweit vorgestellt worden sind, zeige ich nun, wie du den Roboterarm mit einem Bluetooth-Gamecontroller – in unserem Fall dem 2BDVX BG-04 Ultra – präzise und intuitiv steuern kannst.

1. Voraussetzungen

Um dieses Projekt umzusetzen, benötigst du folgende Komponenten und Kenntnisse:

  • Hardware:
    • Das ESP32 Entwicklungsboard (Kein ESP32-C3 oder S3, da mein Controller nur Bluetooth Classic unterstützt.) mit Erweiterungsboard
    • Der fertiggestellte Roboterarm mit MG90s und SG90-Servos (oder ähnlichen).
    • Wireless Controller 2BDVX BG-04 Ultra (oder ein kompatibler Bluetooth Classic Controller). Herstellerdetails: FCC ID 2BDVX-BG-04
    • Separate 5V-Stromversorgung für die Servos!
    • Jumperkabel.
  • Software:
    • Arduino IDE (mit installiertem ESP32 Board Support).
  • Verdrahtung:

2. Einrichtung der Bluepad32 Library

Da unser Controller über Bluetooth Classic kommuniziert, nutzen wir die Bibliothek Bluepad32.

Erst zusätzliche Boardverwaltung URL in Einstellungen eintragen

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json

Dann unter Boardverwaltung! die Bibliothek „esp32_bluepad32“ installieren. Solltet ihr noch nie mit ESP32 in der Arduino IDE gearbeitet haben dann auch noch die esp32 von Espressif

Quelle und Details zur Bibliothek einrichtung, ist hier vollumfassend erklärt: ricardoquesada/bluepad32

Nach der installation gibt es einen weiteren Boardverwaltungsauswahl Punkt. Hier das angeschlossenen ESP32 Dev Modul auswählen

Controller-Test (Optional, aber empfohlen)

Um sicherzustellen, dass dein Controller korrekt erkannt wird, solltest du den beiliegende Test-Sketch Controller hochladen.

Sobald du den Seriellen Monitor öffnest und Ihren Controller in den Kopplungsmodus mit „Share und (X) PS-Taste“ versetzt, wird der ESP32 die Verbindung herstellen und die Joystick-Werte angezeigt.


3. Der Steuerungscode: Bluepad32 trifft Servo

Da die Testverbindung erfolgreich war, können wir nun die Steuerungssignale des Controllers direkt mit der Logik zur Ansteuerung des Roboterarms verknüpfen.
Der folgende Sketch kombiniert die Logik des Gamecontrollers (ausgelesen durch Bluepad32) mit der Steuerung der vier Roboterarm-Achsen (Basis, Schulter, Ellenbogen, Greifer) aus dem vorherigen Tutorial über die ESP32Servo.h-Bibliothek.

Roboterarm-Steuerung mit ESP32 und BG-04 Controller
Dieser Sketch geht von einem Standard-4-DOF-Roboterarm (DOF=Degrees of Freedom) aus, der folgende Achsen über die Joysticks und Schultertasten steuert:

ArmfunktionController-Steuerung
Basis (Rotation)Linker Joystick, Horizontal (X)
Schulter (Auf/Ab)Linker Joystick, Vertikal (Y)
Ellenbogen (Auf/Ab)Rechter Joystick, Vertikal (RY)
Greifer (Öffnen/Schließen)Tasten L1 (Öffnen) und R1 (Schließen)
  • Servos und Stromversorgung: SG90-Servos benötigen 5V. Versorgen Sie die Servos immer über eine separate, externe 5V-Stromversorgung (nicht den ESP32-Pin!). Verbinde auch unbedingt die GND (Masse) dieser externen Stromversorgung mit dem GND des ESP32, um eine gemeinsame Referenz zu gewährleisten.
  • Pins anpassen: Passen die definierten GPIO-Pins an die tatsächliche Verkabelung Ihres Roboterarms an.
  • Invertierung und Min/Max: Die Joystick-Y-Achsen (vertikal) sind oft invertiert (nach oben = negativer Wert). Dies ist im Code berücksichtigt (map(joystickY, 512, -512, …)). Möglicherweise müssen Sie die Minimal- und Maximalwinkel (z. B. 20 und 160 Grad) im Code an Ihren spezifischen Roboterarm anpassen.

Quellcode:

#include <Bluepad32.h>
#include <ESP32Servo.h> 

// =======================
// 1. PIN-KONFIGURATION 
// =======================
#define PIN_SERVO_BASIS     16  // Basis Rotation (Linker Stick X)
#define PIN_SERVO_SCHULTER  15  // Schulter/Unterarm (Linker Stick Y)
#define PIN_SERVO_ELLENBOGEN 14 // Ellenbogen/Oberarm (Rechter Stick RY)
#define PIN_SERVO_GREIFER   13  // Greifer/Klaue (L1/R1 Tasten)

// =======================
// 2. SERVO-WINKEL & GESCHWINDIGKEIT
// =======================
#define SERVO_MIN_WINKEL    20  
#define SERVO_MAX_WINKEL    160 

#define GREIFER_WINKEL_OFFEN 140 
#define GREIFER_WINKEL_GESCHL 10  

// GESCHWINDIGKEITEN: Größere Zahl = schnellere Bewegung
#define ALLGEMEIN_STEP_SIZE 2   // Schrittweite für Basis und Ellenbogen (z.B. 2 Grad)
#define SCHULTER_STEP_SIZE  1   // Schrittweite für die Schulter 

// ZUCKEN/JITTER-UNTERDRÜCKUNG
// Wenn die Joystick-Achse diesen Wert unterschreitet, wird sie als 0 behandelt.
#define JOYSTICK_TOLERANZ   10  // Joystick-Einheiten (-512 bis 512)


// =======================
// 3. GLOBALE OBJEKTE & WINKEL-SPEICHER
// =======================
ControllerPtr myControllers[BP32_MAX_GAMEPADS];
Servo servoBasis;
Servo servoSchulter;
Servo servoEllenbogen;
Servo servoGreifer;

// Startwert Winkel
int aktuellerBasisWinkel = 90;
int aktuellerSchulterWinkel = 90;
int aktuellerEllenbogenWinkel = 90;
int aktuellerGreiferWinkel = GREIFER_WINKEL_OFFEN; 


// =======================
// 4. BLUEPAD32 CALLBACKS 
// =======================

void onConnectedController(ControllerPtr ctl) {
    bool foundEmptySlot = false;
    for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == nullptr) {
            Serial.printf("CALLBACK: Controller verbunden, Index=%d\n", i);
            ControllerProperties properties = ctl->getProperties();
            Serial.printf("Controller Modell: %s, VID=0x%04x, PID=0x%04x\n", ctl->getModelName().c_str(), properties.vendor_id,
                            properties.product_id);
            myControllers[i] = ctl;
            foundEmptySlot = true;
            break;
        }
    }
    if (!foundEmptySlot) {
        Serial.println("CALLBACK: Controller verbunden, aber kein leerer Slot gefunden");
    }
}

void onDisconnectedController(ControllerPtr ctl) {
    bool foundController = false;

    for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == ctl) {
            Serial.printf("CALLBACK: Controller getrennt von Index=%d\n", i);
            myControllers[i] = nullptr;
            foundController = true;
            break;
        }
    }

    if (!foundController) {
        Serial.println("CALLBACK: Controller getrennt, aber nicht in myControllers gefunden");
    }
}


// =======================
// 5. STEUERUNGSFUNKTION (Mit Toleranz und Sanfter Bewegung)
// =======================

void controlRobotArm(ControllerPtr ctl) {
    
    // Joystick-Werte mit Toleranz abfragen
    int joystickX = ctl->axisX();
    int joystickY = ctl->axisY();
    int joystickRY = ctl->axisRY();

    // Toleranz anwenden: Wenn der Wert nahe 0 ist, auf 0 setzen, um Zucken zu vermeiden
    if (abs(joystickX) < JOYSTICK_TOLERANZ) joystickX = 0;
    if (abs(joystickY) < JOYSTICK_TOLERANZ) joystickY = 0;
    if (abs(joystickRY) < JOYSTICK_TOLERANZ) joystickRY = 0;
    
    // -----------------------------------------------------------
    // I. ANALOGE STEUERUNG (Zielwinkel berechnen)
    // -----------------------------------------------------------
    
    // Zielwinkel MUSS IMMER berechnet werden, auch wenn die Achse 0 ist, um den "Haltepunkt" zu kennen
    int zielBasisWinkel = map(joystickX, -512, 512, SERVO_MIN_WINKEL, SERVO_MAX_WINKEL);
    int zielSchulterWinkel = map(joystickY, 512, -512, SERVO_MIN_WINKEL, SERVO_MAX_WINKEL);
    int zielEllenbogenWinkel = map(joystickRY, 512, -512, SERVO_MIN_WINKEL, SERVO_MAX_WINKEL);
    
    // -----------------------------------------------------------
    // II. DIGITALE STEUERUNG (Greifer)
    // -----------------------------------------------------------

    if (ctl->l1()) {
        aktuellerGreiferWinkel = GREIFER_WINKEL_OFFEN;
    } 
    else if (ctl->r1()) {
        aktuellerGreiferWinkel = GREIFER_WINKEL_GESCHL;
    }
    servoGreifer.write(aktuellerGreiferWinkel); 

    // -----------------------------------------------------------
    // III. SANFTE BEWEGUNG (Inkrementelle Anpassung)
    // -----------------------------------------------------------
    
    // Funktion zur schrittweisen Bewegung
    auto moveServo = [](int& aktuellerWinkel, int zielWinkel, int schritt) {
        if (aktuellerWinkel != zielWinkel) {
            if (aktuellerWinkel < zielWinkel) {
                // Erhöhen
                aktuellerWinkel = aktuellerWinkel + schritt;
                // Sicherstellen, dass wir das Ziel nicht überschreiten
                if (aktuellerWinkel > zielWinkel) aktuellerWinkel = zielWinkel; 
            } else {
                // Verringern
                aktuellerWinkel = aktuellerWinkel - schritt;
                // Sicherstellen, dass wir das Ziel nicht unterschreiten
                if (aktuellerWinkel < zielWinkel) aktuellerWinkel = zielWinkel;
            }
            // Winkel auf den zulässigen Bereich beschränken
            aktuellerWinkel = constrain(aktuellerWinkel, SERVO_MIN_WINKEL, SERVO_MAX_WINKEL);
            return true; // Bewegung durchgeführt
        }
        return false; // Keine Bewegung notwendig
    };

    // BASIS
    if (moveServo(aktuellerBasisWinkel, zielBasisWinkel, ALLGEMEIN_STEP_SIZE)) {
        servoBasis.write(aktuellerBasisWinkel);
    }
    
    // SCHULTER (Nutzt den LANGSAMEREN Schritt)
    if (moveServo(aktuellerSchulterWinkel, zielSchulterWinkel, SCHULTER_STEP_SIZE)) {
        servoSchulter.write(aktuellerSchulterWinkel);
    }

    // ELLENBOGEN
    if (moveServo(aktuellerEllenbogenWinkel, zielEllenbogenWinkel, ALLGEMEIN_STEP_SIZE)) {
        servoEllenbogen.write(aktuellerEllenbogenWinkel);
    }

    // -----------------------------------------------------------
    // IV. SERIELLE AUSGABE
    // -----------------------------------------------------------
    Serial.printf("Basis: %3d° | Schulter: %3d° | Ellenbogen: %3d° | Greifer: %3d°\n", 
                  aktuellerBasisWinkel, aktuellerSchulterWinkel, aktuellerEllenbogenWinkel, aktuellerGreiferWinkel);
}

void processControllers() {
    for (auto myController : myControllers) {
        if (myController && myController->isConnected() && myController->hasData()) {
            if (myController->isGamepad()) {
                controlRobotArm(myController);
            } 
        }
    }
}

// =======================
// 6. SETUP & LOOP (Unverändert)
// =======================

void setup() {
    Serial.begin(115200);
    Serial.printf("Firmware: %s\n", BP32.firmwareVersion());
    
    // Servos an die GPIO-Pins binden
    servoBasis.attach(PIN_SERVO_BASIS);
    servoSchulter.attach(PIN_SERVO_SCHULTER);
    servoEllenbogen.attach(PIN_SERVO_ELLENBOGEN);
    servoGreifer.attach(PIN_SERVO_GREIFER);
    
    // Setze Servos auf Startposition (90 Grad Mitte)
    servoBasis.write(90);
    servoSchulter.write(90);
    servoEllenbogen.write(90);
    servoGreifer.write(GREIFER_WINKEL_OFFEN);

    // Initialisiere die aktuellen Winkel-Speicher
    aktuellerBasisWinkel = 90;
    aktuellerSchulterWinkel = 90;
    aktuellerEllenbogenWinkel = 90;

    // Setup the Bluepad32 callbacks
    BP32.setup(&onConnectedController, &onDisconnectedController);
    BP32.enableVirtualDevice(false);
}

void loop() {
    bool dataUpdated = BP32.update();
    
    // Wichtig: Aufruf in jedem Loop für die schrittweise Bewegung
    processControllers();

    // Kleines Delay für Fließfähigkeit (5ms)
    delay(5); 
}

Youtube Video:

Datenschutz-Übersicht

Diese Website verwendet Cookies, damit wir dir die bestmögliche Benutzerkomfort bieten können. Cookie-Informationen werden in deinem Browser gespeichert und führen Funktionen aus, wie das Wiedererkennen von dir, wenn du auf unsere Website zurückkehrst und hilft uns zu verstehen, welche Abschnitte der Website für dich am interessantesten und nützlichsten sind.