# Werte glätten



## McNugget (29 September 2008)

Hallo allerseits.
Habe schon einiges hier im Forum gelesen und leider gesehen, dass ich letztes Wochenende in Bielefeld wohl gefehlt habe. 
Hiermit melde ich mich mal mit einem ersten Thread und natürlich als Neuling in dem gesamten Programmierthema speziell in bezug auf ST mit einer dummen Frage.

Die Suche habe ich schon zu Rate gezogen, konnte aber bisher aus den gegebenen Antworten nichts passendes für mich triangulieren.. (liegt wohl an Unerfahrenheit)

Ich habe zwar Beispiele zum Glätten von Eingangswerten für AWL und SCL gesehen, konnte dies aber nicht in ST übertragen.

Wie sähe ein Codeschnipsel aus, mit dem ich einen WORD-Wert (von Busklemmen eines WAGO Feldbusknoten 750-841) der stark pendelt um Ausreisser bereinige und mit dem ich ein relativ sauberes gemitteltes Ausgangssignal erhalte?

Ich bin froh, dass es im Internet Foren dieser Art gibt.

Gruss

McNugget


----------



## drfunfrock (29 September 2008)

Bsp: 


```
durchschnitt := 0
FOR I:=1 TO 10 DO 
  durchschnitt := durchschnitt + messwert[i];
END_FOR
durchschnitt := durchschnitt/10;
```


----------



## McNugget (29 September 2008)

Wow.

Das war mal schnell.

Vielen Dank.

McNugget


----------



## McNugget (29 September 2008)

OK.. Habe es mal versucht:
 Läuft nicht.

Deklarationsteil:
FUNCTION_BLOCK FIlter_AI
VAR_INPUT
    IN:    WORD    ;
END_VAR

VAR_OUTPUT
    Durchschnitt: WORD;
END_VAR

VAR
 Messwert:ARRAY[1..10 ] OF WORD;
    I: INT;
    Out: BOOL;
END_VAR

Anweisungsteil:
Durchschnitt:=0;

FOR i:=1 TO 10 DO
  Durchschnitt := Durchschnitt + messwert_;
END_FOR
Durchschnitt := Durchschnitt/10;


Der Eingang "IN" müsste doch irgendwo explizit einfliessen, oder?
Ausserdem setzt sich der Baustein doch immer wieder auf "0" zurück. 

Wie gesagt: blutigster Anfänger in puncto Schleifen.

Gruss

McNugget_


----------



## zotos (29 September 2008)

Ohne zu testen:

```
IF j > 10 THEN
    j := 1;
END_IF;

messwerte[j] := ExtMesswert;

durchschnitt := 0;
FOR i:=1 TO 10 DO
  durchschnitt := durchschnitt + messwerte[i];
END_FOR
durchschnitt := durchschnitt/10;
j := j + 1;
```

Man hat ein Array in dem die letzten 10 Messwerte gespeichert werden. Dieses Array wird dann wie von drfunfrock beschrieben aufaddiert und durch die Anzahl der Elemente dividiert was den Durchschnitt ergibt.


----------



## McNugget (29 September 2008)

Vielen Dank Zotos,

ok, hier im Forum geht es etwas fixer zu.. Sehr cool. 

Ich habe es so übernommen, aber nun passiert folgendes:

Das Array füllt sich in allen 10 Zeilenmit Werten zwischen 17000 und 17200, aber der errechnete Durchschnitt pendelt immer so um die 4000.

Da ist doch irgendwo noch der Wurm drin. oder habe ich irgendwas falsch übertragen?

Kann man den Wert Durchschnitt mit etwas anderem als einer 0 initialisieren?
Zum Beispiel einmalig der Aktualwert am Eingang?

Gruss

McNugget


PS: Zotos: Kann es sein, dass Du den Anhalter gelesen hast?


----------



## Larry Laffer (29 September 2008)

