RobotForge
Published·~15 min

Forward kinematics of a 2-DOF planar arm

Two joints, two link lengths, and high-school trig. Compute where the end-effector is, plot the reachable workspace, and you've unlocked the foundation of every arm problem.

by RobotForge
#kinematics#arm#beginner

Every serious robotics textbook spends a chapter on forward kinematics before it says anything interesting. There's a reason: if you can't answer "where is the hand?" you can't do anything with the arm. Let's do the smallest useful case in ten minutes.

The setup

A planar arm with two revolute joints — shoulder and elbow. The shoulder is at the origin. Link 1 has length L1. Link 2 has length L2. Joint angles are θ1 (shoulder) and θ2 (elbow, relative to link 1).

Your job: given (θ1, θ2, L1, L2), find the end-effector position (x, y).

L₁ L₂ θ₁ θ₂ shoulder (0,0) elbow end-effector (x, y) reachable annulus |L₁−L₂| ≤ r ≤ L₁+L₂
2-DOF planar arm: joint angles θ₁, θ₂ place the end-effector anywhere in the orange annulus.

Why forward first, not inverse

Inverse kinematics (given (x, y), find the angles) is the more useful direction in practice — it's what you do when you want the arm to reach a point. But FK is the right place to start because:

  • FK has exactly one answer. IK often has zero or two.
  • FK is the building block. You cannot write IK without knowing FK.
  • FK teaches the coordinate-frame thinking that every future topic requires.

Derive it on paper

Place joint 1 at the origin. Joint 2 sits at the end of link 1:

x1 = L1 · cos(θ1)
y1 = L1 · sin(θ1)

Link 2 is oriented at angle θ1 + θ2 in the world frame (because joint 2's angle is relative to link 1, and link 1 was at angle θ1). So the end-effector is at:

x = L1 · cos(θ1) + L2 · cos(θ1 + θ2)
y = L1 · sin(θ1) + L2 · sin(θ1 + θ2)

That's it. Two lines of trig. Now let's make it real.

Python implementation

import math

def forward_kinematics(theta1, theta2, L1=1.0, L2=1.0):
    """Return (x, y) end-effector position for a 2-DOF planar arm.

    Angles in radians. Joint 2 is measured relative to link 1.
    """
    x = L1 * math.cos(theta1) + L2 * math.cos(theta1 + theta2)
    y = L1 * math.sin(theta1) + L2 * math.sin(theta1 + theta2)
    return x, y

# Quick sanity check: arms folded = origin, arm straight out = (L1+L2, 0)
print(forward_kinematics(0, math.pi))   # (0, 0)   both links overlap
print(forward_kinematics(0, 0))          # (2, 0)   straight out along +x
print(forward_kinematics(math.pi/2, 0))  # (0, 2)   straight up

Plot the workspace

The reachable set of points is a donut — inner radius |L1 − L2|, outer radius L1 + L2. Let's verify visually:

import numpy as np
import matplotlib.pyplot as plt

L1, L2 = 1.0, 0.7
pts = []
for t1 in np.linspace(-math.pi, math.pi, 60):
    for t2 in np.linspace(-math.pi, math.pi, 60):
        pts.append(forward_kinematics(t1, t2, L1, L2))

xs, ys = zip(*pts)
plt.scatter(xs, ys, s=1, alpha=0.3)
plt.gca().set_aspect('equal')
plt.title(f"Reachable workspace, L1={L1}, L2={L2}")
plt.show()

You'll see an annulus. The hole in the middle exists when L1 ≠ L2 — the arm can never quite reach points inside radius |L1 − L2| because neither joint bends that much.

The matrix version (for when you need it)

The trig-based form above is clear but doesn't generalize. For any n-link arm, we use homogeneous transformation matrices. Each link contributes a 3×3 matrix (in 2D) or 4×4 (in 3D) that packages rotation and translation:

def T(theta, L):
    c, s = math.cos(theta), math.sin(theta)
    return np.array([
        [c, -s, L*c],
        [s,  c, L*s],
        [0,  0,   1],
    ])

T1 = T(theta1, L1)
T2 = T(theta2, L2)
T_total = T1 @ T2      # compose
x, y = T_total[0, 2], T_total[1, 2]

This style extends to 6-DOF arms, spatial arms, and parallel mechanisms without changing form. We'll come back to it in Lesson 4 (Product of Exponentials).

What this doesn't cover

  • 3D arms — you need rotation matrices in SO(3), not just cos/sin. Lesson 2.
  • Inverse kinematics — Lesson 5 (analytical) and Lesson 6 (iterative).
  • Velocity and force — that's the Jacobian, Lesson 7.
  • Dynamics — what torques you need to produce a motion. Lesson 8.

Exercise: verify your intuition

Before moving on, answer these without running code:

  1. If L1 = L2 = 1 and θ1 = θ2 = 0, where is the end-effector?
  2. If you rotate θ1 by +π and leave θ2 at 0, where does the arm point?
  3. What's the maximum distance the end-effector can be from the origin? The minimum?

If you got all three, you own this material. If not, re-read the derivation and try again — the rest of the track builds on this.

Next up: rigid-body transforms — the clean way to stop worrying about which angle is relative to what.

Comments

    Sign in to post a comment.