This post contains affiliate links, marked with *. As an Amazon Associate I earn from qualifying purchases, at no extra cost to you. It helps keep the tutorials free.
Quick summary
Build a 6-DOF robotic arm from 3D printed parts and an Arduino Mega. It uses three MG996R servos, three SG90 servos and a PCA9685 servo driver. Six potentiometers control the arm, one per servo. The post contains the parts list, print settings, assembly steps, wiring diagram and the full Arduino sketch.
6-DOF (six degrees of freedom) means the arm has six joints, each driven by its own servo. Three MG996R servos drive the base rotation, the shoulder and the elbow. Three SG90 micro servos drive the wrist pitch, the wrist roll and the gripper. Pitch tilts the gripper up and down; roll turns it around the axis of the last arm link. Strictly, the gripper is not a degree of freedom, but hobby arms usually count it. My servo motor control guide explains how these motors work.
Each servo follows its own 10 kΩ potentiometer, a rotary resistor whose output voltage changes as you turn its shaft. A servo gets its target angle from a PWM signal (pulse-width modulation): a pulse repeated many times per second, whose length sets the angle. The PCA9685 board generates all six PWM signals. The Arduino sends it commands over I2C, a bus that needs only two wires: SDA for data and SCL for the clock. The build video is below.
Bill of materials
Items 1 to 12 are the printed parts. Their STL files in the download have similar names, for example base_link.stl for Base_1 and gripper_link.stl for Link.
| Item | Quantity | Part | Description |
|---|---|---|---|
| 1 | 1 | Base_1 | Base plate (fixed) |
| 2 | 1 | base_link_upper1 | Upper platform (rotating) |
| 3 | 1 | arm_link_1 | First arm link |
| 4 | 1 | arm_link_2 | Second arm link |
| 5 | 1 | arm_link_3 | Third arm link |
| 6 | 1 | gripper_base | Gripper base |
| 7 | 1 | Gripper_2 | Gripper finger |
| 8 | 1 | Gear_right | Gripper gear (right) |
| 9 | 1 | Gear_left | Gripper gear (left) |
| 10 | 1 | Gripper | Second gripper finger |
| 11 | 4 | Link | Gripper link parts |
| 12 | 2 | Spacer | Gripper joint spacers |
| 13 | 3 | MG996R Servo Motor | High-torque servo, 180° version (base, shoulder, elbow) |
| 14 | 3 | SG90 Micro Servo Motor | Micro servo (wrist, gripper) |
| 15 | 16 | ANSI B18.6.4 No.2-32 3/8″ | Truss head screw, No.2-32 |
| 16 | 2 | ANSI B18.6.4 No.2-32 1/4″ | Truss head screw, No.2-32 |
| 17 | 3 | DIN 7985 M1.6×2-Z | M1.6×2 cross-recess screw |
| 18 | 7 | ISO 4762 M3x16 (AS 1420) | M3×16 hex socket screw |
| 19 | 12 | ISO 4032 M3 (AS 1112) | M3 hex nut |
| 20 | 2 | ANSI B18.6.4 No.2-32 1/2″ | Truss head screw, No.2-32 |
| 21 | 3 | MG996R servo horn | Horn supplied with the MG996R |
| 22 | 3 | DIN 7985 M3x6-Z | M3×6 cross-recess screw |
| 23 | 3 | SG90 Servo Horn | Horn for the SG90 |
| 24 | 12 | ANSI B18.6.4 No.3-28 1/2″ | Truss head screw, No.3-28 |
| 25 | 1 | Ball Bearing 6806ZZ (30x42x7) | Deep-groove bearing (30×42×7 mm) |
| 26 | 1 | Arduino Mega | Main controller (the wiring section explains why not the Uno) |
| 27 | 1 | PCA9685 Servo Driver Board | 16-channel PWM driver (I2C) |
| 28 | 6 | 10 kΩ Potentiometer | One per servo |
| 29 | 1 | Breadboard | Wiring base for the potentiometers |
| 30 | 1 | 5 V 10 A Power Supply | Servo power source |
| 31 | 1 | Jumper wires (male-female) | Arduino to PCA9685 |
| 32 | 1 | Jumper wires (male-male) | Breadboard to Arduino |
| 33 | 1 | Wooden board | Arm mounting platform |
Designing the parts in Autodesk Inventor
All parts are modeled in Autodesk Inventor and exported as STL files. STL is the 3D model format that the slicer reads; the slicer is the program that turns the model into printer instructions. Inventor is a parametric CAD (computer-aided design) program: every dimension is stored as a value you can change later, and the assembly view shows whether the parts fit before you print them. If you are new to Inventor or 3D printing, start with my Introduction to 3D Printing and 3D Design with Inventor.
The part sizes follow from the two servo types. The MG996R (about 9 to 11 kg·cm stall torque, depending on voltage) drives the base, shoulder and elbow, where the load is highest. Stall torque is the torque at which the servo stops turning; 1 kg·cm means it holds 1 kg on a lever 1 cm from the shaft. The SG90 (about 1.8 kg·cm) is small and light, so it drives the wrist and gripper, where the load is lowest.
At each joint I modeled a cylindrical rim that keeps the rotation centered, so the joint turns in a clean circle. The servo pockets and the bearing seat are sized for a press fit: each opening is slightly tighter than the part, so you push the part in and friction holds it. The servos are also screwed in place during assembly. Expect some light sanding after printing. The technical drawing below shows all part dimensions, sectional views and how the parts fit together. Use it to check print accuracy.