Das Beispiel von Zotos sieht für mich gut aus ... was mir nicht so gefällt ist das du in deinem Beispiel mit dem Variablentyp WORD arbeitest. Das könnte ggf. schon das Problem sein. Ansonsten stell doch einfach mal den Code deines Bausteins hier ein ...


----------



## repök (29 September 2008)

Also ich würde das so machen:


```
durchschnitt:=(durchschnitt*10+messwert)/11;
```
 
ist etwas übersichtlicher und schneller.


----------



## McNugget (29 September 2008)

Hallo Larry,

die analoge Eingangsklemme liefert direkt ein Word. Ich möchte den Wert so früh wie möglich im System "beruhigen", bzw. glätten, und erst dann mit Typkonvertierungen beginnen, um eventuelle Rundungsfehler etc. möglichst zu minimieren. 
Ist der Ansatz falsch?

Deklaration:
FUNCTION_BLOCK FIlter_AI
VAR_INPUT
    IN:    WORD    ;
END_VAR

VAR_OUTPUT
    Durchschnitt: WORD;
END_VAR

VAR
     Messwerte:ARRAY[1..1000 ] OF WORD;
    I: INT;
    j: INT;
END_VAR


Anweisungsteil:

IF j > 10 THEN
    j := 1;
END_IF;

messwerte[j] := IN;

durchschnitt := 0;
FOR i:=1 TO 10 DO
  durchschnitt := durchschnitt + messwerte_;
END_FOR
durchschnitt := durchschnitt/10;
j := j + 1;


Bin begeistert von der Anteilnahme. ;-)

Gruss

McNugget_


----------



## drfunfrock (29 September 2008)

Ich hatte ebenfalls vergessen, dass man natürlich nicht in eine Word-Var aufaddiert, weil es dann zu Überläufen kommt. Meine Var durchschnitt muss dann grösser als WORD sein. Nimm einfach UDINT.


----------



## repök (29 September 2008)

drfunfrock schrieb:


> Ich hatte ebenfalls vergessen, dass man natürlich nicht in eine Word-Var aufaddiert, weil es dann zu Überläufen kommt. Meine Var durchschnitt muss dann grösser als WORD sein. Nimm einfach UDINT.


 
Da hat er recht. Das passiert mir des öfteren.


----------



## McNugget (29 September 2008)

Ok.. Habe ich gemacht, der Ausgabewert ist realistisch.
Aaaber: nun pendelt der Ausgabewert fast so schnell wie der Eingabewert, liegt wahrscheinlich daran, dass nur 10 Zyklen addiert werden und die Zykluszeit recht kurz ist (unter 15ms).
Kann ich das irgendwie schöner machen? Zyklenanzahl erhöhren wird wohl auch nicht endlos gehen, da ich dann ja auch irgendwann einen Überlauf produziere, oder?

Ausserdem habe ich in einem anderen Thread was davon gelesen, dass man die Werteveränderungen, die ein bestimmtes delta überschreiten, ausblenden kann. 
Das wäre superedel.

Gruss

McNugget


----------



## vierlagig (29 September 2008)

also langsamer wird deine wertänderung, wenn du nur zu bestimmten zeiten einen neuen wert in dein fifo schreibst.

delta und grenzen sind ganz einfache grenzwertabragen, also

IF wert < grenzwert_max AND wert > grenzwert_min
THEN "wert verarbeiten"
ELSE "wert ignorieren"


----------



## zotos (29 September 2008)

Für einen schnellen Test bietet sich die Variante von repök an:


```
If messwert > LLim AND messwert < ULim THEN
   durchschnitt:=(durchschnitt*100+messwert)/101;
END_IF
```

LLim unteres Limit und ULim Oberes Limit. wenn der aktuelle Messwert ein "Ausreißer" ist wird mit dem alten Durchschnitt gearbeitet. Dies beinhaltet natürlich auch die Gefahr das man einen Sensor defekt nicht erkennt usw. das kann man dann aber auch mit einem Zähler abfangen.


