Forum: Mikrocontroller und Digitale Elektronik fortlaufenden Mittelwert berechnen


von Chris (Gast)


Lesenswert?

Hallo,

ich hätte gerne fortlaufend den Mittelwert einer Spannungsmessung 
ermittelt. Angedacht war es so:

1. ich sammle erstmal bis ich 128 Messwerte habe und schreibe die in den 
SRAM
2. die Summe der Messwerte "schiebe" ich 7x (geht schneller als teilen, 
wenn ich die Forumsbeiträge richtig verstanden habe), dann sollte ich 
den Mittelwert haben
3. wenn ein neuer Wert kommt, ziehe ich zuerst den ältesten Wert meiner 
bekannten Summe ab und addiere dann dan neuen Wert dazu und berechne 
erneut den neuen Mittelwert
4. den ältesten Wert im SRAM ersetze ich dann natürlich auch durch den 
neuen


Ist das so sinnvoll? Und springt der Z-Pointer von 128 wieder auf 1 
(weil ich ja nur 128 Bytes im SRAM reserviert hab) oder muss ich das 
selber machen?

-ich mache das übrigens nur aus Spaß an der Freude, also keine Doktor- 
oder Diplomarbeit bzw. Hausaufgabe für Studenten oder sowas...-


Gruß
Chris

von Christoph M. (mchris)


Lesenswert?

Das ganze nennt sich "gleitender Mittelwert" oder auf Neudeutsch "Moving 
Average" Filter.

Dein Verfahren ist die zweite Formel hier:

https://en.wikipedia.org/wiki/Moving_average

von wer (Gast)


Lesenswert?

Brauchst du immer einen Mittelwert über die letzten 128 Werte oder 
willst du eigentlich einen Mittelwert seit Beginn der Aufzeichnung?

von Stefan F. (Gast)


Lesenswert?

