After building my mobile robot MOBROB, one of my goals for several years has been to build my own 6DOF robot arm.

Of course, there are already many DIY 6DOF robot arm projects and tutorials available online. However, when looking at the existing designs, I usually found at least one drawback that didn’t fit what I wanted to build:
- Some use small and inexpensive servo motors, which makes the resulting 6DOF robot arm either relatively small or weak and unstable.
- Many larger designs use NEMA stepper motors combined with expensive gearboxes to achieve the required torque.
- Some projects use custom planetary gearboxes, but producing a precise, low-backlash gearbox requires either extensive machining, a lot of effort, or expensive components.
- Other designs rely heavily on CNC-milled aluminum parts, which again increases the cost and requires machinery I don’t have.
And most importantly, for me the design process is actually the fun part of building a 6DOF robot arm. Simply following an existing tutorial and reproducing somebody else’s design was therefore not very appealing.
So instead, I decided to design my own 6DOF robot arm.
My main requirements were:
- The cost per motor should not exceed approximately €20.
- All custom parts should be printable on my Artillery Sidewinder 3D printer with its 250 × 250 mm build plate.
- Mechanical parts that cannot be 3D printed should be easy to manufacture using basic workshop tools.
- No CNC milling or other expensive machining should be required.
- The robot arm should have a reach of at least 50 cm.
- It should provide a full 6 degrees of freedom, similar to an industrial robot arm.
- The robot should integrate with ROS 2 and MoveIt for visualization and motion planning.
The resulting robot combines NEMA 17 stepper motors, belt gearboxes, aluminum flat bars, 3D-printed components, magnetic encoders, a custom 3DOF wrist, and a Raspberry Pi Pico running the low-level motion control.
By viewing the video, you agree that your data will be transmitted to YouTube and that you have read the privacy policy
In this post, I will go through the main parts of the design:
Joints 1–3: These are the most critical joints because they require the highest torque and mechanical stability.
Robot arm structure: With a target reach of at least 50 cm, the structure needs to be rigid but also lightweight to reduce the load on J2 and J3.
Wrist: My original design used a 2DOF wrist. In the second iteration, I redesigned it as a full 3DOF spherical wrist similar to those found on many industrial robots.
Gripper: I have already described my parallel gripper in another post, so I will keep this section relatively short.
Electronics: A Raspberry Pi Pico based on the RP2040 microcontroller controls the motors and reads the position sensors.
Software: The software consists of the firmware running on the Raspberry Pi Pico and several ROS 2 packages that integrate the arm into ROS 2 and MoveIt.
Joints 1–3
The first three joints have by far the toughest requirements. They need to provide enough torque to lift the complete robot arm, even when it is fully extended. With a planned reach of approximately 50 cm, standard servo motors in my €20-per-motor price range simply cannot provide enough torque.
At the same time, I wanted precise position control and a reasonably high rotational resolution. Stepper motors seemed like a good solution. I already used them successfully in my mobile robot MOBROB, they are inexpensive, easy to control, and provide precise incremental motion.
The problem is torque.
A NEMA 17 stepper motor alone does not provide enough torque for a 6DOF robot arm of this size, so some form of gear reduction is necessary. Many DIY robot arms solve this by using either pre-geared stepper motors or custom planetary gearboxes. Pre-geared stepper motors can become quite expensive, while printing a precise planetary gearbox is difficult. My 3D printer simply isn’t accurate enough to produce a planetary gearbox with the low backlash and mechanical stability I wanted. While searching for alternatives, I came across this project on Thingiverse: NEMA 17 Precision Gearbox with Position Feedback by Emilostuff. Instead of conventional gears, this gearbox uses GT2 belts and pulleys. This is a very interesting solution because it provides a reduction of approximately 14:1 while keeping backlash very low. Combined with a NEMA 17 stepper motor rated at 59 Ncm, the resulting torque should be sufficient for the first three joints of my robot.
Another major advantage of the gearbox is its mechanical construction. The output shafts are already supported by integrated ball bearings, meaning that I don’t need to design separate bearing assemblies for J1, J2, and J3.
Adding absolute position feedback
Stepper motors are normally controlled by counting steps. Under ideal conditions, this means that the controller always knows the position of the motor. A 6DOF robot arm, however, is not exactly an ideal load. Especially J2 and J3 can experience significant torque when the arm is extended. If a stepper motor skips steps under load, the controller’s calculated position no longer matches the actual position of the joint.
For this reason, I added AS5600 magnetic encoders to J2 and J3. These measure the actual joint angle independently of the stepper motor. This gives me two advantages: I can detect positioning errors caused by skipped steps, and I always have direct feedback about the actual physical position of the joints.
For J1, the situation is slightly different because the load is much lower. Here, I use a Hall sensor as an endstop for homing and then track the joint position using the generated stepper pulses. I deliberately placed the Hall sensor outside the gearbox so that it is easy to access and adjust without disassembling the complete joint.
Robot Arm Structure
The next challenge was the structure of the arm itself. I wanted a total reach of approximately 50 cm. At this size, a completely 3D-printed structure would either become too flexible or require a lot of material, making the moving parts unnecessarily heavy. Weight is particularly important for a robot arm because every additional gram near the end of the arm increases the torque required from J2 and J3.
I therefore decided to combine 3D-printed parts with aluminum. Since I don’t have a CNC mill, I wanted to use aluminum profiles that could be processed with basic tools. I settled on 2 × 30 mm aluminum flat bars. They are inexpensive, readily available from hardware stores, and can easily be cut to length with a saw and drilled using a normal drill press. The main links consist of two parallel aluminum bars. Several 3D-printed blocks connect the bars and contain ball bearings for the joints.
This combination turned out to be a good compromise: the aluminum provides most of the structural stiffness, while the 3D-printed components allow me to create more complicated geometries for bearing mounts, spacers, cable routing, and other mechanical features.
Keeping the heavy motors close to the base
One of the most important design decisions was where to place the motors. The NEMA 17 motors themselves are already relatively heavy, and together with the 14:1 gearboxes they become even heavier. Mounting the J3 motor directly at the elbow would significantly increase the load on J2.
I therefore wanted to keep both motors as close to the base of the robot as possible. To achieve this, I use a four-bar linkage mechanism similar to the kinematics used by fruitcore robotics robot arms. Instead of placing the J3 motor directly at the elbow, the motor is located at the shoulder. The four-bar linkage transfers its rotational motion to the elbow joint. This keeps a significant amount of mass away from the end of the arm and reduces the torque required from J2.
Gravity compensation for J2
Even with the 14:1 gearbox, J2 still has to work quite hard when the arm is fully extended horizontally. Instead of simply using an even larger motor or gearbox, I decided to add a gravity compensation mechanism. This is a concept also used in many larger industrial robots: rather than forcing the motor to continuously fight gravity, a spring stores energy when the arm moves downward and returns that energy when the arm is lifted again.
In my design, I use a linear spring connected to J2 through a belt. The belt runs around a specially shaped pulley with a non-constant radius. This is important because the gravitational torque acting on J2 is not constant. When the arm is close to vertical, gravity produces relatively little torque around the joint. When the arm approaches the horizontal position, the gravitational torque becomes much larger. A normal circular pulley would produce approximately the same relationship between spring extension and joint rotation everywhere. Instead, the variable-radius pulley changes the mechanical advantage depending on the arm angle. Near the vertical position, the spring provides relatively little assistance. As the arm approaches the horizontal position, the geometry increases the supporting force.
The goal is not to perfectly cancel the weight of the arm, but to significantly reduce the peak torque that J2 has to provide. This allows me to use the same relatively inexpensive NEMA 17 motor and gearbox while still achieving the arm length I wanted.


