Backlash compensation testing

Hi! I implemented a backlash compensation module for Klipper and wanted to invite interested people for testing. The branch for testing is available here. Essentially, backlash compensation can be configured in printer.cfg via

[backlash_compensation]
smooth_time: 0.005
x_backlash: 0.02
y_backlash: 0.03
# z_backlash: ...

or at run-time via

SET_BACKLASH_COMPENSATION X_BACKLASH=0.02 Y_BACKLASH=0.03 SMOOTH_TIME=0.005

On printers with dual_carriage you need to configure it similarly to input shaper, e.g. via

[backlash_compensation]
smooth_time: 0.005

[delayed_gcode init_shapers]
initial_duration: 0.1
gcode:
  SET_DUAL_CARRIAGE CARRIAGE=<dual_carriage_name>
  SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_shaper> SHAPER_FREQ_X=<dual_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
  SET_BACKLASH_COMPENSATION X_BACKLASH=<dual_carriage_backlash> Y_BACKLASH=<y_gantry_backlash>
  SET_DUAL_CARRIAGE CARRIAGE=<primary_carriage_name>
  SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_shaper> SHAPER_FREQ_X=<primary_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
  SET_BACKLASH_COMPENSATION X_BACKLASH=<primary_carriage_backlash> Y_BACKLASH=<y_gantry_backlash>

The values that you put here are the full backlash of an axis when changing the motion in the opposite direction. I’d generally recommend to calibrate it using a camera (e.g. using this web tool or similar online or offline calibration tool) using approximately the following procedure:

  1. move a nozzle in the center of a camera
  2. move a nozzle 1mm in the negative direction in the X direction
  3. move a nozzle in the positive direction until it is back in the center, making sure to only move it in one direction
  4. record X coordinate X1
  5. move a nozzle 1mm further in the positive direction over X axis
  6. move a nozzle in the negative direction until it is back in the center, never moving it in the positive direction in the process
  7. record X coordinate X2
  8. compute x_backlash = abs(X2-X1)
  9. repeat steps 1-8 for Y axis

I would generally not recommend to use the dial gauge for this purpose (with the exception of Z axis, perhaps), because it will create some forces on the motion system that could skew the results.

