Hi,
ich mache mir gerade Gedanken über eine effektive Mittelwertbildung. Ich
habe einen stark schwankenden (das ist auch richtig so) Wert im Int8
Bereich von 0-100. Nun möchte ich mir für einen definierten Zeitraum und
/ oder Mengenbereich einen Mittelwert berechnen. Es sollte ordentlich
funktionieren aber halt auch effektiv genug sein um nicht unnötig
Resourcen zu verschwenden. Um eine Division werde ich ohnehin nicht
herrum kommen können.
Meine Denkansätze sehen aktuell wie folgt aus (in dem Fall eines
Mengenbereiches mit 100 Messungen):
Ein Array mit einer Größe von 100, in dieses Array werden die Daten von
0-100 nacheinander geschrieben. Ist das Array voll, wird das komplette
Array durch 100 dividiert, das Array gelöscht und wieder von vorne
beschrieben. In etwa so:
1
uint8_tWertArray[100];
2
uint8_tArrayPos=0;
3
uint8_tAlterWert=0;
4
5
uint8_tMittelWert(uint8_tAktuellerWert)
6
{
7
WertArray[ArrayPos]=AktuellerWert;
8
ArrayPos++;
9
10
if(ArrayPos>=99)
11
{
12
uint16_tMittwert=0;
13
14
for(uint8_ti=0;i<=99;i++)
15
{
16
Mittwert+=WertArray[i];
17
WertArray[i]=0;
18
}
19
20
if(Mittwert>0)
21
Mittwert=Mittwert/100;
22
23
AlterWert=(uint8_t)Mittwert;
24
return(uint8_t)Mittwert;
25
}
26
else
27
{
28
returnAlterWert;
29
}
30
}
Vorteil: Es wird nur alle 100 Schritte eine Division durchgeführt.
Nachteil: Ich bekomme nur alle hundert Schritte einen aktualisierten
Wert überreicht.
Variante zwei, die Werte werden nach und nach in das Array geschoben, es
wird berechnet und ausgegeben, ist das Array voll - wird von vorne
wieder angefangen zu beschreiben, die älteren Werte bleiben erhalten:
1
uint8_tWertArray[100];
2
uint8_tArrayPos=0;
3
4
5
uint8_tMittelWert(uint8_tAktuellerWert)
6
{
7
uint16_tMittwert=0;
8
9
WertArray[ArrayPos]=AktuellerWert;
10
ArrayPos++;
11
12
if(ArrayPos>=99)
13
ArraPos=0;
14
15
for(uint8_ti=0;i<=99;i++)
16
{
17
Mittwert+=WertArray[i];
18
}
19
20
if(Mittwert>0)
21
Mittwert=Mittwert/100;
22
23
return(uint8_t)Mittwert;
24
}
Vorteil: Ich bekomme bei jedem Aufruf ein aktuellen Mittwert, spare mir
eine globale Variable. Nachteil: Bei jedem Aufruf kommt eine Division
vor, sowie die komplette Schleife geht immer in den Durchlauf.
Welchen Ansatz sollte man verfolgen, würdet ihr eher auf ein
Regelmäßiges Ergebniss setzen oder eher auf Geschwindigkeit bzw.
Resourcen? In meinem Anwendungsfall würden die 100 Werte in ca. 500ms
zusammen kommen und werden ausschließlich zum Anzeigen gemittelt -
weshalb da Variante 1 schon ausreichend ist.
Gibt es vernünftigere Mittelwertbildungen - welche weniger Resourcen
verschwenden auf einem AVR? Hat jemand eine Parat?!
Gleitender Mittelwert mit 128 Werten.
Wenn du richtig machst, ist es nur eine Subtraktion (ältester Wert),
eine Addition (neuer Wert) und ein Shift (Division).
Das kann man auch gut jedes Mal rechnen.
Ich nutz gern den folgenden Weg:
256 Werte addieren (256 Bytes in ein Word). Vom Word dann nur die
höherwertigen 8 Bits nutzen (entspricht Teilen durch 256). Simpler
geht's nicht, oder?
Tom V. schrieb:> Ich nutz gern den folgenden Weg:> 256 Werte addieren (256 Bytes in ein Word). Vom Word dann nur die> höherwertigen 8 Bits nutzen (entspricht Teilen durch 256). Simpler> geht's nicht, oder?
Das ist ein guter Ansazt, aber für die 256 Werte benötige ich ja auch
ein Array. Oder meinst du, jeweils ein Byte in das Word von hinten
schieben (das älteste fällt ja dann raus), daraus dann die höherwertigen
8Bits? Ich kann mir das gerade nicht bildlich vorstellen :D
Draco schrieb:> ich mache mir gerade Gedanken über eine effektive Mittelwertbildung. Ich> habe einen stark schwankenden (das ist auch richtig so) Wert im Int8> Bereich von 0-100. Nun möchte ich mir für einen definierten Zeitraum und
Wenn diese Werte so stark schwankend sind, hat es wenig Sinn, einen
Mittelwert zu bilden, oder ?
Normal nimmt man in so einem Fall MinWert und MaxWert, addiert die
beiden und macht einen Shift nach rechts.
Oder man beginnt mit MittelWert = NeuWert, addiert NeuWert dazu, shift
nach rechts und macht dasselbe mit jedem neuen Wert. So wird die Kurve
geglättet, aber nicht genauer.
Wie gesagt, es macht wenig (oder keinen) Sinn, in so einem Fall einen
genauen Mittelwert zu bilden.
Draco schrieb:> Genau muss er nicht sein, der Wert geht auf eine analoge Anzeige mit> sehr hoher Latenz.
Und die Anzeige glaettet nicht selbst?
wendelsberg
Nichts für Dich, da sogar zwei Divisionen ausgeführt werden müssen.
Zudem noch float, was besonders böse ist.
#define FILTER 100
float filter_a(int32_t neuer_wert)
{
static float summe;
summe -= summe/FILTER;
summe += neuer_wert;
return summe/FILTER;
}
wendelsberg schrieb:> Mit #define FILTER 128 oder 64 wird es besser, das geht dann ohne> float.
Das ist mir doch sch. egal.
Wenn ich 100 brauche oder float brauche, dann mache ich das so.
Draco schrieb:> Genau muss er nicht sein, der Wert geht auf eine analoge Anzeige mit> sehr hoher Latenz. Ich will sie halt nur glätten.
Dann mach es halt so:
PseudoCode:
1
MinWert=100;
2
MaxWert=0;
3
MittelWert=NeuWert;
4
5
while(1){
6
if(!TasterGedruckt){
7
voidChk4MinAndMaxWert(NeuWert);
8
MittelWert+=NeuWert;
9
MittelWert>>1;
10
ShowMittelWert;
11
}
12
else{
13
ShowMinAndMaxWert;
14
}
15
}
So wird die Anzeige geglättet und wenn du Taster drückst, wird MinWert
und MaxWert angezeigt.
m.n. schrieb:> wendelsberg schrieb:>> Mit #define FILTER 128 oder 64 wird es besser, das geht dann ohne>> float.> Das ist mir doch sch. egal. Wenn ich 100 brauche oder float brauche,> dann mache ich das so.
Und dann wunderst du dich, dass dein uC zu langsam ist. Ganz selten
braucht man dort, wo in einem Programm 100 dran steht, auch
tatsächlich 100. Man will es nur deshalb, weil 100 so gut in der
zweiten Potenz zu deinen 10 Fingern passt.
Oder andersrum: wenn der Mensch nur 9 Finger hätte, müsste an dieser
Stelle dann unbedingt 81 stehen...
Wenn man weiß, dass ein Prozessor nun mal ein zweierpotenzlastiges
Rechenwerk hat, dann kann man ihm dort, wo es nicht weh tut, schon mal
"nach dem Maul" programmieren. Er dankt es dir mit kurzer Rechenzeit...
Draco schrieb:> Meine Denkansätze sehen aktuell wie folgt aus
Sieh dir auch mal den Beitrag "PT1-Filter in C" an.
m.n. schrieb:> Das ist mir doch sch. egal.> Wenn ich 100 brauche oder float brauche, dann mache ich das so.
Du vielleicht, der TO aber fragte:
Draco schrieb:> ich mache mir gerade Gedanken über eine effektive Mittelwertbildung.
wendelsberg
Draco schrieb:> Ich habe einen stark schwankenden (das ist auch richtig so) Wert im> Int8 Bereich von 0-100. Nun möchte ich mir für einen definierten> Zeitraum und / oder Mengenbereich einen Mittelwert berechnen.
Ich würde evtl. einen Medianfilter ins Auge fassen. Der bringt bei
Messwerten mit starken Ausreissern die besten Ergebnisse...
Für Performance lohnt es sich, ein Array[256] zu definieren, einen
uint8_t index dafür und dann neue Werte in index++ reinschreiben.
Dadurch sorgt der uint8_t Überlauf bei 256 schon dafür, dass du einfach
schreiben und lesen kannst ohne dich um irgendwas zu kümmern.
Dann macht man sich in Matlab/Octave ein symmetrisches FIR-Filter und
klappt das in der Mitte zusammen.
Die FIR-Koeffizienten normiert man alle auf 2er Potenzen und die
Division am Ende auch.
Dann werden aus Multiplikationen und Divisionen simple Bitshifts die 1
Takt dauern.
Lothar M. schrieb:> Und dann wunderst du dich, dass dein uC zu langsam ist. Ganz selten> braucht man dort, wo in einem Programm 100 dran steht, auch> tatsächlich 100.
Mein µC ist überhaupt nicht zu langsam, eher atemberaubend schnell!
Eigentlich habe ich exakt 99,8 gebraucht, mich dann aber doch für 100
entschieden.
Die "digitalen Sklaven" treiben es hier soweit, daß der TO sich quasi
dafür entschuldig, daß er eine Division brauchen könnte.
???
Was geht denn mit dir m.n.?! :D
Nein, es geht ja nicht darum das ich da eine Division nutze oder nicht.
Aber warum sollte man eine nutzen wenn es effektiver auch funktioniert?
Und die 100 im Eingangsposting waren auch nur als Beispiel zu sehen, das
heißt ja nicht das ich genau 100 Werte mitteln will oder mag oder im
Endeffekt mache - aber irgendeinen Wert musste ich ja nehmen.
Draco schrieb:> Und die 100 im Eingangsposting waren auch nur als Beispiel zu sehen, das> heißt ja nicht das ich genau 100 Werte mitteln will oder mag oder im> Endeffekt mache - aber irgendeinen Wert musste ich ja nehmen.
Auf solche "krummen" Werte wie 100 reagieren halt viele
Vollblut-Entwickler allergisch, wenn sie absolut sinnfrei sind.
Vmtl. weil man sowas schon x-Mal im Pflichtenheft gesehen hat, und sich
dabei über den Vollpfosten von Vorgesetztem/Auftraggeber/Scheffe
geärgert hat.
=> Tief durchatmen, 128 oder 64 in den Quelltext schreiben, zusammen mit
einem netten Kommentar darüber.
Draco schrieb:>> Ich nutz gern den folgenden Weg:>> 256 Werte addieren (256 Bytes in ein Word). Vom Word dann nur die>> höherwertigen 8 Bits nutzen (entspricht Teilen durch 256). Simpler>> geht's nicht, oder?>> Das ist ein guter Ansazt, aber für die 256 Werte benötige ich ja auch> ein Array. Oder meinst du, jeweils ein Byte in das Word von hinten> schieben (das älteste fällt ja dann raus), daraus dann die höherwertigen> 8Bits? Ich kann mir das gerade nicht bildlich vorstellen :D
Nein, kein Array. Nicht schieben, sondern addieren (in eine temporäre
Variable, die 16-Bit groß ist). Du addierst solange deine Werte, bis Du
das 256 Mal gemacht hast. Danach findest Du den Mittelwert in den 8
höherwertigen Bits. Danach das Spielchen von vorne. Ist einfach zu
realisieren (ist aber kein "gleitender Mittelwert").
Will mann SPeicher sparen und ein IIR-Filter ist ok (d.h. die ANtwort
auf einen Sprung am Eingang dauert quasi unendlich, aufgrund der
endlichen Auflösung macht das aber in der Praxis oft nix ) geht
folgendes:
1
#define numSamples 128
2
3
uint16_tfilteracc;
4
uint16_taverage;
5
6
// init filter
7
average=GetSample();
8
filteracc=average*numSamples;
9
10
while(1){
11
filteracc-=average;
12
filteracc+=GetSample();
13
average=filteracc/numSamples;
14
}
solange numSamples eine Zweierpotenz ist, kommt das ganze mit
Schiebeoperationen aus und braucht auch auf einem AVR kaum Ressourcen
und wenig Speicher. Je nach Datenbreite ggf. 32-bit Variablen nehmen...
Eine sehr resourcensparende Methode ist der exponentielle Mittelwert.
Dessen Zeitkonstante ist fast bliebig einstellbar, und er braucht nur
eine speicherstelle, aber aus mehrerer bytes. Ich verwend jeweils 32bit
integer fuer Signalverarbeitung.
Siehe zB http://www.ibrtses.com/embedded/exponential.html
Draco schrieb:> Nein, es geht ja nicht darum das ich da eine Division nutze oder nicht.> Aber warum sollte man eine nutzen wenn es effektiver auch funktioniert?
Du hast ein minimales Problem und willst mit irgendeiner Optimierung
anfangen, um vermeintliche Effizienz zu erzielen. Wozu das Ganze?
Ein paar Meßwerte/s zu erfassen, zu skalieren und anzuzeigen braucht
sowenig Prozessorleistung, daß eher das Problem besteht, die Ausführung
hinreichend zu bremsen. Zur Information: eine 32-Bit Division auf einem
AVR dauert ca. 20-30 µs, wenn man ihn nicht gerade mit 1 MHz taktet.
Die "Schwarmintelligenz" kann nur noch binär denken und propagiert das
noch als optimale Lösung und bezeichnet sich als "Vollblut-Entwickler".
Der Nächste fordert dann, den Code in Assembler zu schreiben, den man
zuvor als Binärcode eingetippt hat.
Mein Beileid!
Moin,
Bei so einem exponentiellen Mittelwert in Integerarithmetik sollte man
aber stets im Auge haben, dass das auch genauigkeitsbedingt ganz schoen
danebenliegen kann, wenn die Eingangssignale hinreichend fies sind und
RC nicht so ganz klein ist.
Gruss
WK
Oder D. schrieb:> Genau, nur sollte man's mit shift-right anstelle von divisionen machen.
Ich mach das mal für RC = 64:
output = input>>6 + output*(1 - 1>>6);
Das sieht ja sehr gut aus ;-)
Joachim B. schrieb im Beitrag #4497881:
> aber RC 64 sprengt die uint16_t vom ADC Wert, also bis 32 gehe ich mit.
Überleg Dir das besser noch einmal ;-)
Ha, ich war schneller!
> mn. :> Bei so einem exponentiellen Mittelwert in Integerarithmetik sollte man
aber stets im Auge haben, dass das auch genauigkeitsbedingt ganz schoen
danebenliegen kann, wenn die Eingangssignale hinreichend fies sind und
RC nicht so ganz klein ist.
Deswegen muss man nur genuegend Stellen haben.
Wenn man einen 8(16) Bit Wert 256 mal aufsummiert, benoetigt man 16(24)
bit dafuer.
Ich verwend daher fuer 16bit Werte von einem ADC 32bit, das erlaubt mir
2^16 = 64k mal auszusummieren. Das sollte in den meisen Faellen
genuegen.
32Bit Integer hat uebrigens mehr signifikante Stellen wie 32bit Float.
Oder D. schrieb:> 32Bit Integer hat uebrigens mehr signifikante Stellen wie 32bit Float.
Unter Umständen schön für die Auflösung, aber schlecht für die Dynamik
;-)
So, wie Du Dein Vorgehen beschreibst, willst Du dem µC die Arbeit
abnehmen. (Bei meiner obigen Funktion habe ich das ja auch gemacht.)
Das kann man machen, aber wenn man problemorientiert programmieren
möchte, schreibt man die Formel so ab, wie sie ist.
Verwendet man float, kann der Compiler das (1-1/RC) als Konstante
erkennen und optimieren. Hilft man ihm ein wenig auf die Sprünge, wird
aus input/RC ein input * 1/RC. Wiederum eine Multiplikation mit einer
Konstante.
Somit werden zwei Multiplikationen und eine Addition benötigt. Und da
ist float auch nicht langsamer als int32.
> Somit werden zwei Multiplikationen und eine Addition benötigt. Und da
ist float auch nicht langsamer als int32.
Doch. Denn Float probiert nochmals eine Normalisierung.
Und wenn man es macht wie in meinem Beispiel weiter oben, dann braucht
man pro Sample eine Integer-Subtraktion, eine Integer-Addition und genau
einen Shift (der Compiler sollte die Division auf einem AVR ja
hoffentlich automatisch zu einem Shift machen wenn sinnvoll).
Das ganze nicht mit float zu machen ist sinnvoll, da man ja
normalerweise eh etwas ganzzahliges vom ADC bekommt. Da kann man gut
überblicken, welchen Wertebereich man bein Integer benötigt, und ist
sicher am Ende keine RUndungsfehler einzubauen, ohne im Detail
nachzuschauen, wieviele Bits für die Mantisse bei float nun reserviert
sind...
Draco schrieb:> Genau muss er nicht sein, der Wert geht auf eine analoge Anzeige mit> sehr hoher Latenz. Ich will sie halt nur glätten.
Alle 5ms ein neuer Wert (von 0 bis 100) auf eine analoge Anzeige mit
hoher Latenz ?
Wieso willst du da überhaupt etwas glätten ?
Einfach dazuaddieren und einmal nach rechts schieben.
wendelsberg schrieb:> Und die Anzeige glaettet nicht selbst?
Genau.
m.n. schrieb:> Und da> ist float auch nicht langsamer als int32.
Aha!? Hast du schon mal den Unterschied zwischen Integer und Float beim
rechnen angeschaut?
Guck dir mal an, was der Compiler draus macht! Da kannst du noch etwas
dazu lernen.
Speichereffezient und schnell ist die IIR-Lösung. Allerdings muss man da
mit einem IIR-Filter zufrieden sein.
Marc V. schrieb:> Draco schrieb:>> Genau muss er nicht sein, der Wert geht auf eine analoge Anzeige mit>> sehr hoher Latenz. Ich will sie halt nur glätten.>> Alle 5ms ein neuer Wert (von 0 bis 100) auf eine analoge Anzeige mit> hoher Latenz ?> Wieso willst du da überhaupt etwas glätten ?> Einfach dazuaddieren und einmal nach rechts schieben.>> wendelsberg schrieb:>> Und die Anzeige glaettet nicht selbst?>> Genau.
Ich hab da mal nen Video auf YouTube geladen um zeigen was ich meine.
Auf der Anzeige wird die CPU Last angezeigt, sollte sie nicht
anderweitig (Spiel) verwendet werden. Dies ist mir jedoch zu "zittrig" -
weshalb ich es glätten will. Klar glättet die Anzeige an sich natürlich
auch etwas, aber sie ist mir halt für einfach zu nervös. Die Werte
langsamer zu holen bringt natürlich auch nicht viel - da sich das am
Problem des Zitterns nichts ändert.
Der erste Teil ist ohne Glättung, dann klickOr ich den Filter an und der
zweite Teil ist dann mit Filterung (64 Werte). Es wirkt wesentlich
ruhiger.
Achja der Link zum Video: https://www.youtube.com/watch?v=IoSkziDBot4