Wrist
My original design was actually not a 6DOF robot arm. The first prototype was planned as a 5DOF robot with a 2DOF wrist. After building and testing the first parts, however, I realized that adding another degree of freedom should be possible without making the wrist excessively large.
A full 3DOF wrist makes the robot significantly more useful because the orientation of the end effector is no longer as restricted. The wrist has very different requirements compared with J1–J3. It does not need nearly as much torque, but weight and size become much more important. Every gram added to the wrist has to be carried by all the previous joints. Another problem with normal hobby servos is their limited range of motion. A typical range of around 180 degrees is simply not enough for some of the wrist movements I wanted, also the rotational resolution of such servos is not enough.

Differential mechanism for J5 and J6
For J5 and J6, I decided to use a differential mechanism. Two small geared DC motors jointly control both axes through bevel gears. The interesting part is that neither motor belongs exclusively to one joint. Instead, the resulting motion depends on the relative rotation of both motors. When both motors rotate in the same direction, the robot flange rotates around the J6 axis. When the motors rotate in opposite directions, the flange tilts around the J5 axis. By combining the two motor speeds, both movements can also happen simultaneously. This allows me to control two rotational axes using a compact mechanism while keeping both motors relatively close together.
Because DC motors don’t inherently provide position information, I use AS5600 magnetic encoders to measure the actual joint angles. The firmware then uses these measurements for closed-loop position control.
J4 and the modified servo
J4 presented another interesting mechanical problem. The entire forearm needs to rotate while all cables still have to pass through the center of the joint. At the same time, I need to measure the joint angle with a magnetic encoder. The load on J4 is not particularly high, but space is very limited because I wanted to keep the forearm reasonably slim.
My solution was to modify a micro servo motor for continuous rotation. The servo already contains a compact motor, gearbox, and motor driver, making it a very convenient package for this application. Normally, a servo uses an internal potentiometer to measure its output angle and is mechanically limited to a certain range. To convert it to continuous rotation, I removed the mechanical endstop and replaced the internal potentiometer with a fixed voltage divider. The servo can still be controlled with a standard servo PWM signal, but the signal no longer represents a target angle. Instead, it determines the direction and speed of rotation. The actual J4 angle is measured independently using a magnetic encoder.
This creates an interesting combination: mechanically and electrically, I am using the servo as a compact geared DC motor with an integrated motor driver, while the position control is implemented externally in my own firmware.
Gripper

