Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backlash compensation #67

Open
lllars opened this issue Mar 27, 2015 · 14 comments
Open

backlash compensation #67

lllars opened this issue Mar 27, 2015 · 14 comments

Comments

@lllars
Copy link

lllars commented Mar 27, 2015

I would like to see some basic backlash compensation in G2. Unless someone is motivated to to implement it, I'd be willing to take a stab at it, although I'm not sure my programming skills are up to the task.

Looking through the code it seems like maybe the place to implement it would be in the _load_move() section of stepper.cpp. There is already a check for change in direction there. I think implementation could be as simple as adding a few steps upon a positive direction change, and substracting a few steps upon a negative direction change. Of course, some new configuration values would have to be added for the amount of backlash for each axis.

Does this seem like a reasonable strategy and/or a reasonable place to implement it? What variable holds the number of steps to move by in this case, st_run.mot[MOTOR_1].substep_accumulator?

@aldenhart
Copy link
Member

Backlash compensation is one of those issues that is more complex than it seems. It's not as simple as putting in steps when you reverse direction. One test is to determine how the machine should behave when tracing a circle. The direction reversals are very slow. If you wanted to correct by N steps you don't just want to do that all at once and leave a discontinuity at the 90 degree points.

Another issue is acceleration. Reversing direction is one of the times that acceleration management kicks into high gear. Just adding or removing steps or changing direction will cause unexpected discontinuities in acceleration, and possibly motor stalls.

I think it can be done, and I encourage you to experiment. It's probably best handled above the loader level. Take a look at st_prep_line() in stepper.cpp. There is a block of code that bracketed by #ifdef __STEP_CORRECTION which uses a "nudge" strategy for correcting positional errors (i.e. incorporating encoder feedback) without upsetting the step integrity. That's probably the place to start.

@lllars
Copy link
Author

lllars commented Mar 28, 2015

Thanks for the help!

I only have a very basic understanding of backlash compensation, but it seems to me like it actually is better to put in all the steps at once rather than spreading them out. A (probably overly) simplistic way to think about it is that the axis won't move while those steps are being inserted. The motor and leadscrew will turn, but the nut won't actually move at all. Since nothing is moving, accelerations don't matter either, at least as long as the acceleration planner doesn't know about the extra steps (does it?).

I suppose this really is an overly simplistic since view cutting forces could cause the axis to move.

Regardless, st_prep_line() looks like a good place to do some experimenting. Seems like it could be as simple as adding:

// rudimentary backlash compensation
        if (st_pre.mot[motor].direction != st_pre.mot[motor].prev_direction) {
            travel_steps[motor] += st_cfg.mot[motor].backlash;
        }

Does that look workable? I'm assuming travel_steps is full motor steps, not microsteps? I'll give it a try later. Just need to figure out how to add a backlash configuration variable.

@aldenhart
Copy link
Member

You can give it a try, but I suspect it's more complex that this.

To add configuration variables take a look in config_app.cpp. Follow the instructions at the top of the table to add a new variable. You will find many, many worked examples there and in the various module files. Take a look at the sys / ja (junction acceleration) parameter as a good example that's close to what you need. Also see the print function in canonical_machine.cpp.

@lllars
Copy link
Author

lllars commented Mar 30, 2015

I gave it a try Saturday night. The actual code I tried was:

// rudimentary backlash compensation
        if (st_pre.mot[motor].direction != st_pre.mot[motor].prev_direction) {
                travel_steps[motor] += st_cfg.mot[motor].backlash * st_pre.mot[motor].step_sign;
        }

Implementing the config variables was fairly straightforward. It probably makes the most sense for them to be defined in terms of distance units of backlash for each axis. However, currently I've implemented them as the number of steps of backlash for each motor, as that was easier.

So, as you've guessed it doesn't work very well. I tested it by moving the x-axis back and forth. I could tell it was injecting the extra steps at the beginning of each move, as expected. However, for backlash values of more than ~10, the steps are injected faster than the motor can handle, so I need to figure out how to slow that down.

I only have ~3 steps worth of backlash in my x-axis, but I measured no change with such small values, so I suspect that travel_steps[motor] is actually microsteps, not steps. I'm using 16 microstepping on that axis.

For some moves, extra steps were injected at both the beginning and end of the move. Then the next move (in the opposite direction) would have no steps injected. I don't know why steps would be injected at the end, maybe in certain cases the controller is specifying a short segment in the reverse direction to slow down the machine?

My next steps are:

  1. Add time to move segments where backlash is compensated.
  2. Print out some relevant values when backlash is compenstated, in order to better understand the machine state when it is happening.

