Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Manufacturing Technology >> Herstellungsprozess

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Komponenten und Zubehör

Bauteil Menge Details
Arduino UNO 1
PHPoC WiFi Shield für Arduino 1
6-DOF Roboterarm 1 6 Servomotoren

Über dieses Projekt

Einsteiger-Empfehlung: Vorher Tutorials zu Arduino Motor, Servo Motor, WiFi durcharbeiten.

1. Demonstration

(Video/Demo in Originalquelle)

2. Benutzeroberfläche

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface
Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Der Roboterarm hat 6 Motoren, gesteuert über 4 Zonen:

3. Systemarchitektur

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

4. Arbeitsablauf

Client-Seite (Web-UI: JavaScript + HTML + CSS)

Bei Touch/Click/Mouse-Move werden Koordinaten (x, y) erfasst. Ablauf:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

In Zone A: Geometrische Berechnung für Winkel der Motoren 2, 3, 4 nötig (Details am Ende).

Server-Seite (Arduino-Code)

Empfangene Winkel → alle 6 Motoren fahren simultan von aktueller zu neuer Position. Bewegung geglättet durch kleine Schritte + Pausen.

Einzelmotor-Bewegung: Von angle zu new_angle. Wiederholen bis Ziel:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

step_num = Anzahl Schritte, step & time = vordefiniert (Geschwindigkeit/Glatte).

Für simultanes Ankommen: Alle Motoren gleiche step_num (Maximum aller nötigen Schritte), aber individuelles step pro Motor.

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

5. Geometrische Berechnung (Zone A)

Robotik-Problem auf Geometrie zurückgeführt:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Gegeben:

Gesucht: Winkel C, B, A

Lösung: Annahme: Winkel B = A = α. Zusatzpunkte/Strecken einfügen:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface
Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface
Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface
Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Vereinfacht:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

a, b, c, d bekannt → α lösbar. β = π − α. γ über Cosinussatz in BDC und BDA:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Ergebnis: Motoren 2, 3, 4 Winkel = (δ+γ), β, β.

6. Quellcode

Zwei Dateien:

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

RobotArmWeb.ino (Arduino)

#include <Phpoc.h>
#include <Servo.h>

int angle_init[] = {90, 101, 165, 153, 90, 120}; // Startwinkel (motorgeometrisch)
int angle_offset[] = {0, 11, -15, -27, 0, 137};  // Offset Web-Winkel → realer Servo
int cur_angles[] = {90, 101, 165, 153, 90, 120};
int dest_angles[] = {0, 0, 0, 0, 0, 0};
int angle_max[] = {180, 180, 160, 120, 180, 137};
int angle_min[] = {0, 0, 0, 20, 0, 75};
int direction[] = {1, 1, 1, 1, 1, -1};
int angleSteps[] = {3, 2, 2, 2, 4, 4}; // Schrittweite pro Motor (Grad)

Servo servo[6];
PhpocServer server(80);
int stepNum = 0;

void setup() {
  Serial.begin(9600);
  Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
  server.beginWebSocket("remote_arm");
  for (int i = 0; i < 6; i++) {
    servo[i].attach(i + 2); // Pins 2-7
    servo[i].write(angle_init[i]);
  }
}

void loop() {
  PhpocClient client = server.available();
  if (client) {
    String angleStr = client.readLine();
    if (angleStr) {
      Serial.println(angleStr);
      int commaPos1 = -1, commaPos2;
      for (int i = 0; i < 5; i++) {
        commaPos2 = angleStr.indexOf(',', commaPos1 + 1);
        int angle = angleStr.substring(commaPos1 + 1, commaPos2).toInt();
        dest_angles[i] = angle * direction[i] + angle_offset[i];
        commaPos1 = commaPos2;
      }
      int angle5 = angleStr.substring(commaPos1 + 1).toInt();
      dest_angles[5] = angle5 * direction[5] + angle_offset[5];
      stepNum = 0;
      // Schrittzahl berechnen (Maximum aller Motoren)
      for (int i = 0; i < 6; i++) {
        int dif = abs(cur_angles[i] - dest_angles[i]);
        int step = dif / angleSteps[i];
        if (stepNum < step) stepNum = step;
      }
    }
  }
  // Motoren schrittweise bewegen
  if (stepNum > 0) {
    for (int i = 0; i < 6; i++) {
      int angleStepMove = (dest_angles[i] - cur_angles[i]) / stepNum;
      cur_angles[i] += angleStepMove;
      if (cur_angles[i] > angle_max[i]) cur_angles[i] = angle_max[i];
      else if (cur_angles[i] < angle_min[i]) cur_angles[i] = angle_min[i];
      servo[i].write(cur_angles[i]);
    }
    stepNum--;
    delay(20);
  }
}

