← All projects

RKCRobotics, Kinematics & ControlMar – May 2023

17-DOF Humanoid Robot: Bipedal Kinematics & Teleoperation

A 17-DOF aluminum bipedal robot driven by an 18-channel servo controller, with dual-mode teleoperation over a PS2 wireless joystick and a Wi-Fi-hosted ESP32 web server.

  • Bipedal Kinematics
  • Actuator Coordination
  • Rigid-Body Mechanics
  • Wireless Teleop

Hardware & Architecture

  • 17-DOF aluminum bracket frame (Robokits platform) for rigid center-of-mass transfer during locomotion.
  • 18-channel servo controller generating hardware-level PWM for all high-torque metal-gear servos (e.g., MG996R).
  • ESP32 command bridge hosting the Wi-Fi web server and reading the PS2 wireless receiver.
  • Kinematic grouping: 10 DOF lower body (5 per leg), 7 DOF upper body (3 per arm + 1 head pan).

Key Highlights

  • Separates multi-servo PWM generation (servo board) from high-level wireless comms (ESP32).
  • Dual input: Wi-Fi web-server endpoints and PS2 D-pad map to pre-programmed action groups on the servo board.
  • Analog-stick input maps to direct single-servo PWM positioning (e.g., head pan).

Images

Code

humanoid_teleop.inoESP32 Teleop Bridgecpp
#include <WiFi.h>
#include <WebServer.h>
#include <PS2X_lib.h> // Library for PS2 Controller communication

// --- Hardware Pins ---
#define PS2_DAT 19
#define PS2_CMD 23
#define PS2_SEL 5
#define PS2_CLK 18

// --- Wi-Fi Configuration ---
const char* ssid = "HUMANOID_NETWORK";
const char* password = "ROBOTICS_SECURE";
WebServer server(80);

// --- PS2 Controller Object ---
PS2X ps2x;
int error = 0;
byte type = 0;
byte vibrate = 0;

// --- UART Setup for Servo Board ---
// Assuming Servo Controller uses Serial2 (RX: 16, TX: 17)
#define SERVO_SERIAL Serial2

void setup() {
  Serial.begin(115200);
  SERVO_SERIAL.begin(9600); // Baud rate required by the 18-ch servo board

  // 1. Initialize PS2 Controller
  error = ps2x.config_gamepad(PS2_CLK, PS2_CMD, PS2_SEL, PS2_DAT, true, true);
  if (error == 0) {
    Serial.println("[SYSTEM] PS2 Controller Configured Successfully.");
  }

  // 2. Initialize Wi-Fi Access Point
  WiFi.softAP(ssid, password);
  Serial.print("[SYSTEM] Wi-Fi AP Started. IP: ");
  Serial.println(WiFi.softAPIP());

  // 3. Setup Web Server Endpoints
  server.on("/walk", HTTP_GET, []() {
    executeActionGroup(1); // Call action group 1 stored on the servo board
    server.send(200, "text/plain", "Walking Forward");
  });
  server.on("/stop", HTTP_GET, []() {
    executeActionGroup(0); // Call default standing pose
    server.send(200, "text/plain", "Stopping");
  });
  server.begin();
}

void loop() {
  // --- Handle Wi-Fi Commands ---
  server.handleClient();

  // --- Handle PS2 Joystick Commands ---
  if (error == 0) { // Only read if controller is connected
    ps2x.read_gamepad(false, vibrate);

    // Read D-Pad for discrete movements
    if (ps2x.Button(PSB_PAD_UP)) {
      Serial.println("[PS2] Forward Command");
      executeActionGroup(1); // Walk forward
      delay(500); // Prevent command spamming
    }
    else if (ps2x.Button(PSB_PAD_DOWN)) {
      Serial.println("[PS2] Backward Command");
      executeActionGroup(2); // Walk backward
      delay(500);
    }
    else if (ps2x.Button(PSB_CROSS)) {
      Serial.println("[PS2] Stop Command");
      executeActionGroup(0); // Default stance
      delay(500);
    }

    // Example of direct servo control using analog sticks (e.g., Head Pan)
    int headPanAnalog = ps2x.Analog(PSS_RX); // Read Right Stick X-axis (0-255)
    if (headPanAnalog > 150 || headPanAnalog < 100) {
      // Map 0-255 to Servo PWM range (500-2500)
      int pwmValue = map(headPanAnalog, 0, 255, 500, 2500);
      moveSingleServo(16, pwmValue, 500); // Move Head Servo (ID 16) in 500ms
    }
  }
  delay(50);
}

// --- Servo Board Communication Protocol ---
// Function to call pre-programmed action groups on the 18-channel board
// Protocol format: "#<ActionGroupID>G"
void executeActionGroup(int groupNumber) {
  if (groupNumber == 0) {
    SERVO_SERIAL.println("#000G"); // Example command for reset/stop
  } else if (groupNumber == 1) {
    SERVO_SERIAL.println("#001G"); // Example command for walk forward
  } else if (groupNumber == 2) {
    SERVO_SERIAL.println("#002G"); // Example command for walk backward
  }
}

// Function to move a specific servo directly
// Protocol format: "#<ServoID>P<PWMValue>T<TimeMS>"
void moveSingleServo(int servoID, int pwm, int timeMS) {
  String command = "#" + String(servoID) + "P" + String(pwm) + "T" + String(timeMS);
  SERVO_SERIAL.println(command);
  Serial.println("[SERVO] Sent: " + command);
}