ESP-DSP Series 1 | Designing Efficient Filters with IIR Biquad


ESP-DSP is a digital signal processing library officially developed by Espressif, optimized specifically for the ESP32 platform. It enables developers to efficiently implement common ESP-DSP tasks such as filtering, FFT, and vector operations. In IoT and embedded applications, sensor data is often affected by environmental noise, power supply interference, or limitations in ADC precision. This can lead to signal fluctuations or drift, ultimately impacting the accuracy of decision-making and control systems.

To address these issues, digital filters are among the most common and practical solutions. This article focuses on the principles of IIR (Infinite Impulse Response) Biquad filters and their implementation on the ESP32. Using the APIs provided in the ESP-DSP library, weโ€™ll walk through how to build an efficient and low-cost signal cleaning process.

ESP-DSP

What is ESP-DSP?

ESP-DSP is an official digital signal processing (DSP) library released by Espressif, optimized specifically for the ESP32 series of chips. Written in C, it is performance-tuned for the Tensilica Xtensa processor architecture, supporting both floating-point and vectorized operations. This allows high-performance DSP tasks to be executed even on resource-constrained microcontrollers.

The ESP-DSP library provides functionality including:

  • Statistical and mathematical computations
  • Common signal windows (Hann, Hamming, Blackman, etc.)
  • IIR/FIR filters (including biquad)
  • FFT / DFT spectral analysis
  • Vector and matrix operations

What is an IIR Biquad?

IIR (Infinite Impulse Response) is a common type of digital filter. Compared to FIR (Finite Impulse Response) filters, IIR filters require less memory and offer higher computational efficiency, making them well-suited for resource-constrained microcontrollers like the ESP32.

A Biquad is a second-order IIR filter structure. Multiple biquads can be cascaded to build higher-order filters. The basic equation of a biquad filter is:

y[n] = b0ยทx[n] + b1ยทx[nโˆ’1] + b2ยทx[nโˆ’2] โˆ’ a1ยทy[nโˆ’1] โˆ’ a2ยทy[nโˆ’2]

Where:

  • x[n] is the input signal
  • y[n] is the output signal
  • b0, b1, b2, a1, and a2 are the filter coefficients

Development Environment

Before starting your programming, make sure to complete the following preparations:

Project Structure

This is one of the default project structures used by ESP-IDF. When you create a new project using idf.py create-project or set one up via the official ESP-IDF extension in VS Code, you’ll typically see this layout:

esp32-iir-filter/
โ”œโ”€โ”€ CMakeLists.txt
โ”œโ”€โ”€ main/
โ”‚   โ”œโ”€โ”€ CMakeLists.txt
โ”‚   โ””โ”€โ”€ iir_filter_example.c
โ””โ”€โ”€ sdkconfig

Code

The following code demonstrates how to use a low-pass filter to remove high-frequency noise from sensor data, preserving only the stable and slowly changing components of the signal.

#include "esp_log.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
#include "model_data.h"  // Model data in C array format (generated from .tflite using xxd)

static const char *TAG = "TFLM_DEMO";

// Allocate memory for tensors (working memory for TFLM)
constexpr int tensor_arena_size = 8 * 1024;
static uint8_t tensor_arena[tensor_arena_size];