```
If messwert > LLim AND messwert < ULim THEN
   durchschnitt:=(durchschnitt*100+messwert)/101;
   ErrorCount := 0;
ELSE
   ErrorCount := ErrorCount + 1;
END_IF

IF ErrorCount > 20 THEN
   (* Mach was! der Sensor ist kaputt!*);
END_IF
```


----------



## drfunfrock (29 September 2008)

Du kannst die Schleife sehr wohl recht heftig erhöhen, wenn die CPU das mitmacht. Das kannst du ja im Systemmanager sehen, wieviel CPU-Zeit verbraucht wird. Ich denke, eine Schleife bis 100 funktioniert auf einem X86-prozessor recht gut.


----------



## McNugget (29 September 2008)

Sehr nette Anregungen. 
Vielen Dank.

@vierlagig: habe jetzt einen Taktgeber davorgesetzt, und das Array auf 100 erhöht: ein schöner ruhiger Wert.
Zu der Grenzwertverletzung: Wäre das folgende sinnvoll?
IF wert > grenzwert_max AND wert > grenzwert_min
THEN "Return"
ELSE "arbeite die Schleife ab"

 @Zotos: wenn der Sensor defekt ist, würde der Wert doch mit der Zeit gegen Null, bzw. gegen unendlich steigen, oder? (Gilt natürlich nur, wenn der Sensor definitiv einen Schluss oder einen Kabelbruch erlitten hat.) 

Ich habe es bereits bei defekten Transmittern erlebt, dass die von 0-20 mA hoch und runter schwingen. Dazwischen immer mal wieder ein annehmbarer Wert. So ein Fehler liesse sich mit einem solchen array wahrscheinlich auch nnicht herausfinden.

Zudem hat Wago in seinen Klemmen im Datenwort zwei Statusbits, die auf Fühlerbruch hinweisen, aber dazu komme ich die Tage noch mal. Habe auch da noch dumme Fragen.

Vielen Dank schon mal für die Vielzahl an Antworten und Tipps.

Macht ja richtig Spass, so ein Feedback zu bekommen und so nett aufgenommen zu werden.

McNugget


----------



## Larry Laffer (29 September 2008)

McNugget schrieb:


> Ich habe es bereits bei defekten Transmittern erlebt, dass die von 0-20 mA hoch und runter schwingen. Dazwischen immer mal wieder ein annehmbarer Wert. So ein Fehler liesse sich mit einem solchen array wahrscheinlich auch nnicht herausfinden.


 
Ich denke doch.
Du kannst doch in der Schleife genausogut wie du die Summenbildung für den Mittelwert machst auch nach groben Ausreissern gegenüber dem letzten erfassten Mittelwert suchen. Hast du solche erkannt, so kannst du z.B. daraus eine Fehlermeldung generieren oder/und sie bei der Mittelwertbildung ignorieren etc. - Im Prinzip ähnlich wie der Filter von *Vierlagig*.

Zu deiner Abtastrate:
Du könntest den FB auch zyklisch aufrufen und ihm als Parameter die OB1_Zykluszeit und ein gewünschtes Einlese-Intervall übergeben. Die Zykluszeit addierst du auf und führst den FB immer dann aus, wenn die aufaddierte Zeit > der Abtastrate ist.

Gruß
LL


----------



## vierlagig (29 September 2008)

McNugget schrieb:


> @vierlagig: habe jetzt einen Taktgeber davorgesetzt, und das Array auf 100 erhöht: ein schöner ruhiger Wert.
> Zu der Grenzwertverletzung: Wäre das folgende sinnvoll?
> IF wert > grenzwert_max AND wert > grenzwert_min
> THEN "Return"
> ELSE "arbeite die Schleife ab"



eher:

IF wert > grenzwert_max *OR* wert *<* grenzwert_min
THEN "Return"
ELSE "arbeite die Schleife ab"


----------



