This 3DOF robot arm kit is a hands-on introduction to real robotics: three degrees of freedom — base rotation, shoulder, and elbow — each driven by an MG995 metal-gear servo. Assembled, it becomes a desktop manipulator that you position entirely from code, which makes it the natural next step after sweeping a single servo: suddenly angles become poses, poses become sequences, and sequences become pick-and-place motion.
The MG995 is the workhorse of hobby arms for a reason: metal gears, roughly 10–12 kg·cm of torque at 6 V, and a standard 50 Hz PWM interface that every microcontroller here speaks — Arduino, ESP32, Raspberry Pi, and Pico. The kit arrives as parts, and the assembly itself teaches the two lessons every roboticist learns early: center your servos before you bolt on horns, and give motors their own power supply.
That second lesson deserves emphasis up front: three MG995s can briefly demand several amps. Powered correctly (a dedicated 5–6 V supply with shared ground), the arm moves smoothly and repeatably; powered from a microcontroller’s 5 V pin, it resets boards and jitters. This manual covers assembly-critical setup, wiring for all four platforms, pose-sequencing code for each, and the questions that come up in the first hour of ownership.
At a Glance
Degrees of Freedom
3 — base · shoulder · elbow
Servos
3 × MG995 metal gear
Torque
~10–12 kg·cm per servo @ 6 V
Servo Power
4.8 – 7.2 V, external supply
Control
Standard 50 Hz PWM, 3 channels
Works With
Arduino · ESP32 · Pi · Pico
Specifications
Parameter
Value
Configuration
3DOF articulated arm: base rotation + shoulder + elbow
Servos
3 × MG995, metal gear train
Servo torque
~9.4 kg·cm @ 4.8 V · ~11–12 kg·cm @ 6 V
Servo speed
~0.20 s/60° @ 4.8 V · ~0.16 s/60° @ 6 V
Servo travel
~180° per joint (limited further by linkage geometry)
Control signal
50 Hz PWM, ~500–2500 µs pulse
Servo supply
4.8 – 7.2 V DC, dedicated supply strongly recommended
Current draw
Up to ~2.5 A stall per servo — budget 5 A+ for the arm
Payload
Light objects (grams to a few hundred grams near the base)
Center the servos before final assembly. Power each servo and command 90° before attaching horns and brackets. If you bolt the arm together with a servo at an unknown angle, its very first move can slam a joint into its mechanical limit at full metal-gear torque — the number one way these kits get damaged on day one.
ESP32 Wiring
Connection
ESP32
Notes
Base / shoulder / elbow signals
GPIO 25 / 26 / 27
3.3 V signals drive MG995s fine
Servo power (red)
External 5–6 V supply +
Never the 3V3 or VIN pin
Servo ground (brown)
Supply − AND ESP32 GND
Common ground
Move slowly on purpose. Commanding a 90° jump makes three heavy servos lunge at once — the current spike is huge and the motion looks robotic in the bad way. The code examples step a few microseconds per tick, which keeps current gentle and motion smooth.
Raspberry Pi Wiring
Connection
Raspberry Pi
Notes
Base / shoulder / elbow signals
GPIO 17 / 27 / 22
Pins 11 / 13 / 15
Servo power (red)
External 5–6 V supply +
Never the Pi’s 5 V rail
Servo ground (brown)
Supply − AND Pi GND (Pin 6)
Common ground
About Pi PWM jitter. Software PWM under Linux can make servos buzz and twitch. The fix in code is the pigpio pin factory (used in the example); the fix in hardware is a PCA9685 16-channel servo driver, which also frees you to grow past 3 joints later.
Raspberry Pi Pico Wiring
Connection
Pico
Notes
Base / shoulder / elbow signals
GP13 / GP14 / GP15
Any PWM pins work
Servo power (red)
External 5–6 V supply +
Not VBUS through the Pico
Servo ground (brown)
Supply − AND Pico GND (Pin 38)
Common ground
Size the supply honestly. One stalled MG995 pulls ~2.5 A; three can spike past 5 A when the arm lifts something or hits a limit. A 5–6 V / 5 A supply (or a 2S LiPo through a 6 V BEC) is the comfortable minimum. Undersized supplies cause resets, brownouts, and mystery twitching.
Code Examples
Arduino — Smooth 3-Joint Pose Player
arm_poses.ino
#include <Servo.h>
Servo base, shoulder, elbow;
int cur[3] = {1500, 1500, 1500}; // current pulse widths (us)
void setup() {
base.attach(9);
shoulder.attach(10);
elbow.attach(11);
writeAll();
delay(1000); // settle at center
}
void writeAll() {
base.writeMicroseconds(cur[0]);
shoulder.writeMicroseconds(cur[1]);
elbow.writeMicroseconds(cur[2]);
}
// glide all joints to a target pose together
void moveTo(int b, int s, int e, int stepDelay) {
int tgt[3] = {b, s, e};
bool moving = true;
while (moving) {
moving = false;
for (int i = 0; i < 3; i++) {
if (cur[i] < tgt[i]) { cur[i] += 5; moving = true; }
if (cur[i] > tgt[i]) { cur[i] -= 5; moving = true; }
}
writeAll();
delay(stepDelay);
}
}
void loop() {
moveTo(1200, 1700, 1300, 6); // reach forward-left
delay(600);
moveTo(1800, 1400, 1700, 6); // swing right, tuck
delay(600);
moveTo(1500, 1500, 1500, 6); // home
delay(1000);
}
ESP32 — ESP32Servo Sequence
esp32_arm.ino
// Library Manager: install "ESP32Servo"
#include <ESP32Servo.h>
Servo joints[3];
const int PINS[3] = {25, 26, 27};
void setup() {
for (int i = 0; i < 3; i++) {
joints[i].setPeriodHertz(50);
joints[i].attach(PINS[i], 500, 2500);
joints[i].write(90); // center everything
}
delay(1000);
}
void glide(int idx, int from, int to) {
int dir = (to > from) ? 1 : -1;
for (int a = from; a != to; a += dir) {
joints[idx].write(a);
delay(8);
}
}
void loop() {
glide(0, 90, 40); // base left
glide(1, 90, 120); // shoulder down
glide(2, 90, 60); // elbow in
delay(500);
glide(2, 60, 90);
glide(1, 120, 90);
glide(0, 40, 90);
delay(1200);
}
Raspberry Pi — Python (gpiozero + pigpio)
arm_control.py
from gpiozero import AngularServo
from gpiozero.pins.pigpio import PiGPIOFactory
from time import sleep
# sudo apt install pigpio; sudo systemctl start pigpiod
# pigpio gives hardware-timed pulses = no servo jitter
factory = PiGPIOFactory()
def make(pin):
return AngularServo(pin, min_angle=0, max_angle=180,
min_pulse_width=0.0005, max_pulse_width=0.0025,
pin_factory=factory)
base, shoulder, elbow = make(17), make(27), make(22)
def glide(servo, start, end, step=2, dt=0.02):
rng = range(start, end + 1, step) if end > start else range(start, end - 1, -step)
for a in rng:
servo.angle = a
sleep(dt)
# center, then run a small routine
for s in (base, shoulder, elbow):
s.angle = 90
sleep(1)
while True:
glide(base, 90, 45)
glide(shoulder, 90, 125)
glide(elbow, 90, 60)
sleep(0.5)
glide(elbow, 60, 90)
glide(shoulder, 125, 90)
glide(base, 45, 90)
sleep(1)
Raspberry Pi Pico — MicroPython
pico_arm.py
from machine import Pin, PWM
import time
PINS = (13, 14, 15) # base, shoulder, elbow
servos = []
for p in PINS:
pwm = PWM(Pin(p))
pwm.freq(50)
servos.append(pwm)
def write_us(pwm, us):
pwm.duty_u16(int(us * 65535 / 20000))
pose = [1500, 1500, 1500]
for s, us in zip(servos, pose):
write_us(s, us)
time.sleep(1)
def move_to(target, step=6, dt=0.01):
while pose != list(target):
for i in range(3):
if pose[i] < target[i]: pose[i] = min(pose[i] + step, target[i])
elif pose[i] > target[i]: pose[i] = max(pose[i] - step, target[i])
write_us(servos[i], pose[i])
time.sleep(dt)
while True:
move_to((1250, 1750, 1300))
time.sleep(0.5)
move_to((1750, 1350, 1700))
time.sleep(0.5)
move_to((1500, 1500, 1500))
time.sleep(1)
Frequently Asked Questions
What order should I assemble things in?
Servos first, mechanics second. Power each MG995 and command it to 90° (center), then attach its horn and bracket with the joint in its mid-travel position. Assemble base, then shoulder, then elbow, checking after each joint that the linkage sweeps freely by hand-commanding small moves before adding the next stage.
My board resets or the arm twitches wildly. Why?
Power, almost every time. Three MG995s spike well past what a USB port or a board’s 5 V pin can deliver, and the resulting voltage sag resets the microcontroller mid-motion. Use a dedicated 5–6 V supply rated 5 A or better, connect its ground to the controller’s ground, and add a large electrolytic capacitor (1000 µF) across the servo rail if twitching persists.
A servo buzzes constantly and gets warm. Is that normal?
Buzzing under load is the servo actively holding position against gravity — some of it is normal, especially on the shoulder. Constant loud buzzing at rest usually means the commanded angle is slightly past a mechanical limit or the linkage is binding: back the angle off a few degrees and loosen/realign the bracket. A servo left stalled against a hard stop will overheat and strip.
How much can it actually lift?
Torque is 10–12 kg·cm at the servo shaft, but the arm’s reach acts as a lever against you: at a 20 cm reach, 12 kg·cm is roughly 600 g of theoretical holding force — before linkage losses and dynamics. In practice treat it as a light-payload arm: pens, small 3D prints, ping-pong balls with a gripper. It is a learning platform, not a loader.
Why does my arm not reach the angles the code commands?
Two causes. Horn placement: a horn pressed on one spline-tooth off shifts the whole joint ~4° — recenter and reseat it. Geometry: the linkage itself limits travel before the servo’s 180° does, so define soft limits in code for each joint (find them by slowly stepping until the joint just touches its limit, then back off) and never command past them.
Can I do inverse kinematics with 3DOF?
Yes — 3DOF planar-arm IK is the classic textbook case. Base rotation sets the plane; the shoulder and elbow angles come from the two-link IK equations (law of cosines) for a target (x, y). Start with the pose player here, measure your two link lengths, and the math drops in neatly — it is the ideal first IK project.
Should I upgrade to a PCA9685 servo driver?
On Arduino/ESP32/Pico, direct PWM is fine for 3 servos. On the Raspberry Pi, or the moment you add a gripper, wrist, or second arm, the PCA9685 earns its place: hardware-timed 16-channel PWM over I2C, a proper servo power bus with terminal input, and identical code on every platform. Our PCA9685 manual covers it end to end.