STM32 Digital Filters
Introduction
Digital filters are mathematical algorithms that process discrete-time signals to enhance desired signal characteristics while suppressing unwanted components. In embedded systems like STM32 microcontrollers, digital filters are essential for processing sensor data, removing noise, and extracting meaningful information from signals.
STM32 microcontrollers offer several built-in digital filtering capabilities, from simple hardware-based filters to more complex software implementations. This guide will introduce you to digital filtering concepts and show you how to implement them on STM32 devices.
Digital Filtering Fundamentals
Before diving into STM32-specific implementations, let's understand some basic concepts of digital filtering.
What is a Digital Filter?
A digital filter processes a discrete-time signal (a sequence of samples) to modify certain aspects of that signal. Common filtering operations include:
- Noise reduction: Removing random variations that corrupt the signal
- Signal smoothing: Averaging out rapid fluctuations to extract trends
- Frequency selection: Passing or blocking specific frequency ranges
Types of Digital Filters
Digital filters are broadly categorized into:
-
Finite Impulse Response (FIR) filters:
- Output depends only on current and past inputs
- Always stable
- Linear phase response
- Typically requires more computation
-
Infinite Impulse Response (IIR) filters:
- Output depends on current and past inputs, as well as past outputs
- Can be unstable if not designed properly
- Nonlinear phase response
- Usually more computationally efficient
Hardware Digital Filters in STM32
STM32 microcontrollers include several hardware digital filtering capabilities that require minimal CPU intervention:
1. ADC Oversampling and Filtering
Many STM32 devices feature built-in oversampling and filtering for analog-to-digital conversion:
// Configure ADC oversampling (for F4, F7, L4, and H7 series)
void configureADCOversampling(ADC_HandleTypeDef *hadc) {
// Enable oversampling
hadc->Instance->CR2 |= ADC_CR2_OVSR;
// Set oversampling ratio (16x)
hadc->Instance->CR2 |= ADC_CR2_OVSR_2 | ADC_CR2_OVSR_1;
// Set oversampling shift (4 bits)
hadc->Instance->CR2 |= ADC_CR2_OVSS_2;
// Enable ADC
hadc->Instance->CR2 |= ADC_CR2_ADON;
}
Benefits of ADC oversampling:
- Improved signal-to-noise ratio (SNR)
- Increased effective resolution
- Reduced noise
2. Digital Filter for Sigma-Delta Modulators (DFSDM)
Advanced STM32 devices (like the F4, F7, and H7 series) feature a DFSDM peripheral specifically designed for sigma-delta modulators and digital filtering:
// Basic DFSDM filter configuration example
void configureDFSDM(void) {
// Enable DFSDM clock
__HAL_RCC_DFSDM1_CLK_ENABLE();
// Configure DFSDM filter
hdfsdm1_filter0.Instance = DFSDM1_Filter0;
hdfsdm1_filter0.Init.RegularParam.Trigger = DFSDM_FILTER_SW_TRIGGER;
hdfsdm1_filter0.Init.RegularParam.FastMode = DISABLE;
hdfsdm1_filter0.Init.RegularParam.DmaMode = ENABLE;
// Set filter parameters (sinc3 filter with oversampling ratio of 64)
hdfsdm1_filter0.Init.FilterParam.SincOrder = DFSDM_FILTER_SINC3_ORDER;
hdfsdm1_filter0.Init.FilterParam.Oversampling = 64;
hdfsdm1_filter0.Init.FilterParam.IntOversampling = 1;
HAL_DFSDM_FilterInit(&hdfsdm1_filter0);
}
The DFSDM is particularly useful for:
- Digital microphones
- High-precision analog sensors
- Applications requiring advanced signal processing
Software Digital Filters on STM32
While hardware filters are efficient, software implementations offer more flexibility for custom filtering needs:
1. Moving Average Filter (Simple FIR)
A moving average filter is one of the simplest FIR filters, useful for smoothing noisy signals:
#define FILTER_ORDER 16
uint16_t sampleBuffer[FILTER_ORDER];
uint8_t bufferIndex = 0;
// Simple moving average filter implementation
uint16_t movingAverageFilter(uint16_t newSample) {
// Add new sample to buffer
sampleBuffer[bufferIndex] = newSample;
bufferIndex = (bufferIndex + 1) % FILTER_ORDER;
// Calculate average
uint32_t sum = 0;
for (int i = 0; i < FILTER_ORDER; i++) {
sum += sampleBuffer[i];
}
return (uint16_t)(sum / FILTER_ORDER);
}
Input and Output example:
Input: [1023, 1032, 985, 1002, 1050, 1010, 975, 1005, ...]
Output (with FILTER_ORDER=4): [1010, 1017, 1011, 1010, ...]
2. Exponential Moving Average (Simple IIR)
An exponential moving average is a basic IIR filter that gives more weight to recent samples:
// Alpha is a smoothing factor between 0 and 1
// Lower alpha = more smoothing but slower response
#define ALPHA 0.2f
float filteredValue = 0.0f;
// Simple exponential moving average filter
float exponentialFilter(float newSample) {
filteredValue = ALPHA * newSample + (1.0f - ALPHA) * filteredValue;
return filteredValue;
}
Input and Output example:
Input: [25.0, 26.5, 24.8, 25.2, 25.5, 26.0, 24.9, ...]
Output (with ALPHA=0.2): [5.0, 9.3, 12.4, 14.9, 17.0, 18.8, ...]
3. Using CMSIS-DSP Library for Advanced Filters
The ARM CMSIS-DSP library provides optimized DSP functions for ARM Cortex-M processors, including various filter implementations:
First, include the necessary headers:
#include "arm_math.h"
Then implement a more advanced FIR filter:
#define NUM_TAPS 31
// FIR filter coefficients (low-pass filter example)
const float32_t firCoeffs[NUM_TAPS] = {
0.0012f, 0.0023f, 0.0045f, 0.0078f, 0.0125f, 0.0187f, 0.0264f, 0.0353f,
0.0450f, 0.0547f, 0.0638f, 0.0717f, 0.0778f, 0.0815f, 0.0827f, 0.0815f,
0.0778f, 0.0717f, 0.0638f, 0.0547f, 0.0450f, 0.0353f, 0.0264f, 0.0187f,
0.0125f, 0.0078f, 0.0045f, 0.0023f, 0.0012f
};
// Filter instance
arm_fir_instance_f32 firFilter;
// State buffer
float32_t firStateBuffer[NUM_TAPS + BLOCK_SIZE - 1];
// Initialize and use the filter
void setupFilter(void) {
// Initialize the FIR filter
arm_fir_init_f32(
&firFilter,
NUM_TAPS,
(float32_t *)firCoeffs,
firStateBuffer,
BLOCK_SIZE
);
}
void processBlock(float32_t *input, float32_t *output, uint32_t blockSize) {
// Process a block of data through the filter
arm_fir_f32(&firFilter, input, output, blockSize);
}
Real-World Examples
Example 1: Filtering Accelerometer Data
This example demonstrates filtering raw accelerometer data to reduce noise:
#include "stm32f4xx_hal.h"
#include "arm_math.h"
#define FILTER_ORDER 16
#define ALPHA 0.2f
typedef struct {
float x;
float y;
float z;
} AccelData_t;
AccelData_t filteredData = {0};
// Get raw accelerometer readings (hardware-specific)
extern AccelData_t getAccelRawData(void);
// Apply exponential moving average filter to accelerometer data
AccelData_t filterAccelerometerData(void) {
// Get raw readings
AccelData_t rawData = getAccelRawData();
// Apply filter to each axis
filteredData.x = ALPHA * rawData.x + (1.0f - ALPHA) * filteredData.x;
filteredData.y = ALPHA * rawData.y + (1.0f - ALPHA) * filteredData.y;
filteredData.z = ALPHA * rawData.z + (1.0f - ALPHA) * filteredData.z;
return filteredData;
}
// Example usage in main loop
void main(void) {
// Initialize hardware
HAL_Init();
// Configure accelerometer (hardware-specific)
// ...
while (1) {
// Get filtered accelerometer data
AccelData_t accel = filterAccelerometerData();
// Process or display filtered data
// ...
HAL_Delay(10); // Sample at 100Hz
}
}
Example 2: ECG Signal Processing
This example shows how to filter an ECG signal to remove various noise sources:
#include "stm32f4xx_hal.h"
#include "arm_math.h"
#define FILTER_TAP_NUM 51
#define BLOCK_SIZE 32
// Bandpass filter coefficients (0.5Hz to 40Hz)
const float32_t ecgFilterCoeffs[FILTER_TAP_NUM] = {
// ... coefficient values would go here
// These would be generated using a filter design tool
};
// FIR filter instance
arm_fir_instance_f32 ecgFilter;
// Filter state buffer
float32_t ecgStateBuffer[FILTER_TAP_NUM + BLOCK_SIZE - 1];
// ADC buffer for raw ECG data
uint16_t adcBuffer[BLOCK_SIZE];
// Filtered output buffer
float32_t filteredECG[BLOCK_SIZE];
// Initialize ECG processing
void initECGProcessing(void) {
// Configure ADC for ECG sampling
// ...
// Initialize FIR filter for ECG
arm_fir_init_f32(
&ecgFilter,
FILTER_TAP_NUM,
(float32_t *)ecgFilterCoeffs,
ecgStateBuffer,
BLOCK_SIZE
);
}
// Process ECG data
void processECGData(void) {
float32_t adcDataFloat[BLOCK_SIZE];
// Convert ADC readings to float
for (int i = 0; i < BLOCK_SIZE; i++) {
adcDataFloat[i] = (float32_t)adcBuffer[i];
}
// Apply bandpass filter to remove noise
arm_fir_f32(&ecgFilter, adcDataFloat, filteredECG, BLOCK_SIZE);
// Process filtered ECG data (detect QRS complexes, etc.)
// ...
}
Practical Digital Filter Design
When designing digital filters for STM32, consider these practical steps:
-
Define requirements:
- What type of filter do you need? (Low-pass, high-pass, band-pass, etc.)
- What cutoff frequencies are required?
- How much attenuation is needed in the stop band?
- What phase response is acceptable?
-
Choose hardware vs. software:
- Use hardware filters when possible to reduce CPU load
- Use software filters when more flexibility is needed
-
Select filter order:
- Higher order = sharper frequency response but more computation
- Start with a lower order and increase if needed
-
Generate filter coefficients:
- Use tools like MATLAB, Python (scipy.signal), or online calculators
- These coefficients define the filter's behavior
-
Optimize for performance:
- Use fixed-point math when possible (STM32 has efficient fixed-point instructions)
- Leverage CMSIS-DSP library for optimized implementations
- Consider block processing for efficiency
Advanced Topics: Filter Implementation Strategies
Direct Form Structures
There are several ways to implement digital filters in code:
// Direct Form I implementation (FIR example)
float directFormI(float newSample) {
static float x[FILTER_ORDER] = {0}; // Input buffer
float output = 0.0f;
// Shift input samples
for (int i = FILTER_ORDER - 1; i > 0; i--) {
x[i] = x[i-1];
}
x[0] = newSample;
// Compute output
for (int i = 0; i < FILTER_ORDER; i++) {
output += b[i] * x[i];
}
return output;
}
// Direct Form II implementation (IIR example)
float directFormII(float newSample) {
static float w[FILTER_ORDER] = {0}; // State variable
float output = 0.0f;
// Compute new state
w[0] = newSample;
for (int i = 1; i < FILTER_ORDER; i++) {
w[0] -= a[i] * w[i];
}
// Compute output
for (int i = 0; i < FILTER_ORDER; i++) {
output += b[i] * w[i];
}
// Shift state variables
for (int i = FILTER_ORDER - 1; i > 0; i--) {
w[i] = w[i-1];
}
return output;
}
Circular Buffers for Efficient Implementation
Using circular buffers can make filter implementation more efficient:
#define BUFFER_SIZE 32
float circularBuffer[BUFFER_SIZE];
uint8_t bufferIndex = 0;
// Add sample to circular buffer
void addSample(float sample) {
circularBuffer[bufferIndex] = sample;
bufferIndex = (bufferIndex + 1) % BUFFER_SIZE;
}
// Get sample at offset from current position
float getSample(int offset) {
int index = (bufferIndex - offset - 1 + BUFFER_SIZE) % BUFFER_SIZE;
return circularBuffer[index];
}
// FIR filter using circular buffer
float firFilterWithCircularBuffer(float newSample) {
// Add new sample to buffer
addSample(newSample);
float output = 0.0f;
// Compute output
for (int i = 0; i < FILTER_ORDER; i++) {
output += coeff[i] * getSample(i);
}
return output;
}
Summary
Digital filters are powerful tools for signal processing on STM32 microcontrollers. We've covered:
- The fundamental concepts of digital filtering
- Built-in hardware filtering capabilities of STM32 devices
- Software implementation of common filter types
- Practical examples demonstrating real-world applications
- Advanced implementation strategies for optimal performance
By applying these concepts, you can improve the quality of sensor data, reduce noise, and extract meaningful information from signals in your embedded applications.
Additional Resources and Exercises
Resources
- STM32 Reference Manuals (specific to your device family)
- ARM CMSIS-DSP Library Documentation
- "Digital Signal Processing - A Practical Guide for Engineers and Scientists" by Steven W. Smith
Exercises
- Implement a moving average filter and test it with data from an analog sensor.
- Design a low-pass filter with a cutoff frequency of 50Hz and implement it using CMSIS-DSP.
- Compare the performance and filtering quality of an FIR vs. IIR filter on the same dataset.
- Implement a digital filter to detect a specific frequency in an audio signal.
- Use the DFSDM peripheral (if available) to interface with a digital microphone and process the audio data.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)