## hugo (29 September 2008)

möchte nur darauf hinweisen das glätten normalerweise etwas anderes ist als durchschnitt.

der durchschnitt ist je nach anzahl der punkte auch relativ aufwendig.

besser geegnet sollte da eine tiefpassfunktion sein die den oberen frequenzbereich des signals abschneidet.

aber beide funktionen durchschnitt sowie tiefpass findest du auch im source code in der open source library von oscat unter www.oscat.de

der tiefpass heist dort FT_PT1


----------



## vierlagig (29 September 2008)

@hugo:

die mittelwertbildung zur quasi-glättung ist ein erprobtes und gängiges mittel. mir ist bis jetzt noch kein erfahrener programmierer untergekommen, der davon abstand nehmen würde. vorallem ist es schnell und einfach umgesetzt und darüber hinaus, richtig implentiert sehr flexibel.

sicher, es ist nicht die mathematisch korrekte variante, aber bei den meisten messwerten doch ausreichend.


----------



## McNugget (30 September 2008)

Guten Morgen allerseits.

Hoffe, in allen Händen ist eine Tasse heissen Kaffees.

@LL: Du meinst also, pro Schleife überprüfen, ob der gemessene Wert ein gewisses delta überschreitet, und dann überprüfen, ob dies inm Zeitraum x (bzw. in den letzten y Abtastzyklen) n-mal vorgekommen ist? 

Aktuell habe ich einen Taktgeber in die Schleife integriert, der alle paar ms für einen Zyklus einen Wert auf True setzt. Dieser wird nach Abarbeiten der Schleife auf False zurückgesetzt.

@vierlagig: natürlich OR und <. Habe da zu schnell geschossen.

@Hugo: ich müsste doch beide Frequenzausreisser bereinigen also auch die unteren. Oder ist mit "oberen Frequenzbereiche" die relative Differenz zum durchschnittlichen Eingangssignal gemeint?



Wie man sieht, bin ich in dieser Thematik mehr als unerfahren.

Aber klasse, so viel positives Feedback zu bekommen. Macht den Eindruck, als machtet Ihr so was öfter. 

Gruss

McNugget


----------



## drfunfrock (30 September 2008)

vierlagig schrieb:


> @hugo:
> 
> die mittelwertbildung zur quasi-glättung ist ein erprobtes und gängiges mittel. mir ist bis jetzt noch kein erfahrener programmierer untergekommen, der davon abstand nehmen würde. vorallem ist es schnell und einfach umgesetzt und darüber hinaus, richtig implentiert sehr flexibel.



Hugo hat ja in dem  Sinne recht, dass man mit einer angepassten tiefpass Funktion CPU-Zyklen sparen kann und dass kann sich durchaus im Preis der SPS bemerkbar machen. Die Mittelwertbildung ist ja nichts anderes wie ein Tiefpassfilter, aber nicht sonderlich effizient.


----------



## drfunfrock (30 September 2008)

Da du einen Mittelwert hast, kannst du auch die Standartabweichung schnell berechnen. Das sollte eine sehr gute Auskunft über die Qualität des Messwertes geben.


----------



## Larry Laffer (30 September 2008)

McNugget schrieb:


> @LL: Du meinst also, pro Schleife überprüfen, ob der gemessene Wert ein gewisses delta überschreitet, und dann überprüfen, ob dies inm Zeitraum x (bzw. in den letzten y Abtastzyklen) n-mal vorgekommen ist?


 
So hatte ich es gedacht. 
Wobei es hier ja noch Alternativ-Möglichkeiten gibt : 
- Abweichung zum bisher gemessenen Mittelwert (nach oben oder unten) > xyz %
- Über- oder Unterschreitung eines Absolutwertes
- Verhältnis von Wert [x] zu Wert [x+1]
usw.

