DIY 6-DOF Robotic Arm – 3D Print, Wire & Program Step by Step

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.

The video loads from YouTube (Google), which may set cookies. Privacy policy

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.

ItemQuantityPartDescription
11Base_1Base plate (fixed)
21base_link_upper1Upper platform (rotating)
31arm_link_1First arm link
41arm_link_2Second arm link
51arm_link_3Third arm link
61gripper_baseGripper base
71Gripper_2Gripper finger
81Gear_rightGripper gear (right)
91Gear_leftGripper gear (left)
101GripperSecond gripper finger
114LinkGripper link parts
122SpacerGripper joint spacers
133MG996R Servo MotorHigh-torque servo, 180° version (base, shoulder, elbow)
143SG90 Micro Servo MotorMicro servo (wrist, gripper)
1516ANSI B18.6.4 No.2-32 3/8″Truss head screw, No.2-32
162ANSI B18.6.4 No.2-32 1/4″Truss head screw, No.2-32
173DIN 7985 M1.6×2-ZM1.6×2 cross-recess screw
187ISO 4762 M3x16 (AS 1420)M3×16 hex socket screw
1912ISO 4032 M3 (AS 1112)M3 hex nut
202ANSI B18.6.4 No.2-32 1/2″Truss head screw, No.2-32
213MG996R servo hornHorn supplied with the MG996R
223DIN 7985 M3x6-ZM3×6 cross-recess screw
233SG90 Servo HornHorn for the SG90
2412ANSI B18.6.4 No.3-28 1/2″Truss head screw, No.3-28
251Ball Bearing 6806ZZ (30x42x7)Deep-groove bearing (30×42×7 mm)
261Arduino MegaMain controller (the wiring section explains why not the Uno)
271PCA9685 Servo Driver Board16-channel PWM driver (I2C)
28610 kΩ PotentiometerOne per servo
291BreadboardWiring base for the potentiometers
3015 V 10 A Power SupplyServo power source
311Jumper wires (male-female)Arduino to PCA9685
321Jumper wires (male-male)Breadboard to Arduino
331Wooden boardArm 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.

Technical drawing of the robotic arm with its parts, servos and assembly dimensions

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.

Cura slicer preview of the large arm parts with the print settings panel
White PLA arm parts printing on an Anycubic 3D printer

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.

All parts laid out before assembly: printed parts, three MG996R and three SG90 servos, the bearing, horns and screws

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.
The first MG996R servo screwed into the base
Pressing the 6806ZZ ball bearing into the rotating upper platform
Screwing the upper platform onto the horn of the base servo

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.

Servo horns screwed into the arm links and an SG90 horn pressed into a gripper part

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.

Screwing the servos into their pockets: an MG996R in the platform bracket, SG90s in an arm link and the gripper mount

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.

Screwing the arm links to each other and the gripper mount to the last link

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:

  1. Mount the SG90 in gripper_base.
  2. Fit the first gear part on the SG90 horn, then the opposite gear part on the other side so the two gears mesh.
  3. Connect the four Link parts with the two Spacers, then add the last finger part. Use M3×20 screws for the gripper joints.
  4. Tighten the screws and check that both fingers open and close evenly, without binding.
  5. Attach the finished gripper to the last arm link.
Assembling the gripper fingers and gears and screwing the gripper to the wrist

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.

Screwing the base of the arm to the wooden board

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.

PCA9685 servo driver board with labeled pins, the servo power terminal and 16 servo outputs

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.

Fritzing wiring diagram with the Arduino Mega, the PCA9685, six potentiometers and six servos

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

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.

9 thoughts on “DIY 6-DOF Robotic Arm – 3D Print, Wire & Program Step by Step”

  1. 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.

    Reply
    • 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.

      Spring

      Reply
  2. 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

    Reply
    • 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.
      Spring

      Reply
  3. 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

    Reply

Leave a Comment