A few months ago, I built my own version of miniKame, a 3D printed quadruped robot powered by an ESP8266. I built it mostly as per the instructions in the GitHub wiki, but redesigned all the parts in Fusion 360 based off of the original FreeCAD files as an exercise to gain experience using Fusion 360. I also didn’t reuse the project’s code as-is, but instead implemented a very small UDP listener for the ESP8266 that simply reads values for the eight servos from an UDP packet. I implemented the actual control software in Python on my computer, which gave me the ability to experiment more quickly, without having to wait for the ESP8266 to finish flashing after every change. Sending servo values via UDP turns out to be fast enough on my WiFi network, with only occasional lags and hickups.
To get my miniKame walking, I ported some of the original C code to Python, which worked well after some fiddling. I got the robot moving.
Other stuff came up and I set the project aside for a while, but a recent conversation with a friend prompted me to look at the code again. I found that I didn’t understand how the code got the robot to move as smoothly as it does anymore. I probably never truly did, I honestly mostly just ported the C code and fiddled around with the values. But I definitely didn’t understand what was going on anymore looking at it after a couple of months had passed. So I went to work reverse engineering the original C code and what I’d ported to Python a few months back. These are my results.
How miniKame is built
miniKame is a quadruped, which means that it has four legs and four feet that it can use to move around. Each leg is powered by two servos, one controlling the XY-direction (I call that one the “brace”), and one controlling the Z-direction (I call that one the “foot”)1.
Each servo has a limited useful range of motion due to the way miniKame is constructed – at some point the XY-brace simply cannot move further because it’s up against miniKame’s body. So we’ll have to limit the range of motion in software.
What makes miniKame move
In the original miniKame code and in my Python port, each servo is controlled by a sine wave oscillator. That means that for each servo, there’s one sine wave that controls its position at a certain point in time. All oscillators are on the same “clock” – in my Python code, I simply use the milliseconds elapsed since the start of the program. I update the servos once every millisecond in my Python code.
while True: time.sleep(1.0 / 1000.0) t = datetime.now().timestamp() * 1000.0 # leg_fl = leg front left, leg_br = leg back right leg_fl.xy_servo.set_angle(leg_fl_xy_osc.value(t)) leg_br.xy_servo.set_angle(leg_br_xy_osc.value(t)) # Other servos are similar
But how do these eight sine waves (two for each leg) result in a smooth walking motion that actually moves the robot forward? Just looking at the thing moving, then looking at the code, then looking at the moving robot again didn’t make things click for me. I could not figure out how the sine waves were connected to the actual motion. So I built some simulations.
Properties of a sine wave
First, some basics about sine wave. Most of this will probably be familiar from high school. We will discuss amplitude, period, phase shift, and offset of a sine wave2. Feel free to skip this part if you’re comfortable with those terms.
We can get a basic sine wave using the formula y(t) = sin(t).
This plot shows the value of sin(t) from 0 to 10 * π. An unmodified sine wave will range from -1 to 1, starting at zero and reaching zero again for every multiple of π.
We can introduce a few parameters to modify the sine wave. First, we’ll talk about the period. The period is the distance on the x axis between two peaks on the y axis. In the example above that’d be 2π: the first peak occurs at 1.5π, the second peak at 3.5π. You can play around with the period in the following example. I’ve changed the values a bit so that we’re no longer dealing in multiples of π, but in nice round numbers instead.
y(t) = sin(period * t)
Next up: amplitude. The amplitude of a sine wave determines the height of its peaks and the depth of its valleys. The default amplitude of an unmodified sine wave is 1, which is why the plots above show the sine wave ranging from -1 to 1.
y(t) = amplitude * sin(period * t)
Third, we can shift the sine wave around on the x axis. This is called a phase shift, or simply phase. The phase is specified in radians. A phase shift of 1/2π means that at x = 0 the sine wave will be at its maximum (as determined by the amplitude), instead of at zero.
y(t) = amplitude * sin(period * (t + phase))
Finally, we can shift the whole wave vertically. Right now it’s always centered around zero on the y axis, which we can change using an offset.
y(t) = amplitude * sin(period * (t + phase)) + offset
How sine waves are used to move miniKame’s braces
After this refresher on sine waves: How do we actually use sine waves to move the robot? Let’s start with the braces, the parts that move a leg in the X-Y-plane.
Using a sine wave to move these is actually pretty straightforward: We tweak the amplitude until we get the desired range of motion out of the servo, using the offset to adjust for its initial zero position (which can always vary a bit because of different mounting positions). Then we tweak the period until the speed of motion is just right.
When looking at a video of miniKame moving, you’ll notice that not all braces
move in unison. The front left and back right braces
How sine waves are used to move miniKame’s feet
Now we can move our legs in the X-Y-plane by rotating the braces. To actually move the robot forward, we also have to move its feet. What we want is to have the feet touch the ground when the braces are in their frontmost position and stay down while they move backwards. Then we’ll lift the feet up again as the braces reach their back positions. As long as the ground isn’t too slippery, this will move the robot forward.
The motion itself is simple. Because of the way the feet are mounted, all we have to do is move the servo for each foot back and forth to move the foot up and down from the ground. A simple sine wave, not unlike the ones that we’ve used above to move the brace. Amplitude and offset will probably have to be different to achieve the correct range of motion, but all in all still a simple sine wave. But how do we achieve the specific timing we need: move the foot down when the brace is in front, keep it down while the brace moves to the back, and then move it up again?
The way we’ve set up the oscillators for the brace servos, the sine wave reaches its peak position when the brace is in its front position and is at its lowest point when the brace is in its back position. What we want is for the leg oscillator to move through one complete cycle – from peak to valley and back to peak, from its up position to its down position and back up – while the brace oscillator moves through half a cycle – from front to back.
We can achieve that by setting the period of the feet oscillators to half that
of the brace oscillators and using a phase of 0.5π / 90 degrees, the same as
the front left / back right
This seems like it works at first, but there’s a problem. In the first cycle, everything works fine. At t = 0, the brace is in the front position and the foot in the up position. When the brace is halfway through its motion to the back position, at t = 250, the foot is in the down position, touching the ground. When the brace is in the back position, at t = 500, the foot is back up again. Our robot has moved forward. But if we keep increasing the time, we see that the brace naturally starts to move back to its front position. But the foot keeps moving too! In fact, at t = 750, it’s back on the ground. That will undo the forward motion we just made. We want the foot to move only when the brace is moving from front to back, not when it’s moving from back to front.
To solve this, the miniKame code uses a little trick. It disables the foot oscillator for half of the period.
while True: time.sleep(1.0 / 1000.0) t = datetime.now().timestamp() * 1000.0 # leg_fl = leg front left, leg_br = leg back right leg_fl.xy_servo.set_angle(leg_fl_xy_osc.value(t)) if int(t / 500.0) % 2 == 0: leg_fl.z_servo.set_angle(leg_fl_z_osc.value(t)) # Other servos are similar
When the result of
t / 500.0 is even, we’re in the first half of the 1000 tick
long period of the brace, which means that we’re in the first half of its motion,
i.e. the front-to-back motion. If it’s odd, then we’re in the second half, the
back-to-front motion, so we simply don’t pass the oscillator values to the servo.
To see this in action,
activate the period filter checkbox above and move the time slider. You’ll
see that the foot stays up when the brace moves from back to front.
We can use the same trick to make the up-down motion work for both the front left/
The complete motion
Put all of the above together, and you get a miniKame robot that can move forward. To show how it all works together, I built a small simulation using Three.js3. You can see the individual sine waves below the simulation and inside the simulation itself. Click and drag to rotate the camera. Hold shift and drag to move. Scroll to zoom. If you move the camera a bit, it’s pretty easy to see how it all plays together.
Z-direction is not strictly true since it also moves in the XY-direction, but I think that’s irrelevant for understanding how the basic motion works. ↩
I think I got the definitions right. Not a mathematician, though. ↩
If you want to learn Three.js, check out https://threejsfundamentals.org. I had absolutely zero prior knowledge of Three.js and the last time I did any graphics was years ago. Using their guide, I was able to build this simple simulation in a couple of hours. ↩