BacklashCompensation

BacklashCompensation produces a position offset that follows the direction of travel, to take up the lost motion in a gearbox.
3.30–3.34

BacklashCompensation produces a position offset that follows the direction of travel, to take up the lost motion in a gearbox. When an axis reverses, the motor must cross the backlash gap before the load moves; this block adds the offset in the direction the axis is going, so the position loop pre-loads the correct flank of the gear.

The offset slews between its two values rather than stepping, so a reversal does not put a position discontinuity into the loop.

flowchart LR
    i1(["input — velocity, or any signed direction signal"]) --> B["BacklashCompensation"]
    i2(["disable"]) --> B
    B --> o1(["output — the position offset"])
    B --> o2(["isEnabled"])

The offset is +positionCorrection while the input is positive and -positionCorrection while it is negative. At standstill it holds the last direction, because the flank in contact does not change until the axis moves the other way. The transition takes at least $2 \times$ positionCorrection $/$ rateLimit seconds, and settles with a time constant of $1/$omega — 3.2 ms at the default.

Three parameters must be set before this block does anything. enable defaults to false, positionCorrection to 0 and rateLimit to 0, which freezes the output. All three are needed.

To switch compensation off, disable the block — do not zero positionCorrection. A zero correction freezes the offset where it is rather than removing it. See Limits and errors.

Signals

Inputs

Path Unit Range Description
input m/s or rad/s unbounded The direction signal. Inside an actuator loop this is the velocity target. Only its sign matters, after the dead zone. One element per channel.
disable - - True forces every output to zero immediately, without slewing. Use it for a runtime override; use enable for the configured intent.

Outputs

Path Unit Description
output m or rad The position offset, one element per channel. It starts at zero after a controller start, and is not cleared by a stop — so it holds a stale offset until the block runs again. Zero while disabled.
isEnabled - True when enable is true and disable is false. It does not mean the block is producing an offset — a zero positionCorrection or rateLimit leaves it inert.

Parameters

Path Unit Default Range Effect
enable - false - False forces every output to zero.
positionCorrection m or rad 0.0 per channel The offset magnitude — normally half the measured backlash. A zero value makes the block skip that channel entirely, freezing its output rather than clearing it. Not checked, so a large value is a large position shift.
rateLimit m/s or rad/s 0.0 above 0, per channel How fast the offset may slew. Zero freezes the output, which is the default. Set it before anything else.
deadzone m/s or rad/s 0.0 0 upward, per channel Input magnitudes below this count as standstill, so the offset holds. Raise it above the noise on your velocity signal.
omega rad/s 314 above 0, below 2/task period [s] How sharply the offset settles once it is within the rate limit. Shared by all channels, unlike the three above. Not checked.
returnToZero - false - False holds the last direction’s offset at standstill, which is physically correct. True returns the offset to zero at standstill.

All six are persistent and survive a restart. No parameters exist below this block.

Setup

  1. Measure the backlash: drive the axis one way until the load moves, reverse slowly, and record how far the motor turns before the load moves again. That distance is the gap.

  2. Set positionCorrection to half the gap, in motor units, for each channel.

  3. Set rateLimit to a slew rate the loop can absorb. Divide twice the correction by the time you are willing to spend crossing it — 0.1 s is a reasonable starting point.

    Step 3 is mandatory, not optional. rateLimit defaults to 0 and a zero slew rate freezes the output. Without it the block is silently inert, and isEnabled will still read true.

  4. Set deadzone above the noise on your velocity signal, so the offset does not chatter between its two values at standstill.

  5. Leave returnToZero false. Holding the last direction is correct: the gear flank in contact does not change until the axis moves the other way.

  6. Set enable true. Drive the axis one way and confirm output slews to +positionCorrection, then reverse and confirm it slews to the negative value.

  7. Watch the position loop’s error through a reversal, with and without the block. The error at reversal should be smaller with it.