FWIW, on my IDEX 3D printer which has pretty complicated kinematics and which requires high forces to move Y axis I measured X backlash of 0.01-0.02mm for the both tools and Y backlash of ~0.05-0.06mm for the gantry (which was the original reason to implement this). I’d generally think that backlash of 0.01-0.02mm does not require compensation, but around 0.05mm - certainly does, especially on the IDEX printers, since this backlash could create small gaps when printing two-filament object between the individual parts. To test that the code works, I printed Vector3D Califlower calibration test, and here is the screenshot of the results (left, no backlash compensation, right - SET_BACKLASH_COMPENSATION X_BACKLASH=0.02 Y_BACKLASH=0.05 SMOOTH_TIME=0.0025:

I think that the test does demonstrate that the compensation has an effect on the actual prints.

A note on the choice of smooth_time. This is in general the time over which Klipper will smooth out the backlash motion. I’d say the default smooth_time=0.005 is a good choice for backlash up to 0.1 mm. As a rule of thumb, you can estimate the additional acceleration from the backlash compensation to be 2 * backlash / smooth_time^2, and so for larger backlashes you could increase smooth_time to, say, 0.01 (sec) or larger.

In terms of details of implementation, this compensation is implemented at the level of stepper kinematics and works somewhat similarly to input shaping and pressure advance.

This is how some typical scenarios look with it:

Movement from one point to another

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

Back and forth movements

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

Motion along the square (one side)

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

@koconnor FYI, and let me know what you think about it.

6 Likes

Separately, I am aware of another recent PR that implements similar functionality. If you are going to test mine, please also feel free to test this other one, and report your experience. That said, the implementations are totally independent and differ noticeably under the hood. The other PR modifies the end points of the gcode moves (working a bit similar to bed mesh, which also alters GCode moves), while mine modifies the stepper motion to pre-tention the system before performing the actual movements, and then once the motion is finished, it eventually returns the position of the stepper to the equilibrium position. This could result in differences in printing between the two implementations (e.g. due to lack or presence of that pretention before the actual move). Also, mine currently only supports compensation over X and Y (and Z) axis, while the alternative implementation supports additionally compensation over CoreXY (diagonal) axes. It is not difficult to add diagonal compensation to my implementation, however I wonder how the backlash actually works for CoreXY kinematics and whether that is necessary (or if just setting x_backlash == y_backlash would suffice)?

In case someone curious and for completeness, here are the charts of kinematics behavior from the alternative branch:

Movement from one point to another

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

Back and forth movements

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

Motion along the square (one side)

Without input shaper (but as if it was enabled, left) and with MZV input shaper (right):

Naturally, there are no differences between stepper and toolhead position without input shaping because the alternative implementation modifies the end points. With input shaping enabled, the charts look somewhat similar to my implementation, but still there are some differences, mostly in the beginnings and ends of the moves.

Interesting.

It sounds like useful functionality.

I have a few random comments/questions. Don’t take these comments too seriously as I haven’t looked in detail at this.

  • I’m curious if you’ve been able to identify what to look for in prints to see how compensation improves results. That is, what does a pre-compensation print look like when compared to a post compensation print.
  • Why is the implementation tied to input shaper? Is it for implementation convenience or is there some inherent connection between the two?
  • Why do the graphs show a ~20ms delay from the end of a move to a correction movement? At first glance I’d have thought one would correct the backlash immediately after a move that comes to a long duration stop.
  • It’s interesting that you show backlash compensation applied before input shaper. At first glance, I’d have thought backlash would be less likely to cause printer resonance and would thus be something to apply after input shaping is applied.
  • Out of curiosity is there a mathematical model you are using for the backlash?

Again, it looks interesting to me.

Cheers,
-Kevin

Thanks for taking a look at it, Kevin.

For example, on IDEX printers it could manifest in small gaps between parts of the model printed with different tools. On my printer I printed this test cube for comparison (exactly the same gcode, printed on the same machine a few minutes apart, left without compensation, right with compensation enabled):

Alas, it is a bit difficult to capture the defect well (plus, at 0.05 mm backlash it is admittedly small), but you could notice small gaps between black and white parts over Y axis on the left cube. This is not underextrusion, as is hopefully evident by the rest of the print, and they cannot be compensated by the tools Y offset (as the gaps are between parts when both white is on the top and bottom, so reducing a gap in one place via an offset will increase the gap in the other). And these gaps are much less noticeable (if present at all) on the right cube, indicating that backlash compensation reduces the defect indeed. That said, there’s been other reports of backlash and requests for backlash compensation over the years for single-extruder printers and higher magnitudes of backlash, I’m unsure what kinds of defects the users observed there.

There is no inherent connection at all. However, there is a question when to apply backlash compensation: before applying input shaper or afterwards. If it was applied afterwards, then such backlash compensation could introduce some faint ringing, perhaps (given that it could produce high accelerations, even if for the stepper rotor), so in order to prevent that I decided to put it before input shaper (so that input shaper can compensate for echo from backlash compensation). I’m unsure if this is ultimately the right call though. But given this consideration, and the fact that input shaper evaluates toolhead positions directly without any extra callbacks, I decided to not introduce any callback in the input shaper implementation that could be intercepted and overridden by the backlash compensation and instead integrate it directly into the input shaper code (also, most of the printers today use input shaper, I imagine, and for those who don’t, such inclusion introduces only marginal overhead). Then, this also simplifies the next point:

This is to make sure that input shaper ā€˜commits’ to the backlash-compensated final position before returning to neutral position. In fact this delay is dynamic and computed from the shaper duration for the given axis (so in fact these charts ā€˜no input shaping’ were obtained with an ā€˜input shaper’ with the same duration and same pulse times as MZV in the other charts, but with pulses magnitudes of [0, 1, 0], in order to specifically demonstrate this behavior). Compare how the commanded stepper position looks with (left) and without (right) this trick:

Without this trick, the right hand side never pushes the stepper to go to the final position with backlash compensation added to it before going back to the neutral position. You could argue that for symmetry it may be worthwhile to add this offset to the start of the motion too, but in my opinion that is unnecessary, since that would complicate the code and input shaper already gives bias to the future positions that already have that backlash compensation added to them, as opposed to the end of the motion, when neither prior position, nor positions after the move do not reach ā€˜final_position + axis_backlash’ position.

Yep, I do not know, perhaps this was a wrong call on my side. I had to choose something, but I never wrote an alternative implementation ā€˜just to test it’, and it is not trivial to test the reverse order of applying backlash compensation and input shaping using the current code (as some non-trivial modifications would be necessary for it).

For compensation, it uses just a constant backlash compensation magnitude per axis. Then, a bit simplified, in order to compute the actual backlash compensation value and to smoothen it a bit, for each time t the code computes the time until the nearest positive movement of an axis ttp and the time until the nearest negative movement of an axis ttm within smooth_time window from the current time t. Then it computes the backlash compensation value as 0.5 * axis_backlash * (w(ttp) - w(ttm)) where w is a smoothing function. It must have the following properties:

  • w(t) is smooth (i.e. no axis position or velocity jumps while applying backlash compensation)
  • w(0) = 1 - full compensation
  • w(t >= smooth_time) = 0 - no compensation
  • w’(0) = w’(smooth_time) = 0 - zero derivative (i.e. zero compensation speed) in the beginning and end of compensation interval to avoid toolhead velocity jumps when starting or finishing applying backlash compensation

Then somewhat arbitrarily I chose w(t) to give full constant acceleration while t > smooth_time / 2 and full constant deceleration while t < smooth_time / 2 (x is dimensionless t here):

x < 0.5 ? 1. - 2. * x * x : 2. * (1. - x) * (1. - x)

And then there is a bit of tricks to compute ttp and ttm for the past motions to ā€˜prolong’ the motion for the duration of the shaper, but not more than the next motion in the opposite direction.

I hope this helps a little.

Thanks.

Okay, so at a very high-level, if y_backlash=0.030 and there is a request to move from Y=1 to Y=10, you want to make sure that the axis moves fully to Y=10.030 and then backs off to the final Y=10?

If so, FWIW, it seems curious that the back off movement is a 20mm/s ā€œpulseā€ 20ms later instead of being a very slow backoff of ~1mm/s that lasts 20ms.

Hrmm. I think I’ll have to look at that a little closer.

At a very high-level is the idea that, given a nominal_stepper_position(t), you want to use compensated_stepper_position(t) = nominal_stepper_position(t) + (stepper_moving_in_positive_direction(t) ? y_backlash : -y_backlash) ? The weighting and stuff is just smoothing of that core idea?

Again, don’t take anything I say here too seriously, I’m just curious on the implementation.

Cheers,
-Kevin

Well, to Y=10.015, but yes. As to why the pulse instead of slow backoff - because now Y=10.015 position is reached only momentarily after input shaping, and with a slow backoff - never, due to averaging effects of input shaping. How far exactly it’ll go to depends on the input shaper, but I guess it is safe to assume up to around Y=10.0075 or a bit further than that.

Yes, exactly, otherwise the stepper would have to jump its position when starting the motion or changing the direction, which is not possible. FWIW, it would not be difficult to add some velocity-dependent terms into the compensation (and use weighted integration to smoothen the velocity jumps), but I just don’t know how to calibrate that, and I also do not know if that could compensate for any real defects in the prints.

Also, I remembered another reason to put backlash compensation before input shaper, which is a bit of a technical one. Basically, any smoothing of the compensation (either integral-based, or just looking for the nearest time of positive and negative movements) requires traversing a move queue where each move is defined by a polynomial function. However, input shaper does not build an effective post-shaping move queue, and input_shaped_position(m, axis, t) can change abruptly with a passage of time t within the same move m since input shaper traverses the move queue on its own and looks at other moves and not just m. Thus, implementing any efficient calculation of backlash compensation based off input_shaped_position(m, axis, t) would be pretty complicated and annoying (e.g. traversing using a set of pointers - one for each pulse - and solving systems of equations at each step to determine if a direction change occurs and when), so probably not really worth the trouble. And computing the terms independently as, say, input_shaped_position(m, axis, t) + backlash_compensation(m, axis, t) would likely produce bizarre results (e.g. backlash_compensation already waning out while input_shaped_position still moving the stepper in the requested direction).

1 Like