Gleitender Mittelwert / Einstellbar

Rafale

Level-2
Beiträge
10
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo
Ich möchte im Gegensatz zum Siemens Baustein FB "FloatingAverage" einen eigenen Baustein schreiben, wo ich die Anzahl Messpunkte individuell einstellen kann und nicht an einen fixe Länge von 100 gebunden bin.
Ich habe mir das ganze codiert und es funktioniert auch, solange ich positive Messwerte vorgebe. Sobald ich einen negativen Messwert habe funktioniert der Baustein nicht mehr bzw. nach x mal überschreiben des Messwertes mit positiven Werten läuft er wieder. Hat jemand eine Idee woran es liegen könnte?

Input:
-messwert: Real //-10.0 bis +20.0V
-trigger: Bool // 1000ms Rechteck
-anzahl: Int// Anzahl Messwerte für Mittelwert 5..100

Output:
-mittelwert: Real //-10.0 bis +20.0V

Static:
-summe: Real //Summe der eingelesene Messwerte
-statTrigger: Bool // Trigger OLD
-statHistory: Array [0..50] of Real // Ringspeicher
-statCounter: Int // Zähler, Position in Ringspeicher

Temp:
-tempTrigger: Bool // Flanke aus Trigger Signal

Programm:
//Detektion Flanke
#tempTrigger := #trigger AND NOT #statTrigger;
#statTrigger := #trigger;

//Wenn Flanke auftritt, Messwert einlesen und neuer MIttelwert berechnen
IF #tempTrigger THEN
#summe := #summe - #statHistory[#statCounter]; // Ältesten Wert von der Summe subtrahieren
#summe := #summe + #messwert; // Neuen Wert zur Summe addieren
#statHistory[#statCounter] := #messwert; //Neuen Wert im Historienspeicher speichern

// Zähler als Index für Feldzugriffe weiterzählen oder auf 0 setzen
#statCounter := #statCounter + 1;
IF #statCounter >= #anzahl THEN
#statCounter := 0;
END_IF;

// Aus der aktuellen Summe und der Anzahl der Messungen ergibt sich der Mittelwert
#mittelwert := #summe/ #anzahl;

END_IF;

Besten Dank für eure Inputs
Rafi
 
Was sofort auffällt: Im Kommentar zu Anzahl steht 5..100, Du hast aber nur ein Array mit 50 Werten deklariert.
Wie sieht denn Dein Aufruf aus?
Gruß
Erich
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
... du brauchst überhaupt keinen Ringspeicher für deine "letzten Werte" - du brauchst diese nur aufsummieren und dir am Anfang "merken" wieviele du schon hast.
Dein "gleitender Mittelwert" ist dann :
Code:
myAnzahl := myAnzahl +1 ;
if myAnzahl > Vorgabe_Anzahl then myAnzahl := Vorgabe_Anzahl ; End_if ;

Ausgabe_Mittelwert := (last_Mittelwert * (myAnzahl -1) + aktueller_Wert) / myAnzahl ;
last_Mittelwert  := Ausgabe_Mittelwert ;
Die Variable "last_Mittelwert" und "myAnzahl" sollten im STAT-Bereich deklariert sein.

Gruß
Larry
 
Larry, was Du beschreibst führt zu anderen Resultaten.
Bein echten "gleitenden Mittelwert" hat ein Messwert keine Wirkung mehr auf das Resultat, sobald er aus dem gleitenden Fenster herausgefallen ist.
Bei Deiner Methode hat ein Messwert (zwar kleiner werdende) Wirkung auf das Resultat bis die Wirkung unerheblich klein geworden ist.
Nimm als Beispiel: Alle Messwerte Null und ein extremer Ausreißer, dann müsste nach z. B. 10 Werten wieder Null rauskommen, tuts aber nicht.
Gruß
Erich
 
Zuletzt bearbeitet:
Was heißt genau, "Er funktioniert nicht mehr"?

// Aus der aktuellen Summe und der Anzahl der Messungen ergibt sich der Mittelwert
#mittelwert := #summe/ #anzahl;

