Linux on a Beckhoff CX5020

This is likely of no use to anyone but me, ever, but I’ll post it anyway. I’m trying to install Linux on a 10-year old Beckhoff CX5020 industrial computer, and everything works fine until about somewhere in the middle of the user-mode startup process when the screen goes blank and never comes back. I tried disabling X and that didn’t matter. Even booting in single-user mode didn’t help. It still went blank while booting. I could SSH in over the network to debug, but couldn’t get a working monitor.

The problem seems to be the Kernel’s video driver mis-understanding the number and/or type of video ports that the CX5020 has. The solution is easy: add the video=LVDS-1:d option to the kernel’s command line. After that everything seems to work fine. It boots up and runs X as well as could be expected for a 10 year old compact PC. Which is to say not tremendously quickly.

Measuring Backlash, Part 2

(See part 1 for the beginning of the process)

Yesterday, I set out to measure backlash in the Z axis of Beaver HDZero CNC. I concluded that it had about 15 microns (0.015mm, or roughly 0.0005”) of backlash in the Z axis.

Today I’m going to measure the X and Y axes. They’re each a bit different:

  • They use larger ballscrews than the Z axis (1610 vs 1605) with twice the pitch. That means that one turn of the stepper motor goes twice as far. A single step should be 12.5 microns, and the stepper’s encoder should be able to measure about 5 microns.
  • The Y axis has a pair of steppers driven in parallel, one on either side of the gantry. In theory, there’s no reason why this should matter, but I could see extra inaccuracy creeping in.
  • I had a difficult time getting the ball nuts assembled correctly when building this; I repacked each of the Y nuts at least once, but the X nut seemed to work okay so I didn’t repack it. It lost at least a few ball bearings, though, so I probably should have repacked it. We’ll see if that matters.

The process here is exactly the same as the Z axis from yesterday. I fed the CNC a program that tells it to move from 0 to 0.200 mm, in 0.003mm steps. It paused after each step so I could take a measurement using a Mitutoyo 543-302b digital dial indicator. The dial indicator is accurate to 3 microns, which is why I’m moving the CNC in 0.003mm steps. The dial indicator is wired into my laptop via Mitutoyo’s USB cable. The cable makes the indicator act like a USB keyboard; every time I press the button in the middle of the USB cable it “types” the current measurement into the computer.

Here’s the Y axis’s motion:

Y Axis: Intended vs Measured Position.

Compared to the Z axis from yesterday, the “stairsteps” on this are very pronounced, as expected. Comparing the delta between the two lines gives this:

Y Axis: Normalized difference between intended and measured location.

This looks like around 15 microns of backlash again, very similar to the Z axis.

Now, on to the X axis. It’s a bit more interesting.

X axis Intended vs Measured Position.

Notice that it moved differently on the first vs second and third iterations?

X Axis: Normalized difference between intended and measured location.

Uhm, yeah. There’s way more backlash, and it seems to have been drifting at least a bit over time. Repacking that ball nut would probably be a good idea.

Here are all three axes on one graph; it makes it clearer that Y and Z are very similar, but X is an outlier.

Intended vs Measured Position.

Measuring Backlash, Part 1

I wanted to measure how much backlash my CNC has. For today, I’m measuring the backlash in the Z axis, because it’s the easiest, and also because it came fully assembled and there wasn’t much of an opportunity for me to mis-assemble it and screw things up.

As with my previous post on measurements, I’m measuring using a Mitutoyo 543-302b digital dial indicator. Mitutoyo claims that it’s good for 3 microns (0.003 mm) of repeatability, and I was able to match or exceed that during previous tests. I’m using Mitutoyo’s USB cable to pull measurements directly from the indicator into the computer.

My Z axis is a Beaver HDZ. It’s being driven by a Leadshine CS-M22331-L closed-loop stepper motor which has around 2.5 microns of encoder resolution and (as currently configured) a 6 micron pulse size. That is, the smallest unit that my CNC controller can tell the stepper to move right now is about 6 microns, but the underlying hardware tracks and adjusts its motion a bit more accurately than that.

I told the CNC to move its Z axis up and down a tiny amount (0.2mm), broken into 0.003mm (3 micron) steps. After each step, I recorded the height that the CNC was supposed to be at, as well as the height indicated by the dial indicator. Once it reached 0.2mm, I reversed direction and dropped back down to the start point. Then I repeated this 2 more times.

Here’s a graph that shows intended vs actual height over all 3 iterations. The X axis is simply time, indicated by the step number.

