Maslow Home Maslow Community Garden

Holey Triangular Calibration


I just ran into something interesting… I don’t know if something is messed up in the firmware I’ve modified, but when I set zero on the sprockets and feed out 320 links of chain, the sprocket doesn’t return to exactly zero. I’m confident it was really zero when I set zero on it (used chain and bullet level trick). Both sprockets are off by a few degrees. I checked the settings in the log and everything seems “normal”:


And whats even weirder… it knew because when I went to reset zero, I hit automatic and the sprockets returned to zero. Extend chain distance is set to 2032… 2032/6.35 = 320. Since that’s divisible by 10, the 12 o’clock sprocket should have returned to 12 o’clock, precisely. The firmware code looks good… so this isn’t making much sense to me. It uses SingleAxisMove so it doesn’t use triangularInverse, which is where I’ve made changes.


it’s built into the firmware… GC issues B02 L0 R1 to the controller and it feeds out using SingleAxisMove the amount of chain you specified in GC’s Advanced Settings. I specified it to be 2032.

Chain tolerance is zero. I actually removed it from the firmware so the distPerRot is fixed at 63.5 (or whatever chainPitch*teeth is). The only place chain tolerance is used is in inverse triangular and that’s not used for SingleAxisMove, as far as I know… regardless, it’s all zeroed out

encoder steps was 8113.73 in GC


do some manual testing

B09 L R F

( means that you put a value in this location)

will run the motors a given distance. make it a good long distance

B10 L
B10 R

(I will use a shortcut and call this
B10 (LR)
to indicate there could be an L or R in this position)

should return the current position of the motors

B11 (LR) S T

where S is speed (-255 to 255) and T is time in seconds)

and finally

B06 L R

lets you set the position

so to do the full chain stretch testing (see if the stretch is consistant), hook
the chains into a loop, hook them over both sprockets and do

#set units to mm

#set movement to relative

#pull chain snug slowly with the right motor(pulls for 3 seconds, may need to go
b11 r -50 s 3

#pull tight with both motors (this needs to be fixed so both motors can be
controlled at once
b11 l -255 s 1
b11 r -255 s 1

#set our position to 0,0
b06 l 0 r 0