remote_arm.php (PHP/Web-App)

<!DOCTYPE html>
<html>
<head>
<title>Arduino - Arm Robot - Web</title>
<meta name="viewport" content="width=device-width, initial-scale=0.9">
<style>
  body { text-align: center; }
  canvas { background-color: #FFFFFF; }
</style>
<script>
var MOTOR_1 = 0, MOTOR_2 = 1, MOTOR_3 = 2, MOTOR_4 = 3, MOTOR_5 = 4, MOTOR_6 = 5;
var ARM_LENGTH_1 = 95, ARM_LENGTH_2 = 88, ARM_LENGTH_3 = 155;
var SOLE_WIDTH = 160, SOLE_HEIGHT = 73;
var flywheel_img = new Image(); flywheel_img.src = "flywheel.png";
var zone_A_radius = ARM_LENGTH_1 + ARM_LENGTH_2 + ARM_LENGTH_3;
var pivot_x = zone_A_radius + 5;
var pivot_y = zone_A_radius + 5;
var zone_B_radius = 100;
var zone_B_center_x = pivot_x / 2;
var zone_B_center_y = -(zone_B_radius + SOLE_HEIGHT + 20);
var zone_B_last_angle = 0;
var zone_C_radius = 70;
var zone_C_center_x = -pivot_x / 2;
var zone_C_center_y = -(zone_B_radius + SOLE_HEIGHT + 20);
var zone_C_last_angle = 0;
var zone_D_width = 30, zone_D_height = 200.0;
var zone_D_center_x = 0;
var zone_D_center_y = -(zone_B_radius + SOLE_HEIGHT + 110);
var grip_height = 70, grip_width = 100;
var canvas_width = zone_A_radius * 2 + 10;
var canvas_height = zone_A_radius + SOLE_HEIGHT + zone_B_radius * 2 + 180;
var grip_max_angle = 62.0;
var click_state = 0;
var mouse_xyra = {x:0, y:0, r:0.0, a:0.0};
var angles = [90, 90, 180, 180, 90, 17];
var ws = null, ctx = null, last_time = 0;
var a = 0, b = 0, c = 0;

function init() {
  servo = document.getElementById("servo");
  servo.width = canvas_width; servo.height = canvas_height;
  servo.addEventListener("touchstart", mouse_down);
  servo.addEventListener("touchend", mouse_up);
  servo.addEventListener("touchmove", mouse_move);
  servo.addEventListener("mousedown", mouse_down);
  servo.addEventListener("mouseup", mouse_up);
  servo.addEventListener("mousemove", mouse_move);
  ctx = servo.getContext("2d");
  ctx.translate(pivot_x, pivot_y);
  ctx.rotate(Math.PI);
  // Quadratische Gleichungsparameter für Geometrie
  a = 4 * ARM_LENGTH_1 * ARM_LENGTH_3;
  b = 2 * (ARM_LENGTH_2 * ARM_LENGTH_1 + ARM_LENGTH_2 * ARM_LENGTH_3);
  c = Math.pow(ARM_LENGTH_1, 2) + Math.pow(ARM_LENGTH_2, 2) + Math.pow(ARM_LENGTH_3, 2) - 2 * ARM_LENGTH_1 * ARM_LENGTH_3;
}

function update_view() {
  ctx.clearRect(-canvas_width/2, -SOLE_HEIGHT, canvas_width, canvas_height);
  ctx.save();
  // Zone A zeichnen
  ctx.fillStyle = "#D9D9D9";
  ctx.beginPath(); ctx.arc(0,0,zone_A_radius, 0, 2*Math.PI); ctx.fill();
  ctx.fillStyle = "#FFFFFF";
  ctx.fillRect(-pivot_x, -canvas_height + pivot_y, canvas_width, canvas_height - pivot_y - SOLE_HEIGHT);
  // Arm-Segmente
  ctx.strokeStyle = "#00979D"; ctx.fillStyle = "#FF4500"; ctx.lineWidth = 20;
  ctx.rotate(angles[MOTOR_2] / 180 * Math.PI);
  ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_1,0); ctx.stroke(); draw_pivot(0,0);
  ctx.translate(ARM_LENGTH_1,0);
  ctx.rotate(-Math.PI + angles[MOTOR_3] / 180 * Math.PI);
  ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_2,0); ctx.stroke(); draw_pivot(0,0);
  ctx.translate(ARM_LENGTH_2,0);
  ctx.rotate(-Math.PI + angles[MOTOR_4] / 180 * Math.PI);
  ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(ARM_LENGTH_3,0); ctx.stroke(); draw_pivot(0,0);
  ctx.restore();
  // Zone B (Basis)
  ctx.strokeStyle = "#00979D"; ctx.lineWidth = 6;
  var angle = (angles[MOTOR_1] + 45) * Math.PI / 180;
  ctx.save(); ctx.translate(zone_B_center_x, zone_B_center_y); ctx.rotate(angle);
  ctx.drawImage(flywheel_img, -zone_B_radius, -zone_B_radius, zone_B_radius*2, zone_B_radius*2);
  ctx.beginPath(); ctx.arc(0,0,zone_B_radius,0,2*Math.PI); ctx.stroke(); ctx.restore();
  // Zone C (Greifer-Rotation)
  angle = (angles[MOTOR_5] + 45) * Math.PI / 180;
  ctx.save(); ctx.translate(zone_C_center_x, zone_C_center_y); ctx.rotate(angle);
  ctx.drawImage(flywheel_img, -zone_C_radius, -zone_C_radius, zone_C_radius*2, zone_C_radius*2);
  ctx.beginPath(); ctx.arc(0,0,zone_C_radius,0,2*Math.PI); ctx.stroke(); ctx.restore();
  // Zone D (Greifer)
  ctx.fillStyle = "#00DEE6"; ctx.lineWidth = 15;
  var grip_dist = Math.floor(angles[MOTOR_6] / grip_max_angle * zone_D_height);
  ctx.fillRect(zone_D_center_x - zone_D_width/2, zone_D_center_y - zone_D_height/2, zone_D_width, zone_D_height);
  ctx.lineWidth = 1; ctx.strokeStyle = "#FFFFFF";
  // Greiferbacken zeichnen (Gradient)
  var center_x = zone_D_center_x, center_y = zone_D_center_y + grip_dist/2;
  var grd = ctx.createLinearGradient(center_x, center_y, center_x, center_y + grip_height);
  grd.addColorStop(0,"#004A4D"); grd.addColorStop(1,"#b3fcff");
  ctx.fillStyle = grd;
  ctx.beginPath(); ctx.moveTo(center_x + grip_width/2, center_y);
  ctx.bezierCurveTo(center_x + grip_width/4, center_y + grip_height, center_x - grip_width/4, center_y + grip_height, center_x - grip_width/2, center_y);
  ctx.closePath(); ctx.fill(); ctx.stroke();
  center_y = zone_D_center_y - grip_dist/2;
  grd = ctx.createLinearGradient(center_x, center_y, center_x, center_y - grip_height);
  grd.addColorStop(0,"#004A4D"); grd.addColorStop(1,"#b3fcff");
  ctx.fillStyle = grd;
  ctx.beginPath(); ctx.moveTo(center_x + grip_width/2, center_y);
  ctx.bezierCurveTo(center_x + grip_width/4, center_y - grip_height, center_x - grip_width/4, center_y - grip_height, center_x - grip_width/2, center_y);
  ctx.closePath(); ctx.fill(); ctx.stroke();
  ctx.restore();
  // Sockel
  ctx.fillStyle = "#006468";
  ctx.fillRect(-SOLE_WIDTH/2, -SOLE_HEIGHT, SOLE_WIDTH, SOLE_HEIGHT);
}

