Making a Robot Arm: Understanding Rotational Joints

A robot arm achieves its ability to position an end effector (gripper, pen, camera, or tool) anywhere in a three-dimensional workspace by chaining multiple rotational joints — each a servo or motor that rotates one link relative to the previous one — where the number of joints determines the arm’s degrees of freedom (DOF): a 2-DOF arm positions within a plane, a 3-DOF arm reaches any point in a 3D hemisphere, and a 6-DOF arm (like industrial robots) can position the end effector at any point and any orientation in its workspace.

Introduction

Every robot project so far has moved the entire robot through space — the rover drives around obstacles, the line follower crosses the floor. A robot arm works differently: it stays in one place and brings its tool to the work. This distinction — mobile base versus fixed-base manipulator — divides robotics into two great families, and the arm is where you encounter the rich geometry of reaching, rotating, and positioning in three dimensions.

Arms are also where the physical design of a robot matters most. A wheeled rover’s chassis shape matters less than its electronics. A robot arm’s link lengths, joint placement, and structural rigidity determine what it can reach, how accurately it can reach it, and whether it can lift its intended load. Building even a simple 2-DOF or 3-DOF arm teaches the geometry of robotic manipulation in a way that no amount of reading does.

This article covers the concepts and construction of a beginner robot arm: degrees of freedom, joint types, the relationship between joint angles and end-effector position (forward kinematics), workspace analysis, servo selection and control, and a complete 3-DOF arm design you can build and program. Along the way, it introduces the vocabulary and mathematical intuitions that underpin all robot arm design, from this simple hobby arm all the way to the 6-DOF industrial robots welding car frames and assembling electronics.

Degrees of Freedom: How Many Joints Does an Arm Need?

Degrees of freedom (DOF) is the number of independent parameters required to completely specify the configuration of a mechanical system. For a robot arm, each rotating joint contributes one DOF:

DOF and reachable workspace:

1-DOF arm (single joint):
  ──[BASE]──[LINK]──[END]
             ↻ one joint
  Traces an arc in a single plane.
  Use: simple pendulum mechanisms, single-axis scanners.

2-DOF arm (two joints):
  ──[BASE]──[LINK1]──[LINK2]──[END]
              ↻ J1      ↻ J2
  Reaches any point within a 2D disk (the combined reach of both links).
  Use: SCARA robots (horizontal plane work), simple drawing arms.

3-DOF arm (three joints):
  ──[BASE]──[LINK1]──[LINK2]──[LINK3]──[END]
              ↻ J1      ↻ J2     ↻ J3
  With a vertical base rotation: reaches points in a 3D hemisphere.
  Use: most hobby arms, simple pick-and-place, desktop manipulators.

6-DOF arm (six joints):
  The minimum for full 3D position AND orientation control.
  Can place end effector at any point with any orientation in workspace.
  Use: industrial welding/assembly robots, surgical robots, drone arms.

More DOF = more flexibility, more complexity, more control challenges.
A 7-DOF arm is "redundant" (like a human arm) — can reach same
point in infinite configurations, enabling obstacle avoidance in joint space.

For a first robot arm, 3 DOF is the sweet spot: enough capability to be genuinely useful (can reach points anywhere in a hemisphere in front of the base), simple enough to build and control without overwhelming complexity, and enough joints to introduce the interesting geometric challenges of arm kinematics.

What DOF Cannot Tell You: Workspace and Singularities

DOF tells you how many independent motions are possible, but it doesn’t tell you where the arm can reach. Two arms with identical DOF but different link lengths have completely different workspaces. And within a reachable workspace, some configurations are “singular” — positions where the arm loses one or more degrees of freedom and gets stuck:

Singularity example (3-DOF planar arm):

At full extension (all links in a straight line):
  ──[BASE]──[LINK1]──[LINK2]──[LINK3]──[END]

The end effector is at maximum reach. Any small motion
of the endpoint would require extremely fast joint motion
or is geometrically impossible with these joint angles.
Moving the endpoint sideways (perpendicular to the arm) requires
the innermost joint to move while the outer joints compensate —
at full extension, the Jacobian matrix (relating joint velocities
to endpoint velocity) becomes singular (non-invertible).

Practical result: don't design arm tasks that bring the arm
near full extension, and avoid the "dead straight" configuration.
Build with 20% workspace margin: if the arm can reach 30cm,
only use the 24cm radius working zone.

Joint Types in Robot Arms

Not all joints are the same. Understanding joint types helps you choose the right actuator for each joint and understand the arm’s geometry:

Revolute Joint (R)

Rotates around a fixed axis. The most common joint type in robot arms. One degree of freedom per joint. Servo motors, stepper motors, and DC motors with encoders all implement revolute joints.

Revolute joint:

   LINK_IN ──────[JOINT]
                    ↓ axis of rotation (into page)
                 [JOINT]──────LINK_OUT

The output link rotates relative to the input link.
The axis of rotation doesn't move — only the angle changes.
Servo horn, gear, or direct shaft drive implements this.

Prismatic Joint (P)