Chris schrieb:
> die Summe der Messwerte "schiebe" ich 7x (geht schneller als teilen,
> wenn ich die Forumsbeiträge richtig verstanden habe

Wenn du durch 2, 4, 16, 32, .... teilst, erzeugt der GNU C Compiler 
exakt den gleichen Maschinencode, als wenn du das mit Shift-Operationen 
machen würdest. Der Quelltext ist mit Divisionen aber besser lesbar.

von Wolfgang (Gast)


Lesenswert?

Chris schrieb:
> Und springt der Z-Pointer von 128 wieder auf 1 (weil ich ja nur 128 Bytes
> im SRAM reserviert hab) oder muss ich das selber machen?

Warum sollte er das tun?
Es könnte natürlich von der von dir verwendeten 
Programmiersprache/Bibliotheken abhängen. Falls dort Ringpuffer direkt 
unterstützt werden, passiert das möglicherweise automatisch. Ein 
zyklisches Verhalten bei 128er Pufferlänge ist durch einfaches Maskieren 
aber auch leicht selber machbar.

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


Lesenswert?

wer schrieb:
> Brauchst du immer einen Mittelwert über die letzten 128 Werte oder
> willst du eigentlich einen Mittelwert seit Beginn der Aufzeichnung?
Das Zweite ist ein PT1-Fileter wie z.B. ein RC-glied. Und das lässt sich 
mit passender Filterlänge ganz knackig kurz beschreiben und berechnen:
http://www.lothar-miller.de/s9y/archives/25-Filter-in-C.html

Chris schrieb:
> 2. die Summe der Messwerte "schiebe" ich 7x (geht schneller als teilen,
> wenn ich die Forumsbeiträge richtig verstanden habe), dann sollte ich
> den Mittelwert haben
Lass das einfach den Compiler entscheiden. Denn wenn du einen 
vorzeichenbehafteten negativen Wert auf diesem Weg "teilst", dann 
bekommst du einen Rundungsfehler.

> Ist das so sinnvoll? Und springt der Z-Pointer von 128 wieder auf 1
> (weil ich ja nur 128 Bytes im SRAM reserviert hab)
Der kennt ja deine Gedanken nicht. Un er weiß nicht, dass du da "nur" 
128 Bytes reserviert hast.
> oder muss ich das selber machen?
Ja.
Und lass den Pointer mal besser von 0 bis 127 laufen. Das lässt sich 
nämlich tadellos in 7 Bits abbilden und ganz einfach ausmaskieren :
1
unsigned short mittelwert(unsigned short newval) 
2
{
3
   static unsigned short mem[128];
4
   static unsigned short ptr = 0;
5
   static unsigned long avgsum = 0;
6
   // Filterlängen in 2er-Potenzen --> Compiler optimiert 
7
   avgsum -= mem[ptr&0x7F]; // den ältesten Wert abziehen
8
   mem[ptr&0x7F] = newval;  // den neuesten Wert dort abspeichern
9
   ptr++;                   // nächster Wert wird ältester Wert
10
   avgsum += newval;        // neuen Wert aufsummieren
11
12
   return avgsum/128;
13
}

: Bearbeitet durch Moderator
von Mehmet K. (mkmk)


Lesenswert?

Hab' jetzt nicht gross darüber nachgedacht, aber so aus dem Bauchgefühl 
heraus: sollte wegen
1
avgsum -= mem[ptr & 0x7F];
avgsum nicht "signed long" sein?

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


Lesenswert?

Mehmet K. schrieb:
> sollte wegen
> avgsum -= mem[ptr & 0x7F];
> avgsum nicht "signed long" sein?
Wenn der Eingangswert unsigned (und damit nie negativ) ist, kann die 
Summe auch nie negativ werden.

von Diopter  . (diopter)


Lesenswert?

Wenn du genug Speicher hast, kannst du das so machen.
Es geht aber auch einfacher, entweder wie von Lothar M. als PT1-Filter 
vorgeschlagen oder so:
- Beim Start eine Messung als Mittelwert speichern
- Bei jeder Messung den alten Mittelwert 7x links schieben,
  den alten Mittelwert subtrahieren,
  den neuen Wert addieren,
  7x rechts schieben und als neuen Mittelwert speichern

Das Ergebnis ist zwar nicht das Gleiche wie bei deiner Methode, aber es 
ist ein gleitender Mittelwert.

von Dieter R. (drei)


Lesenswert?

Christoph M. schrieb:
> Das ganze nennt sich "gleitender Mittelwert" oder auf Neudeutsch "Moving
> Average" Filter.
>
> Dein Verfahren ist die zweite Formel hier:
>
> https://en.wikipedia.org/wiki/Moving_average

Mit der Formel für MMA (Modified moving average, weiter unten im 
Wikipedia-Artikel) geht es allerdings viel einfacher und ohne Array. Es 
wird nur der aktuelle Wert auf das (n-1) fache des bisherigen 
Mittelwerts addiert und das Ergebnis durch n dividiert.

Das bedeutet, jeder neue Wert trägt 1/n zum gleitenden Mittel bei. 
Initialisiert wird der Mittelwert mit dem ersten Messwert.

Für technische Zwecke (instabile Messwerte glätten) ist das aus meiner 
Sicht das übliche Verfahren.

Oh, ich sehe gerade, während ich noch im Wikipedia-Artikel gesucht habe, 
ob dort auch die Formel steht (tut sie), hat mein Vorredner schon das 
gleiche gepostet.

: Bearbeitet durch User
von DS (Gast)


Lesenswert?

Lothar M. schrieb:
> Mehmet K. schrieb:
>> sollte wegen
>> avgsum -= mem[ptr & 0x7F];
>> avgsum nicht "signed long" sein?
> Wenn der Eingangswert unsigned (und damit nie negativ) ist, kann die
> Summe auch nie negativ werden.

Aber wenn avgsum mit 0 initialisiert wird und das erste, was du machst, 
eine positive Zahl davon abziehst, ist dann der Ärger nicht schon 
vorprogrammiert?

von Dieter R. (drei)


Lesenswert?

DS schrieb:

> Aber wenn avgsum mit 0 initialisiert wird und das erste, was du machst,
> eine positive Zahl davon abziehst, ist dann der Ärger nicht schon
> vorprogrammiert?

avgsum ist die Summe aller Werte NACH Füllung des gesamten Arrays. 
Vorher wird nicht berechnet. Die Summe kann nie kleiner sein als eines 
ihrer Elemente.

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


Lesenswert?

DS schrieb:
> Aber wenn avgsum mit 0 initialisiert wird
Dann wurde sinnvollerweise mem auch mit 0 initialisiert. Und die 
ersten 128 Durchläufe wird dann immer 0 abgezogen.

: Bearbeitet durch Moderator
von Dirk B. (dirkb2)


Lesenswert?

Stefan ⛄ F. schrieb:
> Chris schrieb:
>> die Summe der Messwerte "schiebe" ich 7x (geht schneller als teilen,
>> wenn ich die Forumsbeiträge richtig verstanden habe
>
> Wenn du durch 2, 4, 16, 32, .... teilst, erzeugt der GNU C Compiler
> exakt den gleichen Maschinencode, als wenn du das mit Shift-Operationen
> machen würdest. Der Quelltext ist mit Divisionen aber besser lesbar.

Das darf der Compiler nur machen, wenn dasselbe Ergebnis heraus kommt.
Das ist aber nur bei unsigned Typen der Fall.
Bsp:
-3  / 2 = -1
-3 >> 1 = -2

von Wolfgang (Gast)


Lesenswert?

Dieter R. schrieb:
> Mit der Formel für MMA (Modified moving average, weiter unten im
> Wikipedia-Artikel) geht es allerdings viel einfacher und ohne Array. Es
> wird nur der aktuelle Wert auf das (n-1) fache des bisherigen
> Mittelwerts addiert und das Ergebnis durch n dividiert.

Das ist aber kein gleitender Mittelwert mit gleicher Gewichtung der 
letzten n Werte (FIR-Filter), sondern eine Heruntergewichtung der alten 
Werte mit (n-1)/n, d.h. ein IIR-Filter und damit ganz etwas anderes, als 
vom TO beschrieben.
Der Vorsatz "modified" bringt nur sehr schlecht zum Ausdruck, dass das 
eine völlig andere Funktion ist und mit dem etablierten Begriff 
"gleitender Mittelwert" nichts zu tun hat. Da gleitet nämlich nichts, 
sondern die Gewichtung nimmt mit dem Alter gleichmäßig ab.

von Dieter R. (drei)


Lesenswert?

Wolfgang schrieb:

> Das ist aber kein gleitender Mittelwert mit gleicher Gewichtung der
> letzten n Werte (FIR-Filter), sondern eine Heruntergewichtung der alten
> Werte mit (n-1)/n, d.h. ein IIR-Filter und damit ganz etwas anderes, als
> vom TO beschrieben.
> Der Vorsatz "modified" bringt nur sehr schlecht zum Ausdruck, dass das
> eine völlig andere Funktion ist und mit dem etablierten Begriff
> "gleitender Mittelwert" nichts zu tun hat. Da gleitet nämlich nichts,
> sondern die Gewichtung nimmt mit dem Alter gleichmäßig ab.

Niemand bestreitet dies und es steht auch im zitierten 
Wikipedia-Artikel. Der TE will Messwerte mitteln. Deshalb hatte ich auch 
geschrieben, dass es nach meinem bescheidenen Wissen das übliche 
Verfahren der Messwertmittlung ist.

von batman (Gast)


Lesenswert?

MMA find ich bei ständiger Meßbeobachtung meist geeigneter als ein 
echter (Array-) Mittelwert, wo Flanken zeitverzögert rauskommen, auch 
wenn sich der momentane Wert nicht ändert.
Kommt halt wiedermal auf den Zweck an.

von Nicht W. (nichtsowichtig)


Lesenswert?

Lothar M. schrieb:
> wer schrieb:
>> Brauchst du immer einen Mittelwert über die letzten 128 Werte oder
>> willst du eigentlich einen Mittelwert seit Beginn der Aufzeichnung?
> Das Zweite ist ein PT1-Fileter wie z.B. ein RC-glied. Und das lässt sich
> mit passender Filterlänge ganz knackig kurz beschreiben und berechnen:

Ein P-T1 Glied. PT1 Glied wäre hingegen ein sprungfähiges System.

von Rolf M. (rmagnus)


Lesenswert?

DS schrieb:
> Aber wenn avgsum mit 0 initialisiert wird und das erste, was du machst,
> eine positive Zahl davon abziehst, ist dann der Ärger nicht schon
> vorprogrammiert?

Nein, denn du subtrahierst nur Werte, die du vorher addiert hast. Wo 
soll diese positive Zahl am Anfang herkommen?

Dirk B. schrieb:
> Das darf der Compiler nur machen, wenn dasselbe Ergebnis heraus kommt.
> Das ist aber nur bei unsigned Typen der Fall.
> Bsp:
> -3  / 2 = -1
> -3 >> 1 = -2

Nein. Für signed-Werte, die negativ sind, ist das Ergebnis des Shift 
implementation-defined. -1 wäre auch ein gültiges Ergebnis für diesen 
Shift, sofern das in der Dokumentation des Compilers so angegeben ist.
Aber wenn man auf jeden Fall das richtige Ergebnis will, nimmt man 
einfach die Division und lässt den Compiler entscheiden, was die beste 
Variante ist.

von signed (Gast)


Lesenswert?

Rolf M. schrieb:
> einfach die Division und lässt den Compiler entscheiden, was die beste
> Variante ist.

Genau darum geht es ja.
Im Zweifel entscheidet sich der Compiler halt für die langsame Division 
bei signed.

von Pandur S. (jetztnicht)


Lesenswert?

Bevor du dir die Muehe machst so viel Rechenzeit zu verplempern, schau 
dir den exponentiellen Mittelwert an. Der ist viel schneller, und 
braucht nur eine Speicherstelle.
https://www.ibrtses.com/embedded/exponential.html

Speziell vergleiche die Resultate des gleitenden Mittelwertes zum 
Exponentiellen.

von Jemand (Gast)


Lesenswert?

signed schrieb:
> Genau darum geht es ja.
> Im Zweifel entscheidet sich der Compiler halt für die langsame Division
> bei signed.

Besser langsam als falsch.

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


Lesenswert?

Wolfgang schrieb:
> Das ist aber kein gleitender Mittelwert mit gleicher Gewichtung der
> letzten n Werte (FIR-Filter), sondern eine Heruntergewichtung der alten
> Werte mit (n-1)/n, d.h. ein IIR-Filter und damit ganz etwas anderes, als
> vom TO beschrieben.
Oft ist das, was die Anwendung braucht, etwas ganz anderes als das, 
was der Anwender will. Und erstaunlich oft reicht für die Anwendung 
eben auch das billige RC-Filter, auch der Anwender meint, einen echten 
gleitenden Mittelwert zu brauchen.

Joggel E. schrieb:
> schau dir den exponentiellen Mittelwert an.
Ja, unglaublich, wie viele Namen das selbe Kind hat...  ;-)

von signed (Gast)


Lesenswert?

Jemand schrieb:
> Besser langsam als falsch.

Ich glaube es gibt hier ein Missverständnis.

Es geht darum, dass man unsigned verwenden soll, wenn die Zahl nicht 
negativ werden kann.
Denn sonst sind Divisionen teuer, weil sie eben nicht zu shift optimiert 
werden, auch wenn der Wert tatsächlich nur positiv sein kann.

von A. S. (Gast)


Lesenswert?

signed schrieb:
> Denn sonst sind Divisionen teuer, weil sie eben nicht zu shift optimiert
> werden, auch wenn der Wert tatsächlich nur positiv sein kann.

Das war bei mir bisher das zeitlich teuerste Missverständnis: ein idx/8 
und idx%8 für einen bitbuffer. Wurde halt oft gebraucht, und ich war 
völlig überrascht, als das den Flaschenhals darstellte. Mit Wechsel auf 
unsigned war es noch ferner liefen.

von Chris (Gast)


Lesenswert?

Hi,

zuerst mal sorry für die späte Reaktion, dachte dass ich eine Info per 
Email bekommen, wenn eine Antwort gepostet wird (zumindest hotmail mag 
wohl nicht) - natürlich auch Danke für die Antworten.

Soweit ich Eure Antworten erstmal überflogen habe:

1. Ich hab meine Sachen direkt in Assembler geschrieben (asm-Datei), 
daher muss ich bei diversen Code-Beispielen von Euch erst noch 
"übersetzen"

2. Ich hab ja vorher schon was dazu hier im Forum und Web gelesen 
(Google sei Dank) und daher diesen Weg gewählt, weil ich dachte der 
gleitende Mittelwert ist tatsächlich nie der genaue Mittelwert

3. Ja ich wollte tatsächlich immer die letzten Werte nehmen (128 nicht 
100 weil ich dann einfach mit verschieben statt dividieren arbeiten 
kann, so war zumindest mein Verständnis bis jetzt)

4. Dass der Z-Pointer das nicht alleine erkennt, anhand des reservierten 
Bereichs im SRAM, hab ich schon vermutet, aber naja, besser nochmal 
nachgefragt :)