function event_handler(event) {
  var x, y, r, alpha;
  if (event.touches) {
    x = (event.touches[0].pageX - event.touches[0].target.offsetLeft) - pivot_x;
    y = (event.touches[0].pageY - event.touches[0].target.offsetTop) - pivot_y;
  } else {
    x = event.offsetX - pivot_x; y = event.offsetY - pivot_y;
  }
  x = -x; y = -y;
  // Zone A?
  r = Math.sqrt(x*x + y*y);
  if (r < zone_A_radius && y > -SOLE_HEIGHT) {
    if (x < SOLE_WIDTH/2 && x > -SOLE_WIDTH/2 && y < 0) return false;
    alpha = Math.atan2(y, x); if (alpha < 0) alpha += 2*Math.PI;
    mouse_xyra = {x:x, y:y, r:r, a:alpha};
    return geometric_calculation();
  }
  // Zone B?
  var temp_x = x - zone_B_center_x, temp_y = y - zone_B_center_y;
  var dist = Math.sqrt(temp_x*temp_x + temp_y*temp_y);
  if (dist < zone_B_radius) {
    var ang = Math.atan2(temp_y, temp_x) * 180 / Math.PI;
    if (click_state == 0) zone_B_last_angle = ang;
    else {
      if (Math.abs(ang) > 90 && ang * zone_B_last_angle < 0) {
        zone_B_last_angle = (zone_B_last_angle > 0) ? -180 : 180;
      }
      angles[MOTOR_1] += Math.floor(ang - zone_B_last_angle);
      angles[MOTOR_1] = Math.max(0, Math.min(180, angles[MOTOR_1]));
      zone_B_last_angle = ang;
    }
    return true;
  }
  // Zone C?
  temp_x = x - zone_C_center_x; temp_y = y - zone_C_center_y;
  dist = Math.sqrt(temp_x*temp_x + temp_y*temp_y);
  if (dist < zone_C_radius) {
    var ang = Math.atan2(temp_y, temp_x) * 180 / Math.PI;
    if (click_state == 0) zone_C_last_angle = ang;
    else {
      if (Math.abs(ang) > 90 && ang * zone_C_last_angle < 0) {
        zone_C_last_angle = (zone_C_last_angle > 0) ? -180 : 180;
      }
      angles[MOTOR_5] += Math.floor(ang - zone_C_last_angle);
      angles[MOTOR_5] = Math.max(0, Math.min(180, angles[MOTOR_5]));
      zone_C_last_angle = ang;
    }
    return true;
  }
  // Zone D?
  temp_x = Math.abs(x - zone_D_center_x); temp_y = Math.abs(y - zone_D_center_y);
  if (temp_x < zone_D_width/2 && temp_y < zone_D_height/2) {
    var ang = temp_y / (zone_D_height/2) * grip_max_angle;
    angles[MOTOR_6] = Math.floor(ang);
    return true;
  }
  return false;
}

function geometric_calculation() {
  var c_ = c - Math.pow(mouse_xyra.r, 2);
  var delta = b*b - 4*a*c_;
  if (delta < 0) return false;
  var x1 = (-b + Math.sqrt(delta)) / (2*a);
  var x2 = (-b - Math.sqrt(delta)) / (2*a);
  var x = x1; if (x > 1) return false;
  var alpha = Math.acos(x) * 180 / Math.PI;
  var beta = 180 - alpha;
  var cos_C = (Math.pow(mouse_xyra.r, 2) + Math.pow(ARM_LENGTH_1, 2) - (Math.pow(ARM_LENGTH_2, 2) + Math.pow(ARM_LENGTH_3, 2) + 2*ARM_LENGTH_2*ARM_LENGTH_3*x)) / (2*mouse_xyra.r*ARM_LENGTH_1);
  var angle_C = Math.acos(cos_C);
  var gamma = (angle_C + mouse_xyra.a) % (2*Math.PI) * 180 / Math.PI;
  if (gamma < 0) gamma += 360;
  if (gamma > 180) { var temp = gamma - mouse_xyra.a * 180 / Math.PI; gamma -= 2*temp; beta = 360 - beta; }
  if (gamma < 0 || gamma > 180) return false;
  angles[MOTOR_3] = Math.floor(beta);
  angles[MOTOR_4] = Math.floor(beta);
  angles[MOTOR_2] = Math.floor(gamma);
  return true;
}