I'm still interested in getting this simple model of backlash compensation working, but I'm also starting to see ways in which the physical reality is more complicated. For example, If the machine is moving fast and then stopped quickly, the inertia of the machine will keep it moving until it has taken up the backlash slop. In this case it would have been better to not add any backlash steps to the move.

@lllars
Copy link
Author

lllars commented Mar 30, 2015

I have confirmed that some G0 moves end with a move segment in the opposite direction as the rest of the move. This may be a bug. The magnitude of the offending move segment is ~1/1000 of a microstep.

I have also confirmed that travel_steps is microsteps, not steps.

Increasing the values of travel_steps[] or segment_time results in lots of correction steps, many more than the amount of the increase.

@lllars
Copy link
Author

lllars commented Mar 30, 2015

Ok, I've grokked the pseudo-encoder system and following error correction, which explains all the correction steps when trying to change travel_steps from within st_prep_line()

I think the way forward might be to temper the following error within st_prep_line() by the desired amount of backlash deviation. I'll give this code a try this evening:

#ifdef __STEP_CORRECTION
    // 'Nudge' correction strategy. Inject a single, scaled correction value then hold off

    // rudimentary backlash compensation
    if (st_pre.mot[motor].direction != st_pre.mot[motor].prev_direction) {
        if (st_pre.mot[motor].step_sign == 1) {
            st_pre.mot[motor].backlash_deviation = 0;
        } else {
            st_pre.mot[motor].backlash_deviation = -st_cfg.mot[motor].backlash;
        }
    }

    if ((--st_pre.mot[motor].correction_holdoff < 0) &&
        (fabs(following_error[motor] - st_pre.mot[motor].backlash_deviation) > STEP_CORRECTION_THRESHOLD)) {

        st_pre.mot[motor].correction_holdoff = STEP_CORRECTION_HOLDOFF;
        correction_steps = (following_error[motor] - st_pre.mot[motor].backlash_deviation) * STEP_CORRECTION_FACTOR;

        if (correction_steps > 0) {
            correction_steps = min3(correction_steps, fabs(travel_steps[motor]), STEP_CORRECTION_MAX);
        } else {
            correction_steps = max3(correction_steps, -fabs(travel_steps[motor]), -STEP_CORRECTION_MAX);
        }
        st_pre.mot[motor].corrected_steps += correction_steps;
        travel_steps[motor] -= correction_steps;
    }
#endif

@lllars
Copy link
Author

lllars commented Mar 31, 2015

Tested. All I did was linear moves in air, no cutting yet. But, it seems to work well.

I found I had to decrease STEP_CORRECTION_HOLDOFF in order to have the backlash correction applied within a reasonable timeframe. With the default STEP_CORRECTION_xxx values, the maximum compensation is 0.6 microsteps every 30 milliseconds. My machine needs ~40 microsteps of correction, so any move quicker than 1.2 seconds doesn't get fully corrected. I reduced STEP_CORRECTION_HOLDOFF to zero (min time = 0.2 seconds) and that worked fine for me. Still, this could be a limitation for users with more backlash.

@giseburt
Copy link
Member

This is fascinating. I look forward to hearing more.

@lllars
Copy link
Author

lllars commented Mar 31, 2015

Thinking about this a little more, I realize the timing info I wrote above is not quite correct. It actually takes a bit longer to do the correction because the length (travel_steps) of the first and last few move segments are shorter than the value of STEP_CORRECTION_MAX (currently = 0.6). So, the step correction for those segments will be truncated to be equal to travel_steps for those moves, by the following bit of code:

if (correction_steps > 0) {
            correction_steps = min3(correction_steps, fabs(travel_steps[motor]), STEP_CORRECTION_MAX);
        } else {
            correction_steps = max3(correction_steps, -fabs(travel_steps[motor]), -STEP_CORRECTION_MAX);
        }

So, for the first few move segments, the value of travel_steps will be doubled. After the first few, travel_steps becomes greater than STEP_CORRECTION_MAX, and so it is increased by that amount.

I'm a bit worried about those first few moves. Doubling the move segment distance is equivalent to doubling the jerk, since the time for those segments remains the same. That may result in dropped steps. While this could be accounted for by the user halving the max jerk value for that axis, that would slow down all moves. Instead I propose adding another step correction factor, STEP_CORRECTION_JERK_INCREASE, to be implemented like so:

if (correction_steps > 0) {
            correction_steps = min3(correction_steps, fabs(travel_steps[motor] * STEP_CORRECTION_JERK_INCREASE), STEP_CORRECTION_MAX);
        } else {
            correction_steps = max3(correction_steps, -fabs(travel_steps[motor] * STEP_CORRECTION_JERK_INCREASE), -STEP_CORRECTION_MAX);
        }