For the end effector, I use my 3D-printed parallel-jaw gripper. I have already described its mechanical design in detail in a separate post: 3D-Printed Parallel Gripper.
Therefore, I won’t repeat the complete design here. The gripper is mounted to the J6 flange and is controlled together with the other robot joints from ROS 2.
Electronics
With the mechanical design mostly solved, the next challenge was controlling everything.
In total, the 6DOF robot arm contains:
- three stepper motors,
- two geared DC motors,
- one modified continuous-rotation servo for J4,
- the gripper servo,
- five AS5600 magnetic encoders,
- and one analog Hall sensor.
This requires quite a few GPIO pins and, more importantly, a microcontroller capable of handling several timing-sensitive tasks simultaneously. I chose the Raspberry Pi Pico with the RP2040 microcontroller.
Besides being inexpensive, the RP2040 has two CPU cores and plenty of GPIO pins. This makes it particularly useful for this 6DOF robot arm because generating stepper pulses needs to happen continuously and should not be interrupted by serial communication or other slower tasks.
Motor drivers
The three NEMA 17 stepper motors are controlled using A4988 stepper motor drivers, similar to the setup I already used for my mobile robot MOBROB. The A4988 handles the actual stepper motor current control, while the Raspberry Pi Pico only needs to provide direction and step signals. For the two DC motors in the differential wrist mechanism, I use an L293D motor driver. It contains two H-bridges, allowing both motors to be driven forward and backward. The modified J4 servo already contains its own motor driver and therefore only needs a PWM control signal from the Pico.
Magnetic encoders
For position feedback, I use AS5600 magnetic rotary encoders. These sensors provide a 12-bit angular resolution, corresponding to 4096 positions per revolution. This is more than sufficient for my application.
There is, however, one annoying limitation: all AS5600 sensors use the same fixed I²C address. Connecting several of them to the same I²C bus is therefore not possible directly. One option would be to use several separate I²C buses, but that would require additional GPIO pins. Instead, I use a TCA9548A I²C multiplexer. The Pico selects the required multiplexer channel and can then communicate with the corresponding encoder. This allows all five encoders to be accessed through the same main I²C interface.
For J1 homing, I use an AH3503 analog Hall sensor. Its output is connected to one of the Pico’s ADC inputs. When the magnet attached to J1 approaches the sensor, the firmware detects the change in magnetic field and uses this position as the reference point for the joint.
The following figures show the complete electronics schematic, including the main PCB, the I²C multiplexer PCB, motor drivers, encoders, and other peripherals.



