Forum: Mikrocontroller und Digitale Elektronik [AVR] Mittelwertbildung


von Draco (Gast)


Lesenswert?

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_t WertArray[100];
2
uint8_t ArrayPos = 0;
3
uint8_t AlterWert = 0;
4
5
uint8_t MittelWert(uint8_t AktuellerWert)
6
{
7
  WertArray[ArrayPos] = AktuellerWert;
8
  ArrayPos++;
9
10
  if(ArrayPos >= 99)
11
  {
12
     uint16_t Mittwert = 0;
13
     
14
     for(uint8_t i = 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
     return AlterWert;
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_t WertArray[100];
2
uint8_t ArrayPos = 0;
3
4
5
uint8_t MittelWert(uint8_t AktuellerWert)
6
{
7
  uint16_t Mittwert = 0;
8
9
  WertArray[ArrayPos] = AktuellerWert;
10
  ArrayPos++;
11
12
  if(ArrayPos >= 99)
13
     ArraPos = 0;
14
15
  for(uint8_t i = 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?!

von Frank (Gast)


Lesenswert?

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.

von Tom V. (vzczc)


Lesenswert?

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?

von Draco (Gast)


Lesenswert?

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

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von Draco (Gast)


Lesenswert?

Genau muss er nicht sein, der Wert geht auf eine analoge Anzeige mit 
sehr hoher Latenz. Ich will sie halt nur glätten.

von wendelsberg (Gast)


Lesenswert?

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

von m.n. (Gast)


Lesenswert?

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;
}

von wendelsberg (Gast)


Lesenswert?

m.n. schrieb:
> #define FILTER  100

Mit #define FILTER  128 oder 64 wird es besser, das geht dann ohne 
float.

wendelsberg

von m.n. (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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
   void Chk4MinAndMaxWert(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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von wendelsberg (Gast)


Lesenswert?

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

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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...

von Sascha (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Draco (Gast)


Lesenswert?

???

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.

von Planlos (Gast)


Lesenswert?

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.

von Tom V. (vzczc)


Lesenswert?

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").

von Tassilo H. (tassilo_h)


Lesenswert?

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_t filteracc;
4
uint16_t average;
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...

von Joachim B. (jar)


Lesenswert?

also ich würde die ADC Werte aufaddieren

arduino
1
#define READS 4
2
3
uint16_t lese_analog(uint8_t _port)
4
{ int8_t i;
5
  uint16_t _adc=0;
6
  for(i=0; i<(1<<READS); i++)
7
    _adc += analogRead(_port);    // read the input pin
8
  _adc >>= READS;
9
  return _adc;
10
}

c
1
// ADC init nicht vergessen
2
  ADCSRA |= (1<<ADSC); // eine Dummy ADC-Wandlung
3
  while ( ADCSRA & (1<<ADSC) ) 
4
    ; // auf Abschluss der Konvertierung warten
5
6
// Eigentliche Messung - Mittelwert aus 2^READS aufeinanderfolgenden Wandlungen 
7
  uint16_t result = 0;
8
        for(i=0; i<(1<<READS); i++)
9
  {  ADCSRA |= (1<<ADSC); // eine Wandlung "single conversion"
10
    while ( ADCSRA & (1<<ADSC) ) 
11
      ; // auf Abschluss der Konvertierung warten
12
    result += ADCW; // Wandlungsergebnisse aufaddieren
13
  }
14
  result >>= READS;  
15
  return result;

von Pandur S. (jetztnicht)


Lesenswert?

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

von m.n. (Gast)


Lesenswert?

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!

von m.n. (Gast)


Lesenswert?

Oder D. schrieb:
> Siehe zB http://www.ibrtses.com/embedded/exponential.html

Dort steht: output:=input/RC+output*(1-1/RC)
Wenn ich nicht irre, ist das der gleiche Code, den ich oben geschrieben 
hatte, nur ein wenig anders formuliert.

von Pandur S. (jetztnicht)


Lesenswert?

Genau, nur sollte man's mit shift-right anstelle von divisionen machen. 
Dass man auf Faktor 2 Abstufungen limitiert ist sollte nicht wirklich 
stoeren.

von Dergute W. (derguteweka)


Lesenswert?

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

von m.n. (Gast)


Lesenswert?

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 ;-)

von m.n. (Gast)


Lesenswert?

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!

von Pandur S. (jetztnicht)


Lesenswert?

> 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.

von m.n. (Gast)


Lesenswert?

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.

von Pandur S. (jetztnicht)


Lesenswert?

> 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.

von Tassilo H. (tassilo_h)


Lesenswert?

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...

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

: Bearbeitet durch User
von 123456790 (Gast)


Lesenswert?

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.

von Draco (Gast)


Lesenswert?

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

von Tom (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe mal die auf meiner Festplatte herumliegenden Filterfragmente 
zusammengesucht, ergänzt und verglichen. Der Code ist sicherlich 
verbuggt.

von Tom (Gast)


Lesenswert?

Bild in neuem Tab öffnen, dann kann man zoomen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.