A default value of 0.25 seems reasonable. I'd also suggest that we convert all of the STEP_CORRECTION values into user-tunable configuration variables.

@lllars
Copy link
Author

lllars commented Mar 31, 2015

Just tested this update with some actual cutting and it works great, it cut a circle that is as good as I can measure. So, as I see it, this is ready to be incorporated into the code base. How do I go about that?

Also, here is an example of what happens during a short (0.1 inch) move with 40 microsteps of backlash compensation. Each move segment is one line of output. I've listed my step correction values at the top:

#define STEP_CORRECTION_THRESHOLD   (float)2.00     // magnitude of forwarding error to apply correction (in steps)
#define STEP_CORRECTION_FACTOR      (float)0.25     // factor to apply to step correction for a single segment
#define STEP_CORRECTION_MAX         (float)10.0     // max step correction allowed in a single segment
#define STEP_CORRECTION_HOLDOFF               0     // minimum number of segments to wait between error correction
#define STEP_CORRECTION_JERK_INCREASE      0.25     // max amount of jerk increase during step correction

{"sr":{"posx":0.000,"vel":0.00,"stat":3}}
x0.1
{"r":{},"f":[1,0,4]}
backlash switch  0.000
s_time: 0.000081        f_error: -39.0007       t_steps:  0.0004        c_steps: -0.000
s_time: 0.000081        f_error: -39.0000       t_steps:  0.0098        c_steps: -0.002
s_time: 0.000081        f_error: -39.0004       t_steps:  0.0434        c_steps: -0.011
s_time: 0.000081        f_error: -39.0101       t_steps:  0.1141        c_steps: -0.029
s_time: 0.000081        f_error: -39.0535       t_steps:  0.2324        c_steps: -0.058
s_time: 0.000081        f_error: -39.1676       t_steps:  0.4062        c_steps: -0.102
s_time: 0.000081        f_error: -39.3999       t_steps:  0.6414        c_steps: -0.160
s_time: 0.000081        f_error: -38.8061       t_steps:  0.9418        c_steps: -0.235
s_time: 0.000081        f_error: -39.4476       t_steps:  1.3091        c_steps: -0.327
s_time: 0.000081        f_error: -38.3894       t_steps:  1.7434        c_steps: -0.436
s_time: 0.000081        f_error: -38.6985       t_steps:  2.2432        c_steps: -0.561
s_time: 0.000081        f_error: -38.4419       t_steps:  2.8055        c_steps: -0.701
s_time: 0.000081        f_error: -37.6851       t_steps:  3.4261        c_steps: -0.857
s_time: 0.000081        f_error: -36.4907       t_steps:  4.0993        c_steps: -1.025
s_time: 0.000081        f_error: -35.9167       t_steps:  4.8189        c_steps: -1.205
s_time: 0.000081        f_error: -35.0161       t_steps:  5.5776        c_steps: -1.394
s_time: 0.000081        f_error: -33.8350       t_steps:  6.3673        c_steps: -1.592
s_time: 0.000081        f_error: -32.4126       t_steps:  7.1797        c_steps: -1.795
s_time: 0.000081        f_error: -30.7799       t_steps:  8.0057        c_steps: -2.001
s_time: 0.000081        f_error: -28.9596       t_steps:  8.8364        c_steps: -2.209
s_time: 0.000081        f_error: -26.9654       t_steps:  9.6624        c_steps: -2.416
s_time: 0.000081        f_error: -24.8017       t_steps: 10.4748        c_steps: -2.619
s_time: 0.000081        f_error: -22.4641       t_steps: 11.2645        c_steps: -2.816
s_time: 0.000081        f_error: -19.9389       t_steps: 12.0232        c_steps: -3.006
s_time: 0.000081        f_error: -17.2034       t_steps: 12.7428        c_steps: -3.186
s_time: 0.000081        f_error: -14.2266       t_steps: 13.4161        c_steps: -3.354
s_time: 0.000081        f_error: -10.9694       t_steps: 14.0366        c_steps: -2.742
s_time: 0.000081        f_error: -7.3855        t_steps: 14.5989        c_steps: -1.846
s_time: 0.000081        f_error: -4.4220        t_steps: 15.0987        c_steps: -1.106
s_time: 0.000081        f_error: -3.0209        t_steps: 15.5330        c_steps: -0.755
s_time: 0.000081        f_error: -1.1196        t_steps: 15.9003
s_time: 0.000081        f_error: -0.6526        t_steps: 16.2007
s_time: 0.000081        f_error: -0.5530        t_steps: 16.4359
s_time: 0.000081        f_error: -0.7537        t_steps: 16.6098
s_time: 0.000081        f_error: -1.1896        t_steps: 16.7281
s_time: 0.000081        f_error: -0.7993        t_steps: 16.7988
s_time: 0.000081        f_error: -0.5274        t_steps: 16.8324
s_time: 0.000081        f_error: -0.3262        t_steps: 16.8415
s_time: 0.000081        f_error: -1.1586        t_steps: 16.8417
s_time: 0.000081        f_error: -1.0000        t_steps: 16.8323
s_time: 0.000081        f_error: -0.8418        t_steps: 16.7987
s_time: 0.000081        f_error: -0.6741        t_steps: 16.7280
s_time: 0.000081        f_error: -0.4728        t_steps: 16.6097
s_time: 0.000081        f_error: -1.2009        t_steps: 16.4359
s_time: 0.000081        f_error: -0.8106        t_steps: 16.2007
s_time: 0.000081        f_error: -1.2465        t_steps: 15.9003
s_time: 0.000081        f_error: -0.4471        t_steps: 15.5330
{"sr":{"posx":0.073,"vel":29.91,"stat":5}}
s_time: 0.000081        f_error: -0.3475        t_steps: 15.0987
s_time: 0.000081        f_error: -0.8805        t_steps: 14.5989
s_time: 0.000081        f_error: -0.9792        t_steps: 14.0366
s_time: 0.000081        f_error: -0.5780        t_steps: 13.4161
s_time: 0.000081        f_error: -0.6146        t_steps: 12.7428
s_time: 0.000081        f_error: -1.0307        t_steps: 12.0231
s_time: 0.000081        f_error: -0.7735        t_steps: 11.2645
s_time: 0.000081        f_error: -0.7966        t_steps: 10.4747
s_time: 0.000081        f_error: -1.0612        t_steps:  9.6624
s_time: 0.000081        f_error: -0.5359        t_steps:  8.8364
s_time: 0.000081        f_error: -1.1983        t_steps:  8.0057
s_time: 0.000081        f_error: -1.0347        t_steps:  7.1797
s_time: 0.000081        f_error: -1.0405        t_steps:  6.3674
s_time: 0.000081        f_error: -1.2202        t_steps:  5.5776
s_time: 0.000081        f_error: -0.5875        t_steps:  4.8189
s_time: 0.000081        f_error: -1.1651        t_steps:  4.0994
s_time: 0.000081        f_error: -0.9840        t_steps:  3.4260
s_time: 0.000081        f_error: -1.0834        t_steps:  2.8055
s_time: 0.000081        f_error: -0.5094        t_steps:  2.2432
s_time: 0.000081        f_error: -0.3149        t_steps:  1.7434
s_time: 0.000081        f_error: -0.5581        t_steps:  1.3091
s_time: 0.000081        f_error: -0.3015        t_steps:  0.9418
s_time: 0.000081        f_error: -0.6106        t_steps:  0.6414
s_time: 0.000081        f_error: -0.5524        t_steps:  0.4061
s_time: 0.000081        f_error: -1.1938        t_steps:  0.2324
s_time: 0.000081        f_error: -0.5999        t_steps:  0.1141
s_time: 0.000081        f_error: -0.8323        t_steps:  0.0433
s_time: 0.000081        f_error: -0.9464        t_steps:  0.0098
s_time: 0.000081        f_error: -0.9896        t_steps:  0.0007
{"sr":{"posx":0.100,"vel":0.00,"stat":3}}

