Reaching a point: inverse kinematics of a two-joint arm

Given where the hand should be, find the two joint angles. The law of cosines does it in four lines, and the arm has two answers.

45 min

Cette page n'est pas encore traduite, elle s'affiche donc en anglais.

Forward and inverse

A robot arm is a chain of links joined by motors. Two questions come up all the time:

You knowYou wantDifficulty
forward kinematicsthe joint angleswhere the hand iseasy: add up the links
inverse kinematics (IK)where the hand should bethe joint anglesharder: there may be 0, 1 or 2 answers

Programming an arm with angles is like steering a car by telling it how far to turn each wheel. IK lets you say “put the gripper here” and work out the angles yourself.

Almost every useful arm program is built on IK. A camera finds a cube at some point on the table. A slider on your phone says “10 mm further left”. A drawing program wants the pen to follow a letter. All of them speak in positions, and only IK turns positions into the angles your motors understand.

Forward first

A flat arm with a shoulder at the origin, an upper link of length L1L_1 at angle θ1\theta_1 from the x axis, and a forearm of length L2L_2 bent by θ2\theta_2 relative to the upper link. The tip is at

x=L1cos⁡θ1+L2cos⁡(θ1+θ2)y=L1sin⁡θ1+L2sin⁡(θ1+θ2)\begin{aligned} x &= L_1 \cos\theta_1 + L_2 \cos(\theta_1 + \theta_2) \\ y &= L_1 \sin\theta_1 + L_2 \sin(\theta_1 + \theta_2) \end{aligned}

Each link is an arrow; the tip is where the arrows end when you put them tip to tail. That is vector addition again.

Notice the θ1+θ2\theta_1 + \theta_2. The forearm’s direction in the world is the shoulder angle plus the elbow angle, because the elbow motor sits on the upper arm and turns with it. This “relative angle” habit is how most arms are described, and it matters later: the MeArm in chapter 4 measures its forearm differently.

A quick check with numbers. L1=L2=8L_1 = L_2 = 8 cm, θ1=90°\theta_1 = 90° (upper arm straight up), θ2=−90°\theta_2 = -90° (elbow bent right). The upper arm ends at (0, 8). The forearm points at 90°−90°=0°90° - 90° = 0°, straight right, so the tip is at (8, 8). The formulas agree: x=8cos⁡90°+8cos⁡0°=0+8x = 8\cos 90° + 8\cos 0° = 0 + 8, y=8sin⁡90°+8sin⁡0°=8y = 8 \sin 90° + 8 \sin 0° = 8.

Inverse: two steps with the law of cosines

Step 1, the elbow. The shoulder, the elbow and the target form a triangle with sides L1L_1, L2L_2 and d=x2+y2d = \sqrt{x^2 + y^2}. The law of cosines gives the angle at the elbow directly:

cos⁡θ2=x2+y2−L12−L222 L1L2\cos\theta_2 = \frac{x^2 + y^2 - L_1^2 - L_2^2}{2\,L_1 L_2}

Where does this come from? The law of cosines for the triangle says d2=L12+L22−2L1L2cos⁡γd^2 = L_1^2 + L_2^2 - 2L_1L_2\cos\gamma, where γ\gamma is the inside angle at the elbow. A straight arm has γ=180°\gamma = 180° and θ2=0\theta_2 = 0, so γ=180°−∣θ2∣\gamma = 180° - \lvert\theta_2\rvert and cos⁡γ=−cos⁡θ2\cos\gamma = -\cos\theta_2. Put that in and solve for cos⁡θ2\cos\theta_2: you get the line above.

Step 2, the shoulder. Point the shoulder at the target, then correct for the bend of the elbow:

θ1=atan2⁡(y,x)−atan2⁡ ⁣(L2sin⁡θ2,  L1+L2cos⁡θ2)\theta_1 = \operatorname{atan2}(y, x) - \operatorname{atan2}\!\big(L_2 \sin\theta_2,\; L_1 + L_2 \cos\theta_2\big)

atan2(y, x) is the angle of the point (x, y), and unlike a plain arctangent it knows which quarter of the plane the point is in. The second atan2 is the angle between the upper arm and the line from the shoulder to the tip: it is how far the bent elbow pulls the tip away from where the upper arm points.

