Mammal geometry legs

Before committing to the side-by-side 4-bar linkage leg for SMMB’s new actuators, I wanted to give a try with a more traditional mammal geometry.  That was my preference to begin with, but my initial motor evaluation didn’t find any motors which had sufficient torque without a gearbox, and adding a gearbox in a simple way changed the dimensions enough that mammal geometries weren’t feasible.  I spent some time looking for new options, and I found at least one which was promising, the XOAR Titan 6008.

XOAR Titan 6008
XOAR Titan 6008

On paper it provides the same order of magnitude of torque as the 3508 without needing a gearbox.  It’s diameter is of course larger, but not actually that much once you consider the gearbox hardware I had developed for the Turnigy 3508.  I’ve drafted up a test leg geometry, and the overall dimensions are about the same as for the 3508 variant.  Here you can see a side-by-side CAD model of the gearbox leg vs the direct drive leg.

The first sample, as usual with my 3d printing efforts, needed a moderate amount of whittling and post-machining, but does fit together and operate.

Next up is installing it in the jumping fixture and seeing what it can do there.

 

MLCC ceramic capacitor DC bias derating

While testing SMMBs new actuator under load, I kept getting faults from overvoltage that I had not anticipated.  The firmware only samples voltage once per control cycle, and while that plot did look very interesting, it probably wasn’t representative.  I wired up the scope to be able to sample the voltage and FET control signals during operation and sure enough, the voltage ripple was way higher than I had predicted based on the original design.  Even at only 30A phase current, the voltage ripple on the main power bus was 4.2V.  Note that this was with a nominal operating voltage of only 13V!  I had been trying to operate at 40A, for which it must have only been worse.

40khz_30a

When I originally laid out the board, I used approximately the same bulk capacitance as Ben K. had in his third revision board; my r2 board contained 6x 22uF 35V MLCC (multi-layer ceramic capacitors) from TDK.  However, the voltage ripple observed in the plots was consistent with a much lower overall bulk capacitance.

And now begins another self-education session.  I had always assumed that modern MLCC ceramic capacitors were made of magic pixie dust and had somehow managed to achieve near capacity parity with electrolytic capacitors with no real downsides.  However, after looking into it, I found that in fact, the capacitance of a MLCC capacitor needs to be derated by the DC voltage applied to it.  The derating curve can be quite significant.  For the capacitors I had chosen, at 12V DC bias, the actual capacitance was only 20% of the top line value!  :theresyerproblem:

Fortunately TDK has a handy feature on their website which lets you sort capacitors based on their effective capacitance at any DC operating point.  I found some 10uF options that have about a 20% higher effective capacitance than the 22uF I had selected previously, and in the next revision of the control board I will just need to add a lot more of them.  In the meantime, I’ve double stacked up capacitors to get closer to something reasonable, and increased the switching frequency to 60kHz from 40kHz.  Here’s a plot of 30A when I was testing it at 80kHz, which shows a much more manageable 0.6V ripple.  Hopefully that can be reduced even further with a bit more capacitance in the next revision.

80khz_30a

Reworked control board
Two stories of MLCCs

 

Encoder autocalibration

I have been continuing to iterate on the control and mechanical aspects of the improved actuators for SMMB.  While working on an alternate board mounting strategy, I ended up with a magnet that was much much further from the absolute encoder than before.  This resulted in significant errors in the estimated motor phase at various points in the revolution of the absolute encoder.  In the spirit of copying every single thing Ben Katz did in his project, I implemented a piecewise linear encoder calibration technique.

My effort largely uses the same approach: The virtual “d-axis” current is advanced through phase space at a constant rate and the encoder is sampled simultaneously.  This proceeds until the encoder has advanced through one full revolution.  It then reverses direction and does it again.  Then, a script consumes those values, and first determines the number of poles and the encoder direction.  Then for each encoder value, it estimates what the phase should have been and computes the difference between the actual commanded phase and what was measured.  The approach here is slightly different from Ben’s approach, as it works in the encoder space instead of the phase space.  As with Ben’s work though, this difference has a moving window average applied to it with a size equal to one electrical phase.

