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.

Contents
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:
- Install ESP-IDF (version 5.x or higher): ESP-IDF is the official development framework for programming the ESP32, and it supports multiple operating systems such as Windows, macOS, and Linux.
- ESP32 Development Board: An ESP32 board is required.
- Install the ESP-DSP library.
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.