Slides along an axis without rotating. Extends or retracts. Linear actuators, rack-and-pinion drives, and pneumatic cylinders implement prismatic joints. Less common in hobby arms but important in industrial CNC gantries and delta robots.

Spherical Joint (S)

Allows rotation in all three axes (like a ball-and-socket joint). Three DOF in one joint. Difficult to actuate with standard motors — usually implemented as three consecutive revolute joints with intersecting axes (the “wrist” of a 6-DOF industrial robot is typically three intersecting revolutes approximating a spherical joint).

For a hobby arm, all joints will be revolute. The notation for an arm’s joint configuration uses letters:

Example arm configurations:
  RRR: Three revolute joints → standard 3-DOF hobby arm
  RRP: Two revolute + one prismatic → SCARA robot variant
  RRRRRR: Six revolute joints → standard 6-DOF industrial arm

Servo Motors: The Arm’s Actuators

Hobby servo motors are the natural choice for robot arm joints. They integrate a DC motor, gear train, potentiometer position sensor, and control electronics in a compact package, accepting a PWM signal that directly commands joint angle:

Servo motor PWM control protocol:

Signal: 50Hz PWM (20ms period)
  Pulse width 1000µs (1ms) → servo moves to 0°
  Pulse width 1500µs (1.5ms) → servo moves to 90° (center)
  Pulse width 2000µs (2ms) → servo moves to 180°

Intermediate pulse widths → intermediate angles (linear interpolation)

Arduino: servo.write(angle) handles this automatically
  servo.write(0)   → 1000µs pulse → 0°
  servo.write(90)  → 1500µs pulse → 90°
  servo.write(180) → 2000µs pulse → 180°

Servo Size Classes for Robot Arms

Servo size comparison:

Micro servo (SG90, ~3.5g):
  Torque: 1.8 kg·cm (at 4.8V) ≈ 0.18 N·m
  Speed: 0.1 sec/60° at 4.8V
  Current: 100–200mA stall
  Best for: end effector (gripper), wrist joint (light payload)
  Cost: ~$1–3

Standard servo (MG996R, ~55g):
  Torque: 9.4 kg·cm (at 4.8V) ≈ 0.92 N·m
  Speed: 0.19 sec/60° at 4.8V
  Current: 500–900mA stall
  Best for: shoulder and elbow joints (medium payload)
  Cost: ~$5–12

High-torque servo (DS3225, ~60g):
  Torque: 25 kg·cm (at 6V) ≈ 2.45 N·m
  Speed: 0.15 sec/60° at 6V
  Current: up to 2A stall
  Best for: base rotation with heavy arm, heavy-payload shoulder joints
  Cost: ~$15–25

Torque Requirements: A Critical Calculation

Before buying servos, calculate the torque required at each joint. The joint must support not just the payload but also the weight of all links beyond it:

Torque calculation for a 3-DOF arm:

Arm parameters:
  Link 1 (shoulder to elbow): 15cm long, 80g weight, acts at 7.5cm from shoulder
  Link 2 (elbow to wrist):   12cm long, 50g weight, acts at 6cm from elbow
  Link 3 (wrist to tip):      8cm long, 30g weight, acts at 4cm from wrist
  Payload at tip:             50g

Wrist joint torque (J3) — supports only Link 3 + payload:
  T_wrist = (30g × 4cm) + (50g × 8cm)
           = 120 + 400 = 520 g·cm ≈ 5.2 kg·cm at worst case (horizontal)
  → SG90 (1.8 kg·cm) is insufficient. Use MG996R (9.4 kg·cm) with margin.

Elbow joint torque (J2) — supports Link 2 + Link 3 + payload:
  T_elbow = (50g × 6cm) + (30g × 12+4cm) + (50g × 12+8cm)
           = 300 + 480 + 1000 = 1780 g·cm ≈ 17.8 kg·cm
  → MG996R (9.4 kg·cm) is insufficient at full extension!
  Use DS3225 (25 kg·cm) for elbow, or redesign for shorter links.

Shoulder joint torque (J1) — supports entire arm + payload:
  T_shoulder = all link weights × their distances from shoulder
             = even larger than elbow
  → Definitely requires high-torque servo or counterweighting.

Rule of thumb: use servos rated at 2-3× the calculated requirement.
  Calculations assume worst-case (horizontal arm, full extension).
  Add safety factor for acceleration, dynamic loads, and motor aging.

This calculation is why many beginner robot arms use lightweight materials — foam board, 3D-printed PLA, thin plywood — and small payloads. Reducing link weight dramatically reduces joint torque requirements.

Designing a 3-DOF Robot Arm

A practical 3-DOF arm for beginners uses:

  • Joint 1 (base rotation): Rotates the entire arm around a vertical axis. Full 180° sweep enables working on either side.
  • Joint 2 (shoulder): Tilts the arm up and down from the horizontal.
  • Joint 3 (elbow): Bends the forearm up and down relative to the upper arm.
