A Butterworth IIR filter is a recursive signal processing algorithm or analog circuit designed to pass a specific frequency range with zero ripple in the passband while using feedback to achieve a sharp roll-off with minimal computational overhead. When you are reading noisy analog sensors on an ESP32 or Arduino, throwing a simple moving average at the problem often introduces lag and fails to kill high-frequency EMI spikes. The Butterworth Infinite Impulse Response (IIR) topology solves this by giving you a maximally flat passband and a steep attenuation curve, making it the gold standard for real-time embedded sensor filtering.

What a Butterworth IIR Filter Actually Changes in Your Circuit

In embedded systems, raw ADC data from sources like load cells, thermistors, or piezo vibration sensors is rarely clean. You will see 50Hz/60Hz mains hum, switching regulator noise, and thermal jitter. A basic moving average (which acts as a crude Finite Impulse Response, or FIR, filter) requires storing and summing dozens of past samples to get a sharp cutoff. This eats RAM and introduces severe phase lag, meaning your system reacts slowly to actual physical changes.

An IIR filter changes this dynamic entirely by using feedback. Think of an IIR filter like the shock absorber on a car suspension: it doesn't just look at the current bump in the road (the new input), but also relies on the momentum and rebound of the previous bumps (the past outputs) to smooth out the ride. Because it recycles its own previous outputs, an IIR filter achieves a steep roll-off using only a handful of coefficients and minimal RAM.

Specifically, the Butterworth variant is chosen when you need absolute amplitude accuracy in the passband. Unlike Chebyshev filters, which trade passband ripple for a steeper roll-off, the Butterworth topology guarantees Passband Ripple: 0 dB. If your sensor reads 2.5V at 10Hz, a Butterworth filter will output exactly 2.5V at 10Hz, without the slight amplitude modulation that other filter types introduce.

Worked Numeric Example: 2nd-Order Low-Pass for a 10Hz Vibration Sensor

Let's design a digital Butterworth IIR filter for a real-world bench scenario. You are using an ESP32 to sample an ADXL335 analog accelerometer measuring a motor vibrating at 10Hz. You want to capture that 10Hz signal perfectly, but eliminate 50Hz mains hum and high-frequency switching noise from the motor driver.

  • Sampling Rate ($f_s$): 100 Hz
  • Target Signal: 10 Hz
  • Cutoff Frequency ($f_c$): 15 Hz
  • Filter Order: 2 (Yields a roll-off of 40 dB/decade)

To implement this in C++, we use the bilinear transform to convert the analog Butterworth transfer function into digital difference equation coefficients. Using standard DSP math (or tools like SciPy's signal.butter), we get the following normalized coefficients for a Direct Form I biquad structure:

  • $b_0 = 0.020083$
  • $b_1 = 0.040167$
  • $b_2 = 0.020083$
  • $a_1 = -1.561018$ (Note: in code, we subtract this, so it becomes +1.561018)
  • $a_2 = 0.641351$

The difference equation executed on every ADC read is:

y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]

Here is the exact, copy-pasteable C++ implementation for the ESP32 Arduino core:

struct BiquadFilter {
  float b0, b1, b2, a1, a2;
  float x1, x2, y1, y2; // State variables

  void init() {
    b0 = 0.020083; b1 = 0.040167; b2 = 0.020083;
    a1 = -1.561018; a2 = 0.641351;
    x1 = x2 = y1 = y2 = 0.0;
  }

  float process(float input) {
    float output = b0 * input + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
    x2 = x1; x1 = input;
    y2 = y1; y1 = output;
    return output;
  }
};

BiquadFilter vibrationFilter;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12); // ESP32 12-bit ADC
  vibrationFilter.init();
}

void loop() {
  float raw_adc = analogRead(34); // Read from GPIO 34
  float filtered = vibrationFilter.process(raw_adc);
  
  Serial.print(raw_adc);
  Serial.print(',');
  Serial.println(filtered);
  
  delay(10); // 100Hz sampling rate (10ms period)
}

Where You Meet This In Practice

While we focused on microcontrollers, the Butterworth IIR topology spans both digital code and analog hardware. You will encounter it in:

  • Embedded Sensor Fusion: Smoothing IMU (gyroscope/accelerometer) data in drones and robotics before feeding it into a PID controller or Kalman filter.
  • Active Audio Crossovers: Analog op-amp circuits (using Sallen-Key topologies) that split audio signals into tweeter and woofer bands without altering the amplitude of the frequencies within their designated passbands.
  • Biomedical Instrumentation: Filtering ECG and EEG signals to remove 60Hz powerline interference and baseline wander, where preserving the exact amplitude of the QRS complex is critical for diagnosis.
  • Software Defined Radio (SDR): Channel selection and decimation stages where a flat passband ensures the modulated signal envelope isn't distorted before demodulation.

