PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Gleitender Mittelwert Arduino



TobiasE
21.02.2016, 15:40
Hallo,

ich möchte mit dem Arduino den gleitenden Mittelwert einer Datenreihe mithilfe von einem Array ausrechnen.
Es sind ca. 6000 Werte, wobei immer der Durchschnitt der letzten 255 Werte berechnet werden soll.
Da ich mich mit Arrays nicht so auskenne, bräuchte ich auch den Code dazu.

Vielen Dank für eure Hilfe.

Grüße
TobiasE

TobiasE
28.02.2016, 21:36
So,
hab es jetzt n paar Stündchen probiert und bin auf folgendes gekommen:

int werte[254];
int i,j = 0;
int summe;

void setup() {
Serial.begin(9600);
}

void loop() {
werte[i] = analogRead(0);
i++;
if(i = 255) {
i = 0;
}
delay(100);
durchschnitt();
summe = 0;
j = 0;

}

void durchschnitt() {
while(j < 255) {
summe += werte[j];
j++;
}
Serial.println(summe / 255);
}

Es gibt aber falsche Werte aus, die unmöglich stimmen können.
Wo ist der Fehler?

thx

Peter(TOO)
29.02.2016, 01:49
Hallo,

int werte[254];

Hat 254 Plätze, es sollten aber 255 sein!
In werte[255] steht ein Zufallswert.
Also
int werte[255];

Angesprochen wird das Array dann mit werte[0] bis werte[254]

Beim ersten Aufruf von durchschnitt() steht in summe auch ein Zufallswert.

Tipp:

#define BUFFER_SIZE 255
int werte[BUFFER_SIZE];


for (j=0; j < BUFFER_SIZE; j++)
{
// mach was
}

Variante:

int werte[255];
#define BUFFER_SIZE (sizeof(werte) / sizeof(werte[0]) )

// sizeof(werte) liefert den Speicherplatz für das ganze Array in Bytes
// sizeof(werte[0]) liefert den Speicherplatz für ein einzelnes Array-Element in Bytes

// Die Klammer um den ganzen Ausdruck ist prinzipiell nicht nötig, kann aber schwer zu findende Bugs verhindern.
// z.B.
// #define BUFFER_SIZE sizeof(werte) / sizeof(werte[0])
// ++BUFFER_SIZE
// würde der Compiler nach
// (sizeof(werte)+1) / sizeof(werte[0])
// auflösen und eben nicht nach
// (sizeof(werte) / sizeof(werte[0]))+1



Das passt dann immer, auch wenn du die Array-Grösse mal ändern willst :-)

MfG Peter(TOO)

Thie
29.02.2016, 15:28
...und du solltest auf deine "summe" Obacht geben.
Die ist bei dir wohl vom Type int, somit kann die auf den normalen Arduinos nur rund 32000 groß werden.
Deine summe kann aber bis zu 1023 * 255 = 260865 groß werden!
Dann halt lieber unsigned long für die summe nehmen, dann hat das Platz!

HaWe
29.02.2016, 16:14
außer, sein Arduino hat einen ARM Prozessor (Zero, Due), dann hat int von vornherein die Größe 32-bit. 8-)

Sisor
29.02.2016, 20:23
Und noch ein Klassiker im Code:

if(i = 255) { i=0; }
Es ist wohl gemeint:

if(i == 255) ...
Dieser Fehler verursacht, dass i erstmal auf 255 gesetzt wird. Damit ist i ungleich null, somit der Ausdruck in der Klammer wahr, und der Code i=0 wird ausgeführt. i ist also nachher immer null.
Tipp:
Bei 256 Werten ein Byte als Typ benutzen. Wenn der Wert dann 255 ist und um eins erhöht wird, fängts automatisch wieder bei null an.

TobiasE
29.02.2016, 23:04
Erstmal vielen Dank für die Hilfe.
Das ist der fertige Code:

int werte[255];
int i,j = 0;
unsigned long summe;

void setup() {
Serial.begin(9600);
}

void loop() {
werte[i] = analogRead(0);
i++;
if(i == 255) {
i = 0;
}
delay(100);
durchschnitt();
}

void durchschnitt() {
summe = 0;
j = 0;
while(j < 255) {
summe += werte[j];
j++;
}
float schnitt = summe / 255;
Serial.println(schnitt);
}

Klappt alles wie es soll!
Nach 25,5 Sekunden wird dann der korrekte Mittelwert angezeigt.
Danke nochmal

TobiasE

HaWe
29.02.2016, 23:21
warum delay(100) ? wenn das so sein muss, ok!
aber sonst: analogRead braucht nur ca. 1ms zum auslesen, und dafür wartet es von alleine!


- HaWe

- - - Aktualisiert - - -

ps
ein "gleitender Durchschnitt" geht aber anders ... und schneller.... und ganz ohne Arrays...!