Intended vs Measured Position.

Looking at the underlying data, it’s clear that the stepper has roughly a 6 micron step size. Each step in my test gcode is 3 microns, and it consistently moves every other step. Interestingly, Grbl seems to report back its actual location factoring in stepper resolution, because the Z location logged also moves by 6 microns every other step, not by 3 each step.

The slope between the two lines on the graph isn’t exactly the same, and that’s okay–the dial indicator wasn’t perfectly aligned, so there’s a bit of cosine error over time. I’m really more interested in the shape of the graph, and especially the shape of the difference between the two lines.

Here’s the difference between the two lines, normalized to be centered on 0:

Normalized difference between intended and measured location.

Notice the step in the delta every time it changes direction. That’s backlash. It looks like there’s around 13 microns of backlash in my Z axis, or 0.0005”.

Out of curiosity, I re-ran this test again, but switched my Z stepper from 800 steps per rotation (4 microsteps) to 3200 steps per rotation (16 microsteps). I adjusted my CNC controller to match, so that all features still had the correct size.

The first thing that I noticed is that it went from moving 6 microns every other step to roughly 3 microns every step. This is visible in the location graph; it’s less pixelized:

Locations, with 4x the stepper resolution.

The backlash graph is also a bit less pixelized, which lets me measure the backlash more accurately. I get about 15 microns here:

Differences, with 4x the stepper resolution.

Other than the backlash, though, increasing the microstepping from 4 to 16 actually does seem to give more accurate movement, at least until backlash gets in the way. I’ve seen people claim that it doesn’t actually provide any extra resolution, but that’s clearly not the case with my hardware.

Next up: testing the X and Y axes.

Open Source: Camsync

Another week, another chunk of source code. This week, I’ve uploaded Camsync to Github. It uses last week’s Garmin Virb Library to talk to a Garmin Virb camera over WiFi and sync photo and video files.

I have a Virb Ultra 30 connected to my CNC in my garage, and a Virb 360 on my bike. The goal with Camsync is that I can just leave it running on my desktop and it’ll sync files as they’re available. Since the Virb on the CNC is hard-wired into power, it’s almost always available, so it’ll start copying soon after I record new video. The Virb on my bike comes and goes (as does the bike), but all I need to do is make sure that I leave it turned on while charging, and 360 degree video will trickle out of the camera without any intervention on my part.

There’s still a bit of work remaining; right now, Camsync won’t delete old content, which means that manual cleanup is required from time to time. I’m planning on adding code to delete the oldest videos after downloading to keep total usage under (say) 50%. That should be fairly safe.

Open Source: A Go Library For Talking to the Garmin Virb

Today’s new project is a Go library for talking to Garmin Virb cameras over WiFi. It’s on Github as scottlaird/virb. I’ve implemented almost the entire API covered by Garmin’s Camera Network Services API, including listing, copying, and deleting files; shooting video and still images; and configuring the camera.

This has been tested with Go 1.12, but should be trivially compatible with earlier versions of Go.

I’ve tested it with a Garmin Virb Ultra 30 and a Virb 360. Garmin’s documentation says that the older Virb X and XE should work as well, plus the Garmin Dashcam 45, 55, and 65W, but I don’t have one to test with.

