Störrige Sensorwerte mit einer Durchschnittsberechnung glätten.

Manche Sensordaten fluktuieren, relativ stark. Bei einigen Projekten kann dies zu Fehlauslösern führen und so eine Anbindung an eine Funktion erschweren. Abhilfe kann in diesem Fall eine Durchschnittsberechnung schaffen. Der Durchschnitt wird auch als arithmetisches Mittel bezeichnet und ist einfach auszurechnen. Den Durchschnitt von zwei oder mehreren Zahlen berechnet man, indem alle Zahlen addiert und die Summe durch die Anzahl der Zahlen dividiert.

 

Beispielsweise wird der hier besprochene Algorithmus für den Durchschnitt in diesem Tutorial A08|04 Der Piezo-Vibrationssensor zur Glättung der Eingangswerte genutzt. Der Piezo-Sensor misst Vibrationen und schickt die Ergebnisse zum seriellen Monitor. Wird eine Schwellgrenze überschritten (ein Klopfen mit einer gewissen Intensität) taucht in der Liste das Wort Knock! auf. Wie du in der unteren Abbildung sehen kannst, taucht Knock! zweimal auf, obwohl nur einmal auf den Sensor geklopft wurde. Dieses Verhalten liegt an der Natur des Sensors und muss korrigiert werden.

 

Machen wir zum Durchschnitt eine kleine Beispielrechnung. Siehe hierzu untere Abbildung. Angenommen, wir nehmen acht aufeinander folgende Sensorwerte und betrachten ihre Größen. In unserem Beispiel stellt die Höhe der Balken den Messwert dar. Ein Kästchen sind gleich 100 Einheiten. Demnach gleicht ein Balken mit einer höher von 3 Kästen dem Wert 300. Daraus ergeben sich folgende Messungen: #1 = 200, #2 = 200, #3 = 200, #4 = 500, #5 = 600, #6 = 400, #7 = 300 und #8 = 200. Summiert man alle acht Proben ergibt das 2600. Der Durchschnitt aller acht Messungen ergibt 325 (Summe/Anzahl der Messungen). Das heißt, wir fassen alle acht Werte zu einer zusammen. Das heißt natürlich, dass die ursprüngliche Messung etwas an Präzision verliert.

 
 

Der Aufbau

In diesem Code Snippet benutzen wir ein Piezo-Vibrationsmodul. Aber du kannst jedes andere Sensormodul ähnlicher Natur verwenden, welches die analogRead()-Funktion benötigt.

 

Der Sketch für den Durchschnittswert

Die Hauptfunktion des Sketches ist es, eine LED ein- oder auszuschalten, sobald ein Piezo-Vibrationssensor einer Schwingung mit gewisser Stärke ausgesetzt wird. Das heißt, wir bestimmen einen Schwellwert, der z.B. ein starkes oder schwaches Klopfen definieren kann. Nun ist es so, dass der Vibrationssensor bei nur einem Klopfen oft multiple Schwellwert-Übergänge aufweist (siehe untere Abbildung).

In unserem Beispiel wird zweimal in kürzester Zeit die Schwellgrenze (Knock!) erreicht, obwohl nur einmal geklopft wurde. Die LED wird fälschlicherweise ein- und sofort wieder ausgeschaltet. Die eigentliche Funktion wäre jedoch gewesen: erstes Mal Klopfen > LED ein, zweites Mal Klopfen LED aus.

Um eine Fehlauslösung zu vermeiden, liest der Sketch zehn Sensorwerte ein, summiert sie und dividiert sie durch die Anzahl er genommenen Stichproben. Das Ergebnis wird weitaus verlässlicher. Aufgrund der Übersichtlichkeit wurde der Teil für die Ansteuerung der LED ist in eine externe Funktion verlagert.

/********************************************************
Den Durchschnittswert eines Sensorsberechnen
Mr Robot UXSD / www.mrrobotuxsd.com
*********************************************************/


//Variablen für Durchschnittsberechnung----------------------------------------
const int sensorPin = A1; //der Piezo ist an den analogen Pin 0 angeschlossen

//Variablen um den Durchschnittswert zu berechnen
#define numSamples 10 //die Anzahl der Stichproben
int samples[numSamples]; //das Array in dem die Stichproben platziert werden
float average; //der Durchschnittswert aller Stichproben