Software
The software is split into two main layers. The low-level, timing-critical control runs directly on the Raspberry Pi Pico, while higher-level control, visualization, and motion planning run on a PC using ROS 2.
Raspberry Pi Pico firmware
I develop the firmware in Visual Studio Code using the official Raspberry Pi Pico extension. The Pico controls the stepper motors, DC motors, and modified continuous-rotation servo. A 100 Hz control loop reads the magnetic encoders and adjusts the motor outputs to move each joint toward its target position. The firmware also handles homing, joint limits, and encoder calibration.
Communication with the control PC takes place over USB serial at 115200 baud. The Pico accepts custom text commands for setting joint positions, velocities, and other parameters. In the opposite direction, it publishes the current joint positions and robot state as JSON data at 30 Hz.
Using both RP2040 cores
One challenge is that stepper motor control is quite timing-sensitive. If generating the step pulses were interrupted for too long by serial communication or encoder handling, the motors would no longer move smoothly. I therefore use both cores of the RP2040. Core 0 handles serial communication with the PC. Core 1 runs the motor controllers, reads the encoders over I²C, and continuously generates the stepper pulses. Commands and telemetry are exchanged between the two cores using queues. This means that the timing-critical motion control can continue independently of what is happening on the serial connection.
Closed-loop control of J4
J4 has a particularly interesting control system because of the modified continuous-rotation servo. Unlike a normal positional servo, the modified servo doesn’t know its actual position. A PWM value near the center tells it to stop, while values above or below this point cause it to rotate in either direction.
The problem is that the exact neutral point is not perfectly constant. It can drift slightly depending on temperature, load, and operating time. Even a very small offset can cause J4 to slowly creep when it should remain stationary. Because I already have an external magnetic encoder measuring the joint angle, I can compensate for this in software. The firmware detects unwanted motion and continuously adapts the neutral servo command. Together with the encoder feedback, this effectively turns the modified continuous-rotation servo back into a position-controlled actuator—but with my own external position controller instead of the servo’s original potentiometer.
ROS 2 integration
For integration with higher-level software, I created a ROS 2 driver that acts as a bridge between the Pico’s custom serial protocol and standard ROS interfaces. The driver converts the degree-based values used by the firmware into radians and publishes the measured joint positions as JointState messages.
In the opposite direction, it accepts individual position commands as well as complete joint trajectories. The driver also provides commands for controlling the gripper, homing J1, stopping the robot, and saving or loading the encoder calibration. From the perspective of ROS 2, the custom hardware therefore behaves much more like a conventional ROS-controlled robot.
URDF, RViz, and MoveIt
The robot is represented in ROS 2 using a URDF model. The model describes the links, joint axes, dimensions, movement limits, collision geometry, and gripper. Together with robot_state_publisher, the measured joint positions can be used to visualize the pose of the physical robot live in RViz. Moving the real robot therefore also moves the virtual robot model.
I also created a MoveIt configuration for inverse kinematics, motion planning, collision checking, and trajectory execution. Instead of manually calculating the required angles for all six joints, I can define a target pose for the end effector and let MoveIt calculate a suitable trajectory. The resulting trajectory is passed to my ROS 2 driver, which translates it into commands for the Raspberry Pi Pico. The Pico then takes care of the actual low-level motor and position control.
This separation works quite nicely: ROS 2 and MoveIt handle the computationally more complex tasks such as kinematics and motion planning, while the microcontroller is responsible for reliable real-time control of the physical hardware.
The current URDF still uses simplified collision geometry and approximate physical properties, but it already provides a useful foundation for visualizing the robot and planning motions for the real arm.


Results and Lessons Learned
The final result is a full 6DOF robot arm built mostly from relatively inexpensive and easily available components.
One of my main goals was to avoid relying on expensive robot-specific actuators or CNC-machined parts. Instead, the design combines inexpensive NEMA 17 stepper motors, GT2 belt gearboxes, simple aluminum flat bars, 3D-printed parts, small geared DC motors, and magnetic encoders.
The project also demonstrated how important weight distribution is for a robot arm. Simply choosing motors with more torque is not always the best solution. Moving the J3 motor toward the shoulder and adding gravity compensation to J2 both reduce the required motor torque without significantly increasing the cost of the robot.
The 3DOF wrist was another part that evolved significantly during development. What started as a simpler 2DOF design eventually became a differential 3DOF wrist, giving the robot the same fundamental six-axis kinematic structure found in many industrial robot arms.
There are, of course, still limitations. This is a DIY robot built from 3D-printed components, inexpensive motors, and hardware-store aluminum, so I don’t expect the precision, stiffness, speed, or payload of an industrial robot. Mechanical flex, backlash, and the accuracy of the individual joints all influence the final positioning accuracy of the end effector.
However, for me the main goal of the project was not to build the cheapest possible replacement for an industrial robot. The goal was to understand and build as much of the complete system as possible myself—from the mechanical structure and gearboxes to the electronics, firmware, position control, ROS 2 integration, and motion planning.
And that is also what made this project so interesting. There are still plenty of things I would like to improve in future iterations: optimizing the joint controllers, improving the mechanical stiffness, refining the URDF and collision models, measuring the actual repeatability and payload, and experimenting with more advanced motion planning and control.
But at this point, I finally have something I wanted to build for years: my own fully functional 6DOF robot arm.
Files and Source Code
If you want to take a closer look at the project or build on parts of the design yourself, I have made the software and CAD files available online.
ROS 2 packages — GitHub
The ROS 2 repository contains the robot description, URDF model, ROS 2 driver, MoveIt configuration, and the packages required to control and visualize the robot.
- techniccontroller/ros2_6dof_robot_driver
- techniccontroller/ros2_6dof_robot_description
- techniccontroller/ros2_6dof_robot_moveit_config
Raspberry Pi Pico firmware — GitHub
The firmware repository contains the code running on the Raspberry Pi Pico, including motor control, encoder feedback, homing, calibration, and the serial communication protocol used by the ROS 2 driver.
CAD files — GrabCAD

The mechanical design and CAD files for the robot arm are available on GrabCAD. These include the custom parts for the arm structure, joints, gravity compensation mechanism, and wrist.
Feel free to use the files as inspiration for your own projects, modify the designs, or build your own version of the robot. If you find something useful—or come up with an improvement—I would be interested to see what you build!






0 Comments