Wenn der Speicher des Bausteins leer ist (Start), dann kommt ein Wert hinein und du dividierst bei Anzahl = 50 durch 50.
Also hast du die ersten 50 Werte so ziemlichen Mist als Ergebnis stehen. (Wird immer besser, bis 50 erreicht ist).
Du soltest beim Start eine Variable mitzählen, die die Zahl der wirklich eingelesenen Werte angibt und durch diese teilen.
Genauso, könntest du den Speicher dann auch "Leerlaufen" lassen, wenn du am Ende der Messung bist, falls du einen Zeitimpuls für den Messwerttrigger nutzen willst und dann u.U. keine Werte mehr bekommst.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
@Erich:
Du hast vollkommen Recht ... und es ist auch genau wie du schreibst ...
Dennoch hat mein Vorschlag den Vorteil, dass er den Speicher nicht sonderlich belastet und Zykluszeit.
Im Falle des Wunsches des TE müßte man das Werte-Array ja dynamisch erzeugen können - oder halt so groß wie die maximal vorstellbare Glättung sein könnte. Hier gibt es dann aber auch wieder von der Speicherbelegung eine Obergrenze und das Durchlaufen des Array's würde die Zykluszeit der Steuerung ganz schön belasten. Dagegen nimmt sich doch der (aus miener Sicht kleine) Nachteil meiner Lösung dann vielleicht doch nicht so schlimm aus ...

Gruß
Larry
 
@Larry & @Rafi
... und das Durchlaufen des Array's würde die Zykluszeit der Steuerung ganz schön belasten. ...
Das ist doch das Schöne an Rafis Lösung, dass er nicht jedesmal die Summe neu bildet, sondern den rausgeschmissenen Summanden von der Summe subtrahiert und den neuen Messwert dazu addiert.
Minimale Belastung der ZyklusZeit, aber keine "SelbstHeilung" eingebaut. Die aktuelle Summe passt nicht unbedingt zu den "aktiven" Summanden, da ein "Offset", sobald er - wodurch auch immer - einmal eingefangen wurde, sich beharrlich durch die folgenden Berechnungen zieht.
Leider ist aus Rafis Programm nicht zu erkennen, wie er das Herstellen einer Grundstellung bewerkstelligen will. Am einfachsten wäre wohl das Löschen aller Array-Elemente und der Summe in einem Rutsch - was dann ausnahmsweise einen Zyklus lang auf die ZyklusZeit geht.
Damit die Summe "automatisch" mit gelöscht wird, würde ich das Array-Element statHistory[0] für die Summe benutzen und statHistory[1] bis statHistory[n] für die Summanden.
Dann ist die höchste verwendete IndexNr auch identisch mit der Anzahl n, durch die die Summe dividiert werden muss.
Deine Methode, Larry, habe ich auch bevorzugt angewendet. Der Nachteil war nicht wesentlich.

@Rafi
Hallo
Ich habe mir das ganze codiert und es funktioniert auch, solange ich positive Messwerte vorgebe. Sobald ich einen negativen Messwert habe funktioniert der Baustein nicht mehr bzw. nach x mal überschreiben des Messwertes mit positiven Werten läuft er wieder.
An Deinem Programm kann ich keinen Fehler finden, der das Ausflippen bei negativen Messwerten erklärt.
Wie äussert sich denn das Fehlverhalten, wenn der Baustein "nicht mehr funktioniert"?

Gruss, Heinileini
 
.. Am einfachsten wäre wohl das Löschen aller Array-Elemente und der Summe in einem Rutsch ..
Noch einfacher ist es, beim Start der Messung des komplette Array auf den aktuellen Messwert zu setzen.
Damit erspart man sich auch ..

.. Du soltest beim Start eine Variable mitzählen, die die Zahl der wirklich eingelesenen Werte angibt und durch diese teilen...

.. das Mitzählen und man kann von Anfang an durch "Anzahl" teilen. Ein zweiter Vorteil ist ein gleichbleibendes Dämpfungsverhalten. Ab der ersten bzw. ab der zweiten Messung hat der aktuelle Wert die selbe Gewichtung auf die Summe, was ansonsten beim Füllen des Arrays nicht der Fall wäre.

@Rafi,
Einen grundlegenden Fehler bzgl. neg. Zahlen kann auch ich in deinem Code nicht sehen.

