Building a Micro Servo Robotic Arm with a Raspberry Pi Camera

DIY Robotic Arm with Micro Servo Motors / Visits:7

If you’ve ever watched a industrial robotic arm dance through its paces on a factory floor, you know the hypnotic blend of precision, speed, and mechanical grace. Now imagine shrinking that down to a desktop-sized project that you can build over a weekend, powered by a $35 Raspberry Pi and a handful of micro servo motors. This isn’t just a toy—it’s a fully functional vision-guided pick-and-place system that teaches you real-world kinematics, computer vision, and embedded control. And the secret sauce? The humble yet mighty micro servo.

In this deep-dive, I’m going to walk you through the architecture, the hardware choices, the software stack, and the gritty calibration steps to get your own micro servo robotic arm seeing and grabbing with a Raspberry Pi Camera. Buckle up—this is where hobby robotics meets serious engineering.

Why Micro Servos Are the Unsung Heroes of Desktop Robotics

Before we talk about the Pi or the camera, let’s give credit where it’s due. The micro servo motor (typically the SG90, MG90S, or the digital DS3218) is the muscle fiber of this entire build. These tiny actuators—often no larger than a matchbox—are capable of delivering 1.8 to 2.5 kg-cm of stall torque at 5V. That’s enough to lift a small payload (like a ping-pong ball or a USB stick) with a 3D-printed arm structure.

The Three Things That Make Micro Servos Special

  1. Closed-Loop Feedback (Potentiometer-Based)
    Unlike a stepper motor that needs external encoders, a micro servo has a built-in potentiometer on the output shaft. The control circuit continuously compares the commanded position (via PWM pulse width) to the actual shaft angle, then adjusts the motor current to close the error. This gives you repeatable, absolute positioning—no homing routine needed.

  2. PWM Simplicity
    You control a micro servo with a 50Hz PWM signal (20ms period). A pulse width of 1.0ms commands 0°, 1.5ms commands 90°, and 2.0ms commands 180°. That’s it. The Raspberry Pi’s GPIO pins can generate this with software PWM (though hardware PWM via pigpio is far smoother). This simplicity means you can drive 6–8 servos without any extra servo driver board—though I’ll recommend one later for stability.

  3. Cost-to-Performance Ratio
    A pack of five SG90s costs less than $15. For that, you get 180° of rotation, 0.1s/60° speed, and enough torque to move lightweight PLA or PETG arm segments. The MG90S adds metal gears and a bit more torque for roughly $2.50 each—a no-brainer upgrade for any arm with a wrist or gripper.

The Hidden Gotcha: Current Spikes

Here’s the trap that catches every first-timer: a micro servo can spike to 500mA–1A during a hard stall or rapid acceleration. The Raspberry Pi’s 3.3V logic and 5V rail are not designed to feed that. If you power four servos directly from the Pi’s 5V pin, you’ll get resets, SD card corruption, or even a fried Pi. The solution? A separate 5V 5A power supply for the servos, with a common ground to the Pi. We’ll wire that up properly later.

System Architecture: From Camera to Gripper

Your robotic arm needs a brain, eyes, and muscles. Here’s the high-level block diagram:

[ Raspberry Pi 4B ] <-- I2C --> [ PCA9685 Servo Driver ] <-- PWM --> [ 5x Micro Servos ] ^ | | v [ Pi Camera v2 ] <-- CSI cable --> [ Python Code (OpenCV + custom IK) ] <-- [ Gripper ]

The Arm Kinematics: 4-DOF Plus Gripper

I’m using a classic 4-DOF articulated design (base, shoulder, elbow, wrist) plus a parallel-jaw gripper. That gives you:

  • Base rotation: 180° (servo 1, mounted vertically)
  • Shoulder lift: 180° (servo 2, drives the upper arm)
  • Elbow bend: 180° (servo 3, drives the forearm)
  • Wrist pitch: 180° (servo 4, adjusts gripper angle)
  • Gripper open/close: 180° (servo 5, via a simple linkage)

Why 4-DOF? Because it’s the minimum needed to reach any point in a 2D vertical plane (plus base rotation for 3D reach) while keeping the inverse kinematics (IK) solvable in closed form. No iterative Jacobian solvers needed—just pure trigonometry.

