Skip to main content

Command Palette

Search for a command to run...

Low Shelf Biquad: DC-Range Gain Shaping Without Integrator Wind-Up

Updated
6 min read

A pure integrator (pole at z = 1) delivers infinite DC gain and infinite wind-up risk. A low shelf biquad gives you the same low-frequency gain boost with a defined upper limit, no saturation algebra required. It is the biquad EQ filter that every control-loop designer should reach for when the steady-state error specification tightens but a true integrator is too aggressive.


Transfer Function --- H(z)

The digital low shelf biquad uses the standard biquad second-order form:

$$H(z) = (b0 + b1z^-1 + b2z^-2)/(1 + az^-1 + a2z^-2)$$

Design specification: +6 dB shelf below fc = 200 Hz, unity gain above, fs = 44100 Hz, Q = 0.707 (Butterworth-like shelf slope).

Coefficients are derived from scipy.signal.bilinear() applied to the 2nd-order analog low-shelf prototype:

$$H_a(s) = A * [s^2 + (sqrt(A)/Q)wcs + Awc^2]/[As^2 + (sqrt(A)/Q)wcs + wc^2]$$

where A = 10^(dB/40) = 1.4125 and wc is the pre-warped analog corner frequency.

Coefficient Value
b0 1.0070175046
b1 -1.9658141400
b2 0.9599244516
a1 -1.9660954245
a2 0.9666606716

Difference equation:

$$y[n] = 1.0070175046x[n] - 1.9658141400x[n-1] + 0.9599244516x[n-2] + 1.9660954245y[n-1] - 0.9666606716*y[n-2]$$

Gain verification:

  • DC (f=0 Hz): +6.00 dB

  • Nyquist (f=22050 Hz): 0.00 dB

  • Corner (f=200 Hz): +3.02 dB (half-gain mid-shelf point)


Frequency Response

Magnitude and phase of the low shelf biquad. The +6 dB shelf is flat below 200 Hz; magnitude transitions to 0 dB above the shelf frequency. Phase peaks near fc (+12 deg at 200 Hz) then returns to zero -- negligible above 1 kHz.

Quantitative observations:

  • Passband below 100 Hz: ripple < 0.02 dB

  • Transition slope at fc: ~20 dB/decade (single-pole characteristic)

  • Phase peak: +12 deg at fc; < 1 deg above 1 kHz

  • Group delay increase at DC: ~0.8 ms -- negligible in most control loops


Python Implementation

import numpy as np
from scipy.signal import bilinear, lfilter

GAIN_DB, FC, FS, Q = 6.0, 200.0, 44100.0, 0.7071

def design_low_shelf(gain_db=GAIN_DB, fc=FC, fs=FS, q=Q):
    A     = 10 ** (gain_db / 40.0)
    wc    = 2 * fs * np.tan(np.pi * fc / fs)  # pre-warped
    sqrtA = np.sqrt(A)
    b_a = [A,  A * sqrtA / q * wc,  A**2 * wc**2]
    a_a = [A,      sqrtA / q * wc,       wc**2  ]
    b, a = bilinear(b_a, a_a, fs=fs)
    return b / a[0], a / a[0]

b, a = design_low_shelf()
y = lfilter(b, a, x)

The bilinear transform maps the analog prototype with pre-warping, preserving the shelf frequency within 0.03% of the specified 200 Hz.


C Implementation

Output y is computed before state updates -- DF-IIT signature.

#define B0  ( 1.0070175046f)
#define B1  (-1.9658141400f)
#define B2  ( 0.9599244516f)
#define A1  (-1.9660954245f)
#define A2  ( 0.9666606716f)

typedef struct { float w1; float w2; } FilterState;
static inline void filter_init(FilterState *s) { s->w1 = 0.0f; s->w2 = 0.0f; }

/* Direct Form II Transposed -- y computed BEFORE state updates */
static inline float filter_process_sample(FilterState *s, float x)
{
    float y = B0 * x + s->w1;              /* (1) output first */
    s->w1   = B1 * x - A1 * y + s->w2;     /* (2) state uses y */
    s->w2   = B2 * x - A2 * y;             /* (3) state uses y */
    return y;
}

Coefficients B0-A2 match filter_python.py exactly (scipy bilinear). The DF-IIT structure minimises coefficient sensitivity; on Cortex-M4 FPU float32 the shelf gain error is < 0.1 dB across the full frequency range.

Fixed-point note: a1 = -1.966 approaches the Q15 saturation boundary. Use Q2.29 or float32 on resource-constrained MCUs.


MATLAB Implementation

b = [1.0070175046, -1.9658141400,  0.9599244516];
a = [1.0000000000, -1.9660954245,  0.9666606716];

[H, f] = freqz(b, a, 4096, 44100);
semilogx(f, 20*log10(abs(H)));
title('Low Shelf Biquad -- +6 dB below 200 Hz');

Coefficient literals are identical to filter_python.py -- no re-derivation needed.


Design Trade-offs

Shelf gain vs. loop phase margin. Boosting DC gain by 6 dB adds ~12 deg phase near fc. If the shelf sits within one octave of your cross-over frequency, verify phase margin after insertion using a full loop Bode plot.

Q selection. Q = 0.707 gives the fastest shelf approach without magnitude overshoot. Higher Q sharpens the knee but adds a peak at fc; lower Q spreads the transition over two or more octaves. For control compensation, Q = 0.5-1.0 is the practical range.

Coefficient sensitivity. a1 = -1.966 is within 1.7% of -2, indicating poles close to the unit circle. Always verify the shelf gain after quantising to the target word length.

Low shelf vs. pure integrator. A pure integrator provides unlimited DC gain but requires anti-windup logic. The low shelf caps the boost at +6 dB and is inherently bounded -- no wind-up state to manage.


Key Takeaways

  • Design basis: scipy.signal.bilinear() on the analog low-shelf prototype -- pre-warping preserves exact digital shelf frequency mapping.

  • Control application: +6 dB low shelf below 200 Hz raises open-loop gain in the DC-200 Hz band without integrator wind-up risk; ideal for cascade PI+shelf structures.

  • DF-IIT in C: Assign y first, then update w1/w2 with y -- the Direct Form II Transposed signature minimising coefficient sensitivity on fixed-point hardware.

  • Phase impact: Phase peak of +12 deg at fc returns to < 1 deg above 1 kHz -- compatible with most loops with cross-over frequencies above 500 Hz.

  • Fixed-point caution: Coefficients near -1.97 approach Q15 saturation; use Q2.29 or 32-bit float on MCUs.