PvajLimiter
PvajLimiter bounds a position setpoint in jerk, acceleration, velocity and position at the same time, and guarantees the position limits are never crossed.26 minute read
PvajLimiter bounds a position setpoint in jerk, acceleration, velocity and
position at the same time, and guarantees the position limits are never
crossed. The output satisfies positionLowerLimit ≤ output ≤
positionUpperLimit exactly — not to within a margin, because every cycle the
block asks “if I apply this jerk, can the setpoint still be brought to rest
inside the box?” before it applies it.
It replaces PVALimiter for new work. That block tracks with
a filter and clamps acceleration; this one plans a trajectory and bounds jerk
as well, so motion starts and stops without the corners that shake a machine.
It also runs as a velocity limiter driven by inputDot. Even then it
brings the axis to rest on a position limit rather than through it.
flowchart LR
i1(["input — position setpoint"]) --> B["PvajLimiter"]
i2(["inputDot — setpoint velocity, or the velocity command"]) --> B
i3(["inputDDot — setpoint acceleration"]) --> B
i4(["disable"]) --> B
p1(["Limits0N/positionLowerLimit, positionUpperLimit"]) --> B
p2(["Limits0N/velocityLimit, accelerationLimit, jerkLimit"]) --> B
p3(["Limits0N/maxAccelerationBraking, maxJerkBraking"]) --> B
p4(["Limits0N/enableInput, enableInputDot, enableInputDDot"]) --> B
B --> o1(["output — bounded position"])
B --> o2(["outputDot — velocity"])
B --> o3(["outputDDot — acceleration"])
B --> o4(["outputDDDot — jerk applied this cycle"])
B --> o5(["isEnabled"])
B --> o6(["Limits0N/isLimiting and the four limit flags"])
It runs in one of two modes, chosen per channel by enableInput:
| Mode | enableInput |
enableInputDot |
What is commanded | What you read |
|---|---|---|---|---|
| PVAJ (default) | 1 |
optional — 1 only if inputDot really carries the setpoint velocity |
input is a position setpoint |
output, outputDot, outputDDot, outputDDDot |
| VAJ | 0 |
1 — it is the switch that says inputDot carries a velocity; with it 0 the leaf is not read and the command is zero |
inputDot is a velocity command; input is not read at all |
outputDot, outputDDot, outputDDDot |
Both modes shape the motion with the same J, A and V limits. §7 and §8 give a complete configuration for each.
enableInputDot means one thing in both modes: the inputDot leaf carries a real velocity, read it. Off, the
leaf is never read. In PVAJ mode the block then estimates the setpoint velocity by differencing input; in VAJ mode
there is no input to difference, so the command is zero and the axis is brought to rest. The three switches that
shape the position tracking lead — enableInputDDot, feedforwardGain and suppressInputDDotEstimation — are
optional in PVAJ mode and inert in VAJ mode.
A full stop from
velocityLimitneeds $V^2/2A_b + V A_b/2J_b$ of travel, with $A_b$ and $J_b$ the braking authority. If twice that does not fit between your position limits, states exist inside the box from which the setpoint can no longer be stopped in time — the block tells you so throughwarningSettingsMayCausePositionLimitOvershootrather than refusing the settings.
Every parameter can be changed while the machine is running, and the change takes effect on the very next cycle. Nothing is refused: a value that is not usable as a limit is fixed into one and written back to the leaf, so there is one thing to look at — the limit set — and no status code to cross-check it against (§3).
This block bounds the setpoint, not the machine. The axis follows through a servo loop with a following error, so the mechanism will pass the limit by roughly that error. Back your limits off by the certified worst case.
Everything is per channel: an N-channel instance publishes Limits01..LimitsNN, and a six-axis stage can run
channel 1 in PVAJ mode and channel 2 in VAJ mode. Every parameter named below lives under the channel’s folder —
for channel 1 of an instance called pvajLimiter, positionUpperLimit is
root/Control/pvajLimiter/Limits01/positionUpperLimit. The examples use channel 1.
1. Signals
input, inputDot, inputDDot, output, outputDot, outputDDot and outputDDDot are N-element arrays at
the top of the module. enable, disable and isEnabled are single leaves that apply to the whole stage.
Inputs
| Node | Type | Description |
|---|---|---|
input |
double[N] |
Position setpoint. Ignored entirely while enableInput is 0. A non-finite value is held, not followed. |
inputDot |
double[N] |
Read only while enableInputDot is 1. In PVAJ mode it is then the setpoint velocity, for the tracking lead; in VAJ mode it is the commanded velocity. With enableInputDot 0 the leaf is ignored in both modes. |
inputDDot |
double[N] |
The setpoint acceleration, used only if enableInputDDot says it is real. Unused in VAJ mode. |
disable |
bool |
Stage-wide. While set, every channel passes its input through and limits nothing. |
Outputs
| Node | Type | Description |
|---|---|---|
output |
double[N] |
Limited position. Never outside the box. Reads zero in VAJ mode unless suppressPositionOutput is cleared. |
outputDot |
double[N] |
Limited velocity. |
outputDDot |
double[N] |
Limited acceleration. |
outputDDDot |
double[N] |
The jerk applied this cycle. |
isEnabled |
bool |
enable && !disable. Stage-wide; per-channel enables do not affect it. |
Per-channel status, Limits0N/
| Node | Description |
|---|---|
isLimiting |
One of the four limits clamped the output this cycle. Converging on the setpoint is not limiting. |
positionLimitActive |
The position limiter is shaping this cycle. |
velocityLimitActive |
The velocity limit is binding. |
accelerationLimitActive |
The acceleration limit is binding. |
jerkLimitActive |
The jerk limit is binding. |
softEntryActive |
The state is outside a limit and only motion that makes the violation worse is forbidden (§4). Always false while disabled. |
warningSettingsMayCausePositionLimitOvershoot |
The limit set cannot guarantee the box: a stop from velocityLimit does not fit in half of it. Fix the settings (§3). |
warningNotAbleToStopBeforeLimit |
The current state can no longer be stopped inside the box — the guarantee is already lost. Only reachable by seeding a bad state or narrowing the limits under a moving setpoint. |
Per-channel diagnostics, Limits0N/debug/
Indicative only; nothing here feeds the control law.
| Node | Description |
|---|---|
trackingError |
input - output. Zero while disabled and in VAJ mode. |
trackingErrorDot |
The setpoint velocity in use minus outputDot: inputDot where enableInputDot is set, the estimate differenced from input otherwise (zero in VAJ mode without enableInputDot). This is the tracking error in VAJ mode. |
trackingErrorDDot |
The setpoint acceleration in use minus outputDDot: inputDDot where enableInputDDot is set, the estimate otherwise. |
predictedStopPosition |
Where a full brake from the current state would come to rest, as an absolute position. Plot it next to output and the limits to see the braking margin. |
State0N/
The same facts as the flags above, in the shape GainSchedulingPVALimiter published them, so existing dashboards
keep resolving. Prefer the Limits0N/ flags in anything new.
| Node | Equals |
|---|---|
active |
isLimiting |
positionUpperLimitActive |
positionLimitActive, while the setpoint is in the upper half of the box |
positionLowerLimitActive |
positionLimitActive, while it is in the lower half |
velocityUpperLimitActive |
velocityLimitActive, while outputDot is positive |
velocityLowerLimitActive |
velocityLimitActive, while outputDot is negative |
accelerationUpperLimitActive |
accelerationLimitActive, while outputDDot is positive |
accelerationLowerLimitActive |
accelerationLimitActive, while outputDDot is negative |
There is no jerk counterpart — GainSchedulingPVALimiter had no jerk limit.
2. Parameters
All under Limits0N/, all writable while the loop runs. The names are the same on the tree and in C++.
The limit set
| Node | Default | Description |
|---|---|---|
positionLowerLimit |
-1.0 |
Lower travel limit. |
positionUpperLimit |
1.0 |
Upper travel limit. |
velocityLimit |
1.0 |
Maximum speed, symmetric. |
accelerationLimit |
10.0 |
Maximum acceleration, symmetric. |
jerkLimit |
100.0 |
Maximum jerk, symmetric. |
maxAccelerationBraking |
0.0 |
Elevated acceleration reserved for defending the travel limits. 0 inherits accelerationLimit. |
maxJerkBraking |
0.0 |
Elevated jerk for the same purpose. 0 inherits jerkLimit. |
The block never assumes a unit: whatever input is in, velocityLimit is that per second, accelerationLimit
per second squared and jerkLimit per second cubed. Linear or rotary, the arithmetic is the same.
Mode and behaviour switches
| Node | Default | Description |
|---|---|---|
enable |
1 |
This channel’s own enable, ANDed with the stage-wide enable/disable. Set 0 to hold one axis out while the rest of the stage runs. |
enableInput |
1 |
1 = PVAJ mode, 0 = VAJ mode. |
suppressPositionOutput |
1 |
In VAJ mode, publish output as zero instead of the position the block integrates along the way. No effect in PVAJ mode. |
enablePositionLimit |
1 |
Enforce the travel limits. 0 bypasses them outright — the bounds become infinite and no position action is taken. |
enableInputDot |
0 |
inputDot carries a real velocity — read it. PVAJ mode: optional, with it off the block differences input instead. VAJ mode: required — with it off there is no velocity input and nothing to difference, so the command is zero. |
enableInputDDot |
0 |
inputDDot carries the real setpoint acceleration. Optional, PVAJ mode only, and independent of enableInputDot. Do not set it on an unconnected leaf — that declares the acceleration to be exactly zero and the block believes it. |
suppressInputDDotEstimation |
0 |
Take the setpoint acceleration as zero instead of estimating it from the input’s history. Set it for one reason only: a measured input whose noise the estimate would amplify. |
feedforwardGain |
0.0 |
How much of the braking-distance lead the tracking target is shifted by, 0..1. 1 tracks accurately; 0 keeps the setpoint inside the commanded amplitude. Values outside the range are clamped and written back. |
enableSoftEntry |
1 |
On enabling with the setpoint outside a limit, ratchet that limit to the state instead of driving the setpoint back inside at once (§4). |
enableMaxBrakingAtLimits |
0 |
Plan every limit approach with the braking authority, not just recovery: braking starts later and is harder. Does nothing while the braking values inherit the ordinary ones. |
autoAdjustBraking |
0 |
Raise maxAccelerationBraking / maxJerkBraking automatically until a stop from velocityLimit fits inside the box, and write the result back. See the warning in §3. |
3. Configuring the limits
Nothing is refused. A value that is not usable as a limit is fixed into one, so there is one thing to look at — the limit set — and no status leaf to cross-check it against:
| You write | In force |
|---|---|
velocityLimit or accelerationLimit = 0, negative, or non-finite |
no limit (infinite) |
jerkLimit = 0, negative, or non-finite |
no jerk limit: 1e9 stands in, because the jerk is the block’s control input and an infinite value is not-a-number in its arithmetic |
maxAccelerationBraking / maxJerkBraking = 0 or below the ordinary limit |
inherits or is raised to the ordinary limit, and written back to the leaf |
positionLowerLimit > positionUpperLimit |
swapped |
| the two position limits equal, or either non-finite | no box at all, even with enablePositionLimit set |
A small but positive value is taken literally: velocityLimit = 1e-9 is a very slow axis, not a mistake the block
can recognise — it will freeze the setpoint. 0 is what means “no limit”.
Edits take effect on the next cycle. Start-up fails only if the task period is unknown.
Check warningSettingsMayCausePositionLimitOvershoot after configuring. It is true when a stop from
velocityLimit does not fit in half the box, which means states exist inside the box from which the setpoint could
no longer be stopped in time. Three ways out: lower velocityLimit, raise accelerationLimit / jerkLimit, or
reserve maxAccelerationBraking / maxJerkBraking explicitly. autoAdjustBraking does the last one for you —
but it commands whatever authority the arithmetic demands, which may exceed what the drive can deliver, and
authority the machine does not have is authority the guarantee does not have either. Read the two braking leaves
back afterwards and check them against the worst-case deliverable figures.
4. Enabling with the setpoint outside a limit (soft entry)
This is about the moment you enable. A disabled block passes input straight through, so if the input sits
outside the travel limits when you enable, the limiter starts from a state that is already outside the box. Soft
entry, on by default (enableSoftEntry), decides what happens next.
Without it, the limiter treats the box as gospel and drives the setpoint back inside immediately, at the braking authority — the setpoint snaps to the limit whether the input wants that or not.
With it, the violated limit is ratcheted to the state: the box in force is widened just enough to contain where
the setpoint already is, so the only motion that remains forbidden is motion that makes the violation worse. Two
cases follow, measured on a ±1.0 box with the input parked at 1.5 at the moment of enabling:
| The input then | What output does |
|---|---|
moves further from the limit (1.5 → 3.5) |
held where it was, at 1.500 — the setpoint does not follow the input further out |
| moves back toward the limit | follows the input inward, and entered the box as the input did (at 0.944 in the measurement) |
The ratchet re-tightens every cycle against the new position, so the setpoint can approach the limit but never
retreat from it again. The moment the state is inside, softEntryActive clears and the real box applies in full.
While the state is outside, the velocity and acceleration limits are relaxed to whatever the state already carries, for the same reason: an inherited over-speed is not required to vanish instantly, only not to grow. Nothing about normal operation depends on any of this — every effective value equals the configured one whenever the state is inside the box.
The consequence to plan for: a setpoint outside the box stays outside while the input asks it to. Getting
back inside is the caller’s business — command the input back — and the setpoint comes with it. softEntryActive
tells you it is happening. Clear enableSoftEntry if you would rather the limiter recover by itself, accepting that
the setpoint then moves against the input.
Enabling is not the only way in: resetState() can seed a state outside the box, and narrowing the travel limits
underneath a moving setpoint leaves it outside. Soft entry handles all three the same way.
5. Enabling with a noisy input
§4 covered enabling with the setpoint outside the box. This one is about the derivatives it inherits.
While the limiter is disabled — the stage-wide enable cleared, disable set, or the channel’s own enable
cleared — input passes straight through to output and the internal state is slaved to it, so enabling is
bumpless in position. The derivatives it hands over come from differencing that input: outputDot is the first
difference of input unless enableInputDot supplies a real velocity, and the acceleration and jerk are further
differences.
On the first enabled cycle the block zeroes the inherited acceleration and jerk, and keeps the inherited
velocity. Zeroing the acceleration is essential: differencing multiplies input noise by 1/dt², so noise of
1e-4 on the input at a 1 ms cycle arrives as an acceleration of 100 — ten times a typical
accelerationLimit — and unwinding that takes |a| / jerkLimit seconds with the velocity integrating the whole
way. Measured at jerkLimit = 100, accelerationLimit = 10, velocityLimit = 1 with that much noise, the output
flew 625 away from the input before returning; with the acceleration zeroed the same case peaks at 0.056. The velocity is kept because it is mostly the
input’s real velocity, and zeroing it would be a sudden stop when the limiter is enabled under a moving input.
That is why a noisy input still costs you something at enable. The velocity handed over is noise / dt — 0.1
for noise of 1e-4 at a 1 ms cycle — and it is real as far as the limiter is concerned: the setpoint sets off at
that speed and
then has to be brought back within accelerationLimit and jerkLimit, which moves the position by roughly
v²/(2·accelerationLimit) before it settles. On a path, that is a deviation from the path.
So, for a clean hand-over:
- Give the limiter a smooth input. Filter a measured setpoint upstream rather than feeding raw encoder or
network-quantised values into
input. - Better still, supply the velocity. Connect a filtered velocity to
inputDotand setenableInputDot: no differencing happens at all, so there is no noise to inherit. Do the same forinputDDotwithenableInputDDotif the filter provides it. - Enable while the input is settled where the application allows it. With the input at rest there is no velocity to inherit, noisy or not.
- If the input is measured and cannot be filtered, set
suppressInputDDotEstimationas well (§2), so the tracking lead does not differentiate that noise a second time.
Disabling is unconditional and immediate: the channel stops limiting that cycle and goes back to passing input
through. Note that a disabled block bounds nothing, so a command beyond velocityLimit reaches the limiter
unbounded and has to be braked back once it takes over — bring the command inside the envelope before re-enabling
if that matters.
6. Tuning the tracking accuracy
The switches below change only how accurately the limited output follows an unconstrained input. None of them can affect the position guarantee — they all live in the tracking proposal, which is clamped into the box before it is applied.
enableInputDot / enableInputDDot
Measured settled error on a 0.2 amplitude, 1 Hz sine, with suppressInputDDotEstimation set so that each row shows what the two switches alone buy. Left at its default the estimate fills the acceleration in, and the first two rows collapse to the accuracy of the last two:
enableInputDot |
enableInputDDot |
Setpoint velocity | Setpoint acceleration | Error |
|---|---|---|---|---|
false |
false |
first difference of input |
zero when suppressInputDDotEstimation is set, otherwise estimated |
1.92e-2 |
true |
false |
inputDot |
zero when suppressed, otherwise estimated | 1.81e-2 |
false |
true |
first difference of input, advanced by a·dt/2 |
inputDDot |
< 1e-6 |
true |
true |
inputDot |
inputDDot |
8.0e-8 |
The last two rows are worth five orders of magnitude because the tracking lead is how far the input travels before it could come to rest — leaving its acceleration out pretends the input is coasting.
The two switches are independent claims about two separate leaves. Each says “this one carries the real value”, and each is honoured on its own: a source that publishes position and acceleration but no velocity is a usable combination (third row), where the differenced velocity is lined up with the supplied acceleration by half a step. enableInputDDot also wins over suppressInputDDotEstimation — an explicit claim is not second-guessed — which is the trap to know about: set it on an unconnected leaf and you have declared the acceleration to be exactly zero. The limiter then believes you and locks the setpoint’s acceleration to zero, which tracks the position perfectly on a moving input while publishing an outputDDot of zero. Leave both off if you only have a position.
It needs an exact value. An acceleration differenced from an input carrying 1e-3 of noise measured 6.53e-2, three times worse than leaving it off. Nothing is estimated here unless the estimate is left on (below), and the switch defaults to off for that reason: turn it on if the value is real, leave it off if it is estimated, unconnected or unknown.
feedforwardGain
The feedforward shifts the tracking target ahead by the braking distance at the setpoint’s own speed, cancelling
the lag that “always be able to stop on the setpoint” would otherwise cost. feedforwardGain is how much of that
lead is applied, and it is the feedforward’s only control: 0 is off, 1 is the full lead. The two ends are far
apart, which is what makes the value in between worth tuning. Measured on a 0.5 amplitude sine at jerkLimit = 1745, accelerationLimit = 30,
velocityLimit = 3.14, position input only:
| gain | 0.5 Hz: envelope excess / error / lag | 1.0 Hz |
|---|---|---|
| 0.00 (= off) | −1.4e-5 / 5.4e-2 / 31 ms | −3.7e-4 / 1.67e-1 / 51 ms |
| 0.50 | +8.6e-7 / 2.7e-2 / 15 ms | −1.5e-4 / 8.6e-2 / 26 ms |
| 0.75 | +1.6e-5 / 1.4e-2 / 8 ms | +4.7e-4 / 4.8e-2 / 14 ms |
| 1.00 (= on) | +4.6e-5 / 2.4e-3 / 0 ms | +2.0e-3 / 2.1e-2 / 5 ms |
Monotone and smooth throughout: roughly 7–12 ms of lag and a factor of 2–5 in overshoot per 0.25 of gain.
The landmark worth tuning to is where the envelope excess changes sign — around 0.5 at 0.5 Hz and
0.6–0.7 at 1 Hz here. Just below it the output never exceeds what was commanded while most of the lag
reduction is still there. It moves with frequency, so it is an application tune, not a constant.
Prefer the derivatives where you have them. The overshoot exists because the lead is computed as how far the
input travels before it could come to rest, which assumes the input is coasting when only input is available.
A sine is decelerating into its peak, so the lead is overestimated exactly there. Supplying exact inputDot and
inputDDot removes the cause instead of trading against it, and beats any gain below 1: the same measurement
gives 1.3e-7 of tracking error, no lag, and zero envelope excess. Reach for the gain when only input is real.
The gain is bounded to [0, 1]. 0 is no lead, 1 is the full lead, and there is nothing useful past it:
over-leading was measured — at gain 2, 5.0e-2 excess against 9.0e-2 error; by gain 5 the target is permanently
clamped to the position limit, so the setpoint just parks at the box edge and stops following at all. Out-of-range
values are clamped, not refused, so a bad config value leaves the limiter running on the nearest usable gain
rather than on nothing. The clamp is applied by the setters and again at the top of every cycle — a control.xml
or parameter-server write lands straight on the leaf without passing a setter — and the clamped value is written
back, so feedforwardGain reads what is in force rather than what was asked for. A non-finite value becomes 0.
No gain can touch the position guarantee — the bound is a convenience, not the safety argument. Verified to gain 100 before the bound existed: the position never left the box by any amount, and the emitted motion gained no oscillation. The lead moves the tracking target only, and the target is clamped into the box before the braking-set test runs.
Gain 0: the feedforward off
feedforwardGain = 0 is the feedforward switched off outright — no lead is computed and the tracking target is the input itself. It costs a factor of 38 in tracking accuracy and buys exactly one thing: the output then stays inside the commanded amplitude envelope instead of leading it by the lead distance. On a 0.5 amplitude, 0.2 Hz sine, tracking error is 2.5e-2 off against 6.5e-4 at the full lead; envelope excess is −1.2e-6 off against +4.3e-6. That is the only reason the off end exists — for machines where the setpoint must not exceed what was commanded, quite apart from the position limits.
With enableInputDot and enableInputDDot both set the trade-off evaporates: 1.1e-8 of tracking error and −1.1e-12 of envelope excess. Better on both counts, so leave the gain at 1.0 there.
suppressInputDDotEstimation: the input’s acceleration, and the reversal transient without it
Set it for one reason only: to suppress noise on the input. By default the acceleration is estimated; setting
this takes it as zero instead. The estimate is a second difference, so input noise reaches the lead multiplied by
1/dt² — and above roughly 1e-7 of jitter at a 1 ms cycle, taking the acceleration as zero is the better guess. Everywhere
else — any computed input, and any input whose derivatives you supply — leave it clear. If the input is measured
and you can filter it, do that and supply inputDot / inputDDot instead: that beats both settings at every noise
level.
With only input connected and the acceleration taken as zero, the lead is computed as if the input were
coasting. That is exact on a ramp, but it grows as the square root of the distance-to-stop, so its curvature is
unbounded where the input’s velocity passes through zero. The setpoint answers with full jerk both ways, and
outputDDot shows a spike at every velocity reversal — on a 0.0975 amplitude, 1 rad/s sine at
jerkLimit = accelerationLimit = 10 π, the target’s acceleration one millisecond before the reversal reaches
0.127, more than the sine’s own 0.0975. With the gain at 0 the same
reversal shows a longer, lower bump instead: the output then has to arrive at every instantaneous input position
able to stop there, so it over-brakes into a reversal and re-accelerates only as the input opens a gap again.
By default the limiter supplies the missing acceleration from the input’s own history: the acceleration is the
second backward difference of input, and the velocity is the first difference advanced by half a step
(a · dt / 2), because a plain backward difference is the velocity at k − ½ and that half-step error is larger
than the velocity itself in the last millisecond before a reversal. When enableInputDot is set and
enableInputDDot is not, the acceleration is the difference of inputDot instead. Measured on the sine above,
gain 1:
| derivative source | peak |aOut − aIn| | cycles at ±J per five periods | peak error |
|---|---|---|---|
| none | 0.179 (184 % of aIn) | 118 | 1.6e-4 |
| backward differences, not aligned | 0.056 (58 %) | 21 | 2.5e-6 |
| estimated (the default) | 0.000 | 0 | 0 (locked) |
exact inputDot + inputDDot |
0.000 | 0 | 0 (locked) |
The zero errors are the rest window locking onto the reference state once the derivatives are known; before it did, the estimator measured 3.0e-7 and exact derivatives 1.6e-9.
It is for computed inputs — a trajectory generator, an interpolated path, a kinematics output — not for
measured ones. Differencing amplifies noise by 1/dt and 1/dt²: on a position carrying 1e-8 of noise at a 1 ms cycle
the acceleration estimate is already 10 of noise against 0.1 of signal and the jerk sits on its limit
most of the time. (From 1e-6 of noise upwards even exact derivatives no longer help at this jerk limit, because the
position target itself jitters by e/dt³ ≈ 1000 against jerkLimit = 31 — the limiter is then smoothing a noisy
setpoint, and the reversal is not the problem.) For a sensor-driven input, filter first and hand over the filter’s
derivatives with enableInputDot / enableInputDDot. Off by default.
It does not change what the limits do at a step or a corner. A block wave is shaped by J, A and V alone and tracks
identically with the estimator on or off; a triangle wave tracks identically to the exact-derivative case. Note
that the lead itself overshoots a triangle’s corner by the braking distance at the ramp velocity — 0.058 at a
ramp velocity of 0.5 with jerkLimit = accelerationLimit = 10 π, on and off the estimator alike and with exact derivatives too — because a lead is a
prediction that the input keeps its velocity, and at a non-differentiable corner that prediction is wrong by exactly
that distance. Use gain 0 where a setpoint may never exceed what was commanded.
7. Example configuration — PVAJ mode (position)
An axis driven by a trajectory generator that publishes a position only. Accurate tracking wanted, the travel limits guaranteed at an authority the drive can actually deliver. The limit values here are the block’s defaults, so on a fresh instance only the last three rows are edits:
| Parameter | Set to | Why |
|---|---|---|
positionLowerLimit |
-1.0 |
the travel, backed off by the worst-case servo following error (default) |
positionUpperLimit |
1.0 |
" (default) |
velocityLimit |
1.0 |
what the mechanism can deliver (default) |
accelerationLimit |
10.0 |
" (default) |
jerkLimit |
100.0 |
" (default) |
maxAccelerationBraking |
15.0 |
authority reserved for defending the travel, at worst-case deliverable figures — 0.0 by default, which inherits accelerationLimit |
maxJerkBraking |
1500.0 |
" — 0.0 by default, which inherits jerkLimit |
feedforwardGain |
1.0 |
accurate tracking — 0.0 by default, which is the feedforward off |
enableInput and enablePositionLimit are already 1, which is PVAJ mode with the travel enforced, so they need
no edit. Everything else can stay at its default too.
Connect input to the generator’s position output. enableInputDot and enableInputDDot are optional: leave them at 0 unless
the generator really publishes a velocity and an acceleration on those leaves. With them off the block differences
the input for the velocity and estimates the acceleration from its history, which is what keeps the lead accurate
through a velocity reversal — so a position-only setpoint needs no extra configuration.
What to expect: output follows input closely and never leaves ±1.0; trackingError is the lag;
isLimiting is false while merely converging on a new setpoint and true when J, A, V or the box actually binds;
warningSettingsMayCausePositionLimitOvershoot reads false with these numbers — a stop from 1.0 at 15.0
needs 0.038 of travel, against the 1.0 of half-box available.
Two adjustments worth knowing:
feedforwardGain1lets the setpoint exceed the commanded amplitude slightly on a curved input. Set it to0where that must never happen, at the cost of tracking lag.- If the generator publishes exact derivatives, set
enableInputDotandenableInputDDot— that is more accurate still than any estimate.
8. Example configuration — VAJ mode (velocity)
A conveyor driven by a velocity command, with no end stops. Only J, A and V are to be enforced; the position is meaningless and must not be published. The envelope is again left at the block’s defaults, so the three mode switches are the only edits:
| Parameter | Set to | Why |
|---|---|---|
enableInput |
0 |
VAJ mode: input is not read at all, inputDot is the commanded velocity |
enableInputDot |
1 |
says the inputDot leaf carries that velocity — off, the leaf is not read and the command is zero |
enablePositionLimit |
0 |
no travel limits on this axis — and with the position output suppressed there is no published position for a limit to mean anything against |
suppressPositionOutput |
1 |
output reads zero rather than a position nobody commanded (default) |
velocityLimit |
1.0 |
the envelope the command is shaped into (default) |
accelerationLimit |
10.0 |
" (default) |
jerkLimit |
100.0 |
" (default) |
Everything else can stay at its default.
Connect the velocity command to inputDot. Leave input unconnected — it is not read, and it may hold anything.
enableInputDot must be 1 here. It is the one switch that says the inputDot leaf carries a velocity; with it
0 the leaf is not read in either mode, and in this mode there is no input to estimate a velocity from, so the
command is zero and the axis comes to rest. In PVAJ mode the same switch declares that the leaf carries a real
setpoint velocity for the tracking lead. enableInputDDot, feedforwardGain and suppressInputDDotEstimation,
by contrast, change nothing here: they shape the position tracking lead, which this mode does not have.
What to expect: outputDot is brought onto the command within accelerationLimit and jerkLimit without
overshooting it, and is clipped to velocityLimit; outputDot, outputDDot and outputDDDot are a consistent
triple; output reads 0; trackingErrorDot is the tracking error to watch. A command beyond velocityLimit is
clipped, not refused, and a non-finite command is held rather than followed.
suppressPositionOutput is only about the published leaf — the block keeps integrating the position internally
either way, and the trajectory is identical. Clear it if you want to see how far the axis has travelled; with no
box in force that number is unbounded and drifts as far as the commanded motion takes it.
With end stops instead, set enablePositionLimit to 1 and give the travel: the box is then defended on the
integrated position while the command is still followed in velocity, which is the reason to use this block rather
than a rate limiter. Note that suppressing the output does not switch the box off — the two are independent, so
with both set the box is enforced on a position output does not show. Clear suppressPositionOutput when you
need to see that position, or read it in C++ with getIntegratedPosition().
9. Diagnosing
| Symptom | Likely cause |
|---|---|
| Output frozen after a limit edit | A tiny positive J, A or V taken literally (§3). 0 would have meant no limit. |
| Output ignores the travel limits | The two position limits are equal, or one is non-finite, which is no box at all (§3). |
warningSettingsMayCausePositionLimitOvershoot true |
A stop from velocityLimit does not fit in half the box. §3. |
warningNotAbleToStopBeforeLimit true |
The limits were narrowed under a moving setpoint, or a bad state was seeded. The guarantee is already lost; debug/predictedStopPosition says how far past the limit the setpoint will go. |
| Setpoint sits outside the box and stays there | Soft entry doing its job — softEntryActive is true, and the input is still asking for it. §4. |
| Output lags badly | feedforwardGain is 0, or enableInputDot is off on a generator that publishes a real velocity. |
outputDDot spikes at every velocity reversal of the input |
suppressInputDDotEstimation is set, so the lead assumes the input coasts. Clear it, or supply inputDDot. |
outputDDot reads zero on a moving axis |
enableInputDDot is set on an unconnected inputDDot, which declares the acceleration to be zero. |
output reads zero in VAJ mode |
suppressPositionOutput, by design (§8). |
Stops at a limit harsher than accelerationLimit |
enableMaxBrakingAtLimits is set, so approaches are planned at the braking authority. |
| Deviation from the path just after enabling | A noisy input: the velocity inherited from differencing it is real to the limiter, and removing it moves the setpoint. Filter the input, or supply inputDot. §5. |
| Module refuses to start | The task period is unknown at start-up. |