//Variablen für die LED--------------------------------------------------------
const int ledPin = 13;      //LED angeschlossen an Digitalpin 13
const int knockSensor = A0; //der Piezo ist an den analogen Pin 0 angeschlossen

//Schwellenwert, um die Klopfstärke zu bestimmen
const int threshold = 8;  

//Variable zum Speichern des vom Sensor-Pin gelesenen Wertes
int sensorReading = 0; 

//Variable zur Speicherung des letzten LED-Status, um das Licht umzuschalten
int ledState = LOW; 


void setup() 
{
  Serial.begin(9600);  //intialisiere die serielle Kommunikation
}


void loop()
{
  //10 Proben nacheinander nehmen, mit einer leichten Verzögerung
  for (int i=0; i< numSamples; i++) 
  {
   samples[i] = analogRead(sensorPin); //Array mit Sensorwerten füllen
   delay(4); //leichte Verzögerung
  }

  //Den zuvor berechneten alten Durchschnitt resetten
  average = 0;

  //Den Durchschnitt aller Stichproben berechnen
  for (int i=0; i< numSamples; i++) 
  {
     average += samples[i]; //summiere alle im Array enthaltenen Werte
  }

  //den Durchschnitt berechnen: teile die Summe aller Werte durch die Anzahl
  average /= numSamples; 

  //Sende das Ergebnis zum seriellen Monitor
  Serial.println(average); 

  
  //Verzögerung, um eine Überlastung des Puffers der seriellen 
  //Schnittstelle zu vermeiden

  ledOnOff(); //externe Funktion zur Steuerung der LED
  
  delay(1);  
}



void ledOnOff()
{
   //wenn der Sensormesswert größer als der Schwellenwert ist:
  if (average >= threshold) 
  {
    //Schaltet den Status des ledPin um:
    ledState = !ledState;
    
    //ob HIGH oder LOW, ledState wird hier eingesetzt
    digitalWrite(ledPin, ledState);
    
    //sendet die Zeichenkette "Knock!" zurück an den Computer, 
    //gefolgt von einem Zeilenumbruch > ln  
    Serial.println("Knock! ");
  }

}
 

Der Sketch im Detail

Im Folgenden gehen wir nicht auf die LED-Funktion ein, sondern konzentrieren uns auf den Durchschnitt. Mit numSamples 10 bestimmen wir, wie viele Werte für den Durchschnitt eingelesen werden sollen.

#define numSamples 10 //die Anzahl der Stichproben

Je mehr Werte wir einlesen, umso länger dauert die Berechnung. Aber eine zu lange Zahlenprobe kann dazu führen, dass einer von zwei aufeinander folgenden, gewollten schnellen Schwellenwert-Ausschlägen untergeht. Wird der zuvor definierte Schwellenwert zweimal schnell betätigt, kann es passieren, dass beide Signal zusammengeführt und als eine Betätigung erkannt werden. Siehe hierzu die untere Abbildung. Wird zum Beispiel zweimal sehr schnell auf den Klopfsensor geschlagen, werden eventuell beide Ereignisse als eines ausgegeben. Um dies zu verhindern, sollte die Länge der Messung entsprechend kurz oder lang sein. Natürlich sollte die Zahlenkette auch nicht zu kurz sein, da wir wieder Probleme mit der Datenfluktuation bekommen. Die Messlänge muss für die jeweilige Sensorart experimentell bestimmt werden.

Eine zu lange Messung kann zu Signalverlusten führen

 

Mit int samples[numSamples] initialisieren wir das Array, in dem alle eingelesenen Werte gespeichert werden sollen. In der Variablen float average; berechnen und speichern wir den Durchschnittswert aller Stichproben.


int samples[numSamples]; //das Array in dem die Stichproben platziert werden
float average; //der Durchschnittswert aller Stichproben
 

