Drei Methoden zur Filterung von verrauschten ADC-Messungen mit dem Arduino

Messungen enthalten oft Rauschen. Das Rauschen ist ein Teil des Signals, welches für die weiter Verarbeitung nicht erwünscht ist. Neben dem elektrischen Rauschen entsteht dies auch durch reale Einflüsse auf den Sensor. Vibrationen durch den Motor verursachen Rauschen, wenn Sie z.B. die Beschleunigung eines Autos messen. Das Filtern ist eine Methode, um einen Teil des unerwünschten Signals zu entfernen und ein glatteres Signal zu erzielen.

Filter:

  1. Mittelwertbildung
  2. Mitlaufender Mittelwert
  3. Expotentialfilter

Über die “Serieller Plotter”-Funktion der Arduino IDE wird der Roh-wert als auch die gefilterten Ergebnisse visuell zum Vergleichen ausgegeben.

Generierung von Messdaten:

Um einige ‘verrauschte’ Daten zur Filterung zu erzeugen, wurde ein Thermistor an den Analogeingang 0 eines Arduino Uno angeschlossen. eine detaillierte Anleitung zum Anschluss des Thermistors ist hier hinterlegt.

Der Screenshot des “Seriellen-Plotter” zeigt die drei Algorithmen mit den Roh-werten zusammen. Ein kleiner Temperaturanstieg wurde durch Berühren des Thermistors erzeugt, um zu sehen, wie die verschiedenen Filter reagieren. Die blaue Linie zeigt die Rohtemperaturmessung.

1. Mittelwertbildung

Eine der einfachsten Möglichkeiten, verrauschte Daten zu filtern, ist die Mittelwertbildung.

Die Mittelwertbildung erfolgt durch Addition mehrerer Messungen, wobei die Summe durch die Anzahl der addierten Messungen dividiert wird. Je mehr Messungen Sie in den Durchschnitt einbeziehen, desto mehr Rauschen wird entfernt.  Der Durchschnitt von 101 Messungen wird aber nicht viel weniger verrauscht sein als der Durchschnitt von 100 Messungen.

Hier ist der Code zur Berechnung einer Mittelwertmessung:

float AverageTemperature = 0;
int MeasurementsToAverage = 16;                // Anzahl der in den Mettelwert aufgenommenen Messungen 

for(int i = 0; i < MeasurementsToAverage; ++i)
{
  AverageTemperature += MeasureTemperature();
  delay(1);
}
AverageTemperature /= MeasurementsToAverage;

Zu beachten ist die Verzögerung zwischen den einzelnen Messungen. Diese Verzögerung dient dazu, die Analog-Eingangszeit zwischen den einzelnen Messungen zu stabilisieren. Ohne sie wird der Durchschnitt tendenziell niedriger sein als der wahre Wert.

2. Mitlaufender Mittelwert

Ein Nachteil des Mittelwertfilters ist die Zeit, die für eine Messung benötigt wird. Die Messzeit kann bei Anwendungen mit geringer Leistungaufnahme wichtig sein. Der Arduino verbraucht viel mehr Energie, wenn er wach ist und Ihr Programm läuft, als wenn er sich im Standby-Modus befindet.

Eine Alternative zur Durchführung aller Messungen auf einmal und zur Mittelwertbildung besteht darin, jeweils eine Messung durchzuführen und sie zu einem laufenden Mittelwert zu addieren. Die Berechnung des Durchschnitts ist identisch: Alle Messungen werden Summiert und die Anzahl dividiert.

const int RunningAverageCount = 16;               // Anzahl der in den Laufenden-Mettelwert aufgenommenen Messungen
float RunningAverageBuffer[RunningAverageCount];
int NextRunningAverage;
 
void loop()
{
  float RawTemperature = MeasureTemperature();
 
  RunningAverageBuffer[NextRunningAverage++] = RawTemperature;
  if (NextRunningAverage >= RunningAverageCount)
  {
    NextRunningAverage = 0; 
  }
  float RunningAverageTemperature = 0;
  for(int i=0; i< RunningAverageCount; ++i)
  {
    RunningAverageTemperature += RunningAverageBuffer[i];
  }
  RunningAverageTemperature /= RunningAverageCount;
 
  delay(100);
}

Dies führt zu einer wesentlich langsameren Reaktion auf Änderungen aufgrund der 100 ms Verzögerung zwischen den einzelnen Messungen. Mit anderen Worten, es dauert nur 16 ms für den Durchschnittsfilter, um 16 neue Messungen zu erhalten, aber es dauert 1,6 Sekunden (16 x 100 ms) für den laufenden Durchschnitt, um 16 neue Messungen zu erhalten. Er reagiert also langsamer auf Veränderungen.

Wenn die Verzögerung am Ende der Schleife von 100 ms auf 1 ms reduziert würde, wäre die Antwort des laufenden Mittelwertes gleich dem einfachen Mittelwert. Die längere Verzögerung zwischen den Messungen ist jedoch die Zeit, in der der Arduino in einen Energiesparmodus versetzt werden kann.

Der laufende Mittelwert scheint eine gute Alternative zu einem einfachen Mittelwert zu sein, um eine glattere Ausgabe zu erzielen und den Arduino an anderen Dingen arbeiten zu lassen. Aber es hat eine große Schattenseite: den Speicherverbrauch.

Da für den laufenden Mittelwert zur Berechnung eine Historie vorzuhalten ist, wird das Filtern vieler Messungen schnell unpraktisch. Der Arduino Uno hat nur 2k RAM.

Vergleich der ersten beiden Filter mit dem Roh-wert:

  1. Blau: gezackt sind die Rohwerte
  2. Rot: 1. Filter Mittelwert
  3. Grün: 2. filter Mitlaufender Mittelwert

3. Exponentialfilter

Der letzte Filter ist ein rekursiver Filter. Ein rekursiver Filter bildet einen neuen, geglätteten Wert (y_n) aus dem letzten geglätteten Wert (y_{n - 1}) und einer neuen Messung (x_n):

y_n=w*w_n+(1-w)*y_{n-1}

Die Stärke der Glättung wird über einen Gewichtungsparameter (w) gesteuert. Das Gewicht ist ein Wert zwischen 0% und 100%. Wenn das Gewicht hoch ist (z.B. 90%), glättet der Filter die Messungen nicht sehr stark, sondern reagiert schnell auf Veränderungen. Ist das Gewicht gering (z.B. 10%), glättet der Filter die Messungen stark, reagiert aber nicht sehr schnell auf Veränderungen.

Dieser filter hat folgende Vorzüge:

  • es braucht nicht viel Speicher (gerade genug, um die letzte Messung zu speichern)
  • mit einem einzigen Parameter (dem Gewicht) kann gesteuert werden, wie stark gefiltert wird
  • es funktioniert gut in batteriebetriebenen Anwendungen, da nicht viele Messungen auf einmal durchzuführen sind

Die für diesen Filter verwendetet Bibliothek lautet “Filter“:

#include "Filter.h"
 
ExponentialFilter ExpFilter(20, 0); // Erstellung des Filters mit der Gewichtung 20 und dem Initial-Wert 0
 
loop
{
  float RawTemperature = MeasureTemperature();
  Exp.Filter(RawTemperature);
  float SmoothTemperature = Exp.Current();

  Serial.print(RawTemperature);                           // Roh-Wert ausgeben
  Serial.print(" ");                                      // leerzeichen
  Serial.println(SmoothTemperature);                      // Gefilterten Wert ausgeben
} 

Vergleich des Exponential-Filters mit dem Roh-wert: