Arduino Tutorial: Simple High-pass, Band-pass and Band-stop Filtering

In this post we’ll show you how to implement very simple high-pass, band-pass and band-stop filters on an Arduino.

It is highly recommended that you read our previous post about potentiometers and EMA (Exponential Moving Average) filtering as well as the one about plotting multiple values in the Arduino IDE before continuing since we use similar circuitry, filtering method and plotting method in this tutorial. We use a simple potentiometer and the ADC to create a signal we run through the filters.

Different kinds of filters. X-axis is frequency and Y-axis is "gain" which is how much signal is let through, basically. (Source: Wikipedia)
Different kinds of filters. X-axis is frequency and Y-axis is “gain” which is how much signal is let through (or sometimes even amplified). (Source: Wikipedia)

High-pass Filtering

High-pass filtering is the opposite of low-pass filtering. Instead of smoothing out a signal, you’re left with all the noise and rapid changes. When the original signal stabilizes around any steady value, the high-passed signal goes to zero.

[…] run a low-pass filter and subtract the result from the original signal.

There are problaby more correct and efficient ways to implement high-pass filters, but the way we like to do it is to run a low-pass filter and subtract the result from the original signal. That way you’re left with only the high frequencies of the original signal.

This is the way we implemented it on an Arduino Leonardo:

//Global Variables
int sensorPin = 0;    //pin number to use the ADC
int sensorValue = 0;  //initialization of sensor variable, equivalent to EMA Y
float EMA_a = 0.3;    //initialization of EMA alpha
int EMA_S = 0;        //initialization of EMA S
int highpass = 0;

void setup(){
  Serial.begin(115200);              //setup of Serial module, 115200 bits/second
  EMA_S = analogRead(sensorPin);     //set EMA S for t=1
}

void loop(){
  sensorValue = analogRead(sensorPin);              //read the sensor value using ADC
  EMA_S = (EMA_a*sensorValue) + ((1-EMA_a)*EMA_S);  //run the EMA
  highpass = sensorValue - EMA_S;                   //calculate the high-pass signal

  Serial.println(highpass);
  
  delay(20);                                //20ms delay
}

Band-pass Filtering

Imagine you have a high-pass filtered signal which is too noisy. Then a band-pass filter might be for you. A band-pass filtered signal is basically a smooth high-pass filtered signal.

[…] run two seperate EMA filters with different cutoff frequencies.

Our idea behind the implementation of a band-pass filter is that we run two seperate EMA filters with different cutoff frequencies. We then subtract the filtered signal with the lowest cutoff frequency from the filtered signal with the highest cutoff frequency. That way we’re left with the frequencies between the two cutoff frequencies, which both are crucial to get the performance you want from the filter.

It's easy to see the difference between the high-passed signal (blue) and the band-passed signal (orange) which. The latter is in practice a low-pass filtered high-pass signal.
It’s easy to see the difference between the high-passed signal (blue) and the band-passed signal (orange). The latter is in practice a low-pass filtered high-pass signal.

Our implementation:


//Global Variables
int sensorPin = 0;        //pin number to use the ADC
int sensorValue = 0;      //initialization of sensor variable, equivalent to EMA Y

float EMA_a_low = 0.3;    //initialization of EMA alpha
float EMA_a_high = 0.5;

int EMA_S_low = 0;        //initialization of EMA S
int EMA_S_high = 0;

int highpass = 0;
int bandpass = 0;

void setup(){
  Serial.begin(115200);                   //setup of Serial module, 115200 bits/second
  
  EMA_S_low = analogRead(sensorPin);      //set EMA S for t=1
  EMA_S_high = analogRead(sensorPin);
}

void loop(){
  sensorValue = analogRead(sensorPin);    //read the sensor value using ADC
  
  EMA_S_low = (EMA_a_low*sensorValue) + ((1-EMA_a_low)*EMA_S_low);  //run the EMA
  EMA_S_high = (EMA_a_high*sensorValue) + ((1-EMA_a_high)*EMA_S_high);
  
  highpass = sensorValue - EMA_S_low;     //find the high-pass as before (for comparison)
  bandpass = EMA_S_high - EMA_S_low;      //find the band-pass

  Serial.print(highpass);
  Serial.print(" ");
  Serial.println(bandpass);
  
  delay(20);                              //20ms delay
}

Band-stop Filtering

This is the one that might be the most difficult one to wrap your head around. This filter only let the lowest and the highest frequencies through. This means that it differentiates itself quite a bit from the high-pass and the band-pass since it doesn’t return to zero all the time, but instead follows the steady value of the signal (like a low-pass filter). However, unlike a low-pass filter, it also includes high frequencies such as noise and rapid changes.

[…] subtract the band-pass filtered signal from the original signal.

Our idea behind this implementation is to subtract the band-pass filtered signal from the original signal. This ends up being an inverse band-pass filter. The two cutoff-frequencies are just as important here as in the band-pass filter.

In this graph you can see that at low frequencies the band-stopped signal (red) behaves like the low-passed signal (orange), while at higher frequencies it behaves more like the original signal (blue).
In this graph you can see that at low frequencies the band-stopped signal (red) behaves like the low-passed signal (orange), while at higher frequencies it behaves more like the original signal (blue).

 

An interesting phenomenon where the band-stopped signal on a step-response initially follows the original signal before after a transient behaving like the low-passed signal
An interesting phenomenon where the band-stopped signal on a (near) step-response initially follows the original signal before, after a transient, behaves like the low-passed signal

Our implementation:


//Global Variables
int sensorPin = 0;    		//pin number to use the ADC
int sensorValue = 0;  		//initialization of sensor variable, equivalent to EMA Y

float EMA_a_low = 0.05;		//initialization of EMA alpha (cutoff-frequency)
float EMA_a_high = 0.4;

int EMA_S_low = 0;			//initialization of EMA S
int EMA_S_high = 0;

int highpass = 0;
int bandpass = 0;
int bandstop = 0;

void setup(){
  Serial.begin(115200);              		//setup of Serial module, 115200 bits/second
  
  EMA_S_low = analogRead(sensorPin);		//set EMA S for t=1
  EMA_S_high = analogRead(sensorPin);
}

void loop(){
  sensorValue = analogRead(sensorPin);		//read the sensor value using ADC
  
  EMA_S_low = (EMA_a_low*sensorValue) + ((1-EMA_a_low)*EMA_S_low);  		//run the EMA
  EMA_S_high = (EMA_a_high*sensorValue) + ((1-EMA_a_high)*EMA_S_high);
  
  bandpass = EMA_S_high - EMA_S_low;  		//find the band-pass as before

  bandstop = sensorValue - bandpass;  		//find the band-stop signal

  Serial.print(sensorValue);
  Serial.print(" ");
  Serial.print(EMA_S_low);
  Serial.print(" ");
  Serial.println(bandstop);
  
  delay(20);								//20ms delay
}

The Small Chapter at the Bottom

As previously mentioned this is probably not the most efficient or most correct way to implement these kinds of filters. However, if you need some quick and dirty filter implementations, these ones might not be too shabby. Remember that the delay at the bottom of the loops in these examples are crucial for both the plotting rate and the filter characteristics.

Here is a nice guide on how to find the actual cutoff-frequency for EMA filters. This is applicable on these other filters as well, since we use EMA as a basis for all of them.

Try them out for yourself, play with the cutoff-frequencies and have fun!

Related Posts