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
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
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.
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.
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.htmlChris 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
unsignedshortmittelwert(unsignedshortnewval)
2
{
3
staticunsignedshortmem[128];
4
staticunsignedshortptr=0;
5
staticunsignedlongavgsum=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
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.
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.
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.
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?
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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... ;-)
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.
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.
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
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.
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 :) :) :)
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?
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.
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...