@drfunfrock , hugo:
Ich finde die Glättungs / Mittelwert-Diskussion sehr müßig. Sie trägt zum eigentlichen Problem nichts bei und so nebenher realisiere ich bei Kurven-Auswertungen die Glättung (ich meine hier jetzt tatsächlich Glättung) auch auf eine ähnliche Weise (und es funktioniert).
Man kann es natürlich auch über die Hoch-Tiefpass-Formel machen - ob das aber weniger Aufwand ist ...?

Gruß
LL


----------



## drfunfrock (30 September 2008)

Larry Laffer schrieb:


> @drfunfrock , hugo:
> Ich finde die Glättungs / Mittelwert-Diskussion sehr müßig. Sie trägt zum eigentlichen Problem nichts bei und so nebenher realisiere ich bei Kurven-Auswertungen die Glättung (ich meine hier jetzt tatsächlich Glättung) auch auf eine ähnliche Weise (und es funktioniert).
> Man kann es natürlich auch über die Hoch-Tiefpass-Formel machen - ob das aber weniger Aufwand ist ...?
> 
> ...



Zum 1. Die Mittelwertbildung ist OK. Zum 2. Es geht nicht um den Aufwand beim Programmieren, sondern ob ein Tiefpassfilter effzienter ist und damit auch weniger CPU benötigt. Und das sollte der Fall sein. Ich kann hier mit Labview einen Filter bauen, der hat bei einer Abtastfrequenz von 66Hz (15ms Zykluszeit) und einer Grenzfrequenz von 5Hz gerade, eine ordnung von 2 und dürfte damit wesentlich weniger CPU benötigen.


----------



## stricky (28 Oktober 2008)

McNugget schrieb:


> ist das noch ein thema oder erledigt ?


----------



## McNugget (28 Oktober 2008)

Ist durch Einsatz eines Codeschnipsels aus der OSCAT.Lib erledigt.. Aber es gibt ständig neue "Herausforderungen".. ;-)

Letztendlich kann ich noch zu schlecht strukturierten Text, um so etwas (also z.B. Array mit Mittelwertbildung) selber zu schreiben.. 



Ich fand es sehr interessant, zu sehen, wie man an diese Problematik herangehen kan..
Vielen Dank an alle Beteiligten.

Gruss

McNugget


----------



## stricky (28 Oktober 2008)

ja prima ... hätte sonst noch was für dich gehabt


----------



## vierlagig (28 Oktober 2008)

stricky schrieb:


> ja prima ... hätte sonst noch was für dich gehabt



kannste ja trotzdem mal herzeigen


----------



## McNugget (28 Oktober 2008)

... Genau.. Da gebe ich 4L Recht.

Bin immer gespannt.. Vor allem, wenn es mich schlauer machen kann..

Gruss

McNugget


----------



## stricky (29 Oktober 2008)

anbei ein bißchen code ...

ist damals aus der "not" enstanden, da ich auch immer ausreißer ( manchmal mehr als 50% vom letzten Wert ) hatte ...


```
FUNCTION_block Median

VAR_INPUT
 PV: Real; // Prozessvariable
 Start: bool; // Start Mittelwert
 Reset: bool; // Reset Mittelwert
 Puls:BOOL; // Impuls Meßwert aufnehmen
 Check:REAL; // Abweichung in Prozent +/-   
END_VAR

VAR_OUTPUT
 Out: Real; // Medianwert
 Stabil:BOOL; // PV = Mittelwert ( im Bereich )
END_VAR

VAR
  Werte: ARRAY[1..10] OF REAL;
  Median:ARRAY[1..10] OF REAL;
END_VAR

VAR_TEMP
    i:INT;
    j:INT;
    Help:REAL;
END_VAR

LABEL
   Saved;
END_LABEL

BEGIN
  
Stabil:=False;  
  
IF reset THEN
   Out:=0.0;
ELSIF start AND Out = 0.0 THEN
    FOR i:=1 TO 10 DO
        Werte[i]:=PV;
    END_FOR;
    Out:=PV;
ELSIF Puls AND start THEN
    
  FOR i:=10 TO 2 BY -1 DO
     Werte[i]:=Werte[i-1];
  END_FOR; 
  
  IF start then 
    Werte[1]:=PV;
  ELSE
    Werte[1]:=0.0;
  END_IF;
  
  Median:=werte;
  
  FOR i:=1 TO 9 DO
    FOR j:=i+1 TO 10 DO
      IF median[i] > median[j] THEN
        Help:=median[i];
        median[i]:=median[j];
        median[j]:=help;
      END_IF;
    END_FOR;
  END_FOR;

  FOR i:= 4 TO 7 DO
    Out:= Out + median[i];
  END_FOR;
  
    OUT := out / 5.0;  
    
END_IF;

    Stabil:= ABS(PV-Out) * 100.0 / ABS(PV) < Check; 
    
END_FUNCTION_BLOCK
```

