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
Brauchst du immer einen Mittelwert über die letzten 128 Werte oder willst du eigentlich einen Mittelwert seit Beginn der Aufzeichnung?
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.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
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?
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch Moderator
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...
:
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.