IIRFilter
IIRFilter runs any discrete filter you can express as a numerator and a denominator coefficient set — notch, Butterworth, Chebyshev, lead-lag.8 minute read
IIRFilter runs any discrete filter you can express as a numerator and a
denominator coefficient set — notch, Butterworth, Chebyshev, lead-lag. Where
the named filters give you a cut-off, this one gives you the coefficients
themselves, so a filter design drops straight into configuration with no code
change. It is single channel: one instance filters one signal.
flowchart LR
i1(["input — signal to filter"]) --> B["IIRFilter"]
i2(["disable"]) --> B
B --> o1(["output — filtered signal"])
B --> o2(["isEnabled"])
The filter computes $a_0,y[k] = b_0,u[k] + b_1,u[k-1] + \dots - a_1,y[k-1]- a_2,y[k-2] - \dots$, with $b$ =
numand $a$ =den. DC gain is the sum ofnumdivided by the sum ofden— check it after every coefficient change, because nothing normalises it for you. The coefficients are defined in samples, not seconds, so the same set on a different task rate is a different filter.
This block is bypassed to zero, not to the input. While it is disabled,
output reads zero and input reads zero too. Stability is yours to
guarantee — see Limits and errors.
Do not put this block in a setpoint path unguarded. Because a stopped filter outputs zero rather than its input,
disable,order= 0 orden[0]= 0 on a position, velocity or torque target is a command to zero. An application in C++ can guard the path withIIRFilter::getIsFiltering(), which is false in exactly those three cases, and substitute the unfiltered signal — this is whatActuatorControlLoopdoes for its four target filters, so those four are safe to disable at runtime.
The coefficient arrays hold 7 entries by default, so orders up to 6 — two notches plus a lowpass. An instance can be built with a different ceiling; read it back rather than assuming (see Setup, step 5).
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
input |
signal unit | unbounded | The signal to filter. A single value, not an array. The block writes zero here on every cycle it is disabled, so a trace of input reads zero while the filter is off. |
disable |
- | - | True stops the filter and forces output to zero. Use it for a runtime override from a supervisor; use enable for the configured intent. |
Outputs
| Path | Unit | Description |
|---|---|---|
output |
signal unit × the DC gain | The filtered signal, a single value. Zero — not the input while disabled, while order is 0, and while den[0] is 0. Starts from zero after every controller start and plays out the filter’s full transient. |
isEnabled |
- | True when enable is true and disable is false. It does not tell you the filter is actually running: a zero order or a zero den[0] stops it while this still reads true. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
order |
- | 1 | 0 – the array length minus 1 | How many coefficients are used. A value above the ceiling is corrected silently. 0 stops the filter and outputs zero — it is not a constant gain. |
num |
- | 1, 0, 0, 0, 0, 0, 0 | any | Numerator coefficients $b_0 … b_N$, most recent sample first. Set these together with den, then check the DC gain. |
den |
- | 1, 0, 0, 0, 0, 0, 0 | any, but den[0] must not be 0 |
Denominator coefficients $a_0 … a_N$. den[0] divides every output sample, so den[0] = 0 stops the filter and outputs zero. |
enable |
- | true | - | False stops the filter and forces output to zero. |
All four parameters are persistent and survive a controller restart, which is how a filter design ships with a machine. No parameters exist below this block.
Setup
-
Design your filter against your task period, in whatever tool you use, and export the coefficients. A 1 ms task is 1000 samples per second.
-
Link the source signal into
inputand confirm on a trace thatinputfollows the source while the block is enabled. -
Set
enablefalse.outputreads zero and so doesinput. This is normal for this block and is not a broken link. -
Write
orderfirst, thennum, thenden. Readorderback — a lower value means you exceeded the array length. -
Check the array length by writing
orderto a large number and reading it back. The value you get is the highest order this instance supports; it is fixed when the machine is built. -
Set
enabletrue and confirmisEnabledreads true. -
Feed a constant into
inputand readoutput. Divide the two: the ratio must match the sum ofnumdivided by the sum ofden.Step 7 is the check that catches an unnormalised coefficient set. A set that is right up to a scale factor gives a correctly-shaped filter with the wrong gain, and every loop downstream of it is then mistuned.
-
Step the input and watch
output. If it grows instead of settling, your denominator has an unstable root — setenablefalse immediately.
Tuning
There is nothing to tune here in the usual sense. The work is designing the coefficients and verifying them on the machine.
- Fix the task period before you design anything. Every coefficient depends on it. If the task rate changes later, redesign the whole set — no parameter in this block rescales for you.
- Design in your own tool, then verify the DC gain by hand:
the sum of
numdivided by the sum ofdenshould be 1 for a filter that is not meant to change the signal’s size. - Confirm stability before you enable it. Every root of the denominator polynomial must lie inside the unit circle. The block does not check this and will happily run a filter that diverges.
- Step the input and read the settling time and any overshoot off
output. Compare them against what your design predicted; a mismatch usually means the sample rate assumed in the design tool was wrong. - Keep the order as low as the requirement allows. A high-order set with coefficients spanning many orders of magnitude loses precision, and a narrow notch is the usual culprit.
- Re-verify after any task-rate change, starting from step 1.
Read the settling time off any curve as the point it stops moving toward 1.
| Symptom | Cause | Action |
|---|---|---|
output grew without bound until something tripped |
A denominator root outside the unit circle | Set enable false, redesign the coefficients, and re-check step 3 of Tuning |
output is a constant multiple of what you expected |
The coefficient set is not normalised | Rescale num so the sums divide to 1, or rescale in your design tool |
output reads zero and isEnabled reads true |
order is 0, or den[0] is 0 |
Write a non-zero den[0] and an order of at least 1 |
input reads zero on a trace while the filter is off |
Expected: the block zeroes its own input while disabled | Enable the block before you judge the link |
output reads zero as soon as you disabled the filter |
Expected: this block bypasses to zero, not to the input | Use a switch block downstream if you need the raw signal while bypassed |
order reads back lower than written |
Above the array length for this instance | Work within the value you read back; the length is fixed when the machine is built |
| The coefficients went to zero after you set the order | Setting the order clears the coefficient arrays | Always write order first, then num and den |
| The filter behaves nothing like the design | The design assumed a different sample rate | Redesign at the controller’s actual task period |
| The filter changed behaviour after a task-rate change | Expected: the coefficients are defined in samples, not seconds | Redesign the whole set for the new rate |
| A high-order coefficient still has an effect after you shortened the set | The unused tail of num or den was left in place |
Write zeros over the whole array, then write the new set |
output plays out a large transient every time the filter is enabled |
Expected: the filter memory starts from zero, so the full transient runs | Enable at rest, or gate the consumer off isEnabled plus the settling time |
output went to a non-numeric value and stays there |
A non-numeric value reached input or a coefficient |
Disable the block for one cycle — that clears the filter memory — then fix the source |
| You need to filter several axes | Not possible — this block is single channel | Use one instance per axis |
A pass-through starting point, which is also what the block ships with:
order = 1
num = 1, 0
den = 1, 0
A 2nd-order Butterworth low-pass at 10 Hz for a 1 ms task only:
order = 2
num = 0.0009447, 0.0018894, 0.0009447
den = 1, -1.9111971, 0.9149758
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
| Filter stability | Nothing | Not checked. A denominator with a root outside the unit circle diverges, and the block runs it. Only a controller restart or disabling the block clears the state | Not reported |
den[0] ≠ 0 |
Fixed | A zero den[0] stops the filter and output reads zero |
Not reported; isEnabled still reads true |
order ≤ array length − 1 |
Machine configuration | A larger value is replaced by the ceiling | Silently; read order back |
| DC gain | Nothing | Not normalised and not checked. Whatever the coefficients imply is what you get | Not reported |
output |
Nothing | Unbounded. Limit it downstream if the consumer needs a bound | Not reported |
| Channel count | Fixed | One channel per instance, always | Not reported |
| Array length | Machine configuration | Fixed when the machine is built and not readable directly — discover it with Setup step 5 | 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).