move to the next location (~4" down the chain), first give the chains some

slack, then move both motors
b09 l 5
b09 l 100 r -100

#pull tight again
b11 r -50 s 3
b11 l -255 s 1
b11 r -255 s 1
b10 l
b10 r
#both numbers should match and should be consistant from run to run

(R will be negative so it would be L101.6 R-101.6)

#puse for 3 seconds so you can look at the debug/log output nd see the numbers
g4 s3

loop back to the b11 l 101.6 command and repeat until you’ve gone around the

entire chain. i.e. cut-n-paste this section in 3x(chain length in ft), so 66
times for the stock maslow chains. extra times don’t hurt

Using the b09 command, you can rotate a motor a long distance in one direction
and then back.

I would suggest that you pick a distance that’s a nice big multiple of your
chain lenth size so that you have the same tooth (which you can mark) that
should be at the top at the beginning, middle, and end of the movement

something like

g21 g91
b09 l 3175 r 3175 F2000
g4 s 5
b09 l-3175 r -3175 F2000
g4 s 5
b09 l 3175 r 3175 F2000
g4 s 5
b09 l-3175 r -3175 F2000
g4 s 5
b09 l 3175 r 3175 F2000
g4 s 5
b09 l-3175 r -3175 F2000
g4 s 5
b09 l 3175 r 3175 F2000
g4 s 5
b09 l-3175 r -3175 F2000
g4 s 5

this should be 50 revolutions at max speed towards the center of the machine, a
5 second pause, and then 50 revs back the other way, 4 times.

David Lang


I’ll try those tomorrow (had to go back in house.
RL stuff). But I did issue the same command to feed out 2032 mm of chain (exactly 320 links) via macro and got the same results.


we need some commands that act in encoder steps, not mm/in (both for moving and
for reporting)

I suspect that you are tripping over floating point precision problems in this,
in both directions…

David Lang


Back to calibration… I modified the routine once again to completely eliminate the need to do any kind of measurement to Mark 5. Now, it iterates through different Mark 5 values to find the one that introduces the least amount of error in the calculations. I tend to think this is a better way of doing it so you don’t have to try to measure something that’s very difficult to measure and would screw up the results if it’s off even a little.

So, with that said and having built a new top beam, I’ve run the calibration routine again and seem to maybe get decent results (untested, but at least look realistic). The one additional thing I did was to tie the chain tolerance values together so if the left value increases, so does the right value. The problem I had run into was that both the top beam liked to tilt up and the right chain tolerance value liked to shoot up (I think tilt is offset by the difference between chain tolerances to an extent)… By restricting the left and right chain tolerances to be equal, the top beam tilt stayed fairly level (0.09 degrees) and the chain tolerance stayed low (0.06%… new chains so I hope the tolerance is low).

The computer is running through tens of millions of iterations, but right now it seems stuck, at 3,009,243 being the best iteration and the highest RMS error is 1.5 mm (Hole 7 at the far top right corner). Hopefully, when I plug the parameters in, I’ll get better results.:crossed_fingers: This cut was run using hand measured values for motor distance, educated guess for rotational radius and chain sag, and zero values for top beam tilt and chain tolerances.


Reading through the forums I had a thought. Would it be easier or more efficient to try using a circle or circles for calibration? Calibration could cut circle(s) with center points to measure from and even possibly some overlap for further reference?


I’d think it would make for a good test pattern, but I think calibration needs to be based on points (i.e., kinda like calculating the error between desired point and actual point). I don’t think circles would help that.


Right. I was meaning to measure at say 12-3-6and9 o’clock and possibly conversion points if overlapping. I guess it still boils down to just points.
Sorry if I’m not really being helpful just trying to maybe generate different possible way to look at the problems.


Glad to see the ideas… keep them coming.

I think my method works, but I have to overcome some issues with my setup… for some reason, my holes aren’t symmetrical. Bottom left hole is farther away from center than bottom right… My top left, top right and bottom right holes are 1030 mm from center but bottom left is 1031.5 mm. I’ve measured many times and each time I get the same results. I haven’t figure out why it comes out that way. Only thing I can come up with is the right chain is more stretched/worn than the left. But they came from the same spool of chain and are brand new. Perplexing.


Have you tried switching the left and right chains?
Is there a tolerance rating on the chain? I imagine they’re die wears a certain amount before they replace it and not all links are created equal or from the same batch for that matter.


No, but that’s the plan for this weekend. I don’t know the specs of the chain. They look good, but anything is certainly possible.

the other thing I’m going to look at is where the hose is during the cuts. It’s a 4-inch hose that’s pretty flexible but I’m going to redo the cuts with the hose off to see if it was affecting the placement of the sled when it was so low. I don’t think it was, but best to check.


Hey @madgrizzle,

I have just started looking at this work, specifically related to the math and the calibration routine. I would like to try go through the script and see if there are any opportunities to improve, or if there are any bugs which are causing the thing to work incorrectly.

I was looking at the “CalculateCoordinates” function. I understand why there is complexity associated with leveling the motors. There is the 5th hole, which establishes an angle which impacts the x,y coordinates of all the other holes that are cut. It is like this angle is one freedom that you cannot measure accurately, but impacts every element of your calibration. Even if you do measure it accurately, it is confounded with the level of the motors. I think there is an assumption (about this angle) being made in the CalculateCoordinates function, but I am not sure what it is or its implications.

I want to propose a change here.

  • Option 1: Make this angle part of the optimization. Include this angle as an optimized parameter, and update the x,y coordinates of the holes for each iteration, dependent on changes to this angle.
  • Option 2: Include measurements from pt. 2 and 3 to the bottom of the plywood, and have these measurements establish this angle. This could also be done via measurements from pt 1 and 4 to the top of the worksheet.


Yes, you have to establish the rotation of the constellation of points. Without some known angle, you wouldn’t know from just the distance measurements if the constellation was perfectly level or even 90 degrees rotated… the reason I did not make a reference to the plywood was that I cannot guarantee that the plywood is perfectly parallel with the top beam.

But the problem with this method is the compounding of errors. If you go through it, you will see xy points calculated based upon xy points that were previously calculated. This leads to increasing error and now you have bad data that you are trying to calibrate with.

I’m focused on other things (optical calibration among others) but feel free to take it and try anything out. I’m happy to try to explain my code if you have questions.

My concern about “optimizing” the angle while optimizing the calibration values is that (unless I don’t understand your suggestion) the optimization, even though it may further reduce the reported calibration error, it is just a fudge like the current cut34offsety is a fudge. What I attempt to do is “optimize” the angle to minimize the error in the coordinate calculations… so I just make one measurements of mark 5 and try to calculate the other that results in the lowest error (iirc, for distance between holes 1 & 4)


Thanks @madgrizzle.

After having thought about it for a while, you already have that angle built into the optimization. By including the X and Y coordinates of both the left and right motor, you can effectively rotate the motors about the holes (rather than rotating the holes about their center).

I was finally able to clone the GroundControl repository, and this repository. I don’t want to commit to anything, but I would like to do some development on it.

One thing is the interaction between this and the camera-based calibration. I keep thinking these will come together at some point. The camera-based calibration is a huge opportunity, since it requires very little user-input, and the camera is not complicated or expensive. I am trying to understand whether or not there are any limitations, and the time frame within which it will be functional. Once it is developed, will any of the developments here provide any value afterward?


There’s two philosophies at play here… one is to develop a model that accurately describes the system and all the statics and dynamics to achieve 0.5 mm accuracy and the other is to use a model that gets the sled within maybe 20-30 mm (close enough that a camera can see the square) and use an error correction matrix to get you to 0.5 mm accuracy. I’m working on the latter and that’s my focus. Others, I think, continue to work on the former.

The two could be married such that if you had a good enough model you could use the optical system to perform the “cut” measurements to calibrate it, but I wonder if you can achieve 0.5 mm accuracy with the optical system by itself, what’s the point of calibrating a different model using it to achieve the same level of accuracy?

@johnboiles and I have been working on it, but with the storm that came through, I’ve been detailed from any real work on it for a while now. I’d like to try some benchmark cuts soon. The optical calibration system is available for downloading and trying… just need a calibration pattern and camera system


avoiding the cost of the accurate pattern and camera (as well as the issue of
making a router equivalent weight camera holder)

Also, the camera calibration is very slow to test so many points.

If we can get good enough with the model approach, it may be that we can get
good results with far fewer sample points. Then the touch-based method could be
used to find those points

But in any case, we need at least a few people to have the optical equipment to
be able to test how accurate the models are.

David Lang


I spent the weekend trying to implement a calibration algorithm, like I’ve discussed in various topics in this forum. I used @madgrizzle’s Holey Calibration as a starting point. I created a fork in github at It is still in very early stages of implementation. implements the methods and properties. The script, exercises the calibration as a runnable script. is just a copy of the original, because I was struggling to figure out how paths and modules are handled in Python. I modified the convergence criteria in the ‘forward’ method.
If you want to run it, open the in Python, and run it. There is a dependency on scipy, so you will have to install it beforehand. Here is a link which should describe the installation process:

Here are the results so-far:

  • The calibration runs. Right now, there are four parameters which are calibrated: DistanceBetweenMotors, MotorYOffset, RotationDiscRadius, and ChainSagCorrection.
  • The calibration can converge on a solution which is within 1E-9 of Target.
  • The calibration calculates RotationDiscRadius and ChainSagCorrection to within 1E-9 of the uncalibrated condition. I don’t understand why. In theory, this should adjust to some other value to reduce the error function. However, since the errors get very close to zero, it probably indicates some issue with the implementation.
  • The MotorYOffset is a random number generator. I have accepted this, because the way it is implemented, all y values are differences (e.g. YValue1-YValue2). Because these y distances are all differences, the calibration can translate up or down without affecting the error function. There is no information provided the calibration which could be used to determine the MotorYOffset.
  • The calibration is not changing DistanceBetweenMotors at all. The initial condition translates directly to the optimized output. I have tried multiple initial conditions. Nothing has modified this behavior. This is an issue to me for several reasons. First, this is the biggest calibratable constant in the system, and it should affect the error function. Second, the calibration converges to zero error, regardless of the DistanceBetweenMotors.

Here are the issues I am struggling with:

  • Why RotationDiscRadius and ChainSagCorrection converge back to the uncalibrated values, regardless of starting point, and this result has such a low error (1E-9).
  • Why such a low error is possible. Nobody can measure to with 1E-9 mm accuracy. If this is measured data, the error should never be this low. @madgrizzle, I am using the measured distances, unchanged, from your original holey calibration script. Are those numbers calculated from a GC calibration? Maybe I am just misinterpreting the output from the optimizer.
  • Why DistanceBetweenMotors is not being modified. This is most likely due to the fact that changes in this number are not resolved by the kinematics.forward method. My current suspicion is this is due to something with the class definition, and how the DistanceBetweenMotors property is implemented. I could use some help here. Specifically, is there something syntactically significant about Python class properties that start with ‘_’, like ‘_xCordOfMotor’ in

Right now, I don’t recommend doing any testing on this calibration process, because there are clear bugs that need to be worked out before wasting any wood. If anyone feels well-positioned to help work through some of these bugs, I would welcome the input.


Is it possible you are calibrating to the original computed values rather than the measured values?