encoder_calibration

The plot above shows one run of the calibration.  The top plot shows the phase value in radians plotted against the absolute encoder value as a uint16.  The bottom plot shows the difference between the perfect mapping and what was actually measured, with the moving average in orange.

For this particular mounting configuration, the peak to peak phase error was almost 0.5 radians, or 28 degrees, which results in a fair amount of torque ripple.  Automated calibration also gives significantly more repeatable performance than my previous approach of “command a few phases and eyeball a suitable average”.  This does still leave two other parameters to auto-calibrate, the motor resistance and kV rating.  However, both of those are much easier to measure reliably by hand so fixing them is not as urgent.

Brushless actuator control board, r2

The first revision of the brushless servo control board for SMMB was successful in getting a leg to jump.  I ended up doing a small-run second revision that addressed a few minor problems and added a couple more capabilities.

  • RS422 Debug/Link Port: I had a 3.3V serial port exposed previously for debugging, however it caused my USB-serial converter to dislike itself due to common mode ground shifts and it wasn’t reliable at high baud rates (>3Mbps).  I also wanted to support “linked” modes, where two servos would perform control in the actuator space at full rate.
  • Debug through holes: r1 had a number of debug connections, all of which were unpopulated SMD pads.  I decided that through holes were easier to connect debug wires to.
  • Vertical SWD connector: I had initially thought I would hide the SWD connector within an enclosure.  However, the initial enclosure prototypes made that seem less desirable, so I switched it to vertical.
  • More debugging points: When bringing up the first board, I ended up doing a lot of carefully balancing scope probes on various pins, when there was plenty of board room to just have through hole debug points.  Lesson learned.
  • FET temperature sensing: r1 just had an external temperature sensor port, r2 additionally has a thermistor next to the FETS.

Macrofab’s current pricing scheme provides a great incentive to keep your BOM below 20 parts, as that is the only way to get quick turn service.  Otherwise you pay an extra 2 or 3 weeks of calendar time.  In r1, I went to some lengths to stay under 20, however, it just wasn’t going to work with r2, so I left a few easy-ish or non-critical parts unpopulated to do them myself: the connectors, LEDs, and one really big diode.

Control board r2
PCB as received from Macrofab
Installing LEDs
Installing 0603 LEDs under the microscope
Finished board
Board finished with all parts and leads attached

 

Pinions, set screws, and glue

One of my intermediate goals for building new actuators for SMMB is to get them robust enough to jump continuously for some duration of time.  Progress is slow, as things break, new parts are ordered, repairs are made, and jumping resumes.  The most recent failure is at least interesting enough to me that it is worth writing up.

To recap, I’m building a brushless servo based around a Turnigy Elite 3508 brushless motor and a custom 5x planetary gearbox.  The 3508 is intended for quadcopter applications, so to install a spur gear I first extracted the original shaft, then pressed in a new shaft with two flats on it.  One flat for the set screw attaching the rotor to the shaft (which had a press fit), and a second for the set screw attaching the spur gear to the output of the shaft.

While jumping, the set screw holding the spur gear kept freeing itself, no matter whether thread lock was applied or not.  In retrospect, the reason should have been obvious.  In all my previous RC motor experience, pinions were attached with set screws and a slip fit.  However, all those previous applications also used a motor that spun fast with a high ratio gearbox, so the actual torque applied to the motor shaft through the spur gear was relatively low.  In this application, with just a 5x gear reduction and relatively large amounts of torque, the spur gear was seeing torque on the order of 1 N*m.

As the internet will tell you, set screws are not an appropriate attachment mechanism for anything but the smallest of loads.  Even with a shaft with a flat, the entire torque load ends up being concentrated in just one tiny bit of the set screw until it elastically deforms and eventually either destroys or frees itself.

Ben Katz, who built the inspiration for this work, used a shrink fit to attach the spur gear to the motor.  I knew that, but didn’t have sufficient machining tolerances easily available to me to make that happen.  Other simple mechanical options, like a dowel pin didn’t seem like they would be that much more effective than the set screw.