3-DOF arm configuration (side view):

                         J3 (elbow)
                         ↓
              J2 ────────────────── End effector
            (shoulder)  LINK2
              │
            LINK1
              │
            [BASE] ── J1 (base rotation, looking from above)

With this configuration:

  • J1 sweeps left/right (base yaw)
  • J2 tilts the arm up/down (shoulder pitch)
  • J3 bends the forearm (elbow pitch)

The end effector can reach any point in a hemisphere in front of the base. Adding a wrist joint (J4) and a gripper opens, closes, and orients the grip — a common 4–5-DOF beginner arm configuration.

Materials for Arm Links

Material comparison for arm links:

Foam board (5mm craft foam + paper):
  Weight: ~5g per 10cm link
  Stiffness: low — deflects under load
  Cost: < $1
  Best for: ultra-light test arms, prototyping

3D-printed PLA:
  Weight: ~20–40g per 10cm link depending on infill
  Stiffness: moderate-high
  Cost: ~$0.50–2 in filament
  Best for: custom-shaped links, hub/joint integration
  Notes: design for the servo horn bolt pattern

Laser-cut acrylic (3mm):
  Weight: ~30–50g per 10cm link
  Stiffness: high; brittle under impact
  Cost: ~$1–3 per link depending on size
  Best for: flat planar arms, precise dimensions needed

Aluminum extrusion (15×15 or 20×20):
  Weight: significant per length
  Stiffness: excellent
  Cost: ~$3–8 per link
  Best for: heavy-payload arms, professional builds

Pre-made MeArm-style kits:
  Acrylic sheet laser cut kit, includes all hardware
  Cost: $15–30 complete kit
  Best for: beginners wanting a reliable starting point

Building the Arm: Step-by-Step

Rather than a specific proprietary kit, these instructions describe the construction principles for any 3-servo revolute arm:

Step 1: Mount Base Servo (J1)

The base servo mounts horizontally on a stable platform — a piece of plywood, acrylic, or 3D-printed base plate. The servo horn faces upward and becomes the attachment point for the arm’s first link:

Base servo mounting:

  ┌──────────────────────┐
  │    Base plate        │
  │  ┌────────────────┐  │
  │  │  J1 Servo      │  │
  │  │  (horizontal)  │  │
  │  └────────────────┘  │
  │      ↑ servo horn    │
  │      connects to     │
  │      vertical upright │
  └──────────────────────┘

The servo is fixed to the base. The arm rotates above it.
Mount with M3 screws through servo flange holes.

Step 2: Build the First Link (Shoulder)

The first link connects J1’s horn (at the bottom) to J2’s body (at the top). It must be:

  • Rigid enough not to flex under the arm’s weight
  • Tall enough to elevate J2 above the base (preventing J2 and J3 from hitting the table)
  • Light enough that J1 can rotate it without excessive torque

Attach J2’s servo body to the top of Link 1 such that J2’s axis of rotation is horizontal — this makes J2 a pitch joint (tilts the arm up and down).

Step 3: Build the Second Link (Forearm)

Link 2 connects J2’s horn to J3’s body. Its length determines the arm’s reach and the torque required at J2. Keep it as short as the application allows.

Step 4: Mount the End Effector

J3’s horn connects to whatever end effector the arm carries. For a gripper: a servo-driven parallel jaw or scissor mechanism. For a drawing pen: a simple pen holder bracket. For a camera: a mounting plate.

Wiring Multiple Servos

Each servo requires three connections: power (usually 5V or 6V), ground, and signal. The signal line receives the PWM command from the Arduino:

Wiring for 3-servo arm:

Arduino Pin 9  ──── Servo J1 signal (orange/yellow wire)
Arduino Pin 10 ──── Servo J2 signal
Arduino Pin 11 ──── Servo J3 signal

5V power rail ──┬── Servo J1 VCC (red wire)
                ├── Servo J2 VCC
                └── Servo J3 VCC

GND rail ───────┬── Servo J1 GND (black/brown wire)
                ├── Servo J2 GND
                └── Servo J3 GND

CRITICAL: Do NOT power servos from the Arduino's 5V pin.
At stall, each servo draws 500–900mA. Three servos stalling simultaneously
can draw 1.5–2.7A — far exceeding the Arduino's 500mA USB limit.

Use a separate 5V supply for servos:
  Option 1: 4× AA batteries (6V) → direct to servo power
  Option 2: Buck converter (12V → 5V/3A) from main robot battery
  Option 3: USB power bank with sufficient output current (≥ 2A)

Connect servo GND to Arduino GND (common ground — essential for signal reference).
Do NOT connect servo VCC to Arduino 5V.

Using the Servo Library

The Arduino Servo library handles PWM generation with a simple interface:

#include <Servo.h>

Servo base;     // J1 — base rotation
Servo shoulder; // J2 — shoulder pitch
Servo elbow;    // J3 — elbow pitch

void setup() {
  base.attach(9);       // Attach servo to pin 9
  shoulder.attach(10);
  elbow.attach(11);

  // Move to home position (all servos at 90° — arm pointing straight up)
  moveToHome();
  delay(1000);  // Wait for arm to reach home before any other motion
}

