BacklashCompensation
BacklashCompensation produces a position offset that follows the direction of travel, to take up the lost motion in a gearbox.8 minute read
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
+positionCorrectionwhile the input is positive and-positionCorrectionwhile 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$/$rateLimitseconds, 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
-
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.
-
Set
positionCorrectionto half the gap, in motor units, for each channel. -
Set
rateLimitto 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.
rateLimitdefaults to 0 and a zero slew rate freezes the output. Without it the block is silently inert, andisEnabledwill still read true. -
Set
deadzoneabove the noise on your velocity signal, so the offset does not chatter between its two values at standstill. -
Leave
returnToZerofalse. Holding the last direction is correct: the gear flank in contact does not change until the axis moves the other way. -
Set
enabletrue. Drive the axis one way and confirmoutputslews to+positionCorrection, then reverse and confirm it slews to the negative value. -
Watch the position loop’s error through a reversal, with and without the block. The error at reversal should be smaller with it.
Tuning
- 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.
- Tune
rateLimitagainst 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. - Time the reversal: twice the correction divided by the rate limit. Compare that against how long your axis actually spends reversing.
- Raise
deadzoneuntil the offset stops chattering at standstill, and no further. A large dead zone delays the compensation at low speed, where backlash matters most. - Leave
omegaalone 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. - Re-check
omegaafter a task-rate change — its safe ceiling scales with the task period. - If the axis has different backlash in each direction, this block cannot represent it: it applies the same magnitude both ways. Compensate the average.
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).