In der loop-Funktion nehmen wir zunächst mithilfe der for-Schleife unsere 10 Proben (#define numSamples 10). In jedem Schleifendurchgang wird in samples[i] eine fortlaufende Nummer eingesetzt und damit 10x den Sensor abgefragt: analogRead(knockSensor); => samples[0], samples[1], samples[2], samples[3], samples[4], usw.

  //10 Proben nacheinander nehmen, mit einer leichten Verzögerung
  for (int i=0; i< numSamples; i++) 
  {
   samples[i] = analogRead(sensorPin); //Array mit Sensorwerten füllen
   delay(4); //leichte Verzögerung
  }

Zu jedem dieser samples werden die aktuellen Sensorwerte gespeichert. Dieser Prozess läuft im Mikrocontroller ab und ist für uns nicht sichtbar. Aber der Speicherprozess könnte so aussehen:
samples[0] = 200 //erster Schleifendurchgang mit dem entsprechenden Wert
samples[1] = 200 //zweiter Schleifendurchgang mit dem entsprechenden Wert
samples[2] = 200 //dritter Schleifendurchgang mit dem entsprechenden Wert
…usw.

Das sehr kurze delay(4); wird als künstliche Verzögerung aus Stabilitätsgründen eingesetzt. Zusätzlich kannst du mit dem delay()-Wert auch dein Ergebnis beeinflussen. Du bestimmst mit der Länge, wie viel Abstand die einzelnen Messungen zueinander haben sollen. Du kannst das Ergebnis streuen oder bündeln. Wenn du streust, wird die Messung glatter und ruhiger - dafür etwas weniger präziser. Falls du bündelst, wird das Ergebnis zwar genauer, dafür flattern die Ergebnisse und es kommt zu Fehlmessungen.

delay(4);

Unten ist eine Darstellung, die dir die Auswirkungen der Streuung und Bündelung visualisieren soll. Wenn du dir die Balkengrafen ansiehst, die die eingelesenen Sensorwerte darstellen sollen, erkennst du 3 Piecks. Es sind 3 Werte, die den Schwellenwert überschritten haben und das Knock-Signal an den seriellen Monitor senden. In Wirklichkeit wurde nicht 3x auf den Sensor geklopft, sondern nur einmal. Der Sensor zeigt aber trotzdem mehrere Ausschläge an. Das liegt an der Natur des Sensors.

In der Messung A werden immer gleichzeitig 8 Messungen ohne Überspringen gespeichert und der Durchschnitt ausgerechnet. Es wird also jeder Balken in Reihe gemessen. Wenn das Programm die nächsten 8 Balken einließt erhalten wir zum einen eine Fehlauslösung, da die drei Knocks in Realität EIN Klopfen sind.

Eine bessere Lösung stellt die Messung B dar. Die 8 Messungen werden gestreut, indem jeder zweite “Balken” gemessen wird. Dadurch vermeiden wir Fehlauslöser und die Messung wird ruhiger. Du musst experimentell herausfinden, wie viele Stichproben du brauchst und welche Auflösung die Streuung haben soll.

 

Die Variable average für die Berechnung des Durchschnitts müssen wir in jedem Schleifendurchgang mit average=0; auf 0 setzen (reset). Schließlich wollen wir ja nicht mit den alten Werten aus der letzten Schleife arbeiten.

  //Den zuvor berechneten alten Durchschnitt resetten
  average = 0;
 

In der for-Schleife rufen wir alle bereits eingelesenen Werte aus der vorherigen for-Schleife samples[i] = analogRead(knockSensor); aus und summieren sie alle mit der Zeile average += samples[i]; in der Variable average zusammen.

//Den Durchschnitt aller Stichproben berechnen
  for (int i=0; i< numSamples; i++) 
  {
     average += samples[i]; //summiere alle im Array enthaltenen Werte
  }
 

Es fehlt jetzt nur noch ein Schritt: Sobald die for-Schleife verlassen wurde, wird average mit der Anzahl der Lesevorgänge dividiert average /= numSamples;
=> Die Summe aller Werte durch die Anzahl der Werte. Hierdurch bekommen wir den Durchschnittswert für 10 eingelesene Sensorwerte.

//den Durchschnitt berechnen: teile die Summe aller Werte durch die Anzahl
average /= numSamples;
 

Oft werden sehr kleine Verzögerung am Ende des Sketches eingebaut, um eine Überlastung des Puffers der seriellen Schnittstelle zu vermeiden.

//Verzögerung, um eine Überlastung des Puffers der seriellen 
//Schnittstelle zu vermeiden
delay(1);
 

Verwante Themen

A08|01 Der Klopfsensor

 

Falls du die hier beschriebenen Elektronik-Module nicht hast, kannst du sie in meiner Einkaufsliste finden. Warum ich selber hauptsächlich mit Modulen der Marke Keyestudio arbeite, erläutere ich unter diesem Blog-Artikel.

In dieser Übersicht äußere ich Empfehlungen aller Art.

Hier kommst du wieder zur Übersicht aller Module.