5. Es ist übrigens nicht zeitkritisch, ich hätte erstmal pro Sekunde 
eine Messung durchgeführt, sodass ich einen Zeitraum von gut 2 Minuten 
hätte für meinen Mittelwert (natürlich dauert es auch entsprechend lange 
bis ich ein erstes komplettes "Set" an Messdaten habe), ob dann das 
"Rechnen" im µC etwas länger dauert oder nicht wäre egal (außer 
natürlich es erzeugt ein grundsätzliches anderes Problem, dass ich nicht 
erkannt habe)


So, dann arbeite ich mich mal durch Eure Antworten durch und versteh es 
hoffentlich auch.

Gruß und danke
Chris

von W.S. (Gast)


Lesenswert?

Chris schrieb:
> Ist das so sinnvoll? Und springt der Z-Pointer von 128 wieder auf 1
> (weil ich ja nur 128 Bytes im SRAM reserviert hab) oder muss ich das
> selber machen?

Von was für einem Z-pointer redest du?
Also, wenn du unbedingt einen Mittelwert aus den letzten 128 
ADC-Ergebnissen haben willst, dann ist dein Verfahren im Grunde schon 
richtig. Du brauchst ein Feld mit 128 Plätzen für die ADC-Ergebnisse und 
einen Index, der von 0..127 gehen kann und auch noch eine Variable für 
die Summe. Feld und Summe müssen initialisiert werden, das geht am 
simpelsten mit Ausnullen. Der Index kann irgendwo stehen im Bereich 
0..127.

Und dann machst du so wie du es selbst beschrieben hast: ältesten von 
Summe abziehen, neuesten addieren und an die Stelle des ältesten setzen. 
Dann Index eins weiterstellen.

Ob du nun diesen Index inkrementierst und nach 127 auf 0 setzt oder 
dekrementierst und bei 0 auf 127 setzt ist deine Wahl. Ich nehme mal an, 
den Index meinst du mit Z-pointer.

Es gibt noch ein paar Bemerkungen dazu:
1. Per Mittelwertbildung kann man - sofern die ADC-Ergebnisse 
einigermaßen gauß- oder linear verteilt rauschen - die Anzahl gültiger 
Bits erhöhen. Das geht so zumeist für 1..2 Bit ganz gut.
2. Wenn man die Feldgröße in Grenzen variabel macht, kann man per 
Mittelwertbildung auch das Gesamtergebnis skalieren, ohne dazu eine 
Multiplikation oder gar Division zu benötigen. Diese Skalierung ist 
natürlich nur so fein wie 1/Feldgröße. Bei 128 ist das etwas unter 1%, 
aber das reicht für manche Zwecke bereits aus.
3. Anstelle das Gesamtergebnis 7x rechts zu verschieben, kannst du es 
auch mit sich selbst addieren und dann nur den Hi-Anteil 
weiterverwenden.

