This is my robot. There are many like it, but this one is mine. This robot is my latest obsession. It is the tool I use to push myself to learn new skills. I must master it as I must master my life. Without me, my robot is useless. Without my robot, I am bored. I must use my robot to do cool things. I must share my findings with others for these findings might lead others to the same path, or at least get me some kudos. (Loosely based on the Rifeleman's Creed) |
Intro
In this series of posts, I'll try to share my experiences in learning through building. Specifically, by building a six-degree-of-freedom (6DOF) manipulator, AKA a robot arm. I'll try to cover everything you need to build your own, share some relevant examples and demonstrate one way of diving into robotics. But first, I'd like to clarify my reasoning.
Why write "yet another robotics tutorial"? Over the years I made many attempts at getting through an introductory robotics text book. The first attempt was given up on quite early, retaining little beyond some basic definitions. On successive attempts I had little more success. A promising new book or tutorial might catch my eye, I'd spend a few weeks working through example problems and inevitably giving up yet again. Not only did the maths seem impenetrable, the goal of "doing cool stuff™" was nowhere in sight.
Not wanting to spend hard earned cash on yet another hobby that ends up collecting dust in a cupboard, I decided on a foolproof test. "If you get through one of those books" said I, "that means you're into the hobby enough to put money on it". As should be clear from the above paragraph, no such luck. The urge to play around with robots never left, and scaling the mountain of introductory robotics never seemed any more achievable.
Thankfully, my wife resolved the conflict with a simple question: "How about we buy a 3D printer? I heard they're getting cheap". Agreeing excitedly, I immediately knew the answer to her next question: "What will you build with it?". "A robot" was my response.
Some time later, we're doing a sanity check with a relatively functional arm. We were trying to make the arm follow a specific path and return to it's initial position, but couldn't get it right. Making the arm to return to an initial position was straightforward, but tracing out a path just didn't seem to work. In hindsight, why would it? I was doing it all wrong.
"Maybe all those matrices are actually good for something?" I wondered, uncertain.
Since then, I've become a close personal acquaintance with a variety of matrices, even of the rotational and homogeneous persuasion. Similarly, quaternions no longer seem so malicious nor are reference frames a confusing jumble of notation & convention. There's still a lot to learn, but failing at controlling a physical robot arm helped me get over the hump of jargon and start seeing the forest through the trees.
Whether you're a robotics enthusiast, a maker looking to expand your skills, or simply curious about the concepts behind robotic arms, these posts might have something for you. Join me as we delve into my attempt to scratch a robotics itch using open source software.
Components & Design Considerations
It seems my plan to avoid spending money has failed. I was now in the hole for a 3D printer and who knows how much filament. When it rains, it pours and little did I know how many more purchases were in my future. At that point, being comfortable with spending actual cash, my design considerations changed somewhat. Specifically, since I was now getting access to a limitless variety of plastic parts, I felt it was important to have them make up as much of the robot as possible.
3D Models
Hopping on Thingyverse (and a few others), I started searching for a robot arm. Although the community created some extremely, creative, models, nothing clicked. I didn't like the look of them, you might say. Eventually, somewhat deflated, I decided to try searching for something potentially less ambitious - a 3D printable planetary actuator. At the time I had no idea if it's a good or appropriate choice, just that they looked cool. I also vaguely remembered some YouTubers mentioning them in the context of robotics projects. Luckily, this search lead me first to this actuator and later to the creator's page where I found the EB-300 Collaborative Robot Arm. Not only did the aesthetics catch my eye, on the same page you can find an extensive PDF assembly manual and an electrical guidebook. Both documents were invaluable to me as a robotics newbie.
So that was that, I was going to build an EB-300 (technically, I built the EB-310). I started by printing out the parts for an actuator. The parts are fairly quick to print but require relatively tight tolerances to work. An appropriate test of both the design and the capabilities of my new BambuLab A1 mini. Happily, both came through.
Actuator - Static (blue), input/output (black), planetary gears (orange) | Printed input, planetary gears |
I was especially pleased by how well the gears came out. Not only did they look nice, after some tweaking of print settings, they would smoothly slide into place and, at least visually, seemed to engage well. To me, it seemed that the design has passed it's baptism by fire. As such, I was resolved to go forward with the EB-300 design and it's matching actuators. And since a line has formed for the printer, making all the parts was going to take a while (the bane of all shared 3D printers, I'm sure). As such, I turned to consider the software & hardware aspects of this project.
Software
What I cared about now, was figuring what kind of hardware I'll need so as to:
* Be easily extensible
* Use mature FOSS wherever possible
* Avoid lock-in to a specific micro-controller or vendor
My search lead me to Klipper - an open source firmware for 3D printers. Klipper is written in Python which checks the first requirement. Additionally, it compiles down to a minimal micro-controller firmware, i.e. all the smarts are on the Python side while the firmware is essentially an enhanced G-code parser. At this point, I figured that as long as I buy a Klipper compatible controller I should be able to leapfrog much of the low-level effort required to reliably & accurately drive (multiple) stepper motors. The 3D printing community has definitely made strides since last I looked, about a decade ago. Awesome!
This all seemed too good to be true. A low level firmware controlled via Python, reconfigurable on the fly, with builtin support for controlling multiple steppers concurrently. I mean, even a basic 3D printer needs to synchronise at least 4 stepper. How hard would it be to extend it to 6?
After raiding the forum for a few nights, I found my answer in this thread. Namely, there's no builtin support for 6DOF manipulators, but there's nothing stopping me from implementing one using the MANUAL_STEPPER command.
Score!
A few searches later, prind taught me most of what I needed to get started with Klipper. This excellent repo packages Klipper with Moonraker and supports multiple web interfaces. All this magic is a single `docker compose` call away and helpfully includes instructions for compiling the Klipper micro controller code using docker.
Hardware
At the core of this build are stepper motors. Thankfully, this is a fairly standardised field and off the shelf offerings are typically good enough. In my case, the model I was building was designed around 3 X NEMA17 and 3 X NEMA23 steppers (rated for 2A & 2.8A respectively) of the 4 wire variety.
Other than steppers, a basic Klipper setup is made up of 3 components:
- Host which runs the Klipper software - which in this case is prind using the Mainsail UI running on a Raspberry Pi 4, a common choice
- A Motor Control Unit (MCU) - a micro controller that provides the logic for driving the motors. Here it's a BIGTREETECH Octopus V1.1 which supports Klipper and can control up to 8 drivers
- One or more drivers - I like to think of these as little IC current dams. They are designed to push significant amount of current on an accurate schedule. Modern drivers, such as the TMC2209 used here, are often controlled through a serial interface and can provide advanced features such as stall detection. These could be thought of as a (relatively disposable) sub unit of the MCU that exposes itself to large current swings, in lieu of the MCU itself
Combining the components above with a DC power supply should be all you need to start controlling most off-the-shelf stepper motors.
The only thing missing is a config file for Klipper.
Manual Control Using Klipper
As mentioned above, Klipper can be be reconfigured on the fly. Adding an additional stepper or fixing a miss-config is a file edit and a button click away. Helpfully, the Klipper project hosts example files for a selection of MCUs, even one for the BTT Octopus used here.
Creating a config file
Most examples above, including the one for the BTT Octopus, assume you'll be building a 3D printer. As such, they define sections such as `stepper_x` or `extruder_1` to take advantage of all the Klipper platform smarts, which in this case are redundant.
For our use case, we can use a simpler directive which is often used for debugging steppers in a printer:
[manual_stepper pitch_stpr] step_pin: PF13 dir_pin: PF12 enable_pin: !PF14 microsteps: 1 rotation_distance: 1 [tmc2209 manual_stepper pitch_stpr] uart_pin: PC4 diag_pin: PG6 run_current: 1.2
For a full example config of the BTT Octupus for `manual_stepper` see [this]() repo and for pin names published by the manufacturer see here.
The above file, once applied to Klipper will configure the first slot of the Octopus to communicate over UART with the TMC2209, which then drives the attached stepper. To actually get the stepper to do something, we can use the MANUAL_STEPPER G-code command.
The MANUAL_STEPPER command & synchronised movements
Once configured as above, you should be able to use something like the below example to rotate one stepper and then another 360 degrees:
MANUAL_STEPPER STEPPER=stpr_1 SET_POSITION=0 MOVE=1 SPEED=1 ACCEL=2 SYNC=1 MANUAL_STEPPER STEPPER=stpr_2 SET_POSITION=0 MOVE=1 SPEED=1 ACCEL=2
Note, that using the above `SPEED` & `ACCEL` we make stepper take half a second to accelerate/decelerate to/from it's target velocity for the move. A useful feature that helps lower the strain on moving parts, one that could potentially be tuned based on the mass of a given link.
Alternatively, if `SYNC` is set to 0 or omitted, both steppers would rotate together. By strategically setting this parameter we can start constructing more complicated movements. For example, we could rotate both steppers in different velocities and only start the next movement once both reached their target:
MANUAL_STEPPER STEPPER=stpr_1 SET_POSITION=0 MOVE=1 SPEED=1 ACCEL=2 MANUAL_STEPPER STEPPER=stpr_2 SET_POSITION=0 MOVE=1 SPEED=0.5 ACCEL=1 SYNC=1 MANUAL_STEPPER STEPPER=stpr_1 SET_POSITION=0 MOVE=1 SPEED=1 ACCEL=2
The demonstration below, was made using exactly such constructions:
Although the above works, it's quite cumbersome writing out by hand. Helpfully, Klipper supports writing macros and even uses Jinja as a templating engine.
Macros
I refer you to this writing klipper macros tutorial if you care to know the details, but for most of my use cases, all I needed was:
[gcode_macro position_wrist_rel]
gcode:
{% set pitch = params.PITCH|default(0)|float %}
{% set roll = params.ROLL|default(0)|float %}
{% set yaw = params.YAW|default(0)|float %}
{% set pitch_speed = params.PITCH_SPD|default(2)|float %}
{% set roll_speed = params.ROLL_SPD|default(2)|float %}
{% set yaw_speed = params.YAW_SPD|default(2)|float %}
This convention is useful for providing optional & default parameters for a macro. For my use case, it's nice to have a single macro for controlling an arbitrary number of steppers. A given movement could use or omit any of the above three steppers and Klipper is smart enough to call the macro correctly. For some, this might be reminiscent of [Variadic functions](https://en.wikipedia.org/wiki/Variadic_function)
G4 P10
MANUAL_STEPPER STEPPER=pitch_stpr ENABLE=1
MANUAL_STEPPER STEPPER=roll_stpr ENABLE=1
MANUAL_STEPPER STEPPER=yaw_stpr ENABLE=1
Up until the moment we execute the `MANUAL_STEPPER ENABLE=1 ...`, no current is being pushed through the motor and you should be able to freely turn it by hand. Note the `G4 PX` which instructs Klipper to stall for 10 ms. I've found that enabling/disabling a stepper in quick succession can result in an TMC2209 error state, forcing a restart. The above stall prevents the error if the macro is called just after a disable.
{% if pitch != 0 %}
MANUAL_STEPPER STEPPER=pitch_stpr SET_POSITION=0 SPEED={pitch_speed} ACCEL=4 MOVE={pitch} SYNC=0
{% endif %}
{% if roll != 0 %}
MANUAL_STEPPER STEPPER=roll_stpr SET_POSITION=0 SPEED={roll_speed} ACCEL=4 MOVE={roll} SYNC=0
{% endif %}
{% if yaw != 0 %}
MANUAL_STEPPER STEPPER=yaw_stpr SET_POSITION=0 SPEED={yaw_speed} ACCEL=4 MOVE={yaw} SYNC=0
{% endif %}
MANUAL_STEPPER STEPPER=pitch_stpr SYNC=1
This is the meat of the macro. For each nonzero value of PITCH, ROLL, YAW we call the the appropriate `MANUAL_STEPPER` command with `SYNC=0` so that they start immediately. Lastly, irrespective of how many of the `MANUAL_STEPPER` commands we actually need for this movement, we end with `MANUAL_STEPPER ... SYNC=1`. This makes Klipper wait for all currently executing movements to complete before continuing.
Warning: the acceleration value used above worked for me, please start low when tuning this value for your equipment so as not to damage it.
G4 P10
MANUAL_STEPPER STEPPER=pitch_stpr ENABLE=0
MANUAL_STEPPER STEPPER=roll_stpr ENABLE=0
MANUAL_STEPPER STEPPER=yaw_stpr ENABLE=0
Lastly, and to protect from a call to the macro where all three of PITCH, ROLL & YAW are 0, we stall for 10 ms.
Using something like the above, it was now much more concise to make a somewhat complex movement:
position_wrist_rel PITCH=1 ROLL=3 position_wrist_rel PITCH=1 ROLL=-3 YAW=1 ROLL_SPD=1
The above would rotate two motors at default velocities and then three motors where the roll motor is half as fast than the previous movement. Note that each call to `position_wrist_rel` is book-ended by a linear acceleration/deceleration and a 10 ms stall.
Single motor movements | Multi motor movements |
This is roughly where my project stood when I was failing to make the arm follow a particular trajectory. As it happens, all the terminology and Impenetrable matrices were now starting to seem extremely useful.
Who knew?!
Summary
In this first instalment of my robotics journey, I’ve introduced you to the motivations and challenges that led me to attempt building a 6DOF robot arm, where the focus is as much on the learning journey as it is on the end result. I've touched on my previous attempts and subsequent failures to get into robotics and described one approach for making a tangible robot to play around with.
In Part 2
For those less interested in actually building, in part 2 I will cover describing the above robot mathematically and simulating movements in 3D space. Exactly the kind of things that are useful for planning a trajectory. In the spirit of this series, I will make minimal assumptions and try to get to the "cool stuff™" ASAP.
Honorable Mentions
- Klipper fork for 6DOF focused on controlling multiple pippetters (Open Lab Automata)
- klipper-for-cnc Klipper fork for controlling 7+ axes (naikymen)
- Marlin2ForPipetBot adds support for 9 non-extruder axes to Marlin (DrAndre)
Bill Of Materials
As of writing, the cost of components is approximately:
- NEMA17 - 19 CAD
- 3 X NEMA23 - 100 CAD
- BTT Octopus 1.1 - 70 CAD
- Raspberry Pi 4 - 80 CAD
- 300W adjustable DC power supply - 70 CAD
- ~2 Kg of Assorted PLA filament - 50 CAD
- Fasteners (M4 & M5 bolts, nuts, inserts) - 50 CAD
Total material cost is approximately 580 CAD/430 USD
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
Thanks for the kind comment! Not yet, but it's definitely on my TODO list! Just need to bring it to a form that would be parsable for outsiders :-P
Are you sure? yes | no
Would love to contribute. Feel free to message me on Nostr (fastest, best) or email
https://primal.net/p/npub14cgq353exzmhdsvqjtmw4dq7fvyleuls8umyrvd5umhr4gtx6asq7hqjhl
supermax333@proton.me
Are you sure? yes | no
Great implementation. Been hacking around with the same thing. Did you share this on your Github?
Are you sure? yes | no