The library includes a command-line tool that exists mostly to exercise the library, but can be used to access the camera from the command line or scripts. To install it, just run go get, and Go 1.12+ will fetch all of the dependencies and install it automatically, probably into $HOME/go/bin.

    $ virb --camera medialist
    {Media:[{Date:1539283354 Duration:1800 Fav:false FileSize:18066437201
    FitURL: GroupID:100019
    Index:1 LensMode:360 LowResVideoPath:
    Name:V0190034.MP4 ThumbURL:
    Type:video Url:} {Date:1539295752
    Duration:1801 Fav:false FileSize:18074818018 FitURL:
    GroupID:100020 Index:1 LensMode:360 LowResVideoPath:
    Name:V0200035.MP4 ThumbURL:
    Type:video Url:}
    ... Result:1}

    $ virb --camera snappicture
    {Media:{Name:V0260044.JPG ThumbUrl: Type:photo Url:} Result:1}

I wrote this for another project that’s still under development, but it should be generally useful. It’s pretty trivial to use. Here’s a slight variant on the implementation of the virb snappicture command, condensed for clarity:

package main

import (

func main() {
     camera := ""
     resp, err := virb.SnapPicture(camera)
     if err != nil {

     fmt.Printf("%+v\n", *resp)

Calls that take arguments are slightly more involved, but should still be straightforward. Here’s what it takes to start live streaming from the camera:

package main

import (

func main() {
     camera := ""
     streamType := "rtp"
     maxResolution := 0
     active := "1"
     resp, err := virb.LivePreview(camera, streamType, maxResolution, active)
     if err != nil {

     if resp.Result == 1 {
         fmt.Printf("The live stream URL is %s\n", resp.Url)
     } else {
         fmt.Printf("Live stream failed with error %d\n", resp.Result)

CNC Gantry Deflection

A few weeks ago, I was testing drilling holes in aluminum on my CNC and broke a bit. I’m not going to go into what went wrong here to make the bit break; that’s not the important part right now. What matters is understanding just how much force can be involved in milling, even with a light-duty CNC router like mine. Here’s the video:

One of the fascinating things (to me, at least) was how much the CNC’s gantry moved. If you watch, in addition to seeing the vise shift, you can see the gantry deflect upwards a bit. Not a lot, but I don’t want to see my CNC bend under load.

I’m not an real engineer (no matter why my business cards say), but back-of-the-envelope deflection math is fairly easy. Deflection varies as the square of the unsupported length and as the inverse of the third power of the height of the beam. So, if the gantry is twice as long, then it’ll deflect 4x as far under the same load. Or, if the gantry is 2x as tall, then it’ll deflect 1/8th as much. If you want less deflection, then make it taller with shorter spans. Simple, right?

Since I have a 1,500mm long gantry, I’m going to have way more deflection than people with a smaller CNC.

The Beaver HDZero’s gantry is made out a pair of 40x80mm aluminum extrusions, which are fairly strong. Due to the way they’re used, the 40mm side gets to deal with the vertical load on the system, not the 80mm side. I was curious how bad the deflection problem was, and if there was anything simple that I could do to improve it.

First, I wanted to measure how bad the gantry deflected. To do this, I attached a dial indicator on an indicator holder to my CNC, and then used that to measure the distance between specific locations and the CNC’s base plate. I actually milled a threaded M5 screw hole into my spindle mounting plate just for this sort of use. I unscrewed the indicator holder’s arm from its magnetic base and screwed it right into the mounting plate. That way it moves up and down with the Z axis of the CNC. By adjusting it carefully, I can get extremely precise and repeatable measurements.

The dial indicator set up to measure.

How precise? I’m using a Mitutoyo 543-302b digital dial indicator which displays 1 micron increments and is supposed to be repeatable to 2-3 microns. I was generally able to get results repeatable to within 3 or 4 microns, even after moving my CNC around. Considering that a single stepper motor step on my CNC’s Z axis is around 6 microns, that’s much better than I’d expected.

My first test was simply to move the CNC to the middle of its range and measure its deflection both with and without a known mass. I used a 5lb weight bag that I had handy. With no weight the dial indicator read -2.202mm. Adding 5 lb to the gantry right next to the Z axis increased that to -2.263mm. That’s 0.061mm, or 61 microns of deflection. I probably should have just declared that to be good enough–I’m mostly planning on milling wood, and that’s good enough for wood. Wikipedia says that 70 microns is usually used as the width of a human hair for comparison purposes, so it’s less than a hair’s worth of deflection. In imperial units, it’s around 2.4 thousandths of an inch–some, but not a ton. It’d be kinda nice to be able to be under 1 thou. So I continued.

I measured the deflection along the X asis every 50mm, starting at the center of the span and heading to the edge of the CNC. To do this, I measured the offset at 14 points without the 5 lb weight, and then re-measured each with the weight. At first I did this manually, typing g0 x-200 a lot, but eventually I automated it but putting all of the move commands into a file and using a USB cable to grab measurements directly into a spreadsheet. Once that was set up, I ran each test 3 times back-to-back to minimize error. The median error across sets of 3 samples was only 3 microns.

Deflection with no additional support.

That looks roughly like what I’d expect. Deflection is greatest in the middle of the table (at the left here) and drops off dramatically towards the edges.

Next, I tried adding some bracing. The top and bottom rails on the gantry are 56mm apart, so I bought a few 55mm 20x20 extrusions and some corner brackets to use to connect them. They’re not perfect, but they’re easy to add and I figured I could see if they made any measurable improvement.


Here’s what I measured with 3 of them installed, evenly spaced across the span:

Deflection comparison with 3 extra supports.

That seems to have reduced the deflection by 1/3rd. I don’t think I have enough M4x8 screws to try 7 braces, but I suspect it’d be a small improvement over 3.

I’m also curious if the added bracing improves surface finish at all. Unfortunately, I don’t know how to measure that objectively on a budget.

Update: I realized that the 3 micron repeatability is a probably a function of the closed-loop steppers that I’m using. The way the Z-axis stepper is configured, it’s only good for 6.25 microns per pulse. That is, the smallest step that the controller can tell the motor to make results in the Z axis moving 0.00625mm. But, it’s clearly able to consistently return to the same locations within 0.003mm, even after moving a long ways.

The answer is almost certainly the encoder attached to the stepper motor. The encoder has a resolution of 1,000 lines per rotation; at a minimum that’d be 5 microns on the Z axis. I’m not an expert on encoders by any stretch, but Leadshine says that this motor/driver/encoder combo should be set for an encoder resolution of “4000”; assuming that’s measurable levels per rotation, that’d give us 1.25 microns of precision. In any case, the encoder should make the stepper be repeatable to within 5 / 2.5 / 1.25 microns, no matter what the pulse step size is set to.

Milling My CNC's Base Plate

My CNC build is nearing completion. There are still a lot of things left on my to-do list, but they look a lot more like upgrades and improvements and less like requirements.

I’m almost done with the last big assembly project: milling an aluminum base plate for the CNC. I could just use MDF for mounting things to the CNC, but MDF isn’t nearly as solid as the rest of the machine. I decided to buy a 12” thick piece of aluminum plate that would cover the entire working surface of the CNC, and then mill mounting holes into it using the CNC itself.

I was originally just going to buy 6061 aluminum plate (“aircraft-grade aluminum” to marketing people, the default aluminum alloy to practically everyone else), but discovered that I could buy ATP5 tooling plate for not much more, and it was much flatter and designed for this sort of use. So I ordered a 1400x1000mm (oops–they only use feet and inches, make that 4’ 7-18” x 3’ 3-38”) chunk from Midwest Steel. The shipping on a 103 lb chunk of aluminum was entertaining, but not outlandish, and it actually arrived before I was ready for it.

There were 4 things that I wanted to mill into the plate:

  1. Mounting holes so I could use M5 screws to hold it down to the 20x80 v-slot extrusions that form the bottom of the CNC. I wanted enough screws that it would make the whole CNC more rigid, and so there wouldn’t be any long unbolted spans that could resonate and make noise. By the time I was done, I ended up with a total of 69 mounting screws.

  2. A rectangular grid of holes for mounting things to the base plate, tapped for M6 screws. After playing with the design in Fusion 360 a bit, I decided on a 50x50mm grid, totalling 416 holes. I kind of wish the grid had been tighter, but it was tough to keep the holes from running into the mounting holes from the previous step.

  3. An additional grid of holes for mounting an MDF wasteboard to the Aluminum. When milling wood (most of the time), I’ll want to cover the aluminum up with a sacrificial piece of MDF. The MDF will have all 416 holes from the previous step drilled into it, plus an additional 50 mounting holes (on a 140x150mm grid) used to hold the MDF down to the aluminum.

  4. A set of holes for bolting down milling vises. I have a cheap 6” vise and a couple cheap 4” vises that I want to be able to mount directly to the aluminum. For the 6” vise, this means M16 bolts 200mm apart. For the 4” vises, this means M12 screws 132mm apart.

The whole process came out much better than expected.

One of the M6 mounting holes.
Several holes across the ATP5 plate.

I recorded the whole thing via the Garmin Virb Ultra 30 that I mentioned a few days ago. It’s mounted directly to the spindle and powered by the CNC’s 24V power supply (via a DC-DC converter that drops it down to 12V). I’ve been pulling video off of it over WiFi, so I haven’t had to remove it from the CNC at all in order to use it. It’s just there, ready to go all the time.

Here’s a (poorly) edited version of the basic milling.

So, I’m not a videographer… sorry.

A few things I learned (or re-learned):

  1. The aluminum plate is conductive. So is the frame. Setting the probe block directly on the plate leads to immediate probe failures, because the CNC controller can read continuity through the frame instead of through the tool like it’s supposed to. The easy fix is to put the probe on top of a piece of paper.

  2. Fusion 360’s default CAM bore finishing settings are way too aggressive for aluminum-framed CNCs. It wanted to do a full-depth finishing pass cutting 0.635mm while using a 14” (6.35mm) endmill. That lead to a couple horribly non-round vise-mounting holes. I dropped it down to 0.2mm and it was still working a bit. 0.1mm (times 12.7mm deep) would have been better.

  3. 18” endmills and I still don’t get along. I broke another one. I think that’s #9 this year, and I did it similarly to the way I broke one of the earliest ones. When boring, don’t leave an island of aluminum in the middle, un-cut, or it’ll eventually find a way to bind your bit up and break it.

  4. Fusion 360’s 2D chamfer CAM operation really needs a “select same diameter” or similar option. Clicking on 500 individual holes is insane.

Open Source: cncatc

I’ve open sourced the code that drives my CNC tool changer and put it up on Github as scottlaird/cncatc. At the moment, it’s just an Arduino project (a single .ino file, basically C++) that probably isn’t useful to anyone but me, but I’m planning on cleaning it up a bit and extending it to cover other bits of the tool changing infrastructure.

Things that I’d like to add soonish:

  • A couple weeks of pending bug fixes, which somehow weren’t in the repo that was pushed to Github.
  • A wiring diagram, explaining how it wants to be wired up.
  • Increased modularity, moving tuning constants (and possibly sensor enable/disable settings) to the Arduino’s EEPROM.
  • Adding a usable serial interface with something API-ish that CNCjs can talk to.
  • My CNCjs tool change macros.
  • Code for my “smart” tool rack that I’m working on assembling. It’ll connect via USB and be able to report back to CNCjs which slots are occupied and which are empty.

I haven’t put much time into tool-changer work over the past few weeks. I’ve been trying to finish assembling my CNC and getting it ready for use. So instead of a garage full of slightly broken plywood ATC tool trays, I now have a garage full of aluminum chips. I milled a new mounting plate for my spindle, and I’ve been milling a 1400x1000mm aluminum base plate for the CNC. Those are almost done now, so I’ll be able to spend a bit more time on the tool changer again.

Open Source: UPS Shutdown for Andino

One of the aspects of my CNC that I’m liking is the embedded Raspberry Pi that is powered directly off of the CNC’s 24VDC power supply. When the CNC is powered on, the Raspberry Pi comes on. When the CNC is powered off, the Raspberry Pi goes off.

Except, of course, Linux systems don’t like to have the power yanked out from under them repeatedly, and SD cards tend to like it even less. Also, Raspberry Pis don’t like 24 VDC power. (From reading about people’s problems with micro USB power supplies, sometimes I wonder if they like any power.)

To deal with both of these problems, I bought an Andino X1, which is a Rasbperry Pi in a DIN-mountable industrial enclosure with a 24V power supply and a handful of 24V-compatible inputs. I also bought an Andino UPS, which is a tiny supercap UPS that is designed to power the Andino X1 for a minute or two. The UPS is wired into the CNC’s power, and the X1 is wired into the UPS. When power goes out for more than 30 seconds or so, the UPS trips a “power fail” relay, and if everything’s wired up right it’s possible to get the X1 to shut down cleanly before the power actually goes away.

Andino has released a ton of docs for their boards, including the firmware for the UPS, but they didn’t actually provide a shutdown daemon for the X1. So I had to write one myself. As of this afternoon, it’s now available on Github. It will quite likely work with Andino’s other boards as well, but I don’t have any to test. It’s not particularly complicated, and it has no configuration settings. When the UPS signals power failure, it shuts the system down. That’s it.

Random Gcode

One of the things that I did when I was testing my new CNC was throw random-ish move commands at it. I’d tell it to move a few inches to the left, and then a few inches to the right, then further to the left, and further to the right, and so forth. Eventually, I’d decide that it was working okay and then I’d either turn up the speed or move on to the next part of the assembly process.

There are at least two problems with this, of course. First, that’s not even remotely random. Second, it’s a pain to keep typing ‘G0 X100 Y200 Z20’, and really hard to keep typing it faster than the CNC can move.

To solve the problem, I wrote a quick tool for generating random gcode moves, just for exercising my CNC. The results looked like this:

I mentioned my random gcode generator it in the middle of a HDZero forum discussion and discovered that there was some demand for a random-move generator. This sort of thing can be useful for testing new CNCs and for validating new top speed and acceleration settings.

To use this, enter the minimum and maximum range for each axis on your CNC, in whichever units it uses. For mine, if I zero it to the home point, then X is between 0 and 1350mm, Y is between 0 and 850mm, and Z is between -145 and 0mm. I’ve used these as the default below; you should replace them with whatever makes sense for your device. Then hit “Produce random gcode” and copy the gcode that is generated into a file using Notepad, etc.

gcode goes here