MovingAverageFilter

MovingAverageFilter smooths a noisy signal by averaging it over time, tuned by a sample count rather than by a frequency.
3.30–3.34

MovingAverageFilter smooths a noisy signal by averaging it over time, tuned by a sample count rather than by a frequency. It is the filter to use when you think in samples: “average this over a hundred readings”. Its memory cost is the same at any sample count, so a very long average is as cheap as a short one.

It averages by weighted decay, not over a fixed window: older samples fade out rather than dropping out. So output approaches a step gradually and does not finish after numberOfSamples cycles — it reaches 63% at that point and 90% at a little over twice it.

flowchart LR
    i1(["input — signal to smooth"]) --> B["MovingAverageFilter"]
    i2(["reset"]) --> B
    i3(["disable"]) --> B
    B --> o1(["output — smoothed signal"])
    B --> o2(["isEnabled"])

Time constant $\tau = (N+1) \times$ task period [s], with $N$ = numberOfSamples. At the default 100 on a 1 ms task that is 0.101 s, a cut-off of 1.58 Hz: 63% of a step in 0.1 s, 90% in 0.23 s. Phase lag at the cut-off is 45°, and that lag is the whole trade. Note the time constant scales with the task period, so the same sample count is a different filter in seconds on a different task rate.

For the first numberOfSamples cycles after a start or a reset the filter converges faster than the numbers above, because it is averaging everything it has seen rather than decaying. That is deliberate and useful.

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.
reset - - A rising value sets output to input immediately and restarts the fast-converging phase. It affects every channel at once, and it works whether the block is enabled or not. Use it after a large step you do not want the filter to average across.
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. After a controller start its first value is half of input, then it converges — gate the consumer if that matters.
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
numberOfSamples samples 100 0 upward; no upper limit How heavily to average, shared by all channels. Higher removes more noise and adds more delay, at no extra cost in memory or computation. 0 is a pass-through. Any value is safe — this filter cannot be made unstable.
enable - true - False bypasses the filter: input passes through unchanged.

Both parameters are persistent and survive a controller restart. The inputs and outputs do not. No parameters exist below this block.

Setup

  1. Link the source signal: …/sensor/rawPressure → …/pressureFilter/input. input follows the source on a trace.

  2. Set enable false. output equals input sample for sample and isEnabled reads false.

  3. Set numberOfSamples to 10. Any value is accepted, so there is nothing to read back and check.

  4. Set enable true. isEnabled reads true and output tracks input closely, with the noise still visible.

  5. Raise numberOfSamples in steps of roughly double, watching the noise and the loop consuming output. Stop one step below where that loop feels soft or hunts.

    Step 5 changes what the controller sees. Each doubling of the sample count doubles the delay. Raise it with the axis at rest before trying it in motion — the added delay can destabilise a tightly tuned loop.

  6. Step the input and time how long output takes to reach 63% of the step. It should be about numberOfSamples plus one task periods.

  7. Trace every element of input and output. An element that reads zero while the machine moves is an unwired channel, not a filtered one.

Tuning

  1. Read the ripple you want gone off a trace of input, and count its period in task cycles. Set numberOfSamples to at least a few times that count.
  2. Measure the delay you have bought: numberOfSamples plus one task periods is the time constant, and the delay below the cut-off equals it.
  3. 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.
  4. Double numberOfSamples to halve the noise. It is a cheap knob — there is no memory or computation penalty for a large value — so the only cost is delay.
  5. Check the loop in motion, not at rest. Delay costs phase margin only while the axis is moving fast enough to need it.
  6. Re-check the sample count after any task-rate change. The count stays the same in samples but the delay in seconds moves with the task period.
  7. If you need a filter with a hard cut at a known frequency — to kill a mains harmonic, say — this block cannot do it. A finite-impulse-response filter with equal taps can.

Step response at three sample counts: 10 samples settles within 0.03 s, thedefault 100 crosses 0.63 at about 0.1 s, and 500 is still climbing after asecond.

Read the time constant off any curve as the moment it crosses 0.63.

Symptom Cause Action
Noise still present on output Sample count too low Double numberOfSamples, then re-check the loop for softness
Loop went soft, hunts or oscillates after enabling Delay too high for the loop Halve numberOfSamples; if the noise returns, fix it at the source
output had not finished settling after numberOfSamples cycles Expected: the filter decays rather than using a fixed window, so it reaches 63% at that point Wait about 2.3 times the sample count for 90%
Setpoint tracking lags behind the command The block is in the command path, not the feedback path Lower numberOfSamples, or move the filter onto the measurement only
The filter converged quickly just after start, then got sluggish Expected: it averages everything it has seen for the first numberOfSamples cycles, then switches to steady decay Use reset to get the fast phase back deliberately
output was half the input on the first cycle after a start Expected behaviour of the first sample Gate the consumer off a short delay after start
output averaged across a step you wanted it to follow The filter has no way to know the step was intentional Pulse reset at the moment of the step
Resetting one channel restarted the averaging on all of them Expected: the sample count is shared across channels Use a separate filter per channel if they need independent resets
Changing numberOfSamples had no immediate effect Expected: the filter is still in its fast-converging phase, where the count does not yet apply Pulse reset, or wait for the count to be reached
output follows input exactly, with no smoothing at all numberOfSamples is 0, or enable is false, or disable is true Read isEnabled and numberOfSamples; 0 is a pass-through
The delay changed after a task-rate change Expected: the time constant is the sample count times the task period Rescale numberOfSamples by the ratio of the task periods
You need a hard cut at a specific frequency This filter has no such thing — it is a gentle first-order roll-off Use a finite-impulse-response filter with equal taps
Some channels smooth more than others Not possible — all channels share one sample count Use a separate filter per channel group
output went to a non-numeric value A non-numeric value reached input Fix the source, then pulse reset — that clears the filter completely

A conservative starting point for a pressure signal on a 1 ms task, feeding a loop with a 100 ms response time:

numberOfSamples = 50
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
numberOfSamples Nothing Not checked, and it does not need to be — no value can make this filter unstable. 0 is a pass-through Not reported
Filter stability Guaranteed Stable at every sample count and every task rate. Unlike the frequency-tuned filters, there is no task-rate ceiling to respect Not applicable
output Nothing Unbounded — whatever the input carries reaches the output. Limit it downstream if the consumer needs a bound Not reported
Filter memory reset, or a bypassed cycle Both set output to input and clear the filter completely. There is always a way out of a bad state 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).