├── .gitattributes ├── src ├── LowpassFilter.h ├── HighpassFilter.h ├── ButterworthLPF.h ├── HighpassFilter.cpp ├── LowpassFilter.cpp ├── Filter.cpp ├── Filter.h ├── butterworthTest.m └── ButterworthLPF.cpp ├── examples └── ArduinoFiltering │ └── ArduinoFiltering.ino ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/LowpassFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: LowpassFilter.h 3 | * 4 | * Date: 25/02/2019 5 | * 6 | * Author: Philip Salmony 7 | * Email: philip.salmony@gmail.com 8 | * 9 | * Description: Collection of simple low-pass filters, first and second order. 10 | * Approximately flat in pass-band, -3dB point defined by cut-off frequency. 11 | * Attenuation slope is n * (-20dB/dec), where n is the filter order. 12 | * 13 | */ 14 | 15 | #ifndef LOWPASS_FILTER_H 16 | #define LOWPASS_FILTER_H 17 | 18 | #include "Filter.h" 19 | 20 | class LowpassFilter : public Filter { 21 | 22 | public: 23 | 24 | LowpassFilter(int n, float fc, float fs); 25 | 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/HighpassFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: HighpassFilter.h 3 | * 4 | * Date: 25/02/2019 5 | * 6 | * Author: Philip Salmony 7 | * Email: philip.salmony@gmail.com 8 | * 9 | * Description: Collection of simple high-pass filters, first and second order. 10 | * Approximately flat in pass-band, -3dB point defined by cut-off frequency. 11 | * Attenuation slope is n * (-20dB/dec), where n is the filter order. 12 | * 13 | */ 14 | 15 | #ifndef HIGHPASS_FILTER_H 16 | #define HIGHPASS_FILTER_H 17 | 18 | #include "Filter.h" 19 | 20 | class HighpassFilter : public Filter { 21 | 22 | public: 23 | 24 | HighpassFilter(int n, float fc, float fs); 25 | 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/ButterworthLPF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: ButterworthLPF.h 3 | * 4 | * Date: 25/02/2019 5 | * 6 | * Author: Philip Salmony 7 | * Email: philip.salmony@gmail.com 8 | * 9 | * Description: Collection of Butterworth low-pass filters, first order through to fourth order. 10 | * Starting from analogue prototype transfer functions, using Tustin (bilinear) transform to 11 | * to arrive at digital approximation. 12 | * Buttworth filters are 'maximally flat' before cut-off frequency, i.e. in passband. 13 | * Gain of any order Butterworth low-pass filter at cut-off frequency is -3dB (1/sqrt(2)). 14 | * However, attenuation slope is n * (-20dB/dec), where n is the filter order. 15 | * 16 | */ 17 | 18 | #ifndef BUTTERWORTHLPF_H 19 | #define BUTTERWORTHLPF_H 20 | 21 | #include "Filter.h" 22 | 23 | class ButterworthLPF : public Filter { 24 | 25 | public: 26 | 27 | ButterworthLPF(int n, float fc, float fs); 28 | 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/HighpassFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "HighpassFilter.h" 2 | 3 | HighpassFilter::HighpassFilter(int n, float fc, float fs) { 4 | 5 | // Set filter order and check bounds 6 | order = n; 7 | 8 | if (order > 2) // Only up to second order at the moment... 9 | order = 2; 10 | else if (order < 1) 11 | order = 1; 12 | 13 | // Pre-warp cut-off frequency (Tustin) 14 | prewarp(fc, fs); 15 | 16 | // Reset filter inputs and outputs 17 | reset(); 18 | 19 | // Set filter numerator and denominator coefficients 20 | float wcT = wc * T; 21 | float wcTsq = wcT * wcT; 22 | 23 | switch (order) { 24 | 25 | case 1: 26 | 27 | num[0] = 2; 28 | num[1] = -2; 29 | 30 | den[0] = 2 + wcT; 31 | den[1] = -2 + wcT; 32 | break; 33 | 34 | case 2: 35 | 36 | num[0] = 4; 37 | num[1] = -8; 38 | num[2] = 4; 39 | 40 | den[0] = 4 + 4 * wcT + wcTsq; 41 | den[1] = -8 + 2 * wcTsq; 42 | den[2] = 4 - 4 * wcT + wcTsq; 43 | break; 44 | 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/ArduinoFiltering/ArduinoFiltering.ino: -------------------------------------------------------------------------------- 1 | #include "ButterworthLPF.h" 2 | #include "HighpassFilter.h" 3 | 4 | #define DT_US 10000 5 | #define WN (float) (2 * M_PI * 0.5) 6 | 7 | ButterworthLPF lpf1(1, 20, 100); 8 | ButterworthLPF lpf2(2, 20, 100); 9 | HighpassFilter hpf1(1, 20, 100); 10 | HighpassFilter hpf2(2, 20, 100); 11 | 12 | unsigned long timer; 13 | 14 | unsigned int t; 15 | 16 | const float dt = (DT_US / 1000000.0); 17 | 18 | void setup() { 19 | 20 | Serial.begin(115200); 21 | while (!Serial); 22 | 23 | t = 0; 24 | timer = micros(); 25 | 26 | } 27 | 28 | void loop() { 29 | 30 | if (micros() - timer >= DT_US) { 31 | 32 | float val = sin(WN*t*dt) + 0.001*random(-100, 100); 33 | 34 | Serial.print(val); Serial.print(","); 35 | Serial.print(lpf1.update(val)); Serial.print(","); 36 | Serial.print(lpf2.update(val)); Serial.print(","); 37 | Serial.print(hpf1.update(val)); Serial.print(","); 38 | Serial.println(hpf2.update(val)); 39 | 40 | t++; 41 | 42 | 43 | timer += DT_US; 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/LowpassFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "LowpassFilter.h" 2 | 3 | LowpassFilter::LowpassFilter(int n, float fc, float fs) { 4 | 5 | // Set filter order and check bounds 6 | order = n; 7 | 8 | if (order > 2) // Only up to second order at the moment... 9 | order = 2; 10 | else if (order < 1) 11 | order = 1; 12 | 13 | // Pre-warp cut-off frequency (Tustin) 14 | prewarp(fc, fs); 15 | 16 | // Reset filter inputs and outputs 17 | reset(); 18 | 19 | // Set filter numerator and denominator coefficients 20 | float wcT = wc * T; 21 | float wcTsq = wcT * wcT; 22 | 23 | switch (order) { 24 | 25 | case 1: 26 | 27 | num[0] = wc; 28 | num[1] = wc; 29 | 30 | den[0] = 2 + wcT; 31 | den[1] = -2 + wcT; 32 | break; 33 | 34 | case 2: 35 | 36 | num[0] = wcTsq; 37 | num[1] = 2 * wcTsq; 38 | num[2] = wcTsq; 39 | 40 | den[0] = 4 + 4 * wcT + wcTsq; 41 | den[1] = -8 + 2 * wcTsq; 42 | den[2] = 4 - 4 * wcT + wcTsq; 43 | break; 44 | 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philip Salmony 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Filter.cpp: -------------------------------------------------------------------------------- 1 | #include "Filter.h" 2 | 3 | void Filter::prewarp(float fc, float fs) { 4 | 5 | // Check bounds on fs, prevent divide by zero or negative fs 6 | if (fs < 0) 7 | fs = 1.0; 8 | 9 | // Check that fc <= fs / 2 (Nyquist) 10 | if (fc > fs / 2.0) 11 | fc = fs / 2.0; 12 | else if (fc < 0.0) 13 | fc = 0.0; 14 | 15 | T = 1 / fs; 16 | wc = (2 / T) * tan(M_PI * fc / fs); 17 | 18 | } 19 | 20 | float Filter::getwc() { 21 | 22 | return wc; 23 | 24 | } 25 | 26 | float Filter::getT() { 27 | 28 | return T; 29 | 30 | } 31 | 32 | void Filter::reset() { 33 | 34 | for (int i = 0; i <= order; i++) { 35 | u[i] = 0.0; 36 | y[i] = 0.0; 37 | } 38 | 39 | } 40 | 41 | float Filter::update(float val) { 42 | 43 | int i; 44 | 45 | // Shift samples 46 | for (i = order; i > 0; i--) { 47 | u[i] = u[i - 1]; 48 | y[i] = y[i - 1]; 49 | } 50 | 51 | // Set new input 52 | u[0] = val; 53 | 54 | // Calculate new output 55 | y[0] = 0.0; 56 | 57 | for (i = 0; i <= order; i++) { 58 | y[0] += num[i] * u[i]; 59 | 60 | if (i > 0) 61 | y[0] -= den[i] * y[i]; 62 | 63 | } 64 | 65 | y[0] /= den[0]; 66 | 67 | return y[0]; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Title: Filter.h 3 | * 4 | * Date: 25/02/2019 5 | * 6 | * Author: Philip Salmony 7 | * Email: philip.salmony@gmail.com 8 | * 9 | * Description: Class prototype for implementation of digital filters. 10 | * Assumes implementation using Tustin transform, therefore require 11 | * prewarp function. 12 | * Note that the Tustin transform is only an approximation, which guarantees 13 | * an exact match ONLY AT the desired cut-off frequency. This is especially true 14 | * for cut-off frequencies close to the Nyquist frequency (fs/2). 15 | * To get a better frequency-domain match between analogue prototype and digital 16 | * approximation, sample at a higher frequency. 17 | */ 18 | 19 | #ifndef FILTER_H 20 | #define FILTER_H 21 | 22 | #include 23 | 24 | #define FILTER_MAX_ORDER 4 25 | 26 | class Filter { 27 | 28 | protected: 29 | 30 | float wc, T; 31 | 32 | int order; 33 | float num[FILTER_MAX_ORDER + 1]; 34 | float den[FILTER_MAX_ORDER + 1]; 35 | 36 | float u[FILTER_MAX_ORDER + 1]; 37 | float y[FILTER_MAX_ORDER + 1]; 38 | 39 | void prewarp(float fc, float fs); 40 | 41 | public: 42 | 43 | float getwc(); 44 | float getT(); 45 | 46 | void reset(); 47 | float update(float val); 48 | 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/butterworthTest.m: -------------------------------------------------------------------------------- 1 | % Desired cut-off frequency (Hz) 2 | fc = 1; 3 | 4 | % Sample rate (Hz) 5 | fs = 100; 6 | 7 | % Calculate sample time (s) and angular cut-off frequency (rad/s) 8 | T = 1 / fs; 9 | wc = 2 * pi * fc; 10 | 11 | % Pre-warp cut-off frequency 12 | wcd = (2 / T) * tan(pi * fc / fs); 13 | 14 | % First order 15 | wcT = wcd*T; 16 | Ga1 = tf(1, [1/wc, 1]); 17 | Gd1 = tf([wcT, wcT], [wcT+2, wcT-2], T); 18 | 19 | % Second order 20 | wcTsq = wcT * wcT; 21 | Ga2 = tf(1, [1/wc^2, 1.4142/wc, 1]); 22 | Gd2 = tf([wcTsq, 2*wcTsq, wcTsq], [4+2.8284*wcT+wcTsq,-8+2*wcTsq,4-2.8284*wcT+wcTsq], T); 23 | 24 | % Third order 25 | wcTcu = wcT * wcTsq; 26 | Ga3 = tf(1, [1/wc^3, 2/wc^2, 2/wc, 1]); 27 | Gd3 = tf([wcTcu, 3*wcTcu, 3*wcTcu, wcTcu], [8+8*wcT+4*wcTsq+wcTcu,-24-8*wcT+4*wcTsq+3*wcTcu,24-8*wcT-4*wcTsq+3*wcTcu,-8+8*wcT-4*wcTsq+wcTcu], T); 28 | 29 | % Fourth order 30 | wcTfo = wcTsq * wcTsq; 31 | alpha = 2.6132; 32 | beta = 3.41430612; 33 | Ga4 = tf(wc^4, [1, alpha*wc, beta*wc^2, alpha*wc^3, wc^4]); 34 | Gd4 = tf([wcTfo, 4*wcTfo, 6*wcTfo, 4*wcTfo, wcTfo], [16+8*alpha*wcT+4*beta*wcTsq+2*alpha*wcTcu+wcTfo, ... 35 | -64-16*alpha*wcT+4*alpha*wcTcu+4*wcTfo, ... 36 | 96-8*beta*wcTsq+6*wcTfo, ... 37 | -64+16*alpha*wcT-4*alpha*wcTcu+4*wcTfo, ... 38 | 16-8*alpha*wcT+4*beta*wcTsq-2*alpha*wcTcu+wcTfo], T); -------------------------------------------------------------------------------- /src/ButterworthLPF.cpp: -------------------------------------------------------------------------------- 1 | #include "ButterworthLPF.h" 2 | 3 | ButterworthLPF::ButterworthLPF(int n, float fc, float fs) { 4 | 5 | // Set filter order and check bounds 6 | order = n; 7 | 8 | if (order > 4) // Only up to fourth order at the moment... 9 | order = 4; 10 | else if (order < 1) 11 | order = 1; 12 | 13 | // Pre-warp cut-off frequency (Tustin) 14 | prewarp(fc, fs); 15 | 16 | // Reset filter inputs and outputs 17 | reset(); 18 | 19 | // Set filter numerator and denominator coefficients 20 | float wcT = wc * T; 21 | float wcTsq = wcT * wcT; 22 | float wcTcu = wcT * wcTsq; 23 | float wcTfo = wcTsq * wcTsq; 24 | 25 | float alpha = 2.6132; 26 | float beta = 3.41430612; 27 | 28 | switch (order) { 29 | 30 | case 1: 31 | 32 | num[0] = wcT; 33 | num[1] = wcT; 34 | 35 | den[0] = 2 + wcT; 36 | den[1] = -2 + wcT; 37 | break; 38 | 39 | case 2: 40 | 41 | num[0] = wcTsq; 42 | num[1] = 2 * wcTsq; 43 | num[2] = wcTsq; 44 | 45 | den[0] = 4 + 2.8284 * wcT + wcTsq; 46 | den[1] = -8 + 2 * wcTsq; 47 | den[2] = 4 - 2.8284 * wcT + wcTsq; 48 | break; 49 | 50 | case 3: 51 | 52 | num[0] = wcTcu; 53 | num[1] = 3 * wcTcu; 54 | num[2] = 3 * wcTcu; 55 | num[3] = wcTcu; 56 | 57 | den[0] = 8 + 8 * wcT + 4 * wcTsq + wcTcu; 58 | den[1] = -24 - 8 * wcT + 4 * wcTsq + 3 * wcTcu; 59 | den[2] = 24 - 8 * wcT - 4 * wcTsq + 3 * wcTcu; 60 | den[3] = -8 + 8 * wcT - 4 * wcTsq + wcTcu; 61 | break; 62 | 63 | case 4: 64 | 65 | num[0] = wcTfo; 66 | num[1] = 4 * wcTfo; 67 | num[2] = 6 * wcTfo; 68 | num[3] = 4 * wcTfo; 69 | num[4] = wcTfo; 70 | 71 | den[0] = 16 + 8 * alpha * wcT + 4 * beta * wcTsq + 2 * alpha * wcTcu + wcTfo; 72 | den[1] = -64 - 16 * alpha * wcT + 4 * alpha * wcTcu + 4 * wcTfo; 73 | den[2] = 96 - 8 * beta * wcTsq + 6 * wcTfo; 74 | den[3] = -64 + 16 * alpha * wcT - 4 * alpha * wcTcu + 4 * wcTfo; 75 | den[4] = 16 - 8 * alpha * wcT + 4 * beta * wcTsq - 2 * alpha * wcTcu + wcTfo; 76 | break; 77 | 78 | } 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FilterLib 2 | FilterLib is a collection of digital filters (highpass, lowpass, etc.) written in **C**. The aim is to provide **quick and easy implementations of common filters**, without needing to rewrite the same code over and over again. 3 | Specific filter types can be incorporated in code using a single include statement and filter parameters (such as cut-off frequency and sampling frequency) are easily set using the class constructor. 4 | 5 | ## Repository Structure 6 | Source files (.cpp and .h) are located in the *src* folder. An example implementation for Arduino is located in the *examples* folder. 7 | 8 | 9 | ## Example Usage 10 | 11 | 12 | 13 | ```cpp 14 | #include "ButterworthLPF.h" 15 | 16 | #define PI 3.14159265359 17 | 18 | // Sample time of simulation (in microseconds) 19 | #define SAMPLE_TIME_US 10000 20 | 21 | // Set filter parameters 22 | #define LPF_ORDER 1 23 | #define LPF_CUTOFF_FREQ 10 24 | #define LPF_SAMPLE_FREQ 100 25 | 26 | // Create low-pass filter object 27 | ButterworthLPF lpf(LPF_ORDER, LPF_CUTOFF_FREQ, LPF_SAMPLE_FREQ); 28 | 29 | // Simulated signal parameters 30 | #define BASE_FREQ 5.0 31 | #define NOISE_PWR 0.000001 32 | 33 | float dt; 34 | unsigned long counter; 35 | unsigned long timer; 36 | 37 | void setup() { 38 | 39 | // Initialise serial port 40 | Serial.begin(115200); 41 | while (!Serial); 42 | 43 | // Calculate sample time in seconds 44 | dt = (float) SAMPLE_TIME_US / 1000000.0; 45 | 46 | // Reset counter 47 | counter = 0; 48 | 49 | // Initialise timer 50 | timer = micros(); 51 | 52 | } 53 | 54 | void loop() { 55 | 56 | if (micros() - timer >= SAMPLE_TIME_US) { 57 | 58 | // Generate next sample of sinusoid with additive random noise 59 | float measurement = sin(2 * PI * BASE_FREQ * dt * counter) + NOISE_PWR * random(-1000, 1000); 60 | 61 | // Filter measurement 62 | float filtered = lpf.update(measurement); 63 | 64 | // Print measurement and filtered value 65 | Serial.print(measurement); Serial.print(","); Serial.println(filtered); 66 | 67 | counter++; 68 | timer += SAMPLE_TIME_US; 69 | 70 | } 71 | 72 | } 73 | ``` 74 | 75 | ## Contributing 76 | 77 | Contributions to additional filter types and classes are very welcome. 78 | 79 | ## License 80 | [MIT](https://choosealicense.com/licenses/mit/) --------------------------------------------------------------------------------