void app_main(void) {
    ESP_LOGI(TAG, "Starting TensorFlow Lite Micro");

    // Load the model from the compiled array
    const tflite::Model* model = tflite::GetModel(g_model_tflite);
    if (model->version() != TFLITE_SCHEMA_VERSION) {
        ESP_LOGE(TAG, "Model schema version mismatch");
        return;
    }

    // Register the necessary operators used in the model
    tflite::MicroMutableOpResolver<4> resolver;
    resolver.AddFullyConnected();
    resolver.AddRelu();
    resolver.AddLogistic();

    // Create the interpreter
    tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, tensor_arena_size);
    if (interpreter.AllocateTensors() != kTfLiteOk) {
        ESP_LOGE(TAG, "Tensor allocation failed");
        return;
    }

    // Get pointers to the model's input and output tensors
    TfLiteTensor* input = interpreter.input(0);
    TfLiteTensor* output = interpreter.output(0);

    // Run inference in a loop
    while (1) {
        // Generate two random input values between 0.0 and 1.0
        float x1 = (float)(rand() % 100) / 100.0f;
        float x2 = (float)(rand() % 100) / 100.0f;

        // Assign inputs to the model
        input->data.f[0] = x1;
        input->data.f[1] = x2;

        // Perform inference
        if (interpreter.Invoke() != kTfLiteOk) {
            ESP_LOGE(TAG, "Inference failed");
            continue;
        }

        // Read the prediction result
        float pred = output->data.f[0];
        ESP_LOGI(TAG, "Input: %.2f, %.2f, Prediction: %.3f", x1, x2, pred);

        // Delay for readability
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

Code Explanation:

The compute_lowpass_biquad() function in this example uses the formula for a second-order IIR (Biquad) low-pass filter to calculate the filter coefficients based on three parameters:

  • fs: Sampling rate (how many data points per second)
  • fc: Cutoff frequency (signals above this frequency will be filtered out)
  • Q: Quality factor, which controls the sharpness of the passband (0.707f is a standard value)

Once the five coefficients (b0, b1, b2, -a1, -a2) are generated, the ESP-DSP function dsps_biquad_f32() is used to apply the filter to the sensor data.

Sine wave + random noise (amplitude 0.2)

Compile and Flash

After writing the code, you can use the ESP-IDF tools to build, flash, and monitor:

In the VS Code lower-left ESP-IDF toolbar:

  • Click Build project
  • Click Flash device
  • Click Monitor device

Output and Test Results

After the program starts, you can view the output results in the ESP_Log:

I (282) TFLM_DEMO: Starting TensorFlow Lite Micro
I (282) TFLM_DEMO: Input: 0.33, 0.43, Prediction: 0.422
I (2282) TFLM_DEMO: Input: 0.62, 0.29, Prediction: 0.434
I (4282) TFLM_DEMO: Input: 0.00, 0.08, Prediction: 0.522
I (6282) TFLM_DEMO: Input: 0.52, 0.56, Prediction: 0.374
I (8282) TFLM_DEMO: Input: 0.56, 0.19, Prediction: 0.465
I (10282) TFLM_DEMO: Input: 0.11, 0.51, Prediction: 0.436
I (12282) TFLM_DEMO: Input: 0.43, 0.05, Prediction: 0.519
I (14282) TFLM_DEMO: Input: 0.08, 0.93, Prediction: 0.366
I (16282) TFLM_DEMO: Input: 0.30, 0.66, Prediction: 0.385
I (18282) TFLM_DEMO: Input: 0.69, 0.32, Prediction: 0.422
I (20282) TFLM_DEMO: Input: 0.17, 0.47, Prediction: 0.436
I (22282) TFLM_DEMO: Input: 0.72, 0.68, Prediction: 0.329
I (24282) TFLM_DEMO: Input: 0.80, 0.23, Prediction: 0.441

Conclusion

When working with sensor data, noise and unstable signals often affect the accuracy of system decisions and control. Using the IIR Biquad filter provided by the ESP-DSP library, we can perform real-time signal cleaning efficiently and with minimal resource usage. This makes the ESP32 a great choice for IoT and edge computing applications that require both performance and low cost.

ESP-DSP equips the ESP32 with powerful digital signal processing (DSP) capabilities, making it especially suitable for:

  • Sensor data filtering
  • Audio processing
  • Signal analysis
  • Real-time control systems

With the IIR Biquad filter, developers can effectively eliminate noise from signals while maintaining real-time performance and low resource consumptionโ€”making the ESP32 an ideal platform for IoT and edge computing scenarios.