es werden einfach 10 Werte aufgenommen, mit jedem Puls ein neuer - läuft bei mir meistens mit 100ms Takt - hat sich bewährt.

Dann werden die Werte sotiert und dann nur die "inneren" 4 Werte verwendet. Die anderen verworfen.

Die anderen addiert + dem letzten und durch 5 geteilt.

Das mit dem Ausgang stabil ist nur Spielerei. Wenn der Istwert innerhalb eines Fenster ( Angabe in % ) vom gemittelten Wert liegt ist der da.

Kannst damit schnell auf ausreißer reagieren - falls gewünscht.

Nutze den mittlerweile fast bei jedem Analogwert.

1. funktioniert gut 
2. der braucht kaum zykluszeit
3. du hast keine ausreißer mehr

gruß
christian


----------



## McNugget (29 Oktober 2008)

Wow.. Das ist mal nett.. Sehr nett....
Das ist ja fast noch netter, als die Dämpfung, die ich aktuell verwende.
(natürlich ist es das...)

Cool.

Vielen Dank!


----------



## stricky (29 Oktober 2008)

ich nochmal,

wieso sehe ich hier einen andren ( weniger ) text als in der email aus dem forum ...



> (natürlich ist es das...)
> 
> Als Einziges  fehlt noch, dass zu Beginn der Ausgangswert mit dem Eingangswert initialisiert wird, um nicht erst nach 10 * 100ms einen Wert zu erhalten.
> 
> ...



naja , egeal ... die Initialisierung ist doch drin, 

```
IF reset THEN
   Out:=0.0;
[COLOR=Red]ELSIF start AND Out = 0.0 THEN
    FOR i:=1 TO 10 DO
        Werte[i]:=PV;
    END_FOR;
    Out:=PV;[/COLOR]
ELSIF Puls AND start THEN
    
  FOR i:=10 TO 2 BY -1 DO
     Werte[i]:=Werte[i-1];
  END_FOR;
```

gruß


----------



## RobiHerb (10 November 2008)

*Infinite Memory*



repök schrieb:


> Also ich würde das so machen:
> 
> 
> ```
> ...



Das ist nicht nur übersichtlicher, das liefert auch bessere Ergebnisse. Dieser Filtertyp wird als inifinite Memory Typ klassifiziert. Es vergisst sozusagen nie einen Wert der Vergangenheit, solange die Auflösung des Datentyps ausreichend ist.

Ein Array mit einer festen Anzahl von Elementen, kann hingegen nur diese Anzahl von Werten der Vergangenheit ausmitteln.

Die infinite Memory Filter können noch wesentlich effektiver von den Algorithmen gestaltet werden aber das muss man dann in Büchern über Mathematik nachlesen. 

Ein anderer Weg ist der mit dem "Sliding Window", wie von Stricky angedeutet, dieses Verfahren hat insbesondere Vorteile im Postprozessing von Messreihen, da es im Prinzip nicht den Ärgermacht, dass Verzögerungen bei Sprüngen sich wie bei Tiefpassfiltern einstellen.


----------