3D printing the parts
Load the STL files from the download into your slicer. I sliced the parts in Cura and printed them on my Anycubic i3 Mega. Use these settings:
- Material: PLA filament. Filament is the plastic wire on a spool that the printer melts; PLA is a common 3D printing plastic that prints easily and holds fine detail.
- Layer height: 0.2 mm, the thickness of each printed layer.
- Infill: at least 40% for the base and arm links. Infill is the internal fill of a printed part; 40% means 40% of the inside is solid. My plate in the Cura screenshot below used 30% with the cubic pattern; 40% gives stiffer parts.
- Walls: 1.2 mm wall thickness, which is three outlines with the standard 0.4 mm nozzle. Walls are the solid outlines of each layer.
- Support: on. Support material is printed scaffolding under overhangs (sections that stick out over empty space) that you remove afterwards. Print the arm links lying flat on the bed.
For the plate in the screenshot below, Cura estimated 1 day 12 hours 43 minutes and 280 g of filament. After printing, remove the support material, sand the mating surfaces (the faces where two parts touch) and test-fit each servo in its pocket.


Assembling the arm
Build the arm from the base up, in the order below. Each joint is driven through a servo horn, the small plastic arm that sits on a servo’s output shaft and passes its motion on. Before a horn goes onto a servo shaft, move that servo to its middle position (about 90°). Use a servo tester (a small device that drives a servo to a set angle), or wire the circuit first and turn its potentiometer to the middle. Then fit the horn so the joint sits in the middle of its range.
After each step, turn the new joint slowly by hand and check that nothing rubs or binds. Do not force a joint past its end stop (the limit of its rotation); the SG90 has plastic gears. When my arm was complete, I moved every joint by hand once more before any wiring.

Step 1: Assembling the base
- Securing the servo motor: Install the first MG996R servo in the base mount with the screws supplied with the servo, or with M3×12 cross-recess self-tapping screws. Self-tapping screws cut their own thread into the plastic. This servo rotates the base.
- Adding the ball bearing (6806ZZ, 30×42×7 mm): Press the deep-groove ball bearing (the common type of ball bearing) into its seat in the rotating upper platform. Push it in straight so it does not tilt in the seat. The three numbers are bore, outer diameter and width in millimeters; ZZ means the bearing has a metal shield on both sides. The bearing sits between the fixed base plate and the rotating upper platform. I added it to the design so the platform turns freely and does not tilt on the servo shaft.
- Connecting the servo horn: Attach the horn to the rotating upper platform with M2×12 self-tapping screws. Tighten them well: this horn transmits all of the base servo’s torque to the arm above it.



Step 2: Mounting the servo horns to the arm links
Attach each horn to its arm link with two screws. A misaligned horn causes binding and uneven movement, so check that the holes line up before you tighten. For the SG90s, I cut both ends off the plastic horns so they fit the recesses I designed in the printed parts.

Step 3: Mounting the servo motors to the arm links
Seat each servo in its pocket with the output shaft on the joint axis, as shown in the photo below. Fasten it with two screws and press lightly on the servo: it must not shift or rock.

Step 4: Connecting the arm links
Join the link-and-servo assemblies. Position the horn on one link so it slots into the attachment point of the next link, then secure it with screws. Tighten evenly so the joint moves without looseness.

Step 5: Assembling the gripper
The gripper uses an SG90 micro servo and a pair of 3D printed gears to open and close the fingers. Build it in this order:
- Mount the SG90 in gripper_base.
- Fit the first gear part on the SG90 horn, then the opposite gear part on the other side so the two gears mesh.
- Connect the four Link parts with the two Spacers, then add the last finger part. Use M3×20 screws for the gripper joints.
- Tighten the screws and check that both fingers open and close evenly, without binding.
- Attach the finished gripper to the last arm link.

Step 6: Securing the arm to a wooden board
Mount the assembled arm on a flat wooden board with M3×12 self-tapping screws. The board keeps the arm from tipping during fast movements and holds the breadboard and the power supply.