W.S.

von Chris (Gast)


Lesenswert?

Im Tutorial für SRAM 
"https://www.mikrocontroller.net/articles/AVR-Tutorial:_SRAM"; werden die 
Register 30 und 31 als Paar zum Zugriff auf den reservierten 
SRAM-Bereich als Z-Pointer bezeichnet, dachte das wäre sowas wie ein 
gängiger Begriff und meinte damit dieses Registerpaar.

Danke für den Hinweis den Wert zu "verdoppeln" und dann das Hi-Byte 
verwenden :) :) :)

von Der lustige Peter (Gast)


Lesenswert?

Chris schrieb:
> Hallo,
>
> ich hätte gerne fortlaufend den Mittelwert einer Spannungsmessung
> ermittelt. Angedacht war es so:
>
> 1. ich sammle erstmal bis ich 128 Messwerte habe und schreibe die in den
> SRAM
> 2. die Summe der Messwerte "schiebe" ich 7x (geht schneller als teilen,
> wenn ich die Forumsbeiträge richtig verstanden habe), dann sollte ich
> den Mittelwert haben
> 3. wenn ein neuer Wert kommt, ziehe ich zuerst den ältesten Wert meiner
> bekannten Summe ab und addiere dann dan neuen Wert dazu und berechne
> erneut den neuen Mittelwert
> 4. den ältesten Wert im SRAM ersetze ich dann natürlich auch durch den
> neuen
>