void moveToHome() {
  base.write(90);      // Center: arm faces forward
  shoulder.write(90);  // Level: arm horizontal (adjust based on your arm geometry)
  elbow.write(90);     // Level forearm
}

Forward Kinematics: Where Is the End Effector?

Forward kinematics answers the question: given the joint angles, where is the end effector in 3D space? For a 3-DOF arm this involves trigonometry, but the pattern is entirely learnable.

2-DOF Planar Example (Building Toward 3-DOF)

For a 2-link arm in a vertical plane (base fixed, two revolute joints, both axes parallel):

2-DOF planar arm geometry:

     J2 ───────────── end effector
    /  \
L2 /    angle θ2
  /
J1 ─────── (fixed base, angle θ1 from horizontal)
 \
  L1

End effector position (x, y) from joint 1:
  x = L1 × cos(θ1) + L2 × cos(θ1 + θ2)
  y = L1 × sin(θ1) + L2 × sin(θ1 + θ2)

Where:
  L1, L2 = link lengths (in consistent units, e.g., cm)
  θ1 = angle of link 1 from horizontal (shoulder angle)
  θ2 = angle of link 2 relative to link 1 (elbow angle)

This formula computes the position of the tip given the joint angles. You can compute it in Arduino code to display or log the arm’s position in real time:

// Forward kinematics for 2-DOF planar arm (ignoring base rotation)
const float L1 = 15.0;  // cm, link 1 length (shoulder to elbow)
const float L2 = 12.0;  // cm, link 2 length (elbow to end effector)

void computePosition(float theta1_deg, float theta2_deg,
                     float &x_out, float &y_out) {
  float theta1 = theta1_deg * PI / 180.0;  // Convert degrees to radians
  float theta2 = theta2_deg * PI / 180.0;

  // Forward kinematics equations for planar 2-DOF arm
  x_out = L1 * cos(theta1) + L2 * cos(theta1 + theta2);
  y_out = L1 * sin(theta1) + L2 * sin(theta1 + theta2);
}

// Usage: print end effector position for current servo angles
void reportPosition() {
  float x, y;
  computePosition(shoulder.read(), elbow.read(), x, y);
  Serial.print(F("End effector: x="));
  Serial.print(x, 1);
  Serial.print(F("cm, y="));
  Serial.print(y, 1);
  Serial.println(F("cm"));
}

Extending to 3-DOF with Base Rotation

Adding a base rotation joint J1 that rotates the entire arm around a vertical axis transforms the 2D planar position into a full 3D position. The x-y reach computed above becomes the radial reach from the vertical axis, and the base angle sweeps this reach through a horizontal arc:

3-DOF arm position computation:

Step 1: compute radial reach (r) and height (z) from the planar arm:
  r = L1 × cos(θ_shoulder) + L2 × cos(θ_shoulder + θ_elbow)
  z = L1 × sin(θ_shoulder) + L2 × sin(θ_shoulder + θ_elbow)

Step 2: base rotation (θ_base) sweeps r in the horizontal plane:
  x = r × cos(θ_base)
  y = r × sin(θ_base)
  z = z (height unchanged by base rotation)

So the full 3D end effector position is:
  x = (L1×cos(θ_s) + L2×cos(θ_s+θ_e)) × cos(θ_b)
  y = (L1×cos(θ_s) + L2×cos(θ_s+θ_e)) × sin(θ_b)
  z =  L1×sin(θ_s) + L2×sin(θ_s+θ_e)

Where θ_b=base, θ_s=shoulder, θ_e=elbow angle (all in radians)

Computing and displaying this position in real time as the arm is joysticked around gives immediate intuitive feedback about how joint angles translate to end effector location.

The Complete Control Sketch

/*
 * 3-DOF Robot Arm Control
 * Joystick control via two analog joysticks (3 axes):
 *   Joystick 1 X → base rotation (J1)
 *   Joystick 1 Y → shoulder pitch (J2)
 *   Joystick 2 Y → elbow pitch (J3)
 *
 * Joystick wiring: center-tap potentiometers, 5V/GND/output
 *   JS1_X: A0, JS1_Y: A1, JS2_Y: A2
 */

#include <Servo.h>

// Servo objects
Servo baseServo;
Servo shoulderServo;
Servo elbowServo;

// Servo pin assignments
const int BASE_PIN     = 9;
const int SHOULDER_PIN = 10;
const int ELBOW_PIN    = 11;

// Joystick pins
const int JS1_X = A0;  // Base rotation
const int JS1_Y = A1;  // Shoulder
const int JS2_Y = A2;  // Elbow

// Servo angle state (current positions)
float baseAngle     = 90.0;
float shoulderAngle = 90.0;
float elbowAngle    = 90.0;

// Servo limits (adjust for your physical arm to prevent crashes)
const float BASE_MIN = 10,   BASE_MAX = 170;
const float SHLD_MIN = 20,   SHLD_MAX = 160;
const float ELBW_MIN = 10,   ELBW_MAX = 170;

// Control speed (degrees per loop iteration)
const float SPEED = 0.8;

