Routine verrechnet sich ???? Bitte um Code-Review.

  • Hallo *

    Ich habe ein mir nicht nachvollziehbares Problem bei der Median/Mittelwertberechnung von Messdaten.
    Der unten stehende C++ Code(ausschnitt) läuft auf einem ESP8266 / Arduino.

    Die Median-Funktion soll das ihr übergeben Array sortieren und dann über die mittelsten 5 Werte den Durchschnitt berechnen.
    Dieser Wert wird zurück gegeben und weiter verarbeitet.
    (Die Laufzeitausgabe im Fehlerfall steht weiter unten in einem gesonderten Code-Block)

    Nun kommt es ab und zu (selten, nicht systematisch, unabhängig von der Größe der Messwerte) zu dem Fall, dass der berechnete Wert viel zu klein ist (als ob ein Wert fehlt).
    Das führt natürlich zu sporadischen Ausreissern, die in der Anzeige als hässliche Zacken nach unten zu identifizieren sind.. und natürlich falsch sind.

    Ich wende mich an euch, weil mir langsam die Ideen ausgehen, woran es liegen könnte...
    Der Gesamtcode liegt im erdfeuchte-Git-Repository im Verzeichnis /sensor


    Wer das nachbauen will, muss noch die folgenden Defines/Variablen (global) setzten...

    Code
    #define MEASURING_INTERVALLS 10
    
    
    volatile unsigned long counter = 0;   // interrupt loop counter
    unsigned long soilMoistAveraged;

    Hier der sinnvoll gestrippte Kerncode:

    Und das wird (im Fehlerfall) an der serielle SS angezeigt (83060 ist natürlich Käse... aber wieso ???)

    Ich würde mich echt freuen, wenn mir jemand einen Schubs in die Lösungsrichtung geben könnte...

    :danke_ATDE:
    das Zen

  • Routine verrechnet sich ???? Bitte um Code-Review.? Schau mal ob du hier fündig wirst!

  • Hi Zentris,

    so?

    Bei einem Array mit einer geraden Anzahl an Elementen erhältst Du nicht die mittleren 5 Elemente sondern 5 Elemente um eine Position von der Mitte entfernt. Hier solltest Du auch eine geradzahlige Anzahl an Elementen verwenden, um einen Mittelwert der Elemente um den Median zu erhalten - also entweder 4 oder 6.

    tmp musst Du auf 0 setzen, da da - wie schnasseldag auch geschrieben hat - irgendein Wert drin steht, auf dem die Summe aufgesetzt werden soll. So als wenn Du als Oberkellner noch das Datum mit auf die Rechnung setzt - und dieses Jahr immer 20,16 € mehr bekommst... Schlingel! Oder Schelm?

    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Einmal editiert, zuletzt von Andreas (29. September 2016 um 17:46)

  • Hi Zen,

    Du verwendest temp zunächst zum Dreiecktausch...

    Code
    for (int i=0; i<arraySize; i++) {
        for (int j=arraySize-1; j>i; j--) {
          if( values[j] < values[j - 1] ) {
            tmp = values[j];

    ... und später nochmals als Summe zur Mittelwertbildung ...

    Code
    for (unsigned long i=arraySize/2-relVal; i<arraySize/2+relVal; tmp +=values[i++]) {}

    ... hast temp aber nicht vor der Summenbildung auf 0 gesetzt. Damit steht in temp noch irgendein Wert aus der vorangegangenen Sortierschleife.

    Mach mal:

    Code
    /**
      temp = 0;
      // und dann...
      for (unsigned long i=arraySize/2-relVal; i<arraySize/2+relVal; tmp +=values[i++]) {}

    Ob das jetzt alles ist, sehen meine müden Augen heut Abend auch nicht mehr. Aber zumindest schon mal ein Anfang :)

    Schönen Gruß an die "bessere Häfte"

    schnasseldag


  • Bei einem Array mit einer geraden Anzahl an Elementen erhältst Du nicht die mittleren 5 Elemente sondern 5 Elemente um eine Position von der Mitte entfernt. Hier solltest Du auch eine geradzahlige Anzahl an Elementen verwenden, um einen Mittelwert der Elemente um den Median zu erhalten - also entweder 4 oder 6.

    Andreas: Das würde seinen Median zwar mit einem Offset behaften (er sucht ja nicht um das Mittelelement), aber nicht das Phänomen von Ausreißern erklären. Alternativ kann er aber auch seine Arraygröße auf einen ungeraden Wert setzen. Dann findet sich der Median auch leichter :-).


  • Du verwendest temp zunächst zum Dreiecktausch...
    ...
    Ob das jetzt alles ist, sehen meine müden Augen heut Abend auch nicht mehr. Aber zumindest schon mal ein Anfang :)

    Ups!! Oh Mann! :wallbash:
    Ich stiere da seit Tagen drauf und bin doch blind... :thumbs1:


    Schönen Gruß an die "bessere Häfte"

    Danke .. und Gruß zurück.. schade, dass du nicht kommen kannst.. =(

    LG, das Zen

  • Hi Zentris,


    Ähm... versteh ich jetzt nicht..

    Warum sollte ich das Array um 2 Werte "verkürzen" ? :s :s
    Diese ineinanden geschachtelten For - Schleifen sind eine Sortierung des Arrays...
    das Zen

    Dass da sortiert wird, habe ich schon erkannt. Dein Array beginnt bei Index 0. Das letzte Element hat den Index ArraySize -1. i fängt bei 0 an und wird in der Schleife erhöht, j fängt auf der anderen Seite des Arrays an. Wenn die beiden sich (fast) treffen, werden ggf. noch die Einträge getauscht und dann i erhöht und j fängt wieder bei höchstem Index an. usw.

    Wenn i mit einem zu hohen Wert beginnt, dann hat j keinen Startwert mehr, da er außerhalb seiner Definition liegt. Diese Schleifendurchläufe machen also keinen Sinn.


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Einmal editiert, zuletzt von Andreas (27. September 2016 um 23:11)


  • Ich stiere da seit Tagen drauf und bin doch blind... :thumbs1:


    Das geht den Menschen wie den Leuten! Ich hab' deswegen immer noch meine Brille, auf deren Verschmutzungsgrad ich's dann abwälzen kann.:lol:
    Ja, ich find's auch schade aber beim nächsten Mal klappt's dann bestimmt wieder.

    Grüßle aus dem Süden

    schnasseldag


  • Bei einem Array mit einer geraden Anzahl an Elementen erhältst Du nicht die mittleren 5 Elemente sondern 5 Elemente um eine Position von der Mitte entfernt. Hier solltest Du auch eine geradzahlige Anzahl an Elementen verwenden, um einen Mittelwert der Elemente um den Median zu erhalten - also entweder 4 oder 6.

    Ja, das ist mir klar, ursprünglich wollte ich auch nur 9 Messungen machen... wobei der Versatz doch eigentlich nur ein systematischer Fehler ist und nix machen sollte, oder?


  • ... wobei der Versatz doch eigentlich nur ein systematischer Fehler ist und nix machen sollte, oder?


    Jein, kommt darauf an, was Du mit einem systematischen Fehler meinst. Deine Mittelwertbildung liegt nicht symmetrisch im Zentrum. D.h. Du erhälst systematisch immer Werte, die leicht zu groß oder zu klein (also entweder oder - alternierend natürlich nicht) sind. Wenn die anschließende Meßwertverarbeitung noch eine Kalibrierung hat, dann sollte das ausgeglichen werden. Eine 1-Punktkalibrierung zur Offsetjustage sollte hier reichen.

  • Kaum guckt man ohne Brille...

    Das vergessene Rücksetzten des tmp-Puffers hatte den Nebeneffekt, dass da schon ein Wert drin war...
    Was die Rechnung "meistens" gut aussehen lies...

    Nachdem ich den vor der unteren for - Schleife genullt hatte, lagen _alle_ Werte zu tief...

    Lösung:
    Ich Depp habe den Durchschnitt falsch berechnet... und das schon seit Monaten ... grrrrrrr! :wallbash:
    ist ja peinlich... :mad_GREEN:

    (Nebenbei gerade gesehen, dass die Schleifenzähler vom falschen Type waren... korrigiert)

    :danke_ATDE: :danke_ATDE: :danke_ATDE:

    LG, das Zen

  • Es gibt noch einen anderen Punkt zu berücksichtigen. Am Anfang und am Ende des sortierten Array finden sich die Ausreißer wieder. D.h. eine unsymmetrische Verschiebung außerhalb der Mitte erlaubt den Ausreißern eine gewisse Gewichtung. Ich hätte die Medianoperation von der Mittelwertbildung getrennt. D.h. Deine Meßwerterfassung hat einen Ringpuffer an Werten und Du ermittelst von diesem den Median. Dann schiebst Du den nächsten Meßwert in Deinen Ringpuffer und ermittelst wieder den Median. Und wenn Du nun genügend Mediane zusammen hast, dann machst Du eine Mittelwertbildung über den einzelnen Medianen.
    Im O-Kalkül betrachtet, dürfte das auch bessere Laufzeiten bei gleicher Glättung ergeben. Der Median läuft mit O(n^2) und ist die teuerste Operation. Vermutlich könntest Du ihn wesentlich kleiner auslegen (z.B. 5 Elemente), wenn Du nicht versuchen würdest, Median und Glättung in einer Operation zu erschlagen.


  • Jein, kommt darauf an, was Du mit einem systematischen Fehler meinst. Deine Mittelwertbildung liegt nicht symmetrisch im Zentrum. D.h. Du erhälst systematisch immer Werte, die leicht zu groß oder zu klein (also entweder oder - alternierend natürlich nicht) sind. Wenn die anschließende Meßwertverarbeitung noch eine Kalibrierung hat, dann sollte das ausgeglichen werden. Eine 1-Punktkalibrierung zur Offsetjustage sollte hier reichen.

    Ok, klar.
    Wobei ich eigentlich bei der Auswertung dann 'eh auf volle kHz runden will, den Rest wollte ich als "Rauschen" ansehen - vorrausgesetzt natürlich, dass der Messhub (nass/trocken) ausreichend ist, was bei einigen Sensoren schon grenzwertig war/ist...

    Ich werde auf 9 Messungen umschalten, oder 11 ?
    Pro Messung 1 sec... zeitlich egal...


  • Es gibt noch einen anderen Punkt zu berücksichtigen. Am Anfang und am Ende des sortierten Array finden sich die Ausreißer wieder. D.h. eine unsymmetrische Verschiebung außerhalb der Mitte erlaubt den Ausreißern eine gewisse Gewichtung. Ich hätte die Medianoperation von der Mittelwertbildung getrennt. D.h. Deine Meßwerterfassung hat einen Ringpuffer an Werten und Du ermittelst von diesem den Median. Dann schiebst Du den nächsten Meßwert in Deinen Ringpuffer und ermittelst wieder den Median. Und wenn Du nun genügend Mediane zusammen hast, dann machst Du eine Mittelwertbildung über den einzelnen Medianen.
    Im O-Kalkül betrachtet, dürfte das auch bessere Laufzeiten bei gleicher Glättung ergeben. Der Median läuft mit O(n^2) und ist die teuerste Operation. Vermutlich könntest Du ihn wesentlich kleiner auslegen (z.B. 5 Elemente), wenn Du nicht versuchen würdest, Median und Glättung in einer Operation zu erschlagen.

    Ich hoffe, ich habe verstanden, was du meinst:
    Wenn der Ringpuffer (noch) gefüllt ist (vom letzten Messzyclus) und ich schiebe jetzt z.B. 9 Messungen nach, dann würde ich 9x den Median über je 9 Werte bilden und diese z.B. gleich in einer gesonderten Variable aufsummieren.

    Am Schluss dann den Median-Durchschnitt bilden, indem ich die gesonderte Variable durch die Anzahl der Messungen teile (Durchschnitt).

    Hab ich dich so richtig verstanden ?

    das Zen


  • Wenn der Ringpuffer (noch) gefüllt ist (vom letzten Messzyclus) und ich schiebe jetzt z.B. 9 Messungen nach, dann würde ich 9x den Median über je 9 Werte bilden und diese z.B. gleich in einer gesonderten Variable aufsummieren.

    Am Schluss dann den Median-Durchschnitt bilden, indem ich die gesonderte Variable durch die Anzahl der Messungen teile (Durchschnitt).


    Fast.
    Wenn ich Dich richtig verstehe, dann willst Du immer 9 frische Meßwerte dem Median unterziehen und dann den Wert merken (bzw. im gleitenden Mittelwertfilter aufsummieren). Das kannst Du natürlich machen, wenn Du es Dir erlauben kannst immer 9 frische Meßwerte zu ermitteln, um dann zu einem "frischen" Median zu kommen. Ich dachte eher an einen Ringpuffer mit (9) Raw-Meßwerten. Vorn kommt ein neuer Meßwert rein und hinten fällt der letzte raus. Immer wenn das passiert, ermittelst Du den Median (array aber vorher kopieren) und merkst ihn Dir. Damit ermittelst Du also mit jedem Raw-Meßwert einen Median. Und zwar aus einem frischen Meßwert und den 8 letzten (alten) Meßwerten. Die Medianbildung muß dann natürlich auf einer Kopie der Werte des Raw-Ringpuffers bestehen. Sortierung wäre hier tödlich!

    Noch etwas. Der gleitende Mittelwert ist nicht wirklich ein guter Mittelwert. Das einzig gute an ihm ist, daß er sich sehr einfach ermitteln läßt. Eines seiner Probleme ist allerdings, daß ein Ausreißer sehr lange benötigt, bis er wieder "verschwunden" ist. Da Du aber einen Medianfilter davor hast (der genau AUsreißer beseitigt) dürfte der Gleitende Mittelwert wohl pragmatisch ok sein.
    Automatisch zusammengefügt:
    Ich glaube, Andreas ist schon in die Falle gegangen :)

  • Hi Schnasseldag,


    Ich glaube, Andreas ist schon in die Falle gegangen :)


    Nö, Punkt 0:00 hat's in meinen Rechner im Hotel eingeschlagen. Habe bis eben gebraucht, wieder was Sinnvolles mit ihm anfangen zu können.

    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Einmal editiert, zuletzt von Andreas (29. September 2016 um 18:15)

  • Hi Schnasseldag,


    ZEN: Andreas Hinweis, die äußere Sortierschleife bis arraySize-2 laufen zu lassen, kannst Du aber auch noch annehmen. Das bringt ein paar CPU-Takte (wenngleich es an der Richtigkeit des Ergebnisses nix ändert).

    :bravo2:

    Da passiert eigentlich nichts anderes als i um 1 zu erhöhen, Bei j tut sich nichts mehr, und dann wird nochmals i erhöht... Kann man machen, muss man aber nicht. Man kann auch Variablen auf einen Wert setzen. Wenn die Variable sonst nicht mehr auftaucht, kann man sie auch löschen.


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

  • Mein Messverfahren sieht etwa so aus:

    • Reset
    • ... Menge Sachen mit WLAN, Zeit holen und so
    • 9 Messungen
    • Median/Mittelwert
    • Daten ins Intranet/Internet schicken
    • Laufzeit seit Reset ermitteln, Schlafzeit berechnen, Timer stellen
    • deep sleep einleiten
    • Zeit abgelaufen -> goto 1


    Das "Problem" ist, nach dem Reset aus dem Tiefschlaf ist nix mehr aus dem 'letzten Leben' vorhanden, RAM gelöscht, Reset halt.

    Ich werde demnächst den EEPROM mit Daten füllen, hatte da aber eher die Keys für die Verschlüsselung im Sinn...

    nun ja. Das Wichtigste: Mir wurde geholfen... schnell und kompetent!

    Super! Nochmal Danke :)

    das Zen

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!