Cinématique inverse d'un bras à deux segmentsDéplace la cible. La zone grisée est la portée du bras.
Épaule θ1
60°
Avant-bras θ2 (depuis l'horizontale)
1°
Distance d
139mm

d² = x² + y², cos γ = (L1² + L2² − d²) / (2 L1 L2)

A worked example, all the way through

L1=L2=8L_1 = L_2 = 8 cm, target (10, 6).

Elbow. x2+y2=100+36=136x^2 + y^2 = 100 + 36 = 136. L12+L22=128L_1^2 + L_2^2 = 128. 2L1L2=1282L_1L_2 = 128.

cos⁡θ2=136−128128=0.0625⇒θ2=±86.4°\cos\theta_2 = \frac{136 - 128}{128} = 0.0625 \quad\Rightarrow\quad \theta_2 = \pm 86.4°

Shoulder, elbow-up solution (θ2=−86.4°\theta_2 = -86.4°). The direction to the target is atan2⁡(6,10)=31.0°\operatorname{atan2}(6, 10) = 31.0°. The correction: L2sin⁡θ2=8⋅(−0.998)=−7.98L_2\sin\theta_2 = 8 \cdot (-0.998) = -7.98 and L1+L2cos⁡θ2=8+8⋅0.0625=8.5L_1 + L_2\cos\theta_2 = 8 + 8 \cdot 0.0625 = 8.5, so atan2⁡(−7.98,8.5)=−43.2°\operatorname{atan2}(-7.98, 8.5) = -43.2°. Then

θ1=31.0°−(−43.2°)=74.2°\theta_1 = 31.0° - (-43.2°) = 74.2°

Check with forward kinematics. θ1+θ2=74.2°−86.4°=−12.2°\theta_1 + \theta_2 = 74.2° - 86.4° = -12.2°. x=8cos⁡74.2°+8cos⁡(−12.2°)=2.18+7.82=10.0x = 8\cos 74.2° + 8\cos(-12.2°) = 2.18 + 7.82 = 10.0 and y=8sin⁡74.2°+8sin⁡(−12.2°)=7.70−1.69=6.0y = 8\sin 74.2° + 8 \sin(-12.2°) = 7.70 - 1.69 = 6.0. It lands on the target.

The other solution (θ2=+86.4°\theta_2 = +86.4°) gives θ1=−12.2°\theta_1 = -12.2°. The upper arm points slightly down and the elbow bends up. Same tip, very different arm.

Two answers: elbow up, elbow down

cos⁡θ2\cos\theta_2 is the same for +θ2+\theta_2 and −θ2-\theta_2. Both give a valid arm: one with the elbow bent up, one with it bent down, and the tip in exactly the same place. A real arm usually picks the one that keeps the elbow away from the table, or the one closest to where the arm is now, so it does not swing through a big motion.

Notice something neat in the example: the two solutions swap their numbers. Elbow-up is (74.2°, −86.4°), elbow-down is (−12.2°, 86.4°), and 74.2−86.4=−12.274.2 - 86.4 = -12.2. That is no accident: the two arms are mirror images across the line from the shoulder to the target.

In code

from math import acos, atan2, cos, sin, sqrt

def ik(x, y, l1, l2, elbow_up=True):
    """Joint angles (radians) that put the tip at (x, y), or None if out of reach."""
    c2 = (x * x + y * y - l1 * l1 - l2 * l2) / (2 * l1 * l2)
    if c2 < -1 or c2 > 1:
        return None                      # too far, or too close to the shoulder
    t2 = acos(c2)
    if elbow_up:
        t2 = -t2
    t1 = atan2(y, x) - atan2(l2 * sin(t2), l1 + l2 * cos(t2))
    return t1, t2

def fk(t1, t2, l1, l2):
    """Check: the angles back to the tip position."""
    return (l1 * cos(t1) + l2 * cos(t1 + t2),
            l1 * sin(t1) + l2 * sin(t1 + t2))

Always test IK with FK: put the angles you got back into fk() and check you land on the target. It catches sign mistakes immediately.

from math import degrees

t1, t2 = ik(10, 6, 8, 8)
print(round(degrees(t1), 1), round(degrees(t2), 1))   # 74.2 -86.4
print(fk(t1, t2, 8, 8))                               # (10.0, 6.0), give or take 1e-15

Making it robust: three small upgrades

The four-line ik() is correct, but a real arm needs a little more care.

1. Round-off at full reach. A target exactly at full reach can give cos⁡θ2=1.0000000002\cos\theta_2 = 1.0000000002 because of how computers store decimals, and acos crashes. Allow a tiny margin, then clamp:

def ik_safe(x, y, l1, l2, elbow_up=True, eps=1e-9):
    c2 = (x * x + y * y - l1 * l1 - l2 * l2) / (2 * l1 * l2)
    if c2 > 1 + eps or c2 < -1 - eps:
        return None
    c2 = max(-1.0, min(1.0, c2))         # 1.0000000002 becomes 1.0
    t2 = acos(c2)
    if elbow_up:
        t2 = -t2
    t1 = atan2(y, x) - atan2(l2 * sin(t2), l1 + l2 * cos(t2))
    return t1, t2

2. Reach as far as you can. If a target is too far, you may want the arm to point at it instead of doing nothing. Scale the target back onto the edge of the reachable circle:

def reach_toward(x, y, l1, l2):
    d = sqrt(x * x + y * y)
    if d > l1 + l2:
        k = (l1 + l2) * 0.999 / d        # 0.1 % inside the edge
        x, y = x * k, y * k
    return ik_safe(x, y, l1, l2)

With L1=L2=8L_1 = L_2 = 8 and the target (17, 0), this returns about (2.6°, −5.1°): an almost straight arm pointing right, with the tip at (15.98, 0). Whether “reach as far as you can” is a good idea depends on the job. For a drawing arm it is fine. For an arm that should grab something, it is better to refuse and report the problem.

3. Pick the solution nearest to where you are. Compute both, and take the one with the smallest change of joint angles:

def closest(x, y, l1, l2, current):
    """current = (t1, t2) now. Returns the solution that needs the least movement."""
    best = None
    for up in (True, False):
        s = ik_safe(x, y, l1, l2, up)
        if s is None:
            continue
        cost = abs(s[0] - current[0]) + abs(s[1] - current[1])
        if best is None or cost < best[0]:
            best = (cost, s)
    return None if best is None else best[1]

If the arm is at (0, 1 rad), closest(10, 6, 8, 8, ...) picks the elbow-down answer (−12.2°, 86.4°). If it is at (1, −1 rad), it picks elbow-up. Either way the arm never flips its elbow over without a reason.

Check yourself

What the full course covers

Lesson 1 is free. The other lessons come with the course.

  1. Reaching a point: inverse kinematics of a two-joint arm (this chapter)
  2. Workspace, singularities and joint limits
  3. Coordinate frames and transforms
  4. A real arm in 3D: the MeArm
  5. The Jacobian and numerical IK
  6. Feedback: P, I and D explained with a cruise control
  7. Measuring a DC motor: encoder, step response and a simple model
  8. Tuning a motor speed loop on real hardware
  9. Position over speed: cascaded loops and motion profiles
  10. An arm that follows a target smoothly