// Joystick dead zone (joystick center noise)
const int DEAD_ZONE = 30;

// Link lengths for forward kinematics
const float L1 = 15.0;  // cm
const float L2 = 12.0;  // cm

int readJoystick(int pin) {
  // Returns -100 to +100, with dead zone
  int raw = analogRead(pin);
  int centered = raw - 512;  // Center around 0 (512 = mid ADC value)
  if (abs(centered) < DEAD_ZONE) return 0;
  return map(centered, -512, 512, -100, 100);
}

void setup() {
  baseServo.attach(BASE_PIN);
  shoulderServo.attach(SHOULDER_PIN);
  elbowServo.attach(ELBOW_PIN);

  Serial.begin(9600);
  Serial.println(F("3-DOF Arm Controller — Ready"));

  // Move to home position
  baseServo.write(90);
  shoulderServo.write(90);
  elbowServo.write(90);
  delay(1500);

  Serial.println(F("Home position reached. Use joysticks to move."));
}

void loop() {
  // Read joystick axes
  int js1x = readJoystick(JS1_X);  // Base
  int js1y = readJoystick(JS1_Y);  // Shoulder
  int js2y = readJoystick(JS2_Y);  // Elbow

  // Update angles based on joystick input
  baseAngle     += js1x * SPEED * 0.01;  // Scale to degrees/iteration
  shoulderAngle += js1y * SPEED * 0.01;
  elbowAngle    += js2y * SPEED * 0.01;

  // Clamp to limits
  baseAngle     = constrain(baseAngle,     BASE_MIN, BASE_MAX);
  shoulderAngle = constrain(shoulderAngle, SHLD_MIN, SHLD_MAX);
  elbowAngle    = constrain(elbowAngle,    ELBW_MIN, ELBW_MAX);

  // Write to servos
  baseServo.write((int)baseAngle);
  shoulderServo.write((int)shoulderAngle);
  elbowServo.write((int)elbowAngle);

  // Compute and display end effector position (forward kinematics)
  static unsigned long lastPrint = 0;
  if (millis() - lastPrint > 200) {  // Print every 200ms
    float theta_b = baseAngle     * PI / 180.0;
    float theta_s = shoulderAngle * PI / 180.0;
    float theta_e = elbowAngle    * PI / 180.0;

    float r = L1 * cos(theta_s) + L2 * cos(theta_s + theta_e);
    float z = L1 * sin(theta_s) + L2 * sin(theta_s + theta_e);
    float x = r * cos(theta_b);
    float y = r * sin(theta_b);

    Serial.print(F("B:"));  Serial.print(baseAngle,    1);
    Serial.print(F(" S:")); Serial.print(shoulderAngle, 1);
    Serial.print(F(" E:")); Serial.print(elbowAngle,   1);
    Serial.print(F(" → x:")); Serial.print(x, 1);
    Serial.print(F(" y:")); Serial.print(y, 1);
    Serial.print(F(" z:")); Serial.println(z, 1);
    lastPrint = millis();
  }
}

Smooth Motion: Interpolating Between Positions

Moving servos directly to a target angle causes abrupt, jerky motion — fine for testing but poor for actual tasks. Smooth motion requires interpolating through intermediate positions at a controlled rate:

// Smooth servo motion: interpolate from current to target over duration_ms

void smoothMove(Servo &servo, float &currentAngle, float targetAngle,
                int duration_ms, int steps = 50) {
  float startAngle = currentAngle;
  float stepSize   = (targetAngle - startAngle) / steps;
  int   stepDelay  = duration_ms / steps;

  for (int i = 0; i <= steps; i++) {
    currentAngle = startAngle + stepSize * i;
    servo.write((int)currentAngle);
    delay(stepDelay);
  }
  currentAngle = targetAngle;  // Ensure exact final position
}

// Move all three joints simultaneously to a target configuration
void smoothMoveAll(float targetBase, float targetShoulder, float targetElbow,
                   int duration_ms, int steps = 50) {
  float deltaBase     = (targetBase     - baseAngle)     / steps;
  float deltaShoulder = (targetShoulder - shoulderAngle) / steps;
  float deltaElbow    = (targetElbow    - elbowAngle)    / steps;
  int   stepDelay     = duration_ms / steps;

  for (int i = 0; i <= steps; i++) {
    baseServo.write((int)(baseAngle     + deltaBase     * i));
    shoulderServo.write((int)(shoulderAngle + deltaShoulder * i));
    elbowServo.write((int)(elbowAngle   + deltaElbow    * i));
    delay(stepDelay);
  }

  // Update current angles
  baseAngle     = targetBase;
  shoulderAngle = targetShoulder;
  elbowAngle    = targetElbow;
}

// Example: pick-and-place sequence
void examplePickAndPlace() {
  // Move to pick position (above object)
  smoothMoveAll(90, 120, 45, 1000);  // 1 second to reach position
  delay(300);

  // Lower to object
  smoothMoveAll(90, 100, 60, 800);
  delay(300);

  // Close gripper (if J4 is gripper servo)
  // gripperServo.write(30);  // Closed position
  delay(500);

  // Lift up
  smoothMoveAll(90, 130, 30, 800);
  delay(300);

  // Move to place position
  smoothMoveAll(45, 120, 45, 1200);
  delay(300);

  // Lower to place
  smoothMoveAll(45, 105, 60, 800);
  delay(300);

  // Open gripper
  // gripperServo.write(80);  // Open position
  delay(500);

  // Return to home
  smoothMoveAll(90, 90, 90, 1500);
}

Common Mistakes and Safety

Mechanical binding: Servos commanded past their physical joint limit will stall, draw maximum current, and overheat. Always set software limits (the constrain() calls in the control sketch) conservatively — 10–15° inside the physical endpoints. Watch for binding sounds (servo humming continuously) and immediately move the joint away.

Power supply undersizing: Three MG996R servos stalling simultaneously draw 2.7A. A 1A power supply will sag in voltage, causing erratic behavior and potential microcontroller resets. Size the servo power supply for at least 2× the maximum expected current.

Cable interference with joints: Servo signal, power, and sensor cables must not wrap around joints or restrict motion. Route cables through the center of the arm if possible, or use flexible flat ribbon cable with enough slack to allow full joint range without pulling taut.

Slow home movement at startup: Never snap all servos to home position simultaneously at startup — the sudden current surge can reset the Arduino. Use smoothMoveAll() to move slowly to home during setup().

A robot arm brings manipulation — the ability to interact with, grasp, move, and position objects — into your robotics practice. Where mobile robots navigate space, arms work within space, bringing a tool to a precise location and orientation.

The foundational concepts — degrees of freedom, revolute joints, torque requirements, forward kinematics — provide the vocabulary and mathematics for understanding any robot arm, from this simple 3-DOF desktop arm all the way to a 6-DOF industrial manipulator. The servo control code, the smooth interpolation technique, and the forward kinematics computation scale directly to more complex arm designs.

The next step from this foundation is inverse kinematics — the reverse problem: given a desired end effector position in 3D space, what joint angles achieve it? That problem is significantly more complex mathematically but builds directly on the forward kinematics established here, and unlocks position-commanded arm control: telling the arm “go to this point” instead of “move to these angles.”

Workspace Analysis: Understanding What Your Arm Can Reach

Building an arm without analyzing its workspace is like designing a house without measuring the lot. The workspace defines the boundary of where the end effector can physically travel — and certain positions are more valuable to reach than others depending on the application.

Computing the 2D Workspace (Planar Arm)

For the 2-link planar portion of a 3-DOF arm (ignoring base rotation), the reachable workspace is an annular region — a ring shape bounded by the fully extended arm on the outside and the folded arm on the inside:

Workspace boundaries for L1=15cm, L2=12cm:

Maximum reach (full extension): L1 + L2 = 15 + 12 = 27cm from base
Minimum reach (full fold-back): |L1 - L2| = |15 - 12| = 3cm from base

The arm can reach any point in the annular region from 3cm to 27cm,
subject to joint angle limits.

If shoulder joint is limited to 30°–150° (avoiding floor collision):
  Only the upper half of the annulus is accessible.

Practical working region: 5cm–22cm (avoiding singularities near extremes)

Visualizing the Workspace with Arduino

You can compute and print the workspace boundary to the Serial Monitor, then plot it:

// Print reachable workspace points for 2D visualization
// Copy Serial output to Excel / Python / Desmos to plot

const float L1 = 15.0;
const float L2 = 12.0;
const float SHLD_MIN_DEG = 30.0;
const float SHLD_MAX_DEG = 150.0;
const float ELBW_MIN_DEG = 10.0;
const float ELBW_MAX_DEG = 170.0;

void printWorkspace() {
  Serial.println(F("x_cm,y_cm"));  // CSV header
  for (float s = SHLD_MIN_DEG; s <= SHLD_MAX_DEG; s += 5.0) {
    for (float e = ELBW_MIN_DEG; e <= ELBW_MAX_DEG; e += 5.0) {
      float sr = s * PI / 180.0;
      float er = e * PI / 180.0;
      float x = L1 * cos(sr) + L2 * cos(sr + er);
      float y = L1 * sin(sr) + L2 * sin(sr + er);
      Serial.print(x, 2);
      Serial.print(F(","));
      Serial.println(y, 2);
    }
  }
  Serial.println(F("Done."));
}

// Call from setup() once, copy output to spreadsheet, plot as scatter plot

Plotting this output reveals the actual reachable region — often surprising to builders who assumed a larger workspace. It also reveals dexterity islands — regions where many different angle combinations converge, making precise positioning easy — versus dexterity deserts near the workspace boundary where the arm struggles to make fine adjustments.

Placing the Arm for the Task

Once the workspace is understood, physically place the arm’s base so the target work area falls within the dexterous region:

Placement strategy:

If picking objects from a tray:
  Place arm base so tray is between 40–70% of maximum reach
  (not too close where arm is over-folded, not too far where arm is near singularity)