Kläre uns doch bitte mal genau dein Vorhaben.

Und nicht was Du wie in welcher Reihenfolge berechnen willst.

Was versprichst Du Dir?

von Peter D. (peda)


Lesenswert?

Die Sparmethode entspricht einem RC-Glied, der gleitende Mittelwert 
einem Integrator.
Wenn man nicht mit RAM knapsen muß, sollte man den gleitenden Mittelwert 
immer vorziehen, da er schneller einschwingt.

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


Lesenswert?

Peter D. schrieb:
> Wenn man nicht mit RAM knapsen muß, sollte man den gleitenden Mittelwert
> immer vorziehen, da er schneller einschwingt.
Und wenn man mit Rechenzeit auch nicht knapsen muss, dann kann man 
gleich einen sortierenden Filter anwenden und den Median ausrechnen 
;-)
https://www.mikrocontroller.net/articles/Median_Filter

> da er schneller einschwingt.
Ich vermute fast, dass deutlich mehr als 99% der Mittelwertfilter nicht 
großartig auf Reaktionszeiten untersucht wurden. Der Programmierer hat 
einfach irgendeine Filterlänge genommen und es hat funktioniert. Fertig.
Und dafür reicht dann der exponentielle Mittelwert allemal.

Chris schrieb:
> dachte das wäre sowas wie ein gängiger Begriff
Ach, du sprichst von AVR-Controllern. Die anderen 789 uC-Typen haben 
nämlich keinen Z-Pointer. Da ist dieser Begriff dann logischerweise auch 
nicht so gängig...

: Bearbeitet durch Moderator
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.