Tuning

  1. Get the magnitude from the measurement in Setup step 1, not by feel. Too much offset is as bad as none — it pre-loads the wrong flank.
  2. Tune rateLimit against the position loop. Too fast and the offset itself is a disturbance the loop must reject; too slow and the compensation arrives after the reversal is over.
  3. Time the reversal: twice the correction divided by the rate limit. Compare that against how long your axis actually spends reversing.
  4. Raise deadzone until the offset stops chattering at standstill, and no further. A large dead zone delays the compensation at low speed, where backlash matters most.
  5. Leave omega alone unless the settle at the end of the slew is visibly slow. It only shapes the last part of the transition. Keep it below 2 divided by the task period.
  6. Re-check omega after a task-rate change — its safe ceiling scales with the task period.
  7. If the axis has different backlash in each direction, this block cannot represent it: it applies the same magnitude both ways. Compensate the average.

Offset through a direction reversal at three rate limits, with a correctionof 0.5: rate 20 snaps across almost at once, rate 5 takes about 0.2 s, and rate2 takes about 0.5 s.

Read the crossing time off any curve as the span between the two flat levels.

Symptom Cause Action
output stays at zero with isEnabled true rateLimit is 0, or positionCorrection is 0 Set both; rateLimit defaults to 0 and freezes the output
output froze at a value and will not change positionCorrection was set back to 0, which skips the channel instead of clearing it Disable the block to clear the offset
The offset did not return to zero when compensation was switched off Same cause Use enable false, not positionCorrection = 0
The offset chatters between its two values at standstill deadzone below the noise on the velocity signal Raise deadzone
The offset swings at standstill even with a dead zone set returnToZero is true Set it false — holding the last direction is correct
The position jumped when the block was disabled Expected: disabling steps the output to zero without slewing Disable only at standstill, or accept the step
The position jumped after a controller restart Expected: the offset is not cleared by a stop and holds a stale value Disable and re-enable once at start
The position loop fights the offset rateLimit too high, so the offset itself is a disturbance Lower rateLimit
Compensation arrives too late at reversal rateLimit too low, or deadzone too large Raise the rate limit; lower the dead zone
The error at reversal got worse, not better Too much correction, so the wrong flank is pre-loaded Halve positionCorrection and re-measure the backlash
The offset oscillates or grows during the transition omega at or above 2 divided by the task period Lower omega
One channel adopted another channel’s direction A stationary channel can pick up a moving channel’s offset sign when returnToZero is false Use one instance per channel, or set returnToZero true
Backlash differs by direction and the block cannot match it The magnitude is the same both ways by construction Compensate the average
A non-numeric value appeared on output A non-numeric velocity reached input Disable the block for a cycle to clear it, then fix the source

A starting point for an axis with 1 mm of measured backlash on a 1 ms task, crossing in about 0.1 s:

enable             = true
positionCorrection = 0.0005
rateLimit          = 0.01
deadzone           = 0.001
omega              = 314.0
returnToZero       = false

This is a starting point, not a final tuning. Work Setup step 1 with your own axis.

Limits and errors

Limit Set by What happens Reported
rateLimit above 0 Nothing Not checked, and it defaults to 0. A zero slew rate freezes the output permanently while isEnabled still reads true Not reported
positionCorrection non-zero Nothing A zero value makes the block skip that channel, so its output holds its last value instead of returning to zero Not reported
positionCorrection magnitude Nothing Not checked. The offset shifts both the measured position and the position target, so a large value is a large shift Not reported
omega below 2/task period [s] Nothing Not checked. At or above the bound the transition oscillates or grows instead of settling. 2000 on a 1 ms task Not reported
output Nothing Bounded in practice by positionCorrection, but nothing enforces that Not reported
Disabling Fixed Steps the output to zero without slewing, unlike a direction reversal Not reported
Offset across a stop Nothing Not cleared at start. There is no reset input — disabling is the only way to clear it Not reported
Non-numeric input Nothing Not guarded. A disabled cycle clears the result Not reported
Channel independence Partial The held direction at standstill is shared between channels, so on a multi-channel instance a stationary channel can adopt a moving one’s sign. Use one instance per channel where this matters Not reported
Channel count Machine configuration Fixed once the controller starts Not reported

The block raises no errors or warnings and logs nothing. Every failure above shows as a value on a trace, not as a message.


Verified against motorcortex-control3 3.30.0 (bc348fd).