function draw_pivot(x, y) {
  ctx.beginPath(); ctx.arc(x,y,10,0,2*Math.PI); ctx.stroke(); ctx.fill();
}

function ws_onmessage(e_msg) { e_msg = e_msg || window.event; }
function ws_onopen() {
  document.getElementById("ws_state").innerHTML = "OPEN";
  document.getElementById("wc_conn").innerHTML = "Disconnect";
  angles_change_notice(); update_view();
}
function ws_onclose() {
  document.getElementById("ws_state").innerHTML = "CLOSED";
  document.getElementById("wc_conn").innerHTML = "Connect";
  ws.onopen = ws.onclose = ws.onmessage = null; ws = null;
}
function wc_onclick() {
  if (!ws) {
    ws = new WebSocket("ws:///remote_arm", "text.phpoc");
    document.getElementById("ws_state").innerHTML = "CONNECTING";
    ws.onopen = ws_onopen; ws.onclose = ws_onclose; ws.onmessage = ws_onmessage;
  } else ws.close();
}
function mouse_down() { if (!ws) return; if (event.touches && event.touches.length > 1) click_state = event.touches.length; if (click_state > 1) return; if (event_handler(event)) { click_state = 1; angles_change_notice(); } event.preventDefault(); }
function mouse_up() { click_state = 0; }
function mouse_move() {
  if (!ws) return;
  var now = Date.now(); if (now - last_time < 50) return; last_time = now;
  if (event.touches && event.touches.length > 1) click_state = event.touches.length;
  if (click_state > 1 || !click_state) return;
  if (event_handler(event)) { click_state = 1; angles_change_notice(); }
  event.preventDefault();
}
function angles_change_notice() {
  if (ws && ws.readyState == 1) { ws.send(angles.join(",") + "\r\n"); update_view(); }
}
window.onload = init; setTimeout(update_view, 500);
</script>
</head>
<body>
<h2>Arduino - Control Arm Robot via Web</h2>
<canvas id="servo"></canvas>
<p>WebSocket : <span id="ws_state">null</span><br></p>
<button id="wc_conn" type="button" onclick="wc_onclick()">Connect</button>
</body>
</html>

Schaltplan

Externe Stromversorgung für die 6 Motoren erforderlich.

Fernsteuerung eines 6-DOF Arduino-Roboterarms per Webinterface

Herstellungsprozess

  1. Simple Pi Robot: 2-WD Autonomer Rover mit Raspberry Pi bauen
  2. Arduino Power Control Center: N-FET, P-FET, Relais & RTC Kit
  3. Fernsteuerung eines Roboterarms per IoT: Eigenbau mit Arduino MKR1000
  4. Servogesteuerter Hindernisvermeidungs-Roboter: Bauen mit Arduino & HC-SR04
  5. Autonomer Linienfolger-Roboter mit Arduino UNO
  6. DIY Arduino Luftbefeuchter-Steuerung mit Relais – Sichere Hochspannungs-Installation
  7. Arduino UNO & WiFi Shield: Echtzeit-Temperaturanzeige im Web via Serial
  8. LittleArm 2C: Robuster 3D-gedruckter Arduino-Roboterarm für STEM-Bildung
  9. Autonomer Hausassistent-Roboter bauen: Komplette Teileliste & Setup-Anleitung
  10. Smartes Indoor-Navigations-Roboter-Kit für Arduino-Projekte