Low-pass filter

From Helpful
Jump to: navigation, search


Digital

EWMA

These are primarily notes
It won't be complete in any sense.
It exists to contain fragments of useful information.

The exponentially weighed moving average (EWMA) is the name for what is probably the easiest realization of the (first-order) lowpass on discrete time-domain data. (in the grander theory of filters, EWMA is a first-order infinite impulse response filter(verify))


Intuitively, the moving local average will respond sluggishly to the quick changes (high-frequency content) while still following the overall tendency of the signal (lower-frequency content).

(EWMA has the word 'exponential' in it because each new filtered output effectively uses all of the values before it, it focuses on the most recent and effectively old ones with exponentially decaying weights. See the wikipedia links for more discussion.)


It is weighed by a variable (see α) to be able to vary its sensitivity.

In cases of not-so-regular sampling α is only related to speed of adaptation (per new sample) than to frequency content.
It's still relevant, but the notes on frequency content apply less strictly.
In applications that sample at a regular interval, you can relate α to frequency content.
For example sound. Also e.g. some sensor systems, where it's optional, but can sometimes be be more meaningful this way.


When you want to calculate a filtered output series from an input series, you can loop through a list doing something like:

filtered_output[i] = α*raw_input[i] + (1-α)*filtered_output[i-1]

...or the equivalent:

filtered_output[i] = filtered_output[i-1] + α*(raw_input[i]-filtered_output[i-1])

The latter form may feel more intuitive/informative: the change in the filtered output is proportional to the amount of change, and to to the filter strength α.

Both may help consider how using the recent filtered output gives the system a sort of inertia:

  • A smaller α (larger 1-α in the former) (also makes for larger RC) means the output will adjust more sluggishly, and should show less noise (since the cutoff frequency is lower(verify)).
  • A larger α (smaller 1-α) (smaller RC) means that the output will adjust faster (have less inertia), but be more sensitive to noise (since the cutoff frequency is higher(verify))


Since the calculation is local and in one direction, you can choose to keep only the latest value. This make sense e.g. when your primarily goal is presenting a filtered version of a crude sensor, or noisy influences

current_output  = α*current_input + (1-α)*previous_output
previous_output = current_output


You often want implement the above in floating point, even if you return ints, to avoid problems caused by rounding errors.

Most of the problem: when alpha*difference (itself a floating multiplication) is less than 1, it becomes 0 in a (truncating) cast to an integer.
For example, when alpha is 0.01, then a signal differences smaller than 100 will make for an adjustment of 0 (via integer truncation), so the filter would never adjust to the actual value.
You can do it in int only, it just takes more care


Graphical example

A screenshot from arduinoscope - a moving graph, with the newest samples on the left.

The top signal is the input (a few seconds's worth of an ADC sampling from a floating pin, with a finger touching it every now and then). The ones below are EWMA'd versions of it, at increasing strengths.


Some things to note about it:

  • the shape: a slowish exponential adjustment to step-like responses, much like a charging capacitor - fast intially, then slower and slower (because change is proportional to difference)
  • the suppression of single fast large spikes/deviations
  • that it is certainly possible to filter too hard (although that judgment depends a lot on nature of the content, and your purpose).
  • the filtered version of the the full-range oscillation comes out halfway not so much because of filtering, but also largely because most raw samples around there are clipped/saturated at both ends of the ADC's range. (This is essentially false signal. you don't want clipping)


On α, τ, and the cutoff frequency

α is the smoothing factor, theoretically between 0.0 and 1.0.

In practice usually <0.2 and often <0.1 or smaller, because above that you're barely doing any filtering.


In DSP it is often based on:

  • Δt, regularly written dt: the time interval between samples (reciprocal of sampling rate)
  • a choice of time constant τ (tau), a.k.a. RC (the latter seems a reference to a resistor-plus-capacitor circuit, which also does lowpass. Specifically, RC gives the time in which the capacitor charges to ~63% of the potential difference[1])(verify)


Specifically:

α = dt / (RC + dt)

In practice you'll often choose an RC that is at least a few multiples of dt, which means that α is on the order of 0.1 or less. (if using an RC close to dt you'll get alphas higher than ~0.5, which again barely does anything)