Hardware Bill of Materials (BOM) – What You’ll Actually Need

Here’s the exact parts list I used. You can swap equivalents, but stick to these specs:

| Component | Model / Spec | Price (approx) | |-----------|--------------|----------------| | Microcontroller | Raspberry Pi 4B (2GB or 4GB) | $35–$55 | | Camera | Raspberry Pi Camera v2 (8MP, Sony IMX219) | $25 | | Servo Driver | PCA9685 (16-channel, 12-bit I2C) | $5 | | Micro Servos | 4x MG90S (metal gear) + 1x SG90 (gripper) | $15 total | | Power Supply | 5V 5A DC adapter (for servos) | $10 | | Structure | 3D-printed arm (PLA, 20% infill) or acrylic kit | $0–$30 | | Misc | Jumper wires, breadboard, M2/M3 screws, standoffs | $10 |

Pro tip: Don’t cheap out on the MG90S for the shoulder and elbow. The SG90’s plastic gears will strip within 30 minutes of aggressive motion. Metal gears are mandatory for any joint that bears the arm’s weight.

Wiring the Micro Servos Without Melting Your Pi

Let’s get the wiring right, because this is where 90% of “my arm doesn’t move” issues originate.

Step 1: The PCA9685 Breakout

The PCA9685 is a lifesaver. It has an I2C interface (so you only need 4 wires to the Pi: VCC, GND, SDA, SCL) and generates clean, stable 50Hz PWM on 16 channels. It also has a built-in level shifter, so you can feed it 3.3V logic from the Pi while driving 5V servos.

  • Pi 3.3V → PCA9685 VCC (logic power)
  • Pi GND → PCA9685 GND
  • Pi SDA (GPIO2) → PCA9685 SDA
  • Pi SCL (GPIO3) → PCA9685 SCL
  • External 5V 5A → PCA9685 V+ (servo power)

Step 2: Servo Connections

Each servo has three wires: brown (GND), red (VCC), and orange/yellow (signal). Connect:

  • All brown wires → PCA9685 GND (also tie to the Pi’s GND)
  • All red wires → PCA9685 V+
  • Each orange wire → a separate PWM channel (e.g., 0–4)

Critical: The Pi’s 5V pin should not power the servos. Use the external supply. The common ground is essential—without it, the PWM signals will be floating and the servos will jitter like crazy.

Step 3: Camera Connection

The Pi Camera v2 plugs into the CSI port (the flat ribbon cable). Enable it via sudo raspi-config → Interface Options → Camera. Test with raspistill -o test.jpg. If you get an image, you’re golden.

Software Stack: From Python to OpenCV to Inverse Kinematics

Now the fun part—making the arm think. I’m using Python 3.9 + OpenCV 4.5 + pigpio for hardware PWM (the PCA9685 is controlled via Adafruit_PCA9685 library, but I’ll show you a cleaner wrapper).

Setting Up the Servo Driver

First, install the required libs:

bash sudo apt update sudo apt install python3-pip python3-opencv sudo pip3 install adafruit-circuitpython-servokit pigpio sudo systemctl enable pigpiod

Then, initialize the PCA9685 with a servo frequency of 50Hz:

python from adafruit_servokit import ServoKit

kit = ServoKit(channels=16)

Calibrate pulse ranges for MG90S (usually 500-2500us)

But we'll set min/max pulse in microseconds for finer control

kit.servo[0].setpulsewidthrange(500, 2500) kit.servo[0].actuationrange = 180

def setservoangle(channel, angle): # Clamp angle to 0-180 angle = max(0, min(180, angle)) kit.servo[channel].angle = angle

Inverse Kinematics: The Math Behind the Motion

This is the heart of the arm. We want to specify a target (x, y, z) in 3D space relative to the base, and have the arm’s joints move to that point. For a 4-DOF arm, we can solve it in two stages.

Stage 1: Base Rotation (θ1)

Given a target point (x, y, z), the base rotates to face it:

θ1 = atan2(y, x)

The horizontal distance from the base to the target is r = sqrt(x² + y²).

Stage 2: Shoulder and Elbow (θ2, θ3)