If writing or drawing on a surface:
  Surface should be in the arm's "sweet spot" — directly in front of base,
  at a distance where shoulder ≈ 45°–90° and elbow ≈ 60°–120°

If mounting on a mobile robot:
  Mount the arm elevated, facing forward, with working area below and ahead
  Arm base height above ground = L1 (so folded arm clears chassis)

Recording and Playing Back Positions

Once you can manually jog the arm to positions, the next step is recording those positions and playing them back — the foundation of “teach and repeat” programming used widely in industrial robotics:

// Position recording and playback system
// Record up to MAX_POSES arm configurations, then play them back

const int MAX_POSES = 20;
int recordedBase[MAX_POSES];
int recordedShoulder[MAX_POSES];
int recordedElbow[MAX_POSES];
int poseCount = 0;

void recordCurrentPosition() {
  if (poseCount >= MAX_POSES) {
    Serial.println(F("Memory full! Maximum poses recorded."));
    return;
  }
  recordedBase[poseCount]     = (int)baseAngle;
  recordedShoulder[poseCount] = (int)shoulderAngle;
  recordedElbow[poseCount]    = (int)elbowAngle;
  poseCount++;

  Serial.print(F("Pose "));
  Serial.print(poseCount);
  Serial.print(F(" recorded: B="));
  Serial.print(baseAngle, 0);
  Serial.print(F(" S="));
  Serial.print(shoulderAngle, 0);
  Serial.print(F(" E="));
  Serial.println(elbowAngle, 0);
}

void playbackSequence(int repeatCount = 1) {
  Serial.print(F("Playing back "));
  Serial.print(poseCount);
  Serial.print(F(" poses, "));
  Serial.print(repeatCount);
  Serial.println(F(" times"));

  for (int rep = 0; rep < repeatCount; rep++) {
    for (int i = 0; i < poseCount; i++) {
      smoothMoveAll(recordedBase[i], recordedShoulder[i], recordedElbow[i], 800);
      delay(200);
    }
  }
  Serial.println(F("Playback complete."));
}

// Serial command interface: 'r' = record, 'p' = play, 'c' = clear
void handleSerialCommands() {
  if (Serial.available()) {
    char cmd = Serial.read();
    switch (cmd) {
      case 'r': recordCurrentPosition(); break;
      case 'p': playbackSequence(3);     break;  // Play 3 times
      case 'c':
        poseCount = 0;
        Serial.println(F("Sequence cleared."));
        break;
    }
  }
}

With this system: manually jog the arm to each desired position using the joystick, press ‘r’ in the Serial Monitor to record it, repeat for all positions, then press ‘p’ to play the sequence. This is genuinely useful for repetitive tasks like sorting objects, watering plants, or simple assembly operations.

Adding a Gripper as the Fourth Joint

A gripper transforms a positioning arm into a manipulation arm — one that can pick up and release objects. The simplest gripper for a hobby arm is a servo-driven parallel jaw:

// Gripper control — add to existing arm code

Servo gripperServo;
const int GRIPPER_PIN  = 6;
const int GRIP_OPEN    = 80;  // Angle for open (adjust for your gripper geometry)
const int GRIP_CLOSED  = 20;  // Angle for closed (gripping object)
const int GRIP_NEUTRAL = 50;  // Partial open (carrying without firm grip)

void setupGripper() {
  gripperServo.attach(GRIPPER_PIN);
  gripperServo.write(GRIP_OPEN);  // Start open
}

void openGripper()   { gripperServo.write(GRIP_OPEN);   delay(300); }
void closeGripper()  { gripperServo.write(GRIP_CLOSED); delay(300); }

// Grip force estimation from servo current (if using current sensing):
// Normal grip: servo holds position, current near zero
// Object too large: servo stalls, current spikes → detect and stop
// This prevents crushing delicate objects by monitoring servo load

// Simple version without current sensing: use intermediate angle for delicate objects
void gentleGrip() {
  // Move gripper slowly to closed, stopping early
  for (int angle = GRIP_OPEN; angle >= GRIP_CLOSED + 10; angle -= 2) {
    gripperServo.write(angle);
    delay(20);
  }
}

The gripper adds a 4th servo and transforms the arm into a complete pick-and-place system. Position the arm above an object, lower it, close the gripper, raise the arm, move to the target position, lower, open the gripper, raise.

Troubleshooting the Robot Arm

Problem Likely Cause Diagnosis / Fix
Arm drifts slowly in one direction when joystick centered Joystick center not exactly 512 ADC Measure center ADC value, adjust readJoystick() baseline; increase DEAD_ZONE
Servo jitter (twitching without input) Electrical noise on signal wire; long signal cables Add 100Ω resistor in series with signal wire; shorten cables; add 100nF cap from signal to GND at servo
Arm gradually loses position over time Servo gear wear; load too heavy for servo Reduce payload; upgrade to higher-torque servo
Arduino resets when multiple servos move simultaneously Power supply current limit exceeded Use separate dedicated power supply for servos ≥ 2A rating
Servo makes grinding noise Mechanical binding; commanded past physical limit Set software angle limits (constrain()) 15° inside physical endpoints
Smooth movement vibrates or stutters Step size too large; step delay too short Increase steps parameter in smoothMoveAll; check for shared timer conflict (Servo library uses Timer 1)
Forward kinematics position seems wrong Link length constants wrong; angle reference incorrect Measure actual link lengths; verify servo zero (0°) matches assumed geometry
Arm sags/droops at certain positions Insufficient servo torque for that configuration Calculate worst-case torque at current joint angles; upgrade servo or reduce link length