Peter(TOO)
29.02.2016, 23:51
Hallo,

ein "gleitender Durchschnitt" geht aber anders ... und schneller.... und ganz ohne Arrays...!
Ohne Array?
Ich habe das immer mit einem Ringbuffer gemacht.


Pseudo Code:
Summe -= ringbuffer[oldest];
Summe += new_data;

ringbuffer[oldest] = new_data;
ringbuffer_index++;


MfG Peter(TOO)

HaWe
01.03.2016, 09:13
ja, Ringpuffer ist natürlich perfekt und auch perfekt auf die Stichprobengröße angepasst.

Angenähert geht es aber auch mit dem gleitenden Mittelwert per Lowpass-Filter, damit bekommt man bis auf ein paar unerhebliche Nachkommastellen das gleiche Ergebnis:

float ETA=0.95; // ETA anfangs festlegen, ggf. noch justieren, kann auch bei 0.90 oder 0.99 liegen=> rumprobieren!
Mittelwert = ETA*(Messwert) + (1-ETA)*Mittelwert;

Dann spart man sich die extrem langsamen Array-Operationen und den Speicher dafür. Allerdings sind es dann nicht exakt 255 Werte, die gemittelt werden, sondern nur größenordnungsmäßig was in der Art.
Aber:
man kann ja auch die obige Formel benutzen, und dabei bis 255 zählen,
dann den Mittelwert ablesen,
und dann wieder neu starten.
Aber: er gleitet, mit minimalem Aufwand. ;)

Klebwax
02.03.2016, 09:50
Die Frage ist, was man wirklich machen will. Meist geht es darum, ein verrauschtes Signal etwas zu glätten. In HW würde man da einen Tiefpass einsetzen. Irgendwie hat sich aber die Meinung verbreitet, ein gleitender Mittelwert wäre ein Tiefpass. Mir fiel bei einem Thread auf Mikrocontroller.net auf, das jemand den gleitenden Mittelwert als Kerbfilter für 50Hz Störungen einsetzt. Das ist dann sicher kein Tiefpass. An anderer Stelle hat dann der User Yalu X. aus ebendiesem Forum den Frequenzgang eines gleitenden Mittelwertes, nennt man wohl auch FIR Filter, zusammen mit dem Frequenzgang eines Tiefpasses, ein IIR Filter, geplottet. Hier das Bild:
31399

Die rote Kurve ist der aus der Analogtechnik gut bekannte Tiefpass, die grüne der gleitende Mittelwert. Man erkennt dort deutlich das Kerbfilter zusammen mit einem Tiefpass.

Der gleitende Mittelwert hat auf jeden Fall zwei Nachteile: er kostet viel mehr Prozessorresourcesn, Speicher sowie Rechenzeit, und er verursacht, hier nicht dargestellt, Phasensprünge im Signal. Wenn man also das Kerbfilters nicht braucht, und das macht nur Sinn, wenn man die Abtastrate und Buffergröße genau an das Störsignal fester Frequenz anpasst, ist ein einfacher Tiefpass besser.

Die Rechnung eines Tiefpasses ist einfach, hier etwas Pseudocode:

Output = Oldvalue * n + Newvalue * (1 - n) // wobei n < 1
Oldvalue = Output

In Integerrechnung für n = 0,5 gehts noch einfacher

Output = (Oldvalue + Newvalue) / 2 // kann auch ein shift right werden
Oldvalue = Output

n zusammen mit der Abtastrate gibt die Grenzfrequenz des Tiefpasses an. Man muß bei der Integerrechnung nur darauf achten, daß beim Teilen nicht unten zuviele Bits verloren gehen. Man sollte also die Messwerte vorher nach oben im Bereich des Integers verschieben. Diese einfache Rechnung kann man leicht z.B. in der Auslesefunktion des ADC unterbringen.

MfG Klebwax

Peter(TOO)
02.03.2016, 23:31
Hallo Klebwax,

n zusammen mit der Abtastrate gibt die Grenzfrequenz des Tiefpasses an. Man muß bei der Integerrechnung nur darauf achten, daß beim Teilen nicht unten zuviele Bits verloren gehen. Man sollte also die Messwerte vorher nach oben im Bereich des Integers verschieben. Diese einfache Rechnung kann man leicht z.B. in der Auslesefunktion des ADC unterbringen.

Ich habe oft einige Zeit aufgewendet um FP-Operationen aus meinen Projekten zu eliminieren.
Spart einiges an Speicher (weil die FP-Bibliothek nicht eingebunden werden muss) und viel Rechenzeit.

Da hatten sich einige Kunden gewundert, weil alle anderen Entwickler meinten, dies wäre nur mit neuer Hardware zu machen.