When applied to samples that come from strict intervals (e.g. for sound), RC's relation to content frequencies is well defined.

For example, the knee frequency (where it starts falling off, approximately the cutoff frequency) is:

1 / (2*pi*RC)


For example, when RC=0.002sec, the knee/cutoff is at ~80Hz.

At 200Hz, 2000Hz, and 20000Hz sampling, that makes for alphas of 0.7, 0.2, and 0.024, respectively. (At the same sampling speed: the lower alpha is, the slower the adaptation to new values, and the lower the effective cutoff frequency)(verify)

For a first-order lowpass:

  • at lower frequencies, the response is almost completely flat,
  • at this frequency the response is -3dB (has started declining in a soft bend/knee)
  • at higher frequencies it it drops at 6db/octave (=20dB/decade)

(Higher-order variations fall off faster and have a harder knee)


Note there will also be a phase shift, which lags behind the input. It depends on the frequency; it starts earlier than the amplitude falloff, and will be -45 degrees at the knee frequency(verify).

Arduino example

Note: This is a single-piece-of-memory version, for when you're interested only in the (latest) output value.

// keeps values for each analog pin, assuming a basic arduino that muxes 6 pins. 
//   you can save a few bytes of memory by putting this on lower-numbered pins and lowering...
#define LOWPASS_ANALOG_PIN_AMT 6
 
float   lowpass_prev_out[LOWPASS_ANALOG_PIN_AMT], 
         lowpass_cur_out[LOWPASS_ANALOG_PIN_AMT];
int        lowpass_input[LOWPASS_ANALOG_PIN_AMT];
 
 
int adcsample_and_lowpass(int pin, int sample_rate, int samples, float alpha, char use_previous) {
  // pin:            arduino analog pin number to sample on   (should be < LOWPASS_ANALOG_PIN_AMT)
  // sample_rate:    approximate rate to sample at (less than ~9000 for default ADC settings)
  // samples:        how many samples to take in this call  (>1 if you want smoother results)
  // alpha:          lowpass alpha
  // use_previous:   If true,  we continue adjusting from the most recent output value.
  //                 If false, we do one extra analogRead here to prime the value.
  //   On noisy signals this non-priming value can be misleading, 
  //     and with few samples per call it may not quite adjust to a realistic value.
  //   If you want to continue with the value we saw last -- which is most valid when the
  //     value is not expected to change significantly between calls, you can use true.
  //   You may still want one initial sampling, possibly in setup(), to start from something real.
 
  float one_minus_alpha = 1.0-alpha;
  int micro_delay=max(100, (1000000/sample_rate) - 160); // 160 being our estimate of how long a loop takes 
               //(~110 for analogRead at the default ~9ksample/sec,  +50 grasped from thin air (TODO: test)  
  if (!use_previous) { 
    //prime with a real value (instead of letting it adjust from the value in the arrays)
    lowpass_input[pin] = analogRead(pin);
    lowpass_prev_out[pin]=lowpass_input[pin]; 
  }
 
  //Do the amount of samples, and lowpass along the way  
  int i;
  for (i=samples;i>0;i--) {
    delayMicroseconds(micro_delay);
    lowpass_input[pin] = analogRead(pin);
    lowpass_cur_out[pin] = alpha*lowpass_input[pin] + one_minus_alpha*lowpass_prev_out[pin];
    lowpass_prev_out[pin]=lowpass_cur_out[pin];
  }
  return lowpass_cur_out[pin];
}
 
 
int resulting_value;
 
void setup() {
   Serial.begin(9600);
 
   //get an initial gauge on the pin. Assume it may do some initial weirdness so take some time
   //  Takes  approx 300ms (300 samples at approx 1000 samples/sec)
   resulting_value = adcsample_and_lowpass(0, 1000, 300, 0.015, false); //0 meaning A0
}
 
void loop() {
   // updates can often be shorter, possibly much more than this
   resulting_value = adcsample_and_lowpass(0, 1000, 150, 0.015, true);  
   Serial.println(resulting_value);
}

Semi-sorted

Aliasing and oversampling

See also


Other filters you can use include:

Analog

Basic RC filter