Intro
For those who skipped part 1, let me remind you where we stand. Last time I told you about how I 3D printed a robot based on the Toolbox Robotics EB-310 Collaborative Arm design. Using off the shelf components, and an open source 3D printer firmware I demonstrated how to move each joint independently or in concert, but couldn't figure out how to get the arm to move to a specific location.
This post follows directly and tries to answer the question:
how can I do something actually useful?
In this chapter, we'll cover how to move a robotic arm to a given position and trace a path in 3D space. After which we'll apply this knowledge in a simulator, to control a virtual clone of our arm (which I've started calling Manny). Finally, we'll attempt to have my physical robotic arm replicate the same movements we observed in the simulator. |
For readers who are already familiar with controlling robots, do check out my ManiPylator repo which demonstrates a complete lab environment for simulating and controlling a robot manipulator using exclusively FOSS, including Genesis, robotics-toolbox-python, spatialmath-python, SymPy, Mosquitto and Klipper.
Describing a Robot Arm
Having settled on a design for Manny in part 1, it would be useful to write some movement equations. Happily enough, this is a solved problem. The derivations have been done, formulas exist, and they're written in a format called Denavit-Hartenberg parameters . Briefly, by figuring out the distances and angles between the motors of our robot, we can use a stock formula to calculate where the end effector will end up. Unfortunately, this is often complicated by DH parameters being a convention, and multiples exist. Some sources report Modified DH parameters or other variants, and while not impossible to compare, I found confusing and tedious. For example, I learned that the simple act of finding an example problem and working through it can be infuriating, after more than once the answer was written using a different convention to my own.
Although covered in depth in all the introductory texts I tried, DH parameters confused me far more than sharpened my understanding. Finding them difficult to keep straight and visualise, I was hoping for an alternative. I came upon the Elementary Transform Notation (ETS) (as described in this paper) available as part of the robotics-toolbox-python package. While still having to deal with right hand rule shenanigans, the notation immediately struck me as easier to grasp. Furthermore, breaking up the process into a sequence of elementary operations helped me visualise the task, while making finding mistakes more likely. And best of all, at the end of the process we get the same result. A formula that given a set of motor angles directly calculates the position (and direction) of the end effector.
While DH parameters have historical traction and ETS definitely helped me "see" things clearly, the most useful way I found for describing Manny was a URDF. This XML based format is widely used in the ROS ecosystem and is often provided by vendors. In addition to the details encoded in DH/ETS, URDF also includes the capability for specifying meshes, a feature we'll soon use, when we visualise Manny in a simulator.
Thankfully, I was exempted from handcrafting the file by using onshape-to-robot to export my OnShape model directly to a URDF, available here. After taking this welcome shortcut, I had an interoperable description of Manny that I could use with most robotics software, including robotics-toolbox-python and the recently available Genesis.
Before we can do that, we need to take a short detour through mathland. As my goal here is to get to the cool stuff ASAP, my focus will be on the bare minimum details that I find necessary. That notwithstanding, do note that in an academic setting such topics often have multiple prerequisite courses dealing with linear algebra, calculus and physics.
Starting out blind in robotics, I couldn't really "get" why so much of the discussion revolves around DOFs (re: Degrees of Freedom). For one, It's an extremely visible parameter when searching for commercial arms. Other than that, Reddit is full of people asking about 6 vs 7 DOF robots, with the associated flame wars. From what I could gather, it seemed like an arbitrary choice, which just happens to work out nicely in the math.
Speaking about the math, when I tried to dive deeper, all I could find were random matrices getting multiplied, leading to some "useful formulas" and much jargon (used in the sense of: hard to decipher shorthand and confusing to follow notation). Personally, outside of The Big Bang Theory, researching this question was my first introduction to Lie groups & manifolds. It definitely seemed like to much work to find out how many motors I need to buy. More specifically, I was trying to figure out if I could get away with buying fewer motors, while maintaining similar "performance" (not that I understood, at the time, what the word meant in robotics). Unfortunately, that was not to be. You see, dear reader, I failed to consider direction:
![]() |
Note the power tool in the same position in both scenarios (generated by ChatGPT) |
As it happens, it's not enough to move an end effector to the right spot. Pointing it in the right direction also "consumes" DOFs. As such, since we exist in a 3D world, we can think of the problem of a 3+3DOF manipulator. One where we need 3DOF for position, and 3DOF for direction.
Bottom line: although examples of 4, 5 and 7 of DOF arms exist, in an introductory context, It makes sense to focus on the 6DOF case. Luckily enough, it just happens to cover all possible poses in 3D space. For the analytically inclined, who might not be satisfied by this misdirection, I recommend the excellent notebooks by Jesse Haviland introducing spatial geometry.
Defining a Pose
We now have most of what we need to start moving Manny, with one exception. We lack a concise way of writing down coordinates. On the one hand, position is a straightforward column (tuple, some would say) of 3 numbers. On the other, specifying direction is notoriously fiddly, a fact that could be confirmed by anyone who has ever confidently uttered "move to the right", followed an almost hysterical "THE OTHER RIGHT!".
As such, multiple conventions exist, where Rotation Matrices, Euler Angles, Unit Quaternions (Versors) are some of the most common ones. For completeness I recommend familiarising yourself with the relation between unit quaternions and rotation matrices, but suffice it to say that from a practical perspective either will do. As they are easy to convert between, I picked the one that's easier to explain. Without further ado, let us introduce the rotation matrix:
![]() | |
Unit cube with rotation axes | Rotations about the axes |
Helpfully enough, rotations around the axes can be multiplied to point in any arbitrary direction. The result of multiplying Rx(θ), Ry(η) and Rz(ρ) is called the Rotation Matrix. Next we need to accept two features of a rotation matrix. First, rotation around the origin can be thought of as a matrix multiplied by a point/vector:

Second, if we add another column to the rotation matrix, we'll be able both rotate and translate using a single multiplication. The resulting 4 by 4 matrix (because we like square matrices) would be:

The above formulation is called the homogeneous transformation matrix (or homogeneous transform) and it encodes a pose, or position + direction in 3D space. A concept the usefulness of I failed to to appreciate for the longest time. Specifically, I seemed to have completely missed the fact that matrix multiplication could be thought of as the equivalent to giving directions in the form of:
- Go straight for 2 meter
- Turn 30 degrees counter clockwise
- Go backwards for 1 meter
- Point your arm 60 degrees clockwise
- etc.
In short, we can write down a pose using 9 numbers for direction and 3 for position. Writing these numbers in a square matrix lets us combine individual poses through matrix multiplication. We can use this feature to figure out the pose of the end effector of a serial manipulator, like Manny.
We are now ready to address two fundamental questions:
- If I rotate Manny's motors to θ1 through θ6, what will be the pose of the end effector?
- If I'd like Manny's end effector to take a given pose, to what θ1 through θ6 should the motors rotate?
The first question is known as forward kinematics and the second as inverse kinematics. For an introduction to both, I recommend the excellent demonstrations for forward and inverse kinematics by Robotics Explained to build a conceptual understanding in 2D. Helpfully, both robotics-toolbox-python and Genesis provide a straightforward way of calculating kinematics directly from a URDF:
![]() | ![]() |
Kinematics in Genesis | Forward kinematics in robotics-toolbox-python. I left out inverse kinematics since I couldn't figure out how to produce a correct results, but i found reading the underlying code instructive |
Note: while forward kinematics has a relatively straightforward solution for a serial manipulator, inverse kinematics approaches vary. Some are faster, while others are more accurate or handle specific use cases better. Inverse kinematics is the focus of active research and which is beyond the scope of this post. For a review of recent machine learning driven approaches see this paper or the robotics-toolbox-python documentation for some traditional approaches. For a forward kinematics worked example, a comparison of inverse kinematics algorithms available in robotics-toolbox-python and detailed instructional tutorials for robotics-toolbox-python see another of Jesse Haviland's repos.
In part 1 we went over how to handcraft G code to rotate stepper motors. Since we are now ready to start creating more advanced movements, a similar expansion of our capabilities is required. While so far we've been discussing the simplified case of open loop control where signals travel only from the controller to the robot, we'd like to keep in mind that in the future we might want to expand our system with closed loop control where signals go both ways. Furthermore, a way to compartmentalise our control signals from any specific hardware (or simulator) would be extremely valuable as we add additional features. Luckily enough, Klipper includes support for one solution to this problem, exchanging commands and status over a message queue.
By using a message queue as an intermediary between Klipper and our control software, we gain multiple benefits. First, it's trivial to "listen in" on control signals as they are published. Additionally, control signals are guaranteed to arrive (potentially in order) and will not be silently dropped due to network issues. Lastly, any arbitrary device can be attached to the message queue so we're not tying ourselves to a specific implementation. Similarly, from the robot side, it does not care where command signals are coming from, and only has to listen to a single command channel.
To demonstrate this capability, the ManiPylator repo includes a simple way of spinning up a Klipper stack with simulated hardware that supports bidirectional communication over MQTT.
Specifically, by crafting a few macros Klipper can publish global state. Similarly, using the Moonraker MQTT APi we can publish arbitrary G code macros for Klipper to execute. Finally, closing this loop with a pair of absolute and relative movement macros which update the global state, we get a fairly robust control system. One that even has an emergency stop button in the form of the M112
Klipper command. Although Klipper typically works synchronously, this command overrides any currently running G code, a feature I've used extensively.
Using the above approach, we have a consistent interface for interacting with Manny either in his physical or simulated form.
One of the challenges I often encountered when learning maths or physics "on the blackboard" was converting my conceptual understanding to software. Symbolic maths do the brunt of the heavy lifting during the first, while numeric maths rule the second. In my experience, crossing this bridge is typically tedious and often error prone. Helpfully, SymPyprovides a convenient bridge between these two worlds and is integrated with robotics-toolbox-python.
As an example problem (and a general sanity check), I'd like Manny to trace a curve in 3D space. To simplify, we'll keep the end effector pointing straight down and parallel to the floor. This is, luckily enough, what we would need to do, if we attached a pen to the end effector and had Manny draw on a flat piece of paper. Furthermore, since the direction is constant, our pose need only parameterise the position column, whereas the rotation matrix will be constant.
For the curve itself, I chose a heart. Specifically the eighth curve on this page, reproduced below:
![]() | ![]() |
Parametric heart curve | Symbolic, numeric representation of a heart curve in matrix format |
Using the above example, we can easily calculate a set of 100 positions on the curve and return them as a homogeneous transform:
![]() | ![]() |
Calculating poses along a curve | 100 samples on the curve in 2D space |
Next, once we've calculated the inverse kinematics for each of these poses we should be able to simulate Manny tracing this path. For a worked example used to generate the below video, see this notebook in the ManiPylator repo.
![]() | |
Manny and samples along the curve | Simulation of Manny tracing the path using Genesis
|
Once I was confident enough that I understand what's going on in the simulator, it was time to see if experiment matches theory. To test my work I opted for a simple setup, tie a laser pointer to the last joint and trace the above heart ~10 cm off the page. At each position a 2 second dwell time allows for marking the position. On a second pass I drew a circle around the first, second pass positions. The assumption was that if on a third pass the laser passes through the circle, I could call the experiment a success:
Tracing 1st, 2nd pass | 3rd pass viewed from above |
The poor calibration notwithstanding, I'm extremely happy with the result. Not only did theory (approximately) match experiment, I now have a straightforward setup for further robotics experimentation and learning. Furthermore, while the movement is far from smooth, the accuracy and reproducibility is much better than I was expecting from a 3D printed robot. Finally, to make this informal experiment slightly more scientific, I made some measurements:
![]() | ![]() |
Expected | Measured |
Summary
In this second installment of my robotics journey, I tried to introduce the basic concepts required for delving deeper into robotics, while avoiding much of the terminology. By leverging these basics and some open source software, I demonstrated how one could start playing around with robotics in a tangible way using the Genesis simulator and by directly controlling Manny. I've touched on some important concepts while sidestepping most of the details as my goal here is to get to the "cool stuff™" ASAP.
For the interested, the ManiPylator repo contains a lab environment replicating this setup and some instructional notebooks demonstrating a virtual/physical Manny.
Next we have a serious challenge to tackle. Can we make Manny's movements smoother? Can we avoid the start/stop behaviour inherent to how we use Klipper? Are all those terms like Jacobian, singularity and manipulability finally come into play? And most importantly, will Manny actually manage to do something useful?
All this and more in part 3. Maybe?
- Why the name change from ManiPilator to ManiPylator? starting out, all I had was a Raspberry Pi. It seemed like the most important aspect, so I named the project accordingly. As the project progressed, it was clear that although the Pi is a nice little computer to have, it is far from crucial. The Klipper stack can run on any container-capable machine and Python has taken over my repo. As such, ManiPylator.
- Why publish state/send commands over a message queue? Honestly, that's what ROS does. Since Moonraker has builtin support, it made the decision to adopt this architecture that much easier
- Is the repo production ready? Definitely not. If you manage to spin up the lab environment without issue, I would be shocked. More work is needed, but I like where it's heading.
- What did you do for wiring? I used a 12 conductor cable and terminated it with 2 pairs of 12 pin Deutsch DT connectors. Some assembly required. See image below
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.