Now we’re in a 2D plane defined by r and z. Let L1 = upper arm length (e.g., 10cm) and L2 = forearm length (e.g., 10cm). The distance from the shoulder to the target in this plane is:

D = sqrt(r² + z²)

But wait—the wrist has a length too. For simplicity, I’ll assume the wrist is a fixed offset pointing straight forward, so we subtract it from D before solving. Let’s call the wrist offset W (e.g., 4cm). Then the effective distance is:

D_eff = sqrt(r² + (z - W)²) # if wrist is vertical

That’s messy. Instead, I’ll use a simpler approach: treat the wrist as a tool center point (TCP) and solve the 2-link IK for the shoulder (θ2) and elbow (θ3) directly.

Using the law of cosines:

cos(θ3) = (L1² + L2² - D_eff²) / (2 * L1 * L2) θ3 = acos(clamp(cos(θ3), -1, 1))

And for the shoulder:

θ2 = atan2(z, r) - atan2(L2 * sin(θ3), L1 + L2 * cos(θ3))

Stage 3: Wrist Pitch (θ4)

To keep the gripper parallel to the ground (or at a desired angle), we set:

θ4 = 90° - (θ2 + θ3) # in degrees, compensation for the arm’s slope

This is a simplified “keep the wrist level” strategy—perfect for pick-and-place.

Putting It All Together: A Python Class

Here’s a compact class that wraps the IK and servo commands:

python import math import numpy as np

class MicroServoArm: def init(self, kit, L1=10.0, L2=10.0, W=4.0): self.kit = kit self.L1 = L1 # cm self.L2 = L2 # cm self.W = W # wrist offset cm self.servo_map = {'base': 0, 'shoulder': 1, 'elbow': 2, 'wrist': 3, 'gripper': 4}

def set_pose(self, x, y, z, grip_angle=0):     # 1. Base angle     theta1 = math.degrees(math.atan2(y, x))     r = math.sqrt(x**2 + y**2)      # 2. Shoulder-elbow IK     # Reduce r and z by wrist offset (assume wrist points forward)     r_eff = r - self.W     z_eff = z     D = math.sqrt(r_eff**2 + z_eff**2)     # Check reachability     if D > (self.L1 + self.L2) or D < abs(self.L1 - self.L2):         print("Target out of reach!")         return      cos_elbow = (self.L1**2 + self.L2**2 - D**2) / (2 * self.L1 * self.L2)     theta3 = math.degrees(math.acos(np.clip(cos_elbow, -1, 1)))     theta2 = math.degrees(math.atan2(z_eff, r_eff) - math.atan2(self.L2 * math.sin(math.radians(theta3)), self.L1 + self.L2 * math.cos(math.radians(theta3))))      # 3. Wrist to keep level     theta4 = 90 - (theta2 + theta3)      # 4. Command servos     self.set_servo('base', theta1)     self.set_servo('shoulder', theta2)     self.set_servo('elbow', theta3)     self.set_servo('wrist', theta4)     self.set_servo('gripper', grip_angle)  def set_servo(self, name, angle):     ch = self.servo_map[name]     self.kit.servo[ch].angle = max(0, min(180, angle)) 

Vision-Guided Pick and Place: The Camera as the Eye

The Raspberry Pi Camera v2 (with its IMX219 sensor) gives you a 3280x2464 still resolution, but for real-time tracking we’ll run at 640x480 at 30fps. The workflow is:

  1. Calibrate the camera to the arm’s base (find the homography matrix).
  2. Detect the target object using color segmentation or a Haar cascade.
  3. Convert pixel coordinates to real-world (x, y) in the arm’s frame.
  4. Command the arm to move to that point and grip.

Camera Calibration for a Fixed Overhead View

The simplest setup: mount the camera directly above the arm’s workspace, looking down. Then the mapping from pixels to centimeters is just a scale factor (assuming no lens distortion). But for a more flexible setup, we use an Aruco marker placed at the arm’s base and compute a perspective transform.

Here’s a quick snippet to get the transform:

python import cv2 import numpy as np

Known physical coordinates of 4 points (in cm from arm base)

src_pts = np.float32([[10, 10], [20, 10], [20, 20], [10, 20]])

Corresponding pixel coordinates (from an image with markers)