From Arm to Robot: What Comes Next

The 3-DOF arm built here is the foundation for several natural next steps in manipulation robotics:

Inverse kinematics (IK): Computing the joint angles required to reach a given (x, y, z) position. For a 2-link planar arm, IK has a closed-form solution using the law of cosines. For a full 3-DOF arm with base rotation, it’s a 3-equation system. IK unlocks position-commanded control: “move the gripper to position (10, 15, 8)” instead of “set shoulder to 75°.”

Path planning: Defining smooth trajectories through joint space or Cartesian space, avoiding joint limits and singularities. Simple path planning uses linear interpolation in joint space (the smoothMoveAll function). More sophisticated path planning uses Cartesian-space interpolation, ensuring the end effector moves in a straight line (useful for drawing and assembly tasks).

Force feedback: Adding a force/torque sensor at the wrist allows the arm to detect contact, measure grip force, and comply with the environment — pressing gently rather than crashing. Hobby implementations use strain gauges or FSR (force-sensitive resistor) pads under the gripper.

Mobile manipulation: Mounting the arm on the collision-avoiding rover from Article 75 creates a mobile manipulator — a robot that navigates to an object and picks it up. Combining the rover’s obstacle avoidance with the arm’s manipulation requires coordination between the mobile base and arm, the fundamental challenge of mobile manipulation.

Each of these is a genuine research and engineering challenge that this simple arm makes tangible. Understanding the limitations of your 3-DOF arm — what it can’t reach, where it becomes imprecise, what loads exceed its servos — is exactly the engineering judgment that scales to designing more capable systems.

Hot this week

Building a Light-Seeking Robot: Programming Autonomous Behavior

Build a light-seeking robot using photoresistors and Arduino—learn analog sensing, comparative behavior, stimulus-response programming, and braitenberg vehicle principles.

Building a Line-Following Robot: The Basics of Navigation

Build a line-following robot step by step—learn IR sensor arrays, on/off control, proportional steering, and PID control for smooth, reliable line tracking on any surface.

Your First Mobile Robot: A Simple Collision-Avoiding Rover

Build your first mobile robot—complete step-by-step guide to a collision-avoiding rover using Arduino, HC-SR04 ultrasonic sensor, L298N motor driver, and two DC motors.

Analog-to-Digital Conversion: How Robots Read Sensor Values

Learn how analog-to-digital conversion works in robotics—understand ADC resolution, sampling rate, reference voltage, noise reduction, and sensor calibration techniques.

Input/Output Pins: Your Robot’s Connection to the World

Learn how microcontroller I/O pins work in robotics—digital vs analog pins, INPUT/OUTPUT modes, PWM, current limits, protection, and practical wiring for sensors and actuators.

Topics

Building a Light-Seeking Robot: Programming Autonomous Behavior

Build a light-seeking robot using photoresistors and Arduino—learn analog sensing, comparative behavior, stimulus-response programming, and braitenberg vehicle principles.

Building a Line-Following Robot: The Basics of Navigation

Build a line-following robot step by step—learn IR sensor arrays, on/off control, proportional steering, and PID control for smooth, reliable line tracking on any surface.

Your First Mobile Robot: A Simple Collision-Avoiding Rover

Build your first mobile robot—complete step-by-step guide to a collision-avoiding rover using Arduino, HC-SR04 ultrasonic sensor, L298N motor driver, and two DC motors.

Analog-to-Digital Conversion: How Robots Read Sensor Values

Learn how analog-to-digital conversion works in robotics—understand ADC resolution, sampling rate, reference voltage, noise reduction, and sensor calibration techniques.

Input/Output Pins: Your Robot’s Connection to the World

Learn how microcontroller I/O pins work in robotics—digital vs analog pins, INPUT/OUTPUT modes, PWM, current limits, protection, and practical wiring for sensors and actuators.

Memory in Robotics: RAM, Flash, and EEPROM Explained

Understand RAM, Flash, and EEPROM memory in robotics—learn what each type stores, how much is available, how to avoid running out, and practical optimization techniques.

Understanding Clock Speed and Processing Power in Robot Brains

Learn how clock speed and processing power affect robot performance—understand MHz, instruction cycles, benchmarking, and matching processor specs to your robot's real needs.

C++ Performance Profiling and Optimization Techniques

Master C++ performance optimization. Learn how to profile code, eliminate bottlenecks, leverage CPU caches, use Google Benchmark, and apply modern C++ techniques for maximum speed.

Related Articles

Popular Categories