Why the arm uses a PCA9685 servo driver
An Arduino can drive servos directly with the Servo library. On the Uno, this library uses hardware Timer1, one of the chip’s internal counters. This disables analogWrite(), the function for PWM output, on pins 9 and 10. It also clashes with other libraries that need the same timer. The PCA9685 generates the pulses on its own chip, so the Arduino only sends a new position over I2C. One board has 16 channels, so six servos leave ten channels free.
PCA9685 pinout reference
- GND: Common ground, shared with the Arduino.
- OE (Output Enable): Active low, meaning the outputs are on while this pin is low. The board pulls it low, so leave it unconnected.
- SCL: I2C clock line.
- SDA: I2C data line.
- VCC: Logic power, 3.3 to 5 V. This powers only the PCA9685 chip, not the servos.
- V+: Servo power rail, up to 6 V. Connect the external supply here through the polarized screw terminal, a connector that clamps each wire with a screw and has a fixed side for plus and minus.
Each channel has a 3-pin header (three metal pins for PWM, V+ and GND) that takes a standard servo plug directly.

Wiring the Arduino and the servo driver
Use an Arduino Mega for this build; in the video I used a Mega-compatible board. On the Uno, pins A4 and A5 are also the I2C lines SDA and SCL, so they cannot read potentiometers while the PCA9685 is connected. On the Mega, SDA and SCL are pins 20 and 21, and A0 to A5 stay free for the six potentiometers.
- PCA9685 to Arduino: Connect VCC to 5 V, GND to GND, SDA to pin 20 and SCL to pin 21. Use male-female jumper wires, short cables with a pin on one end and a socket on the other. The GND wire connects both grounds, which the I2C signals need as a common reference.
- Servos to PCA9685: Plug the servos into the PCA9685 in this order: channel 0 base, 1 shoulder, 2 elbow, 3 and 4 the two wrist servos, 5 gripper. The potentiometer on A0 controls channel 0, A1 controls channel 1, and so on. Match the servo’s brown or black wire to the header row marked GND on the board; the orange or yellow signal wire goes to the PWM row.
- Potentiometers to Arduino: Wire the outer legs of each potentiometer to 5 V and GND on the breadboard, a board with spring contacts for building circuits without soldering. Connect the center pin (the wiper, which picks up the voltage between the outer legs) to A0 through A5 with male-male jumper wires.
- Servo power supply: Connect the 5 V 10 A supply to the V+ and GND screw terminals of the PCA9685. If your supply is an open-frame unit with mains screw terminals (L, N, earth), those terminals carry mains voltage. Cover them, and leave the mains wiring to a qualified person. The servo current flows from the supply through the PCA9685, not through the Arduino. Do not power the servos from the Arduino’s 5 V pin; it cannot supply enough current. An MG996R can draw up to about 2.5 A when it stalls, an SG90 up to about 0.65 A. All six together can draw up to 3 × 2.5 A + 3 × 0.65 A = 9.45 A, so the supply is rated 5 V 10 A. Power the Arduino over its USB cable or through its DC jack.
The diagram draws both supplies as batteries. In my build, the PCA9685 ran from a power supply instead of the four AAA batteries.