dst_pts = np.float32([[200, 150], [350, 155], [340, 300], [190, 290]])

H, _ = cv2.findHomography(srcpts, dstpts)

To convert pixel to physical: inv(H) * [px, py, 1]

Store H_inv = np.linalg.inv(H). Then for any detected object pixel (px, py):

python def pixel_to_physical(px, py, H_inv): pt = np.array([px, py, 1.0]) phys = H_inv @ pt phys /= phys[2] return phys[0], phys[1] # x, y in cm

Color-Based Object Detection

For a demo, let’s detect a bright green ping-pong ball. We’ll convert to HSV and threshold:

python def find_ball(frame): hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) lower = np.array([35, 100, 100]) upper = np.array([85, 255, 255]) mask = cv2.inRange(hsv, lower, upper) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: largest = max(contours, key=cv2.contourArea) (x, y), radius = cv2.minEnclosingCircle(largest) if radius > 5: # ignore noise return int(x), int(y), int(radius) return None

Then in your main loop:

  1. Grab a frame.
  2. Find the ball’s pixel center.
  3. Convert to physical (x, y) using the homography.
  4. Set the arm’s target to (x, y, z=2cm) with gripper open.
  5. Wait for the arm to settle, then close the gripper.
  6. Lift to (x, y, z=8cm) and move to a drop-off zone.

Calibration Tricks for Micro Servo Precision

Micro servos are not precision instruments out of the box. Here’s how to get them to behave like one.

Deadband Compensation

The MG90S has a deadband of about 5–10 microseconds in PWM. This means a command of 90° might result in 89.5° or 90.5° depending on which side you approach from. To fix this, always approach a target angle from the same direction (e.g., always increase the angle, then slightly overshoot and come back). Or, in software, add a small offset per servo:

python servo_offset = {'base': -3, 'shoulder': 2, 'elbow': -1, 'wrist': 0}

Apply offset in set_servo()

Torque Stalling and Jitter

If you hear a buzzing sound, the servo is stalling—it’s trying to move but can’t. This often happens at extreme angles or when the arm is holding a load. Reduce the max speed by sending incremental angle steps with a small delay:

python def smooth_move(channel, target_angle, step=2, delay=0.02): current = kit.servo[channel].angle if current is None: current = 90 direction = 1 if target_angle > current else -1 for ang in range(int(current), int(target_angle), direction * step): kit.servo[channel].angle = ang time.sleep(delay)

This also prevents the current spike from slamming into a hard stop.

Zero-Point Calibration

Every servo has a different neutral point. Before assembling the arm, power each servo alone and send a 90° command. Then attach the horn so it points straight up (or at your defined zero). This mechanical calibration is more reliable than trying to fix it in software.

Putting It All Together: A Full Demo Routine

Here’s a script that runs a full vision-guided pick-and-place cycle:

python import cv2 import time from arm import MicroServoArm from vision import findball, pixelto_physical

Initialize

kit = ServoKit(channels=16) arm = MicroServoArm(kit) cap = cv2.VideoCapture(0) H_inv = np.load('homography.npy') # precomputed

Home position

arm.setpose(15, 0, 10, gripangle=90) # arm up, gripper open time.sleep(2)

while True: ret, frame = cap.read() if not ret: break

ball = find_ball(frame) if ball:     px, py, radius = ball     x, y = pixel_to_physical(px, py, H_inv)     print(f"Ball at ({x:.1f}, {y:.1f}) cm")      # Move to above the ball     arm.set_pose(x, y, z=8, grip_angle=90)  # gripper open     time.sleep(1.5)      # Descend and grip     arm.set_pose(x, y, z=2, grip_angle=90)     time.sleep(1)     arm.set_pose(x, y, z=2, grip_angle=20)  # close gripper     time.sleep(0.5)      # Lift and move to drop zone (e.g., 20, 0, 8)     arm.set_pose(x, y, z=8, grip_angle=20)     time.sleep(1)     arm.set_pose(20, 0, z=8, grip_angle=20)     time.sleep(1.5)      # Release     arm.set_pose(20, 0, z=2, grip_angle=90)     time.sleep(1)  cv2.imshow("Frame", frame) if cv2.waitKey(1) & 0xFF == ord('q'):     break 