PS:
Sollte es tatsächlich nur um eine Dämpfung gehen und weniger um statistische Auswertungen, dann wäre die von Larry angesprochene Variante natürlich zu bevorzugen.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Sobald ich einen negativen Messwert habe funktioniert der Baustein nicht mehr
Hast Du eventuell beim Test eine negative Anzahl vorgegeben anstatt einem negativen Messwert?
Oder war der negative Messwert betragsmäßig viiiieeel größer als die positiven Testwerte? Hast du mal ein Beispiel für das "funktioniert nicht mehr"?


// Aus der aktuellen Summe und der Anzahl der Messungen ergibt sich der Mittelwert
#mittelwert := #summe/ #anzahl;
probiere mal:
Code:
#mittelwert := #summe/ INT_TO_REAL(#anzahl);

// Zähler als Index für Feldzugriffe weiterzählen oder auf 0 setzen
#statCounter := #statCounter + 1;
IF #statCounter >= #anzahl THEN
#statCounter := 0;
END_IF;
Das könnte man auch so formulieren:
Code:
#statCounter := (#statCounter + 1) MOD #anzahl;

Harald
 
Das Filtern geht einfacher und vor allem billiger, wenn einer Systemtheorie nimmt. Der einfachste Filter ist dieser hier

Wert_Gefiltert = Neuer_Sensor_Wert * f + Alter_Gefilterter_Wert * (1-f)

f wählt man von 0..1 . Wenn f = 1 ist, wird gar nichts gefiltert. Ist f nahe bei 0 wird hauptsächlich der alte Wert übernommen.



Dann kann man auch noch ganz klassisch vorgehen und einen Filter Online entwickeln. Es kostet 10s

http://t-filter.engineerjs.com/

Ein Tiefpass ist übrigens immer eine Art Glättung. Der C-Kode vom Generator lässt sich leicht übersetzen. Ein Bsp mit Tiefpass bis 5Hz und 100Hz Abtastfrequenz.

Code:
static double filter_taps[SAMPLEFILTER_TAP_NUM] = {
  0.02341152899192398,
  0.06471122356467367,
  0.12060371719780817,
  0.16958710211144923,
  0.1891554348168665,
  0.16958710211144923,
  0.12060371719780817,
  0.06471122356467367,
  0.02341152899192398
};

void SampleFilter_init(SampleFilter* f) {
  int i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i)
    f->history[i] = 0;
  f->last_index = 0;
}

void SampleFilter_put(SampleFilter* f, double input) {
  f->history[f->last_index++] = input;
  if(f->last_index == SAMPLEFILTER_TAP_NUM)
    f->last_index = 0;
}

double SampleFilter_get(SampleFilter* f) {
  double acc = 0;
  int index = f->last_index, i;
  for(i = 0; i < SAMPLEFILTER_TAP_NUM; ++i) {
    index = index != 0 ? index-1 : SAMPLEFILTER_TAP_NUM-1;
    acc += f->history[index] * filter_taps[i];
  };
  return acc;
}

Das ist sehr viel einfacher zu warten
 
So sieht's bei Mir aus.

FUNCTION_BLOCK fbUintGleitend
VAR_INPUT
iui_Wert : UINT; (* Eingabewert *)
iui_Anzahl : UINT := 50; (* Anzahl Werte *)
END_VAR
VAR_OUTPUT
oui_Wert : UINT := 16#7FFF; (* Gleitender Wert (Initial Halbzeit) *)
END_VAR
VAR
xIFC : BOOL := TRUE; (* erster Zyclus *)
lr_Wert : LREAL; (* Letzter Wert * Anzahl Werte *)
END_VAR

IF xIFC THEN
(* erster Zyclus *)
lr_Wert := UINT_TO_LREAL(iui_Wert); (* Istwert uebernehmen *)
xIFC := FALSE;
ELSE
lr_Wert := ( ( lr_Wert * UINT_TO_LREAL(iui_Anzahl - 1) )
+ UINT_TO_LREAL(iui_Wert)
) / UINT_TO_LREAL(iui_Anzahl);
END_IF;

oui_Wert := LREAL_TO_UINT(lr_Wert);



 
Zurück
Oben