Zum Skalieren/Abgleichen von analogen Eingängen habe ich meistens mit Brüchen gearbeitet, also
(x * k1) / k2;
Aufpassen muss nur, dass man bei der Multiplikation mit k1 keinen Überlauf bekommt, aber mit den richtigen Werten für k1 und k2 bekommt man die krümmsten Faktoren hin. Intern habe ich dann z.B. mit 1/100 Grad gerechnet, dann braucht man auch keine Komma-Zahlen. Das Komma gabs dann nur bei der Ausgabe und da ist es nur Zeichenmanipulation.

Wie schon gesagt wurde, muss man etwas mit den Wertbereichen aufpassen, damit man auf keinen Fall Under-/Overflows erzeugt. Aber auch FP hat da ihre Tücken!

Da hatten ja auch die Amis mit ihren Patriot-Raketen Probleme.
Da wurde die Zeit als FP mit einfacher Genauigkeit verarbeitet, das sind um die 7 Stellen. Gibt man die Zeit in Sekunden an, geht das so 115 Tage gut, dann hat die Zeit keine Auflösung von 1 Sekunde mehr.


float zeit = 0.0;

while (-1) zeit = zeit + 1.0;

Hier erreicht zeit einen endlichen Wert und verändert sich dann nicht mehr. Die Laufzeitbibliothek erzeugt hier dann aber auch keinen Fehler-Trap :-(

Weitere Probleme ergeben sich bei der Konvertierung, endliche Dezimalbrüche ergeben teilweise unendliche FP-Werte, werden also zwangsweise gerundet.

Rundungsfehler können bei Buchhaltungsprogrammen Probleme verursachen, die Bilanz geht dann nicht wirklich auf.
Unter CP/M gab es den CBASIC-Compiler, dieser hat intern mit 14-Digits BCD gerechnet. War etwas langsamer als andere BASIC, auch weil es P-Code erzeugt hat. Aber bis 999'999'999'999.99 (999 Mrd.) rechnet dies also noch auf den Cent genau. Damit kann dann auch Billy sein Vermögen auf den Cent genau verwalten :-)

Bis Anfang der 80er Jahre habe ich alle technischen Berechnungen mit CBASIC gemacht.


Und wie du schon geschrieben hast, zusammen mit Abtastraten, ergeben sich dann die lustigsten Frequenzgänge!

Wenn man ein 50Hz-Signal alle 20ms abtastet, erhält man eine konstante Spannung, welche proportional zur Phasenverschiebung der beiden Frequenzen ist. Nicht unbedingt das was man wollte :-(

Wenn man nicht genau aufpasst, kann es mit dem gleitenden Mittelwert sogar schlechter werden.
Bei deinem Diagramm liegt der -6dB-Punkt (analoger Tiefpass) bei etwa 2.25kHz. Der gleitende Mittelwert unterdrückt diese Frequenz aber fast gänzlich.

MfG Peter(TOO)

HaWe
03.03.2016, 09:01
was redet ihr denn von Abtastfrequenz?
wieso Abtastfrequenz?
wir reden doch hier nicht von einem ADC-Abtaster mit RC-Glied wie in der Audio-Technik!
Ein Tiefpassfilter in der Statistik ist ein Rauschunterdrückungsfilter, genau wie ein arithmetisches Mittel, ein Median, oder ein Kalmanfilter, da geht es nicht um Abtastfrequenz sondern Trägheit, wodurch der Filter automatisch neu hereinkommende Werte mehr oder weniger "glättet", um Sensorrauschen zu minimieren, genau wie es auch andere Filter tun. Und die Werte werden 1:1 genau dann verarbeitet, wenn sie "hereinkommen".

Schmeißt doch hier nicht alles durcheinander, da wird man ja ganz wirr!
Der Tiefpassfilter hat gegenüber dem arithmetischen Mittel nur eben den Vorteil, dass er von ganz alleine gleitet und man keine Riesen-Arrays verwalten muss.

HaWe
03.03.2016, 19:51
oh nein Witkatz, da hast du was falsch verstanden:
"Durchschnitt" oder "Mittelwert" sind keine Synonyme für "arithmetisches Mittel".
Auch ein Median liefert einen Mittelwert oder Durchschnittswert, und auch ein Kalmanfilter und ein Tiefpassfilter (oder auch ein Hochpassfilter) tun das.
Der Grund, dass man Mittel- oder Durchschnittswerte verwendet, liegt in statistischen Fehlern und den Streuungen der Einzelmessungen begründet, die normalverteilt oder auch nicht-normalverteilt sein können. Dementsprechend werden bestimmte Mittelwertverfahren im einen oder anderen Fall bevorzugt angewendet, um den/die wahren Zustandswert(e) im Messraum (die man nicht kennt) möglichst gut anzunähern.
Für Stichproben mit ganz wenigen aber extremen Ausreißern ist z.B. ein Medianfilter besser geeignet als ein arithmetisches Mittel, das arithmetische Mittel wiederum ist besser für normalverteilte Messwert-Stichproben geeignet.