LowPass1
LowPass1 smooths a noisy signal before a controller consumes it.6 minute read
LowPass1 smooths a noisy signal before a controller consumes it. One cut-off
frequency governs every channel, and the channel count is fixed by the machine
configuration. It also publishes the rate of change of its own smoothed
output, so you never have to differentiate output yourself.
flowchart LR
i1(["input — signal to smooth"]) --> B["LowPass1"]
i2(["disable"]) --> B
B --> o1(["output — smoothed signal"])
B --> o2(["outputDot — rate of the smoothed signal"])
B --> o3(["isEnabled"])
$f_c = \omega/2\pi$ —
omega6.283 rad/s is a 1 Hz cut-off. Time constant $\tau = 1/\omega$ [s]: 63% of a step in $\tau$, 90% in $2.3,\tau$, 99% in $4.6,\tau$. At the cut-off: gain 0.71, lag 45°, which is a delay of $0.125/f_c$ [s]. Below the cut-off the delay is $\tau$. That delay is the whole trade of this block.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
input |
signal unit | unbounded | The signal to smooth. One element per channel; the channel count is fixed by the machine configuration. |
disable |
- | - | True bypasses the filter: input passes straight to output. Use it for a runtime override from a supervisor; use enable for the configured intent. |
Outputs
| Path | Unit | Description |
|---|---|---|
output |
signal unit | The smoothed signal, one element per channel. Equals input exactly while bypassed. Starts from zero after every controller start, and is not cleared by a stop. |
outputDot |
signal unit per second | The rate of change of output, one element per channel. While bypassed this becomes the raw sample-to-sample difference of input divided by the task period — see the symptom table. |
isEnabled |
- | True when enable is true and disable is false. A single value for the whole block, not one per channel. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
omega |
rad/s | 6.283 (1 Hz) | 0.1 – 0.628/task period [s] | Cut-off, shared by all channels. Lower removes more noise and adds more delay. Out-of-range values are corrected silently — see Limits and errors. |
enable |
- | true | - | False bypasses the filter: input passes through unchanged. |
omega and enable are persistent — both survive a controller restart. The
inputs and outputs do not; input and disable return to their linked sources
and output starts from zero. No parameters exist below this block.
Setup
-
Link the source signal:
…/actuator/actualVelocity→…/velocityFilter/input.inputfollows the source on a trace. -
Set
enablefalse.outputequalsinputsample for sample andisEnabledreads false. -
Set
omegato 300. Read it back; a lower value means your task rate capped it, and tells you the highest cut-off this task rate allows. -
Set
enabletrue.isEnabledreads true andoutputstill tracksinputclosely, with the noise still visible. -
Halve
omegain steps, watching the noise and the loop consuming it. Stop one step above where that loop feels soft or hunts.Step 5 changes what the controller sees. Lower the cut-off with the axis at rest before trying it in motion — the added delay can destabilise a tightly tuned loop.
-
Trace every element of
inputandoutput. An element that reads zero while the machine moves is an unwired channel, not a filtered one.
Tuning
- Read the frequency of the ripple you want gone, in Hz, off a trace of
input. Setomegato $2\pi$ times that frequency as a starting point, then lower it until the ripple is gone. - Measure the delay you have bought. Step the source and read the time
outputtakes to cross 63% of the step; that time is $\tau$, and the delay below the cut-off equals it. - Keep that delay below a tenth of the response time of the loop consuming
output. Above that, the loop pays for the smoothing in stability. - Check the loop in motion, not at rest. Delay costs phase margin only while the axis is moving fast enough to need it.
- Re-check
omegaafter any task-rate change. The upper bound scales with the task period, so a slower task can clamp a cut-off that used to be accepted. - If you need the noise gone and the delay, fix the noise at its source — shielding, grounding, or a higher-resolution sensor. No cut-off gives you both.
Read the time constant off any curve as the moment it crosses 0.63.
| Symptom | Cause | Action |
|---|---|---|
Noise still present on output |
Cut-off too high | Halve omega, then re-check the loop for softness |
| Loop went soft, hunts or oscillates after enabling | Delay too high for the loop | Double omega; if the noise returns, fix it at the source |
| Setpoint tracking lags behind the command | The block is in the command path, not the feedback path | Raise omega, or move the filter onto the measurement only |
omega reads back lower than written |
Outside the accepted band for this task rate | Raise the task rate or accept the cap |
omega reads back as 0.1 |
Written below the fixed lower bound | Write 0.1 or more; a slower filter is not available |
omega used to be accepted and now clamps |
The task period grew, so the upper bound fell | Raise omega’s task rate back, or lower omega |
output ramps from zero for a second after every start |
Expected: the filter starts from zero | Gate the consumer off isEnabled plus a short delay |
output stays at zero while the signal moves |
Channel unwired — an unwired element reads zero | Trace input element by element and link every one |
outputDot shows one large spike the moment the filter is bypassed |
Expected: while bypassed, outputDot is a raw difference over one task period |
Gate any consumer of outputDot on isEnabled |
output follows input exactly, with no smoothing at all |
enable is false, or disable is true |
Read isEnabled; it tells you which of the two is holding the bypass |
| Some channels smooth more than others | Not possible — all channels share one omega |
Use a separate filter per channel group |
output went to a non-numeric value and stays there |
A non-numeric value reached input or omega |
Fix the source; the block cannot recover its own state, so restart the controller |
A conservative starting point for a velocity signal on a 1 ms task, feeding a position loop with a 100 ms response time:
omega = 60.0
enable = true
This is a starting point, not a final tuning. Work step 1 with a trace of your own signal.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
omega ≥ 0.1 rad/s |
Fixed | A slower value is replaced by 0.1 rad/s | Silently; read omega back |
omega ≤ 0.628 / task period [s] |
Task rate — 628 rad/s on a 1 ms task, 62.8 rad/s on a 10 ms task | A faster value is replaced by the edge. This is what keeps the filter stable at any setting | Silently; read omega back |
output |
Nothing | Unbounded — whatever the input carries reaches the output. Limit it downstream if the consumer needs a bound | Not reported |
outputDot |
Nothing | Unbounded, and largest on the cycle the bypass engages | Not reported |
| Channel count | Machine configuration | Fixed once the controller starts; it cannot be changed at runtime | 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).