This is the build that moves you past single-component wiring and into real mechanical assembly. You're putting together a four-joint articulated arm -- base rotation, shoulder, elbow and gripper -- and learning how to drive several actuators together in a coordinated way rather than one at a time. It's the most physically involved project in the lineup, and the most satisfying to watch move.
What the Controller Does
This build specifically uses the ESP32-S3, not the smaller ESP32-C3 used elsewhere in the catalog. Four independently controlled servo channels need more usable GPIO than the compact C3 board comfortably offers, so the S3 is the right board for this project rather than an arbitrary upgrade.
What the Servos Do
Each of the arm's four joints is driven by its own small hobby servo: one for base rotation, one for the shoulder, one for the elbow, and one for the gripper. Each servo is controlled independently over its own PWM signal -- the width of the pulse sent to a given servo tells it which angle to hold, and the four channels don't interfere with each other in code as long as each is wired to its own signal pin.
What the Robotic Arm Frame Does
The 4DOF Robotic Arm Frame listing already includes its own SG90-compatible servos bundled in at each joint. You do not need to separately buy a standalone servo product for this build -- the frame arrives with the actuators it needs, and adding more would just be redundant.
Power and Current Draw
This is the real engineering constraint on this build, and it's a power problem, not a code problem. Four servos moving at the same time can draw more current than a USB port alone comfortably supplies. Powering everything from USB is fine for gentle, single-joint tests, but once you're moving multiple joints together, that combined draw can brown out the board or trigger an unexpected reset. Plan on a separate 5V power supply for the servos themselves as soon as you move past basic single-joint testing, with a shared ground between that supply and the ESP32-S3.
Software and Library Concepts
The real software challenge here isn't reading four servos -- it's sequencing their movement so it looks and behaves well. Commanding a servo straight to a new target angle makes it snap there as fast as it mechanically can, which looks jerky and puts unnecessary mechanical stress on the joint. Instead, step each servo's angle gradually toward its target over a series of small increments (sometimes called easing), so the arm's motion looks deliberate and controlled rather than twitchy.
Testing and Troubleshooting
One joint doesn't move at all: check that specific servo's signal wire and confirm the pin assignment in your code actually matches how you wired it -- this is the single most common issue on a four-servo build.
The whole arm or the controller itself resets when multiple joints move together: this is a power and current issue, not a code bug. Add a dedicated 5V supply for the servos rather than trying to debug it in software.
Movements look jerky or snap suddenly to a new position: you're commanding the servo directly to its target angle. Add gradual angle-stepping in code instead of jumping straight there.
The gripper closes too hard or not far enough: double check the angle range you're commanding against the gripper servo's actual mechanical limits before assuming it's a wiring fault.
Optional Future Upgrade: Inverse Kinematics
Once joint-by-joint control is solid, a natural next step is inverse kinematics -- commanding the gripper to a target X/Y/Z position and having the code calculate the four joint angles needed to get there, rather than setting each joint by hand. It's a genuinely harder software problem than anything else in this build, and a good reason to keep the mechanical assembly itself reliable first.
Wiring
Base servo signal → GPIO4 (`SERVO_BASE_PIN`) on ESP32-S3
Shoulder servo signal → GPIO5 (`SERVO_SHOULDER_PIN`)
Elbow servo signal → GPIO6 (`SERVO_ELBOW_PIN`)
Gripper servo signal → GPIO7 (`SERVO_GRIPPER_PIN`)
All 4 servo V+ wires → external 5V supply (NOT the ESP32-S3's 5V/USB pin)
All 4 servo GND wires → external supply GND, and that GND also tied to ESP32-S3 GND (shared/common ground is required)
This project's parts list is the acrylic 4DOF frame kit only — it does NOT include the 4 SG90 micro servos themselves, so add 4x SG90-compatible servos to your cart if you don't already have them; the kit is built to accept them. Four SG90s under load can spike well past what a USB port or the board's onboard 5V pin can supply, causing brownouts/resets, so use a separate 5V supply (e.g. a 5V/2A bench supply or battery pack) for the servos with a common ground back to the ESP32-S3 — never share only the signal wire without a common ground, or the servos will jitter or not respond.
Wiring Diagram
Starter Code
#include <ESP32Servo.h>
#define SERVO_BASE_PIN 4 // PWM-capable GPIO - adjust to match your ESP32-S3 board's pinout diagram
#define SERVO_SHOULDER_PIN 5 // PWM-capable GPIO - adjust to match your ESP32-S3 board's pinout diagram
#define SERVO_ELBOW_PIN 6 // PWM-capable GPIO - adjust to match your ESP32-S3 board's pinout diagram
#define SERVO_GRIPPER_PIN 7 // PWM-capable GPIO - adjust to match your ESP32-S3 board's pinout diagram
Servo baseServo;
Servo shoulderServo;
Servo elbowServo;
Servo gripperServo;
// SG90 servos: ~500-2400us pulse width covers their full ~0-180 degree range.
// Adjust these if your specific servos bind or don't reach full travel.
const int SERVO_MIN_US = 500;
const int SERVO_MAX_US = 2400;
struct Pose {
int base, shoulder, elbow, gripper;
};
// Small demo sequence. Replace with your own poses once you've found safe
// joint limits for your specific frame (mechanical limits vary by build).
Pose poses[] = {
{90, 90, 90, 90}, // neutral / home position
{45, 70, 110, 30}, // reach left, open gripper
{45, 70, 110, 90}, // close gripper
{135, 70, 110, 90}, // swing right, still holding
{135, 70, 110, 30}, // release
{90, 90, 90, 90} // return home
};
const int NUM_POSES = sizeof(poses) / sizeof(poses[0]);
void moveTo(Pose p) {
baseServo.write(p.base);
shoulderServo.write(p.shoulder);
elbowServo.write(p.elbow);
gripperServo.write(p.gripper);
}
void setup() {
Serial.begin(115200);
// ESP32Servo needs its PWM timers allocated before attaching servos
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
baseServo.setPeriodHertz(50);
shoulderServo.setPeriodHertz(50);
elbowServo.setPeriodHertz(50);
gripperServo.setPeriodHertz(50);
baseServo.attach(SERVO_BASE_PIN, SERVO_MIN_US, SERVO_MAX_US);
shoulderServo.attach(SERVO_SHOULDER_PIN, SERVO_MIN_US, SERVO_MAX_US);
elbowServo.attach(SERVO_ELBOW_PIN, SERVO_MIN_US, SERVO_MAX_US);
gripperServo.attach(SERVO_GRIPPER_PIN, SERVO_MIN_US, SERVO_MAX_US);
moveTo(poses[0]); // start at home position
delay(1000);
}
void loop() {
for (int i = 0; i < NUM_POSES; i++) {
moveTo(poses[i]);
delay(1000); // hold each pose for a second - reduce once movements are confirmed safe
}
}