Limiter
Limiter clamps a signal between a lower and an upper bound.7 minute read
Limiter clamps a signal between a lower and an upper bound. It is the block
that sits between a controller and an actuator that cannot take more than a
certain torque, and it is the simplest protection in the library.
An adaptive variant is also available. It starts from wherever the signal already is and closes in on the configured bounds only as the signal allows — so switching it on around a signal that is already outside its band does not produce a step. A plain limiter in the same situation clamps at once.
flowchart LR
i1(["input — signal to clamp"]) --> B["Limiter"]
i2(["disable"]) --> B
B --> o1(["output — clamped signal"])
B --> o2(["isLimiting — per channel"])
B --> o3(["isEnabled"])
The adaptive variant adds two outputs:
flowchart LR
i1(["input"]) --> B["AdaptiveLimiter"]
i2(["disable"]) --> B
B --> o1(["output"])
B --> o2(["isLimiting"])
B --> o3(["isEnabled"])
B --> o4(["activeLowerLimit — the bound in use now"])
B --> o5(["activeUpperLimit"])
output=inputclamped to [lowerLimit,upperLimit], per channel. There is no time constant, no lag and no filtering — the clamp is exact and instant. Nothing here depends on the task rate.
The limiter ships switched off. enable defaults to false, so a fresh
limiter passes everything through. This matters most when a limiter is one
block inside a larger one: its bound is not protecting anything until it is
enabled.
If you write the limits the wrong way round, the block swaps them for you and you will read back the swapped pair.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
input |
signal unit | unbounded | The signal to clamp. One element per channel; the channel count is fixed by the machine configuration. |
disable |
- | - | True bypasses the limiter: input passes straight to output and every flag clears. Use it for a runtime override from a supervisor; use enable for the configured intent. |
Outputs
| Path | Unit | Description |
|---|---|---|
output |
signal unit | The clamped signal, one element per channel. Equals input exactly while bypassed, and exactly whenever the input is inside the bounds. |
isLimiting |
- | True per channel while input is outside the configured bounds. On the adaptive variant this means “the signal wants more than you allowed”, which is not the same as “the output was changed” — see below. |
isEnabled |
- | True when enable is true and disable is false. A single value for the whole block. |
activeLowerLimit |
signal unit | Adaptive variant only. The bound actually in use this cycle, per channel. It is never tighter than lowerLimit and may be looser while the signal is outside. |
activeUpperLimit |
signal unit | Adaptive variant only. The upper equivalent. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
enable |
- | false | - | False bypasses the limiter entirely. Set this, or the block does nothing. |
lowerLimit |
signal unit | −1 | below upperLimit |
The lower bound, per channel. Set it to what the consumer can actually accept. If it ends up above upperLimit the two are swapped silently. |
upperLimit |
signal unit | +1 | above lowerLimit |
The upper bound, per channel. |
adaptive |
- | true | - | Adaptive variant only. False makes it behave as a plain limiter. A plain limiter cannot be made adaptive at runtime. |
All parameters are persistent and survive a controller restart. No parameters exist below this block.
Setup
-
Set
lowerLimitandupperLimitto what the consumer can accept. The defaults of ±1 are placeholders, not machine values. -
Leave
enablefalse and confirmoutputfollowsinputexactly. -
Drive the input past a bound and confirm
isLimitinggoes true for that channel whileoutputstill follows — the flag works while bypassed only in the sense that it stays false; here it will read false, which confirms the bypass. -
Set
enabletrue and repeat.outputshould now stop at the bound andisLimitingshould read true.Step 4 changes what reaches the consumer. If the signal is already outside the band when you enable a plain limiter, the output steps to the bound. Enable it with the signal inside the band, or use the adaptive variant.
-
Trace every element of
input,outputandisLimiting. Limits are per channel, so an element with the wrong bound is easy to miss. -
On the adaptive variant, watch
activeLowerLimitandactiveUpperLimit. They should converge onto the configured bounds as the signal moves inside them.
Tuning
There is nothing to tune. The bounds are a machine property, and the only real decision is which variant to use.
- Set the bounds from the consumer’s datasheet, not by watching the signal. A limiter tuned to what the signal happens to do is not protecting anything.
- Use the plain limiter wherever the bound is a hard safety limit. It clamps immediately and always, which is what a protection limit must do.
- Use the adaptive variant wherever the bound shapes a signal rather than protecting the machine, and a step on engagement would be felt. It never introduces a discontinuity, and it tightens toward the configured bounds only as the signal allows.
- Do not use the adaptive variant as a safety limit. If the signal never returns inside the band, the active bounds never tighten and nothing is ever clipped.
- Watch
isLimitingduring commissioning. Persistent limiting means either the bound is too tight or something upstream is commanding more than the machine can deliver — and the second is usually the real problem. - Nothing here needs re-checking after a task-rate change.
Read each bound off the height at which its line goes flat.
| Symptom | Cause | Action |
|---|---|---|
Nothing is limited and isLimiting stays false |
enable defaults to false |
Set enable true |
isLimiting reads true but output equals input |
You are on the adaptive variant, and the active band still admits the signal | Read activeLowerLimit and activeUpperLimit; use the plain limiter if you need a hard bound |
| The output stepped when the limiter was enabled | Expected on the plain limiter: it clamps at once | Enable with the signal inside the band, or use the adaptive variant |
| The adaptive limiter never clips anything | Expected: the active bounds only tighten when the signal comes back inside them | Use the plain limiter for a hard bound |
| The limits read back swapped | Expected: they were written the wrong way round and the block corrected them | Write the lower bound to lowerLimit |
| The output is pinned at one value | lowerLimit and upperLimit are equal |
Separate them |
| One channel limits and the others do not | Expected: the bounds are per channel | Check every element of both limit arrays |
| The output is unbounded inside a larger block | That block’s internal limiter has not been enabled | Enable it in its own sub-tree |
| A non-numeric value passed straight through | Expected: the clamp does not reject non-numeric values | Fix the source; on the adaptive variant, bypass the block for a cycle to clear its active bounds |
| The signal is clipped harshly and the machine bumps | A hard clamp is what this block does | Use a soft limiter if you need a rounded approach to the bound |
A starting point for bounding an actuator command at ±10 in its own units:
enable = true
lowerLimit = -10.0
upperLimit = 10.0
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
lowerLimit below upperLimit |
Fixed | If they are inverted the two are swapped in place | Silently; read both back |
output |
lowerLimit, upperLimit while enabled |
Clamped exactly, with no lag. Unbounded while enable is false |
isLimiting, per channel |
| Limit values | Nothing | Not checked. Any finite pair is accepted, and equal limits pin the output to that value | Not reported |
isLimiting meaning |
Fixed | Reports the input against the configured bounds. On the adaptive variant the output may not have been changed at all | Not reported |
| Adaptive band | Fixed | The active bounds can only ever be wider than the configured ones, never tighter. They are not reset at start | Published as activeLowerLimit / activeUpperLimit |
| Non-numeric input | Nothing | Passes through unchanged. On the adaptive variant it also poisons the active bounds until a bypassed cycle re-derives them | 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.32.1 (340db23).