A robotic arm is an articulated mechanism capable of positioning an end effector through the coordinated movement of several joints.
So far we have moved one servo (in the Safe Box) or two (in the Solar Tracker). But coordinating four or more joints to move an object in three-dimensional space is a huge qualitative leap.
In this article, we are going to assemble and control an anthropomorphic robotic arm (imitating a human arm) with 4 Degrees of Freedom (4-DOF).
What is a Degree of Freedom (DOF)? In robotics, each independent joint that the robot can control is considered a degree of freedom. Our arm will have:
- Waist (Base): Horizontal rotation.
- Shoulder: Elevation of the main arm.
- Elbow: Elevation of the forearm.
- Gripper (End Effector): Opening and closing.
Forward and Inverse Kinematics
Before programming, we must understand the mathematical problem we face. There are two ways to control a robot:
- Forward Kinematics: We tell the robot the angles of each motor (e.g., “Set the elbow to 90º and the shoulder to 45º”) and the robot’s hand ends up in a resulting position. This is easy to program but difficult to use if we want to pick up an object from a table.
- Inverse Kinematics: We indicate a coordinate, for example
(x, y, z), and the controller calculates what angles the joints need to reach it.
In this initial project, we will implement Manual Forward Kinematics: we will use potentiometers to directly control the angle of each joint. This is the necessary previous step to verify that the mechanics work before diving into the complex equations of inverse kinematics.
Components
- [1x|blue] Arduino Uno | Or Nano + Expansion Shield (recommended)
- [2x|green] SG90 Servo | For Gripper and Base
- [2x|orange] MG90S Servo (metal gears) | For Shoulder and Elbow {recommended}
- [4x|yellow] 10kΩ Linear Potentiometers | Manual joint control
- [1x|red] Regulated External Power Supply | Sized for the stall current of the servos
The mechanics are critical. You can use a lightweight kit or 3D print the structure, but you must verify that the torque of the servos is sufficient for the weight and length of each arm.
Never power 4 servos from the Arduino 5V pin. The peak consumption can reset the board (Brown-out) or burn the regulator. You need an external power source (batteries or transformer) sharing the ground (GND) with the Arduino.
Connection Diagram
Wiring can quickly become a mess. Keep the wires organized and leave enough slack at each joint.
Control Potentiometers
Each potentiometer acts as a voltage divider. The center pin goes to the corresponding analog input.
[ { “from”: “Potentiometer 1 (Wiper)”, “to”: “Pin A0”, “color”: “yellow”, “note”: “Ends to 5V and GND” }, { “from”: “Potentiometer 2 (Wiper)”, “to”: “Pin A1”, “color”: “yellow”, “note”: “Ends to 5V and GND” }, { “from”: “Potentiometer 3 (Wiper)”, “to”: “Pin A2”, “color”: “yellow”, “note”: “Ends to 5V and GND” }, { “from”: “Potentiometer 4 (Wiper)”, “to”: “Pin A3”, “color”: “yellow”, “note”: “Ends to 5V and GND” }, { “from”: “Potentiometer ends”, “to”: “Arduino 5V and GND”, “color”: “gray” } ]
Arm Servos
Servo.h generates pulses using timers, so these pins do not need hardware PWM.
[ { “from”: “Base Servo Signal”, “to”: “Pin 3”, “color”: “green” }, { “from”: “Shoulder Servo Signal”, “to”: “Pin 5”, “color”: “green” }, { “from”: “Elbow Servo Signal”, “to”: “Pin 6”, “color”: “green” }, { “from”: “Gripper Servo Signal”, “to”: “Pin 9”, “color”: “green” }, { “from”: “Servos VCC (all)”, “to”: “External 5V Source”, “color”: “red”, “note”: “NOT to Arduino 5V pin” }, { “from”: “Servos GND (all)”, “to”: “Source GND + Arduino GND”, “color”: “black”, “note”: “Mandatory common ground” } ]
The Code
We are going to create scalable code. Instead of copy-pasting the same thing 4 times, we will use Arrays and Loops to manage the joints. This makes the code professional and easy to extend to 6-axis robots.
Objects and Structures
We will use the Servo.h library. Additionally, we will define a “map” of our limits. Not all servos can rotate from 0 to 180º in the assembled robot; sometimes the mechanics collide before that.
#include <Servo.h>
// Define the number of joints
const int NUM_SERVOS = 4;
// Create the Servo objects
Servo brazo[NUM_SERVOS];
// Connection pins
const int pinesPot[NUM_SERVOS] = {A0, A1, A2, A3};
const int pinesServo[NUM_SERVOS] = {3, 5, 6, 9};
// --- MECHANICAL CALIBRATION ---
// Adjust these values to avoid forcing the motors
// Order: {Base, Shoulder, Elbow, Gripper}
const int anguloMin[NUM_SERVOS] = {0, 15, 0, 10}; // Physical minimum
const int anguloMax[NUM_SERVOS] = {180, 165, 180, 100}; // Physical maximum
// Variables for smoothing (avoid jitter)
int valorLeido[NUM_SERVOS];
int anguloActual[NUM_SERVOS];
setup
We initialize the servos and iterate through the arrays to configure everything.
void setup() {
Serial.begin(9600);
for (int i = 0; i < NUM_SERVOS; i++) {
brazo[i].attach(pinesServo[i]);
// Initialize at a safe position (90 degrees)
// so the robot does not "jerk" when turned on
brazo[i].write(90);
}
Serial.println("Robot Initialized. Manual Control Activated.");
delay(1000);
}
Reading and Mapping in loop
Here is where we translate the movement of our hand (potentiometer) to the movement of the robot.
A common problem is jitter. Cheap potentiometers fluctuate. To avoid this, we will only update the servo if the value changes significantly.
void loop() {
for (int i = 0; i < NUM_SERVOS; i++) {
// 1. Read the analog value (0 - 1023)
int lectura = analogRead(pinesPot[i]);
// 2. Map to the allowed angle range for that joint
int anguloDeseado = map(lectura, 0, 1023, anguloMin[i], anguloMax[i]);
// 3. Simple filter to avoid jitter
// Only move if the difference is greater than 2 degrees
if (abs(anguloDeseado - anguloActual[i]) > 2) {
brazo[i].write(anguloDeseado);
anguloActual[i] = anguloDeseado;
}
}
// A small delay to stabilize the ADC
delay(15);
}
Exponential Movement Smoothing
The previous code works, but the movement will be as jerky as your hand. Industrial robots move elegantly. We can improve this by adding a bit of software inertia.
Replace the block in the loop with this concept:
// Smoothing factor (0.1 = very smooth/slow, 0.9 = fast/jerky)
float alpha = 0.1;
// In global variables we change anguloActual to float
// float anguloSuavizado[NUM_SERVOS];
// Inside the loop...
int lectura = analogRead(pinesPot[i]);
int target = map(lectura, 0, 1023, anguloMin[i], anguloMax[i]);
// Exponential moving average formula
anguloSuavizado[i] = (alpha * target) + ((1.0 - alpha) * anguloSuavizado[i]);
brazo[i].write((int)anguloSuavizado[i]);
This will make the robot seem to have “weight” and inertia, moving in an organic and fluid way.
Challenges and Next Steps
Building the arm is just the beginning. The real challenges come now:
- Record and Playback: Could you add a button that, when pressed, saves the position of the 4 servos in an array? If you save several steps, you could make the robot repeat a sequence (pick object -> move it -> release it) automatically.
- Inverse Kinematics: The “Holy Grail”. Implement the math so you can tell the robot
goTo(10, 5, 4)(coordinates in cm) and it calculates the angles itself. - Bluetooth: Replace the potentiometers with a mobile app that sends coordinates via Bluetooth (HC-05).