What seemed like more of an option after a little calculation, was glue!  Or rather, LOCTITE 680 brand retaining compound .  The shaft is 4mm in diameter, and the spur gear covers about 8mm of the shaft’s length.  That means there is \pi * 4mm * 8mm =100mm^2 of contact surface area.  A 1 N*m torque at 2mm radius thus results in 1 N*m * 2mm / 100mm^2 ~= 5 MPa of shear strength required.  LOCTITE 680 has a shear strength when cured of approximately 25 MPa, which gives a 5x safety margin.

Retaining compound was applied, jumping recommenced, at least until the next thing broke…

 

bazel for gstreamer – plan

After OpenCV, the other major dependency the mjmech software has, which is necessary to complete the raspberry pi 3b+ bazel build setup, was gstreamer. Unlike the previous dependencies, this one is a doozy — gstreamer has an enormous transitive dependency set. Additionally, we needed to use its ffmpeg wrappers, which brings in more dependencies.

In this post, I’ll just try to map out the dependencies that we ended up actually needing, so that they can be tackled one by one.

gstreamer core

The core gstreamer base, actually has a minimal dependency set, only really glib. glib in turn has a small dependency set, just libffi, pcre, and zlib.

gst-plugins-base

Most of the functionality within the gstreamer ecosystem is contained within “plugins”, i.e. modules of software which implement a constrained interface and can be installed into media pipelines. All plugins use the common routines defined within gst-plugins-base, and a few low level plugins are defined here. This begins the dependency explosion, as some of these base plugins start to call into non-trivial pieces of the free desktop graphical stack. Direct dependencies here include libx11, libxv, from x.org, and pango.

In turn those packages have a large fanout. libx11 and libxv depend upon videoproto, libxext, xorgproto, libxcb, libxau, libxdcmp, xtrans, and xcb-proto. pango depends upon freetype, harfbuzz, fribidi, cairo, bzip2, freetype, pixman, fontconfig, util-linux, and expat.

gst-plugins-good and gst-plugins-bad

mjmech doesn’t rely on any of the plugins in gst-plugins-good, so nothing was necessary there! We do use some from gst-plugins-bad, but those have no additional dependencies.

gst-plugins-ugly

Here, mjmech uses the x264 plugin, which in turn depends on the x264 external library.

gst-libav

These are the plugins which wrap ffmpeg (nee libav, nee ffmpeg). ffmpeg in turn depends additional on nasm to assemble for x86.

Next steps

Now that we’ve enumerated all the dependent packages, we can start to work on packaging them. First up are those necessary for gstreamer core, so libffi, pcre, and zlib.

bazel for opencv

The next level of difficulty in bazel-ifying packages for mjmech was opencv.

First, for the impatient, Apache 2.0 licensed sources are available on github: https://github.com/mjbots/bazel_deps/tree/master/tools/workspace/opencv

OpenCV’s native build system consists of nearly 200 cmake files with over 20,000 total lines of code, plus assorted helper scripts and prototype files which are substituted into.  Fortunately, I didn’t need to support the full complexity of the opencv build system.  Things I didn’t bother to touch:

  • Autodetecting the location of any packages:  Since this is embedded within a bazel project, all the dependencies I was going to use were already bazel-ified.
  • GPU support:  I didn’t bother with CUDA, OpenCL, or anything else GPU, as the GPU on the raspberry pi3 has only a minimally supported OpenCL stack, at best.
  • Examples: I had no real need to build any of the sample or demonstration applications.
  • Modules: I only needed, for now, a small fraction of the total opencv module set.

Intended result

OpenCV is broken up into numerous “modules”, each of which is largely independent and implements a relatively self-contained piece of functionality.  Each module can depend upon other opencv modules and other external packages.  I wanted to reach a point with the bazel configuration where each module could be described at a relatively high level, which didn’t seem too implausible since the module structure is relatively constant across modules.

Think something like:

cvmodule(
  name = "core",
)

cvmodule(
  name = "imgproc",
  deps = [":core"],
)

# ... etc

Bazel implementation