cap.release() cv2.destroyAllWindows()

Performance Tuning: Speed vs. Torque vs. Accuracy

You’ll quickly notice a trade-off. If you move the arm too fast, the inertia causes overshoot and the servos buzz. If you move too slow, it’s tedious. Here’s my recommended tuning approach:

  • For the base and shoulder: Use a step of 3° with a 10ms delay. They have the most inertia.
  • For the elbow and wrist: Use a step of 2° with a 15ms delay. They need more finesse.
  • For the gripper: Just slam it—it’s binary open/close.

Also, consider adding a servo acceleration profile. Instead of linear steps, use a sine curve:

python import math def eased_move(channel, start, end, duration=1.0): steps = int(duration / 0.02) for i in range(1, steps+1): t = i / steps eased = 0.5 - 0.5 * math.cos(math.pi * t) # ease-in-out angle = start + (end - start) * eased kit.servo[channel].angle = angle time.sleep(0.02)

This reduces mechanical stress and current spikes dramatically.

Common Pitfalls and How to Avoid Them

1. Servo Horn Slip

The plastic spline on the SG90/MG90S can strip if you overtighten the screw. Use a tiny drop of threadlocker and tighten just until snug. If you see the horn rotate independently of the shaft, replace the horn immediately.

2. Power Supply Brownouts

Even with a 5A supply, long cable runs can cause voltage drop. Use 18AWG wire for the servo power lines. And always add a 470µF electrolytic capacitor across the power rails near the servos to handle transient spikes.

3. I2C Bus Errors

The PCA9685 can hang if the Pi’s I2C bus gets noise. Add pull-up resistors (1.8kΩ to 3.3V) on SDA and SCL if you’re using long jumper wires. Also, set the I2C clock to 100kHz (default) rather than 400kHz for stability.

4. Camera Auto-Exposure Issues

The Pi Camera’s auto-exposure can cause flickering in indoor lighting, which wreaks havoc on color detection. Disable auto-exposure and set a fixed shutter speed:

python from picamera2 import Picamera2

picam2 = Picamera2() config = picam2.createstillconfiguration() picam2.configure(config) picam2.set_controls({"ExposureTime": 10000, "AnalogueGain": 2.0}) picam2.start()

Or if you’re using OpenCV’s VideoCapture, set cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) (note: the value is a bitmask—0.25 disables auto).

Taking It Further: Where to Go From Here

Once you have the basic arm working, the sky’s the limit. Here are three upgrades that will blow your mind:

Add a Second Camera for Stereo Vision

Mount a second Pi Camera at a 90° angle to get depth information. This allows the arm to pick up objects at arbitrary heights, not just from a flat table.

Implement a Closed-Loop PID on Joint Angles

Instead of open-loop PWM, add an IMU (MPU6050) on each arm segment to measure the actual joint angle. Then run a PID controller to correct for gravity and friction. This turns your micro servo arm into a poor man’s collaborative robot.

Use TensorFlow Lite for Object Classification

Swap the color detection for a MobileNet SSD model running on the Pi’s GPU (via tflite-runtime). Now the arm can identify and sort different objects—like separating red M&Ms from blue ones.

Final Thoughts on the Micro Servo Experience

Building a micro servo robotic arm with a Raspberry Pi Camera is not just a weekend project—it’s a crash course in mechatronics. You’ll wrestle with torque curves, fight with PWM jitter, and debug homography matrices at 11 PM. But when that little gripper closes around a target for the first time, guided purely by a camera and a handful of $2 servos, you’ll feel like you’ve built a tiny industrial robot.

The beauty of micro servos is their accessibility. They’re cheap enough to replace, simple enough to understand, and just capable enough to teach you the fundamentals of robotics. So go ahead, order that pack of MG90S, fire up your 3D printer, and start building. Your desk deserves a little mechanical companion.

Copyright Statement:

Author: Micro Servo Motor

Link: https://microservomotor.com/diy-robotic-arm-with-micro-servo-motors/raspberry-pi-camera-micro-servo-arm.htm

Source: Micro Servo Motor

The copyright of this article belongs to the author. Reproduction is not allowed without permission.

About Us

Lucas Bennett avatar
Lucas Bennett
Welcome to my blog!

Tags