Implementation Gotchas and Stability Limits

The math looks clean, but silicon reality introduces edge cases. The most common mistake hobbyists make is trying to run high-order IIR filters on 8-bit microcontrollers like the Arduino Uno (ATmega328P).

Hardware FPU Warning: The coefficients $a_1$ and $a_2$ are very close to -1.5 and 0.6. On an 8-bit MCU without a hardware Floating Point Unit (FPU), 32-bit software floats lose precision during the recursive multiplication steps. This causes 'limit cycles'—the filter output will spontaneously oscillate or lock up at a fixed DC value even if the input is zero. Upgrading from a $4 Arduino Nano to a $6 ESP32-C3 purely to get a hardware FPU for IIR math is a common and necessary bench pivot.

If you must use an 8-bit chip, you are forced to use fixed-point integer math (Q-format), which requires scaling coefficients by $2^{14}$ or $2^{15}$ and carefully managing bit-shifts to prevent overflow. Alternatively, use the ARM CMSIS-DSP library if you migrate to a Cortex-M0 or higher, which includes highly optimized, assembly-level biquad cascade functions designed to prevent these exact overflow issues.

Filter Topology Comparison for Embedded Systems
CharacteristicButterworth IIRChebyshev Type I IIRWindowed FIR
Passband RippleNone (0 dB)Yes (e.g., 0.5 dB)None (Linear Phase)
Roll-off SteepnessModerateVery SteepDepends on Tap Count
Phase LinearityNon-linearNon-linearPerfectly Linear
CPU / RAM CostVery Low (5 MACs)Very Low (5 MACs)High (50+ MACs)
Stability RiskModerate (Quantization)High (Quantization)Unconditionally Stable

For a deeper theoretical breakdown of why digital filters behave this way under quantization, the Analog Devices introduction to digital filters provides excellent architectural diagrams of the Direct Form I and II structures.

Frequently Asked Questions

Why choose an IIR filter Butterworth over an FIR filter for microcontrollers?

You choose a Butterworth IIR filter when CPU cycles and RAM are severely constrained. An FIR filter might require 50 to 100 multiply-accumulate (MAC) operations per sample to achieve the same 40 dB/decade roll-off that a 2nd-order Butterworth IIR achieves in just 5 MAC operations. On a 240MHz ESP32, this difference is negligible, but on a 16MHz AVR or a low-power battery-operated BLE sensor waking up for 2ms to take a reading, the IIR filter's computational efficiency is the only way to hit your power budget.

Does a Butterworth IIR filter introduce phase shift in my sensor data?

Yes, and this is its primary drawback. All IIR filters, including the Butterworth, introduce a non-linear phase shift. This means different frequencies within your passband are delayed by slightly different amounts of time. If you are measuring the absolute amplitude of a single-frequency vibration, this doesn't matter. However, if you are analyzing the shape of a complex waveform (like an ECG pulse or a square wave) where the timing relationship between harmonics is critical, the phase distortion will smear the signal. In those specific cases, you must use a linear-phase FIR filter or apply forward-backward filtering (which requires offline processing, not real-time).

How do I calculate Butterworth IIR coefficients without doing the bilinear transform math by hand?

Nobody calculates these by hand on the bench. The standard workflow is to use Python with the SciPy library. You import scipy.signal, define your Nyquist frequency (half your sampling rate), and call butter(order, cutoff, btype='low', fs=sampling_rate). This outputs the exact $b$ and $a$ arrays. You then copy those float values directly into your C++ struct. For analog hardware design, Texas Instruments provides the FilterPro desktop tool (or modern web equivalents) which generates the exact resistor and capacitor values for Sallen-Key op-amp topologies based on your desired Butterworth cutoff.

What happens if my IIR filter Butterworth output starts oscillating wildly?

Wild oscillation or output saturation (hitting the maximum integer/float limit) is a classic sign of coefficient quantization error or an unstable pole placement. This almost always happens when you try to implement a high-order filter (like a 4th or 6th order) as a single monolithic equation. The fix is to break the high-order filter down into cascaded 2nd-order sections (biquads). A 4th-order Butterworth becomes two 2nd-order biquads running in series. This keeps the poles further away from the unit circle edge in the Z-plane, vastly improving numerical stability on 32-bit floating-point hardware.