Skip to content

Commit 5d33a0e

Browse files
committed
Simple EMA Filters version 2.0.0
1 parent 6a44371 commit 5d33a0e

26 files changed

+602
-308
lines changed

README.md

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ Not yet available as Arduino library.
5454
* [CI](https://github.com/ArminJo/Arduino-Utils?tab=readme-ov-file#ci)
5555

5656
# SimpleEMAFilters
57-
An EMA (**Exponential Moving Average**) filter behaves like an RC lowpass filter with RC = SamplePeriod((1-alpha)/alpha) see [here](https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter).<br/>
57+
An EMA (**Exponential Moving Average**) filter behaves like an RC Lowpass filter with RC = SamplePeriod((1-alpha)/alpha) see [here](https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter).<br/>
5858
An EMA filter is implemented by e.g. the following statement:
5959

60-
```c++
60+
```c+
6161
int16_t sLowpass3;
6262
int16_t Lowpass5;
6363
...
@@ -70,93 +70,102 @@ The alpha's for the implemented **ultra fast EMA filters** are 1/2, 1/4, 1/8, 1/
7070

7171
### For a 1 kHz sampling rate (1/1000s sampling interval) we get the following equivalent cutoff (-3db) frequencies:
7272
Simplified formula for small alpha is CutoffFrequency = (SampleFrequency / (2&pi; * ((1/alpha) - 1));
73-
- For alpha 1/2 (shift 1) -> (160 Hz / 1) With 1 = (1/alpha) - 1
74-
- 1/4 (shift 2) -> 53 Hz (160 Hz / 3) With 3 = (1/alpha) - 1
75-
- 1/8 -> 22.7 Hz (160 Hz / 7)
76-
- 1/16 -> 10.6 Hz (160 Hz / 15)
77-
- 1/32 -> 5.13 Hz (160 Hz / 31)
78-
- 1/256 -> 0.624 Hz (160 Hz / 255)
73+
- For alpha 1/2 | shift 1 -> 160 Hz
74+
- 1/4 | >>2 -> 53 Hz (=160 Hz / 3); 3 = (1 / (1/4)) - 1
75+
- 1/8 | >>3 -> 22.7 Hz (=160 Hz / 7)
76+
- 1/16 | >>4 -> 10.6 Hz (=160 Hz / 15)
77+
- 1/32 | >>5 -> 5.13 Hz (=160 Hz / 31)
78+
- 1/64 | >>6 -> 2.54 Hz (=160 Hz / 63)
79+
- 1/128 | >>7 -> 1,26 Hz (=160 Hz / 127)
80+
- 1/256 | >>8 -> 0.624 Hz (=160 Hz / 255)
7981

8082
Using the [more exact formula](https://github.com/MakeMagazinDE/DigitaleFilter/blob/main/Filter-Berechnungen.xlsx), alpha = 1 - e ^ -((2&pi; * CutoffFrequency) / SampleFrequency)
8183
=> CutoffFrequency = (-ln(1-alpha) * SampleFrequency) / 2&pi; we get the values:
82-
- 110 Hz
83-
- 46 Hz
84-
- 21.2 Hz
85-
- 10.3 Hz
86-
- 5.05 Hz
84+
- 1/2 -> 110 Hz
85+
- 1/4 -> 46 Hz
86+
- 1/8 -> 21.2 Hz
87+
- 1/16 -> 10.3 Hz
88+
- 1/32 -> 5.05 Hz
8789

8890
The **maximum output value for integer filters** is: InputValue - ((1 << (ShiftValue -1)) -1), i.e **85 for InputValue 100 and ShiftValue 5** (Lowpass5).
8991

9092
### The [SimpleEMAFilters.hpp](https://github.com/ArminJo/Arduino-Utils/blob/master/src/SimpleEMAFilters.hpp#L66) contains:
9193
- A set of **ultrafast** EMA (Exponential Moving Average) filters which require only **1 to 2 microseconds**.
92-
- 3 highpass and bandpass filters, generated by just subtracting one lowpass from input (highpass) or from another lowpass (bandpass).
93-
- 16 and 32 bit Biquad filters.
94+
- 3 Highpass and Bandpass filters, generated by just subtracting one Lowpass from input (Highpass) or from another Lowpass (Bandpass).
95+
- 16 and 32 bit Biquad filters / State Variable Filter (SVF).
9496
- Display routines for Arduino Plotter.
9597

9698
All implemented filters are applied at once to the input test signal calling `doFiltersStep(int16_t aInput)` and the results can in turn easily be displayed in the Arduino Plotter.
9799

98-
### Plotter outputs representing e.g. a 42, 21, 10.5, 5.2 Hz square / sine wave at a sample rate of 1 ms (or 84, 42 ... Hz at a sample rate of 0.5 ms and so on)
100+
### Arduino 1.8.19 plotter outputs are showing a 42, 21, 10.5, 5.2 Hz square / sine wave at a sample rate of 1 ms (or 84, 42 ... Hz at a sample rate of 0.5 ms and so on)
101+
The outputs are generated by the EMAFilterDemo example of this library.
99102

100-
Arduino Plotter output for a rectangle input signal with a amplitude of +/- 100. E.g. LowP3 is the Lowpass with alpha 1/8 implemented by `>> 3` and the cutoff frequency of 21 Hz.
103+
Output for a rectangle input signal with a amplitude of +/- 100.
104+
E.g. LowP3 is the Lowpass implemented by `>> 3` corresponding to alpha = 1/8 with a cutoff frequency of 21 Hz.<br/>
101105
![ArduinoPlotter output SignificantFilters](pictures/SignificantFilters.png)
102106

103107
Note the following facts:
104-
- Highpass is always Input - Lowpass.
105-
- Therefore highpass -here HighP3_16- can have an overshoot of 100%.
106-
- The output of DoubleLowpass3 and 4 is more and more resembling a delayed sine.
108+
- Highpass is always Input - Lowpass, therefore Highpass -here HighP3_16- can have an overshoot of up to 100%.
109+
- BandStop is always Input - Bandpass.
110+
- The output of DoubleLowpass3 and 4 resembes a delayed sine the higher the frequency is.
107111
- The output of LowP5_32 and DoubleLowP4_16 have almost the same amplitude but DoubleLowP4_16 is way more resembling a (delayed) sine.
108112
- The output of TripleLowP3_16 has a higher amplitude than DoubleLowP4_16 with both looking quite like a (delayed) sine.
113+
- The output of Lowpass8 behave like a integrator here, resulting in a triangle output, because it has a very low cutoff frequency.
114+
- The output of BiQuad tends to oscillate with its resonance frequency.
109115

110116
<br/>
111117

112118
Arduino Plotter output for a rectangle input signal with very low amplitude of +/- 20 where you see clipping effects due to the limited resolution of the used 16 bit math.
113-
![ArduinoPlotter output LowPass_LowInput](pictures/LowPass_LowInput.png)
114-
The Lowpass3_int32 and Lowpass5_int32 are 32 bit fixed point implementations for higher resolution which requires **4.2 &micro;s instead of the 1.8 / 2.5 &micro;s of 16 bit**.
119+
![ArduinoPlotter output SignificantFilters_LowInput](pictures/SignificantFilters_LowInput.png)
120+
The Lowpass3_int32 and Lowpass5_int32 are 32 bit fixed point implementations for higher resolution which requires **3.06 / 3.94 &micro;s instead of the 2.0 / 2.63 &micro;s of 16 bit**.
115121
<br/>
116122

117123
Arduino Plotter output for a sine input signal with a amplitude of +/- 100.
118124
![ArduinoPlotter output for LowPass with sine input](pictures/SignificantFilters_Sine.png)
119-
Note the **different attenuations** at different frequencies.
125+
Note the **different attenuations** at different frequencies and the **different phase shifts** of the filters.
120126
<br/><br/>
121127

122128
Arduino Plotter output for a triangle input signal with a amplitude of +/- 100.
123129
![ArduinoPlotter output triangle input](pictures/SignificantFilters_Triangle.png)
124-
Note that higher order filters and low pass with high shifts are almost a perfect sine.
130+
Note that higher order filters and Lowpass with high shifts / cutoff frequencies are almost a perfect sine.
125131

126132
Arduino Plotter output for a sawtooth input signal with a amplitude of +/- 100.
127133
![ArduinoPlotter output for sawtooth input](pictures/SignificantFilters_Sawtooth.png)
128134

135+
![ArduinoPlotter output for BiQuad](pictures/BiQuad.png)
136+
BiQuad filters / State Variable Filters (SVF) with no dampening (factor = 1) and dampening factor 1/4.
137+
You can see the oscillation with its resonance frequency.
138+
129139
Arduino Plotter output for a random input signal with a amplitude of +/- 100.
130-
![ArduinoPlotter output for lowpass on random input](pictures/LowPass1_3_5_8_Random.png)
140+
![ArduinoPlotter output for Lowpass on random input](pictures/LowPass1_3_5_8_Random.png)
131141

132142
<br/>
133143

134144
### Arduino Plotter output for a rectangle input signal with 4 different frequencies 42 Hz > 21 Hz > 10.5 Hz > 5.2 Hz
135145
Click on the pictures to enlarge them.
136146

137-
| All lowpass filters. | Highpass filters.<br/>Generated by subtracting lowpass from input. |
147+
| All Lowpass filters. | Highpass filters<br/>Generated by subtracting Lowpass from input. |
138148
| :-: | :-: |
139-
| ![AllLowPass](pictures/AllLowPass.png) | ![HighPassFilters](pictures/HighPassFilters.png) |
140-
| Bandpass and reject filters.<br/>Bandpass is generated by subtracting one lowpass from onother with different shift value.<br/>Reject filter is input minus bandpass.| High order Lowpass. |
141-
| ![BandPassAndReject](pictures/BandPassAndReject.png) | ![HighOrderLowPass](pictures/HighOrderLowPass.png) |
142-
| LowPass1, 3, 5 and 8.<br/>Lowpass8 behave like a integrator here, resulting in a triangle output. | LowPass3, 5 and 8.<br/>Comparison of different implementations with 16 bit integer, 32 bit integer and 32 bit float. |
143-
| ![LowPass1_3_5_8](pictures/LowPass1_3_5_8.png) | ![LowPass16And32Bit](pictures/LowPass16And32Bit.png) |
144-
| BiQuad filters with damping factor = 0.<br/>You see the overshoot. | |
145-
| ![BiQuadFilters](pictures/BiQuadFilters.png) | |
149+
| ![AllLowPass](pictures/AllLowPass.png) | ![Low and HighPass Filters](pictures/LowAndHighPass.png) |
150+
| Bandpass and Bandstop filters.<br/>Bandpass is generated by subtracting one Lowpass from another with different shift value.<br/>Bandstop filter is input minus Bandpass.| Selected Lowpass.<br/>Lowpass8 behave like a integrator here, resulting in a triangle output. |
151+
| ![BandPassAndBandstop](pictures/BandPassAndBandStop.png) | ![SelectedLowPass](pictures/SelectedLowPass.png) |
152+
| LowPass3, 5 and 8.<br/>Comparison of different implementations with 16 bit integer, 32 bit integer and 32 bit float. | |
153+
| ![LowPass16And32Bit](pictures/LowPass16And32Bit.png) | |
154+
146155

147156

148-
The floating point implementation of the 1/32 EMA filter takes **24 to 34 &micro;s**.<br/>
149-
There are convenience functions implemented for EMA filter, but normally it is better to implement it inline by e.g.
157+
The floating point implementation of the 1/32 EMA filter takes **20 to 34 &micro;s**.<br/>
158+
There are generic functions implemented for EMA filter, but normally it is faster to implement it inline by e.g.
150159

151160
```c++
152161
int16_t Lowpass3;
153162
int16_t Lowpass5;
154163
int32_t Lowpass5_int32;
155164
..
156-
Lowpass3 += ((InputValue - Lowpass3) + (1 << 2)) >> 3; // 1.8 us, alpha = 0.125, cutoff frequency 22.7 Hz @1kHz
157-
Lowpass5 += ((InputValue - Lowpass5) + (1 << 4)) >> 5; // 2.5 us, alpha = 1/32 0.03125, cutoff frequency 5.13 Hz @1kHz
158-
Lowpass5_int32 += ((((int32_t) InputValue) << 8) - Lowpass5_int32) >> 5; // Fixed point 4.2 us, value is Lowpass5_int32 >> 8
159-
Lowpass8_int32 += ((((int32_t) InputValue) << 16) - Lowpass8_int32) >> 8; // Fixed point 2.0 us because of fast shift, value is Lowpass8_int32 >> 16
165+
Lowpass3 += ((InputValue - Lowpass3) + (1 << 2)) >> 3; // 2.0 us, alpha = 0.125, cutoff frequency 22.7 Hz @1kHz
166+
Lowpass5 += ((InputValue - Lowpass5) + (1 << 4)) >> 5; // 2.6 us, alpha = 1/32 0.03125, cutoff frequency 5.13 Hz @1kHz
167+
Lowpass5_int32 += ((((int32_t) InputValue) << 8) - Lowpass5_int32) >> 5; // Fixed point 4.0 us, value is Lowpass5_int32 >> 8
168+
Lowpass8_int32 += ((((int32_t) InputValue) << 16) - Lowpass8_int32) >> 8; // Fixed point 2.13 us because of fast 8 bit shift, value is Lowpass8_int32 >> 16
160169
```
161170

162171
**!Attention!** The 16 bit implementations are limited to a **maximum input value of +/- 16383** for rectangular input (which is the worst input case). The reason is, that the term `InputValue - Lowpass3` must always fit into a 16 bit signed integer.

0 commit comments

Comments
 (0)