Programming the servo control
The Arduino program (called a sketch) reads the voltage on each analog pin, converts it to a servo pulse width and sends it to the PCA9685.
How the code works
- Library initialization: The sketch includes Wire.h for I2C and Adafruit_PWMServoDriver.h for the PCA9685. In setup(), which runs once when the board starts, it calls pwm.begin() and sets the PWM frequency to 60 Hz. Hobby servos are specified for 50 Hz (one pulse every 20 ms), but most of them also work at 60 Hz. If you change it to 50 Hz, recalculate the 125 and 575 values below.
- Reading the potentiometers: Inside loop(), which repeats continuously after setup(), analogRead(A0) through analogRead(A5) return a value between 0 and 1023. The value follows the voltage on the wiper, which changes as you turn the potentiometer.
- Mapping to a servo pulse: A tick is one of the 4096 steps in each PWM period. At 60 Hz the period is 16.7 ms, so one tick is about 4.07 µs. The map() function converts the 0 to 1023 range into 125 to 575 ticks (about 0.5 ms to 2.3 ms), which correspond to the servo’s full rotation range.
- Setting the PWM output: pwm.setPWM(channel, 0, ticks) sends the computed pulse to the matching PCA9685 channel. The second parameter (0) is the tick at which the pulse switches on, the third the tick at which it switches off within the 4096-step cycle.
- Loop delay: A 20 ms delay limits the loop to at most 50 updates per second, so the I2C bus is not flooded with commands.
Before uploading, install the Adafruit PWM Servo Driver Library via Sketch > Include Library > Manage Libraries in the Arduino IDE, the program in which you write and upload sketches. Wire.h ships with the Arduino IDE. Select your board and COM port (the serial port under which the board appears on your computer over USB), then click Upload. On my PC, the board showed up as COM7.
Arduino sketch: full source code
/**
* Author: Omar Draidrya
* Date: 2024/05/05
* Controls 6 servos via PCA9685 with potentiometer input.
*/
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); // Default address 0x40
void setup() {
Serial.begin(9600); // Init serial for debugging
pwm.begin(); // Init PCA9685
pwm.setPWMFreq(60); // 60 Hz for servos
}
void loop() {
// Servo 0
int potValue0 = analogRead(A0); // Read A0
int servoPos0 = map(potValue0, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(0, 0, servoPos0); // Set servo 0
// Servo 1
int potValue1 = analogRead(A1); // Read A1
int servoPos1 = map(potValue1, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(1, 0, servoPos1); // Set servo 1
// Servo 2
int potValue2 = analogRead(A2); // Read A2
int servoPos2 = map(potValue2, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(2, 0, servoPos2); // Set servo 2
// Servo 3
int potValue3 = analogRead(A3); // Read A3
int servoPos3 = map(potValue3, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(3, 0, servoPos3); // Set servo 3
// Servo 4
int potValue4 = analogRead(A4); // Read A4
int servoPos4 = map(potValue4, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(4, 0, servoPos4); // Set servo 4
// Servo 5
int potValue5 = analogRead(A5); // Read A5
int servoPos5 = map(potValue5, 0, 1023, 125, 575); // Map to pulse range
pwm.setPWM(5, 0, servoPos5); // Set servo 5
delay(20); // Small delay for smooth updates
}
Calibrating and testing the arm
I uploaded the sketch first and connected the servo supply afterwards. Before you switch on the servo supply, turn every potentiometer to the middle. At power-on, each servo moves straight to the position of its potentiometer. Then turn each potentiometer slowly; the matching joint should follow. If a joint moves the wrong way, swap the outer wires on that potentiometer to reverse the direction.
Fine-tune the map() values (125 and 575) for each servo. Some servos need slightly different minimum and maximum tick values to stop buzzing at the mechanical end stops. Test the arm with a light object. In my first test, the arm picked up a small Eiffel Tower model and set it down on a board. In a second test, it moved a few other small models the same way.
What to do next
- Inverse kinematics: calculating the joint angles from a target position of the gripper.
- Wireless control with a Bluetooth module, as in my Bluetooth-controlled robotic arm upgrade.
- Recording a sequence of movements and replaying it.
Download the 3D print files (STL)
The STL files are available in the OmArTronics shop. The code, wiring diagram and parts list are on this page.

can u tell how to connect potentiometer with bread board
I am building an AI Robot and want to use your robotic arm in the build. I want to mount it on the front side vertically. I noticed on past photos there was a tension spring attached to the rotating base to the first arm piece but not listed in the parts list or build instructions. I wish to implement the spring back into the build because of its vertical placement on my robot. Can you provide me with the specifications for the spring? This robotic arm is in my opinion is one of the better designed and documented arms of its type out on the web.
Thanks a lot! I’m glad you liked the design
Yes, that tension spring connects the rotating base to the first arm segment to help support the shoulder joint, especially when the arm is mounted vertically.
The spring is about 2 cm long (at rest) and 9 mm in diameter.
I actually reused it from an old desk lamp stand, but you can find similar extension springs easily online or in hardware stores.
It’s optional but really helps balance the arm and reduce servo stress.
Hello! I wanted to ask the function of the atachment points on the base and the first link, are they for a spring? it is modelled but the pictures of the assembly don’t have this feature, nor is it referenced on the post. Thank you! this is a nice design
Yes, those points are for a spring. The spring is optional — it helps the shoulder joint carry part of the arm’s weight.

This feature was added in a later version of the design.
You can also use a rubber band, but I personally prefer a spring.
The one I used is about 2 cm long (at rest) and 9 mm in diameter.
I actually took it from an old desk lamp stand, but you can easily find similar springs online or in hardware stores.
Hi,
I got different bearings form factor (60mm) and I got two different sizes for my servomotors
2x MS24 20kg for the base and 3x MG996R for the rest of joints and the gripper.
To be simple, I want to pimp your very cool design and make it muscle to the gym 🙂
Is there a way you kindly communicate STEP files and of course, IF I publish my results I will mention as the designer of the arm
Thanks in advance
Hi Jeremy, thanks a lot, here is a new version with STEP files, build guide, and more: https://omartronics.com/product/omarm-zero-6dof-esp32-robotic-arm/