As you can see the backlash compensation is completed in about the first 1/3 of the move.

@aldenhart
Copy link
Member

Nice work. Please report back your results as you have a chance to run this. Also, do you have a fork of the github repo where we can look at the code? lastly, some description of the machine and your settings would be useful. You have it set up for your case and I wonder what's involved in the more general case. Really cool.

@lllars
Copy link
Author

lllars commented Apr 1, 2015

Here is a fork. Be careful if you test it, I inverted the global enable pin to make it work with my set-up. Backlash can be set in MICROsteps using the $1bl, $2bl, etc variables. I haven't broken out the step_correction variables yet. Let me know if that would be useful.

My machine is a cnc router cobbled together from some surplus linear motion stages. Working envelope is 20"x12"x5" (X Y Z). Nema 23 stepper motors, ballscrews on X and Y, acme on Z. Stepper motor drivers are IM483. Spindle is a 2.25hp router. The Y-axis has preloaded bearings and ballnut, and thus has essentially zero backlash. The X-axis has neither, and as a result has ~0.006" of backlash. Photo here.

I've used the backlash code to cut a couple real parts now. The parts are similar to a gear and include holes for bearings to fit into. No complaints.

@johnlauer
Copy link

This looks awesome. I can't wait to try this. I think this is especially useful for timing belt-based machines.

@niklaspollonen
Copy link

Why wasn't this merged? Was there additional problems with it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants