Tutorial · Fortgeschritten · 22 Min. Lesezeit

Inverse Kinematik eines Zwei-Gelenkarms

Inverse Kinematik für einen planaren Zwei-Gelenkarm verstehen: Ellbogen-oben- und -unten-Lösungen, Arbeitsraum, Singularitäten und unerreichbare Ziele.

Vorwärtskinematik ist leicht: Bei gegebenen Gelenkwinkeln die Gliedtransformationen multiplizieren und man weiß, wo das Werkzeug ist. Inverse Kinematik läuft andersherum – bei gegebener Zielposition Gelenkwinkel finden, die sie erreichen –, und in dieser Richtung leben die interessanten Probleme. Ein planarer Zwei-Gelenkarm ist das kleinste Beispiel, das sie alle zeigt. Im Roboterarmsimulator erkunden.

Der erreichbare Arbeitsraum

Mit Gliedlängen L₁ und L₂ erreicht das Werkzeug nur Punkte in einem Kreisring (einem Ring). Der Außenradius ist L₁ + L₂ (Arm voll gestreckt) und der Innenradius |L₁ − L₂| (Arm voll gefaltet). Alles außerhalb des Rings ist zu fern; alles im Loch ist zu nah. Keine Gelenkwinkel erreichen diese Punkte – die korrekte Antwort lautet schlicht „unerreichbar“.

Ein Zwei-Gelenk-Roboterarm zweimal von derselben Basis zum selben Zielpunkt gezeichnet, einmal mit nach oben gebeugtem und einmal mit nach unten gebeugtem Ellbogen, was zwei verschiedene Gelenkkonfigurationen zeigt, die dieselbe Position erreichen. Ein äußerer Bogen markiert die Reichweitengrenze bei Summe beider Gliedlängen, und ein innerer gestrichelter Bogen markiert die innere Grenze. Ein Hinweis besagt, dass jenseits der Reichweitengrenze keine Lösung existiert und arccos NaN liefert.
Zwei Sätze Gelenkwinkel setzen das Werkzeug exakt an dieselbe Stelle. Zwischen beiden zu wählen ist deine Entscheidung, nicht die der Mathematik. SVG herunterladen

Zwei Lösungen: Ellbogen oben und Ellbogen unten

Für die meisten Ziele im Ring kann sich der Ellbogen auf zwei Arten beugen und das Werkzeug trotzdem auf denselben Punkt setzen. Das sind die Ellbogen-oben- und Ellbogen-unten-Konfigurationen. Sie stammen aus einem ± im Kosinussatzwinkel des zweiten Gelenks:

cos(θ₂) = (x² + y² − L₁² − L₂²) / (2·L₁·L₂)
θ₂ = ± acos(cos(θ₂))        # + is one elbow, − is the other
θ₁ = atan2(y, x) − atan2(L₂·sin θ₂, L₁ + L₂·cos θ₂)

Weil es zwei gültige Antworten gibt, ist Inverskinematik keine einzelne Funktion – sie ist eine Wahl. Roboter wählen die Konfiguration, die Gelenke von ihren Grenzen fernhält, Bewegung aus der aktuellen Pose minimiert oder einem Hindernis ausweicht.

Singularitäten

An den Arbeitsraumrändern – Arm voll gestreckt oder voll gefaltet – verschmelzen beide Lösungen zu einer. Das sind Singularitäten: Nahe ihnen kann eine kleine Werkzeugbewegung eine riesige Gelenkbewegung verlangen. Planer werden langsam oder routen um singuläre Posen, weil der Arm kurz die Fähigkeit verliert, das Werkzeug in eine Richtung zu bewegen.

Wann gibt es keine Lösung?

Ein Ziel hat keine Lösung, wenn es außerhalb des erreichbaren Rings liegt, wenn es einen Gelenkwinkel verlangen würde, den die Hardware verbietet, oder wenn es eine Orientierung verlangt, die die vorhandenen Gelenke nicht erzeugen können. Ein guter Simulator unterscheidet diese Fälle ehrlich, statt auf die nächste Pose zu springen und Erfolg vorzugeben.

Analytische gegen numerische Löser

Für zwei, drei Glieder hat die Geometrie eine geschlossene (analytische) Lösung – man rechnet die Gelenkwinkel direkt, exakt und schnell. Das macht analytische IK ideal zum Lehren und Testen. Redundante oder voll räumliche Arme haben meist keine geschlossene Form und brauchen numerische Löser, die sich iterativ einer Lösung nähern – allgemeiner, aber langsamer und schwerer zu durchschauen, weil sie Lösungen verfehlen oder nahe Singularitäten hängenbleiben können.

Der Löser, sicher geschrieben

Die Gleichungen oben sind korrekt und erzeugen auf echter Hardware trotzdem NaN. Hier dieselbe Mathematik mit jedem Fehler explizit behandelt:

enum IkStatus { IK_OK, IK_TOO_FAR, IK_TOO_CLOSE, IK_JOINT_LIMIT, IK_SINGULAR };

struct IkResult {
  IkStatus status;
  float theta1, theta2;      // radians
};

IkResult solveIk(float x, float y, float L1, float L2, bool elbowUp) {
  IkResult r{};

  const float r2 = x * x + y * y;
  const float reach = sqrtf(r2);

  // 1. Reachability FIRST, before any trigonometry.
  //    A small epsilon keeps a target exactly on the boundary from
  //    producing an acos argument of 1.0000001 through rounding.
  const float eps = 1e-4f;
  if (reach > L1 + L2 - eps)          { r.status = IK_TOO_FAR;   return r; }
  if (reach < fabsf(L1 - L2) + eps)   { r.status = IK_TOO_CLOSE; return r; }

  // 2. Now acos is guaranteed to be in range.
  float c2 = (r2 - L1 * L1 - L2 * L2) / (2.0f * L1 * L2);
  c2 = constrain(c2, -1.0f, 1.0f);              // belt and braces
  const float s2 = sqrtf(1.0f - c2 * c2);

  r.theta2 = elbowUp ? atan2f(s2, c2) : atan2f(-s2, c2);

  // 3. atan2, never atan — this is what gets all four quadrants right.
  r.theta1 = atan2f(y, x) - atan2f(L2 * sinf(r.theta2), L1 + L2 * cosf(r.theta2));

  // 4. Warn near the boundary, where the arm is ill-conditioned.
  if (reach > (L1 + L2) * 0.95f) { r.status = IK_SINGULAR; return r; }

  // 5. Mechanical limits are part of the answer, not an afterthought.
  if (r.theta1 < T1_MIN || r.theta1 > T1_MAX ||
      r.theta2 < T2_MIN || r.theta2 > T2_MAX) { r.status = IK_JOINT_LIMIT; return r; }

  r.status = IK_OK;
  return r;
}

Fünf Details trennen das von der Lehrbuchversion – und jedes entspricht einem echten Fehler:

Erreichbarkeit vor Trigonometrie. Liegt das Ziel jenseits L1 + L2, erzeugt die Kosinusregel ein Argument außerhalb −1 bis 1 und acos liefert NaN. Dieses NaN fließt als Winkelunsinn in den Servobefehl – weshalb ein unerreichbares Ziel so oft eine heftige Bewegung statt Bewegungsverweigerung erzeugt.

Das Epsilon. Ein Ziel, das rechnerisch exakt auf der Grenze sitzt, kann nach Gleitkommarundung c2 = 1,0000001 liefern. Erreichbarkeit mit kleiner Marge zu testen fängt es ab, bevor acos es tut.

atan2f, nicht atan. Einfaches atan nimmt einen einzigen Quotienten und kann ein Ziel vorne nicht von seinem Spiegelbild hinten unterscheiden – der Arm arbeitet also in zwei Quadranten und spiegelt in den anderen beiden. Das ist der häufigste einzelne Fehler in einer ersten IK-Implementierung.

Eine Singularitätswarnung statt Verweigerung. Nahe Vollstreckung ist die Lösung weiter gültig, aber schlecht konditioniert – eine kleine Werkzeugbewegung verlangt eine riesige Gelenkbewegung. Sie zu flaggen lässt den Aufrufer langsamer werden oder darum herumrouten.

Gelenkgrenzen als Status. Ein Arm, dessen Schulter nicht unter −10° darf, hat Ziele, die geometrisch erreichbar und mechanisch nicht sind. Lautloses Klemmen erzeugt einen Arm, der irgendwo nahe dem Ziel landet und Erfolg meldet.

Zwischen beiden Lösungen wählen

Die Mathematik liefert beide Äste und kann nicht sagen, welchen nehmen. Das ist eine Robotikentscheidung – und es gibt vier gängige Regeln:

Regel Den Ast wählen, der… Gut für
Minimalbewegung Den aktuellen Gelenkwinkeln am nächsten liegt Weiche fortlaufende Pfade – die übliche Voreinstellung
Fester Ast Immer Ellbogen oben (oder immer unten) ist Vorhersehbarkeit; einen Tisch unter dem Arm meiden
Gelenkgrenzenreserve Beide Gelenke am weitesten von ihren Grenzen hält Arme mit eingeschränktem Weg
Hindernisvermeidung Mit bekanntem Hindernis nicht kollidiert Vollgestellte Arbeitsräume

Minimalbewegung ist die richtige Voreinstellung – und der Grund ist, was ohne geschieht: Wechselt der Löser mitten im Pfad lautlos den Ast, kippt der Arm durch eine große, schnelle, völlig ungeplante Bewegung. An einem echten Arm ist das alarmierend und kann destruktiv sein.

// Pick the branch closest to where the arm already is.
IkResult up   = solveIk(x, y, L1, L2, true);
IkResult down = solveIk(x, y, L1, L2, false);

if (up.status == IK_OK && down.status == IK_OK) {
  const float costUp   = fabsf(up.theta1   - cur1) + fabsf(up.theta2   - cur2);
  const float costDown = fabsf(down.theta1 - cur1) + fabsf(down.theta2 - cur2);
  return (costUp <= costDown) ? up : down;
}
return (up.status == IK_OK) ? up : down;

Gerade Linien brauchen IK bei jedem Schritt

Zwischen zwei Posen im Gelenkraum zu interpolieren – jedes Gelenk linear von Start- zu Endwinkel bewegen – ist einfach und lässt das Werkzeug einer Kurve folgen. Für viele Aufgaben ist das in Ordnung. Für alles, was geradeaus fahren muss, interpoliert man im kartesischen Raum und löst IK bei jedem Schritt:

void moveStraight(float x0, float y0, float x1, float y1, int steps) {
  for (int i = 1; i <= steps; i++) {
    const float t = (float)i / steps;
    const float x = x0 + (x1 - x0) * t;
    const float y = y0 + (y1 - y0) * t;

    IkResult r = solveClosest(x, y);
    if (r.status != IK_OK) { abortMove(r.status); return; }   // do not skip the point
    commandJoints(r.theta1, r.theta2);
  }
}

Zwei Dinge werden beim ersten Mal sichtbar. Gelenkgeschwindigkeit ist nicht konstant, obwohl Werkzeugtempo es ist – nahe der Arbeitsraumkante müssen sich Gelenke für dieselbe Werkzeugbewegung weit schneller bewegen. Und eine Gerade kann durch einen Bereich laufen, den der Arm nicht erreicht, selbst wenn beide Endpunkte in Ordnung sind – weshalb die Statusprüfung in der Schleife zählt statt nur an den Enden.

Von Gelenkwinkeln zu Servozählwerten

Der Löser liefert Gelenkwinkel in einem mathematischen Frame. Ein Servo kennt diesen Frame nicht: Seine Null liegt dort, wo das Horn montiert wurde, und es dreht eventuell andersherum.

struct Joint {
  uint8_t channel;
  float   offsetDeg;   // servo angle when the joint is at mathematical zero
  int8_t  sign;        // +1 or -1
  float   minDeg, maxDeg;
};

int servoAngle(const Joint &j, float jointRad) {
  const float deg = jointRad * 57.2957795f;
  const float clamped = constrain(deg, j.minDeg, j.maxDeg);
  return (int)(j.offsetDeg + j.sign * clamped);
}

Diese Struktur ist die gesamte Brücke zwischen Kinematik und Hardware – und sie explizit zu halten ermöglicht, einen Löserfehler von einem Kalibrierfehler zu unterscheiden. Ein Arm, der sich weich bewegt und nirgends nahe dem Ziel landet, ist fast immer das, nicht die Mathematik.

Fehlersuche

Symptom Wahrscheinliche Ursache Lösung
Erreicht vorne korrekt, spiegelt hinten atan statt atan2 atan2 löst alle vier Quadranten auf
Liefert NaN, Arm stößt acos-Argument außerhalb Bereich Erreichbarkeit vor der Trigonometrie testen
Ziele nahe der Basis scheitern Im |L1 − L2|-Loch Korrekt – der Arbeitsraum ist ein Ring, keine Scheibe
Arm kippt mitten im Pfad heftig Löser wechselte Ast Den Ast nächst der aktuellen Pose wählen
Gelenkgeschwindigkeitsspitze nahe Vollstreckung Singularität Geplante Pfade innerhalb ~95 % Maximalreichweite halten
Rundreise korrekt, landet am falschen Ort Servooffset oder -vorzeichen Offset und Richtung jedes Gelenks kalibrieren
Konsistent zu kurz bei Vollreichweite Schwerkraftdurchhang, nicht Mathematik Schulterdrehmomentbudget prüfen; gegenbalancieren
Landet je nach Anfahrt anders Getriebespiel Ein Ziel immer aus derselben Richtung anfahren
Geradenbewegung scheitert mittendrin Pfad verlässt den Arbeitsraum zwischen Endpunkten Status bei jedem Interpolationsschritt prüfen

Selbst ausprobieren

Im Roboterarmsimulator das Ziel herumziehen und dem Arm live beim Lösen zusehen. Den Ellbogenast umschalten, um die Alternativlösung zu sehen, das Ziel aus dem Ring schieben, um ein ehrliches „unerreichbar“ zu sehen, und zur Arbeitsraumkante fahren, um eine Singularität zu spüren.

Den Graphen erkunden

Teil dieser Bauprojekte

Projekte und Lernpfade, die dieses Tutorial enthalten.

Weiterführende Literatur

Quellen