Hallo zusammen.
Ich möchte gern einen gleitenden Mittelwert programmieren.
Ich haben einen festen Messzyklus meines AVR's. In jeder Runde soll nun
der Messwert aufaddiert werden und nach z.B. 100 Messzyklen unter 100
geteilt werden und dann der Mittelwert ausgegeben werden (an PC).
Wenn ich mit dem normalen Mittelwert arbeite, muss ich genau
100*Messzykluszeit warten bis das Ergebis da ist. Solang kann ich aber
nicht warten.
Ich würde lieber eine "Anschwingphase" in kaufen nehmen, wie es bei
einem gleitenden Mittelwert der Fall ist. Dafür würde ich aber nach den
ersten 100 Messzyklen nach jedem Messzyklus einen gemittelten Wert
übergeben bekommen.
Kann mit jemand erklären, wie ich den gleitenden Mittelwert verstehen
muss?
Habe ich einen Puffer für 100 Werte und fülle diesen mit einem Messwert
pro Messzyklus und teile jedesmal durch 100?
Danke euch
Hallo
Du kannst z.B. den Mittelwert gleich dem ersten Messwert nehmen. Sei n
die Anzahl der Messungen.
Der nächste Mittelwert ist dann ((n-1)* alter Mittelwert + Messwert)/n
Wenn du n von 2 bis 100 laufen lässt erhälst du vom ersten Messwert an
Mittelwerte.
Du kanst das derart machen, das du eine Puffer hast der Größe 100
P[100];
Nach jeder messung schmeißt du den ältesten Wert raus fügst deinen neuen
Wert ein.
Im Übrigen nimm lieber 128 Werte dann kannst du einfach durch schiften
nach rechts teilen das geht sehr viel fixer.
also wenn die mittelwertbildung schnell gehen soll, dann nehme ich zb
bei 8bit werten 256 messwerte (können aber auch 128, 64, 32 etc sein).
den mittelwert ermittle ich in einem 16-bit wert. dazu müsste man also
256 messwerte aufsummieren und dann durch 256 teilen. das bedeutet, das
divisionsergebnis steht um höherwertigen byte des 16-bit registers schon
fertig da, wenn ich die messwerte aufsummiere.
der ablauf sieht nun so aus:
1. der 16bit wert wird aus 0 gesetzt (initialisierung)
2. wenn ein messwert eintrifft, wird vom 16-bit register der wert des
höherwertigen bytes vom 16bit wert (also der aktuelle mittelwert)
subtrahiert, sodass der 16bit wert auf den 255/256-teil absinkt und dann
addiere ich den messwert zum 16bit wert hinzu. damit bildet sich der
neue wert zu:
16-bit(neu)=255/256*16-bit(alt)+1/256*messwert.
im höherwertigen byte des 16-bit wertes steht damit immer der aktuelle
mittelwert, eine zwischenspeicherung von messwerten ist nicht nötig.
wie gesagt, mit 128, 64, 32 ..messwerten funktioniert das ähnlich, sind
evtl ein paar verschiebeoperationen nötig.
Die Formel von karadur mittelt dir nicht einfach die letzten n Werte, es
ist eher so eine Art Tiefpass, dessen Stärke durch n festgelegt wird.
Dein Mittelwert wird von allen vorangegangenen Werten bestimmt, aber die
aus der näheren Vergangenheit haben mehr Gewicht.
Wichtung:
letzter Punkt 1/n
vorletzter Punkt (n-1)/n^2
vorvorletzter (n-1)^2/n^3
usw.
Nachteil: Es ist nicht der geforderte Mittelwert.
Vorteil: Du musst nicht ein ganzes Array speichern, sondern immer nur
den letzten Mittelwert.
Ich habe n auf 2 gesetzt. Die Werte springen total auf und ab. Sieht
irgendwie so aus, als ob etwas überläuft.
Aber ich alle verwendeten Variablen als unsignes long definiert.
Meine Messwerte liegen im Bereich von 0-4000.
Frank Schlaefendorf wrote:
> 2. wenn ein messwert eintrifft, wird vom 16-bit register der wert des> höherwertigen bytes vom 16bit wert (also der aktuelle mittelwert)> subtrahiert, sodass der 16bit wert auf den 255/256-teil absinkt und dann> addiere ich den messwert zum 16bit wert hinzu. damit bildet sich der> neue wert zu:
Ein interessanter Ansatz, aber schlussendlich nicht korrekt. Bei wenig
schwankenden Werten und großem n sicher erträglich, aber für die
Mittelung von wenigen stärker schwankenden Werten ist das nicht so das
Wahre.
> 16-bit(neu)=255/256*16-bit(alt)+1/256*messwert.
Die Formel entspricht nicht der Beschreibung. Warum soll der neue
Messwert erst geteilt werden?
> im höherwertigen byte des 16-bit wertes steht damit immer der aktuelle> mittelwert, eine zwischenspeicherung von messwerten ist nicht nötig.
Nicht "immer", sondern erst, wenn n Werte aufgelaufen sind.
@Stefan KM
Hier noch eine weitere Formel, die einem Tiefpass entspricht:
sample += (aktWert - mittelWert);
mittelWert = (sample >> 3);
Das Ganze kannst Du dann über die Shiftanzahl gewichten.
Wenn die Werte nicht ständig springen funktionierts auch sehr gut.
Schönes Wochenend
Thomas B. wrote:
> Ein interessanter Ansatz, aber schlussendlich nicht korrekt. Bei wenig> schwankenden Werten und großem n sicher erträglich, aber für die> Mittelung von wenigen stärker schwankenden Werten ist das nicht so das> Wahre.
doch, denn ein neuer messwert kann sich immer nur eine erhöhung des
mittelwertes von maximal 1 bewirken, da er nur mit der wichtung 1/256 in
den mittelwert eingeht
>> 16-bit(neu)=255/256*16-bit(alt)+1/256*messwert.> Die Formel entspricht nicht der Beschreibung. Warum soll der neue> Messwert erst geteilt werden?
der 8 bit-messwert wird mit dem 16-bitwert addiert, sodass er mit dieser
wichtung in den neu ermittelten mittelwert (höherwertiges byte des
16-bit wertes) eingeht. damit ist die division im prinzip mit der
addition des 8bit wertes bereits erledigt, eine extra divisionsoperation
ist nicht nötig.
> Nicht "immer", sondern erst, wenn n Werte aufgelaufen sind.
es wird zu beginn der mittelwert aus 255 nullen (oder einem anderen
startwert) und dem neuen messwert ermittelt und dadurch ein einschwingen
auf den mittelwert und kein sprung!
würde man zu beginn wenige werte mitteln, so hätte der mittelwert (zb
bei stark schwankenden werten) sowieso keinerlei aussagekraft.
Frank Schlaefendorf wrote:
> würde man zu beginn wenige werte mitteln, so hätte der mittelwert (zb> bei stark schwankenden werten) sowieso keinerlei aussagekraft.
Richtig, denn das macht ein "normales" analoges RC-Glied auch nicht ;-)
Diese Fkt. realisiert einen Filter mit unendlicher Sprungantwort...über
die gewichtung kannst du einstellen wie stark gefiltert wird...
Auf dem AVR vlt nicht unbedingt als float aber das Prinzip sollte klar
sein...
(Ich hoffe das funzt soo... ;) )
Frank Schlaefendorf wrote:
> doch, denn ein neuer messwert kann sich immer nur eine erhöhung des> mittelwertes von maximal 1 bewirken, da er nur mit der wichtung 1/256 in> den mittelwert eingeht
Korrekt wäre bei einem gleitenden Mittelwert eben genau den Messwert zu
entfernen, der jetzt vor dem relevanten Intervall liegt. Dafür 1/256 des
aktuellen Messwerts zu verwenden ist eine Näherung, aber eben nicht
korrekt. Ich sag' ja nicht, dass es keine sinnvolle Näherung wäre...
> der 8 bit-messwert wird mit dem 16-bitwert addiert, sodass er mit dieser> wichtung in den neu ermittelten mittelwert (höherwertiges byte des> 16-bit wertes) eingeht. damit ist die division im prinzip mit der> addition des 8bit wertes bereits erledigt, eine extra divisionsoperation> ist nicht nötig.
Eben, so ist es und genau das sagt die Formel nicht.
>> Nicht "immer", sondern erst, wenn n Werte aufgelaufen sind.> es wird zu beginn der mittelwert aus 255 nullen (oder einem anderen> startwert) und dem neuen messwert ermittelt und dadurch ein einschwingen> auf den mittelwert und kein sprung!> würde man zu beginn wenige werte mitteln, so hätte der mittelwert (zb> bei stark schwankenden werten) sowieso keinerlei aussagekraft.
Freilich gibt es so keinen Sprung, aber es ist eben auch kein Mittelwert
der Messwerte. Es wird erst dann ein Mittelwert, wenn der "Puffer"
vollgelaufen ist. Vorher sind Werte mit drinnen, die nicht von
Messwerten stammen.
Thomas B. wrote:
> Freilich gibt es so keinen Sprung, aber es ist eben auch kein Mittelwert> der Messwerte. Es wird erst dann ein Mittelwert, wenn der "Puffer"> vollgelaufen ist. Vorher sind Werte mit drinnen, die nicht von> Messwerten stammen.
ja, aber wenn du zwei messwerte hast und das arithmetische mittel
berechnest, hast du auch keinen mittelwert deines signals, ausser es sit
konstant.
Frank Schlaefendorf wrote:
> ja, aber wenn du zwei messwerte hast und das arithmetische mittel> berechnest, hast du auch keinen mittelwert deines signals, ausser es sit> konstant.
Hu? Also warum soll der Mittelwert von zwei Werten nicht der Mittelwert
sein? Das ist er sehr wohl.
Für die korrekte Implementierung des gleitenden Mittelwertes könnte man
einen Ringpuffer nehmen, etwa so (ist jetzt in Java, da ich aus C echt
raus bin, aber C müsste eigentlich fast identisch sein, statt überall
32-Bit-int sind natürlich die kleinstmöglichen Einheiten zu wählen):
1
final int n_max=0;
2
int n=0;
3
int aeltester_Wert=0;
4
int puffer[]=new int[n_max];
5
int mittel=0;
6
int mittelwert (int neuerWert) {
7
if (n<n_max) {
8
mittel=(n*mittel+neuerWert)/(n+1);
9
puffer[n]=neuerWert;
10
n++;
11
} else {
12
mittel+=(neuerWert-puffer[aeltester_Wert])/n_max;
13
puffer[aeltester_Wert]=neuerWert;
14
aeltester_Wert=(aeltester_Wert+1)%n_max;
15
}
16
return mittel;
17
}
Bei den Integer-Divisionen ist allerdings immer ein Abrunden dabei, bei
ungünstigen Folgen könnten sich die Rundungsfehler ansammeln und der
Mittelwert vom tatsächlichen Mittel abweichen. Alternative, falls der
maximale Input mal der Zahl der zu mittelnden Werte noch ins int passt:
1
final int n_max=0;
2
int n=0;
3
int aeltester_Wert=0;
4
int puffer[]=new int[n_max];
5
int mittel=0;´// eigentlich Mittelwert*n
6
int mittelwert (int neuerWert) {
7
if (n<n_max) {
8
mittel+=neuerWert;
9
puffer[n]=neuerWert;
10
n++;
11
} else {
12
mittel+=neuerWert-puffer[aeltester_Wert];
13
puffer[aeltester_Wert]=neuerWert;
14
aeltester_Wert=(aeltester_Wert+1)%n_max;
15
}
16
return mittel/n;
17
}
Wegen der genannten Einschränkung und der großen Speichernutzung würde
ich eher den RC-Glied-ähnlichen Filter von oben verwenden :)
Die einzige Möglichkeit einen gleitenden Mittelwert korrekt zu
implementieren geht über die Speicherung aller n Werte. Dazu einen
Ringbuffer verwenden wie Slotta schrieb. Nimm für n 2er Potenzen für
einfache Division mit n.
Slottas erster Algorithmus ist ungünstig, weil Division durch n+1 nicht
mehr mit shiften geht. Die zweite Variante ist besser.
Thomas O. (Gast)
>sample += (aktWert - mittelWert);>mittelWert = (sample >> 3);
Ist leider nicht richtig, Im Gleichspannungsfall läuft deine Gleichung
gegen
mittelWert=1/9 aktWert
D.h. ist um den Faktor 9 zu klein.
Moritz wrote:
> Slottas erster Algorithmus ist ungünstig, weil Division durch n+1 nicht> mehr mit shiften geht. Die zweite Variante ist besser.
Wie gesagt, der erste Algorithmus hat auch seine Probleme mit den
Rundungsfehlern. Der zweite rundet nur bei der Ausgabe, geht dafür eben
nur, wenn der Mittelwert*Zahl der zu mittelnden Werte noch in die
Datenstruktur (wahrscheinlich meist ein 16-bit-unsigned-int) passt.
Wenn du dir den 2. Algo noch mal genau ansiehst, da kannst du auch nicht
einfach die Division durch n durch einen Shift ersetzen, dafür müsstest
du diesen Teil für die Einschwingphase getrennt abhandeln. Ist halt der
Preis dafür, dass du bereits am Anfang halbwegs vernünftige Werte
bekommst.
Alternative wäre, das Feld vorzubelegen und den Mittelwert entsprechend
zu initialisieren und dann gleich über alles hinweg zu mitteln. Dann
kannst du aber den ersten Mittelwerten praktisch keine Information
entnehmen, da sie praktisch komplett von der Initialisierung abhängen.
Wenn du die ersten Messwerte wegwerfen kannst ist das sicher die bessere
Lösung, da du dir später die Fallunterscheidung sparst und in jedem Fall
mit einem billigen Shiftoperator wegkommst.
ajax (Gast)
Bau Dir dazu mal 'ne Excel Tabelle und Du wirst sehen wie schön das
Ganze auch bei Gleichspannung - mit nicht wechselnden Werten -
funktioniert.
> Die einzige Möglichkeit einen gleitenden Mittelwert korrekt zu> implementieren geht über die Speicherung aller n Werte.
Das stimmt, allerdings kann man sich die Frage stellen, ob Stefan KM das
überhaupt will/braucht. Wenn "nur" ein Tiefpass entsprechend einem
RC-Glied nachgebildet werden soll, dann reicht die Speicherung des
aktuellen Summenwertes aus. Ein Kondensator speichert ja auch nicht die
letzten 100 Werte, sondern "nur" die aktuelle Summe.
Was er will, ist doch eine Art "besseres" RC-Glied, das (ohne 5*tau)
- nach der 1. Messung direkt diesen Wert
- nach der 2. bis zur 99. Messung den Mittelwert aus diesen Messungen
- ab der 100. den MW aus allen vorangegangenen Messungen
ausgibt.
1
unsignedlongmittelwert(unsignedlongnewval)
2
{
3
staticshortn=1;
4
staticunsignedlongavgsum;
5
unsignedlongavg;
6
if(n<100){
7
avgsum+=newval;
8
avg=avgsum/n;
9
n++;
10
}
11
else{
12
// Konstanten kann der Compiler evtl. besser optimieren
ajax wrote:
> Thomas O. (Gast)>>sample += (aktWert - mittelWert);>>mittelWert = (sample >> 3);>> Ist leider nicht richtig
Wahrscheinlich war so etwas gemeint:
1
diff=sample-avg;
2
avg+=(diff>>n);
Ist nur eine andere Schreibweise für den exponential moving average
filter mit Koeffizient 1-2^(-n).
eine tiefpasssimulation bringt auf jeden fall einen sinnvoleren wert,
als das arithmetische mittel, da länger zurück liegende werte viel
schwächer gewichtet werden als im mittelwert, die geschichte des signals
also viel stärker beachtet wird. der bloße mittelwert "hüpft" weiter
sinnlos rum! der teifpass konvergiert (je nach wichtung und filtertiefe)
evtl langsamer. die wichtung des filters (also seine grenzfrequenz) muss
also evtl angepasst werden.
meine aktuelle Version des Tiefpasses schaut so aus:
Initialisieren mit
averSum = n * sample;
averSum += sample - (aver = averSum / n);
Input: sample
Output: aver
n sinnvollerweise ein Vielfaches von 2, damit die Division mit Shifts
ausgeführt werden kann.
Bei dieser Version gibt es keinen Rundungsfehler, da in averSum der
n-fache Wert von sample aufsummiert wird.
Thomas O.
>Bau Dir dazu mal 'ne Excel Tabelle und Du wirst sehen wie schön das>Ganze auch bei Gleichspannung
Du hattest recht, ich habe mich leider verrechnet :-(
Deine Gleichung stimmt und ist die gleiche wie die von Andreas Schwarz.
Interessant ist, dass auch in dem Beitrag "Mittelwert Berechnung" jemand
die RC-Tiefpassfunktion mit dem gleitenden Mittelwert verwechselt. Der
RC-Tiefpass ist eben kein gleitender Mittelwert, sondern nähert sich
exponentiel dem Eingangswert an. Während der gleitende Mittelwert als
Mittelwert eines Teiles eines Eingangssignals berechnet wird, wobei das
Mittelwertfenster eben über diese Eingangssignal "gleitet".
Jetzt die große Preisfrage: wie berechnet sich das äuqivalent zu TAU
beim RC Tiefpass für den oben angegebenn IIR-Filter?
>>diff = sample - avg;>>avg += (diff >> n);
Tau=f(Abtastfrequenz,n) ?
Durch die Kenntnis von Tau bekommt man auch ein Gefühl für die Dauer des
Einlaufens des Ausgangswertes.
Gruß,
ajax
Halt,
Datum: 01.12.2008 19:33
war meine vorletzte Version.
folgende hat den Vorteil, einen Sample aktueller zu sein (der aktuelle
Sample geht noch ins Ergebnis ein):
aver = (averSum += sample - aver) / n;
Die Initialisierung schaut dann so aus:
averSum = (aver = sample) * n;
Auch das mit dem Rundungsfehler stimmt nicht ganz, man müsste
aver = ((averSum += sample - aver) + n/2) / n;
schreiben, da die Integer-Division immer abrundet und nie aufrundet.
Mit dieser Formel wird der Wert etwas schneller erreicht.
Der Vorteil meiner Formel ist, dass
- bei konstantem Eingangswert dieser auch erreicht wird.
- nur eine Division auftritt.
>Jetzt die große Preisfrage: wie berechnet sich das äuqivalent zu TAU>beim RC Tiefpass für den oben angegebenn IIR-Filter?>>diff = sample - avg;>>avg += (diff >> n);>Tau=f(Abtastfrequenz,n) ?
Ich habe noch mal ein wenig nachgedacht, weil mich die Frage selbst
interessiert hat.
Angenommenn, man hat eine Gewichtungsfaktor a für einen neuen Wert,
Ausgang=Eingang*a+AlterAusgang*(1-a)
Dann erhält man für Tau
Tau=-1/(fab*ln(1-a))
für kleine a gilt
Tau~=1/(a*fab)
d.h. bei eine Abtastfrequenz von 1KHz und einer Gewichtung a von 1/100
erhält man ein Tau von 0.1 Sekunden.
Die Grenzfrequenz für den Tiefpass ergibt sich dann mit
fg=1/(2*pi*Tau)
zu 1.6Hz
Was haltet Ihr davon?
Gruß,
ajax
>http://www.ibrtses.com/embedded/exponential.html
Bei den Gleichungen im Link fehlt die Zeiteinheit.
Tau bei einem RC-Tiefpass hat die Einheit "Sekunde"
N hingegen ist einheitenlos. Erst in Verbindung mit der Abtastzeit
ergibt sich ein Zeitbezug, was ja für eine Filtereigenschaft die
wesentliche Größe ist.
>http://www.ibrtses.com/embedded/exponential.html
Die in dem Link vorgestellt Berechnungsvorschrift benötigt etwas mehr
Rechenzeit, wohingegen diese Formel
>>diff = sample - avg;>>avg += (diff >> n);
nur
1xSubtraktion
1xShift
1xAdition benötigt