To achieve this in bazel requires three phases: first, a repository rule to actually download the opencv tarball, second something written in Starlark which could implement a cvmodule like rule, and finally a BUILD file equivalent to call it and enumerate the opencv modules I cared about.

The repository rule was relatively straightforward.  It just uses the standard bazel mechanisms to download a tarball and template expand a known BUILD file into place.

The Starlark file is where all the real work lies:

First, we just define a common set of preprocessor defines which will be applied to all opencv translation units:

_OPENCV_COPTS = [
    "-D_USE_MATH_DEFINES",
    "-D__OPENCV_BUILD=1",
    "-D__STDC_CONSTANT_MACROS",
    "-D__STDC_FORMAT_MACROS",
    "-D__STDC_LIMIT_MACROS",
    "-I$(GENDIR)/external/opencv/private/",
]

The only real interesting piece there is the $(GENDIR) fragment, which causes bazel to substitute in the path to the generated output directory. Since a number of the headers that we will be using later on are generated as part of the bazel build process, we need to ensure that the compiler can actually find them.  The alternate formulation which doesn’t require knowledge of GENDIR is to list it in the includes attribute.  That however, exposes the headers to all downstream modules, which we don’t want.

Next, we enumerate the set of processor specific options that we will support:

_KNOWN_OPTS = [
    ("neon", "armeabihf"),
    ("vfpv3", "armeabihf"),
    ("avx", "x86_64"),
    ("avx2", "x86_64"),
    ("avx512_skx", "x86_64"),
    # ...

OpenCV has support for a comprehensive set of special instruction set extensions for a variety of different processors. They can either be compiled in and always used, or dispatched at runtime, although these bazel rules ignore runtime dispatching entirely. Some of the generated headers need to know the full possible set of options for a given architecture, thus this list here.

Module definition macros

Now we can start getting into the module definition Starlark macros.  To handle some generated headers that are common to every opencv module, I ended up creating an opencv_base macro that is module independent.  It needs to be called from the BUILD file, to generate a number of the private headers, but exposes no logically public labels.  It does require a passed in “configuration” dictionary (which will also be passed in to the primary module generation macro).

CONFIG = {
    "modules" : [
        "aruco",
        "calib3d",
        "core",
        "imgcodecs",
        "imgproc",
    ],
# ...

The big annoyance here is that you have to enumerate the set of modules twice.  Once in that config, and again in the set of invocations to the module creation macro.  The reason is the generated opencv_modules.hpp file, which provides preprocessor defines describing which modules are included in the build.

Next is the implementation of the module generation macro, which I ended up calling opencv_module.  It needs just a few arguments for the modules I ended up converting:

  • name – should be self-evident
  • config – the same configuration dictionary from above
  • excludes – source files to skip compiling from this module
  • dispatched_files – the list of files in this module which contain processor specific specializations
  • deps – a set of bazel labels describing the dependency set

This macro goes about generating the appropriate .simd_declarations.hpp and .simd.hpp files, a non-functional stub OpenCL implementation, and then goes on to define the actual bazel cc_library.

Resulting BUILD file

At this point, the BUILD file can glue all these things together. It ended up being not too far away from my initial ideal:

opencv_base(config=CONFIG)

opencv_module(
    name = "core",
    config = CONFIG,
    dispatched_files = {
        "stat" : ["sse4_2", "avx"],
        "mathfuncs_core" : ["sse2", "avx", "avx2"],
    },
    deps = ["@eigen"],
)

opencv_module(
    name = "imgproc",
    config = CONFIG,
    dispatched_files = {
        "accum" : ["sse2", "avx", "neon"],
    },
    deps = [":core"],
)

# ...

Download

As mentioned at the start, all the source are available on github:

Slow motion leg jump

After the initial leg jumping with the prototype brushless actuator for SMMB, I spent some time actually tuning the control loops and making the firmware not incredibly convoluted to get started.  I also acquired a high speed camera for analysis.

So, here is a brief update of the final jump before I seem to have toasted one of my DRV8323 motor drivers.  It jumped for about 400ms of hang time, running at about half of the maximum current the system should be capable of pulling.