PDA

View Full Version : Werte glätten



McNugget
29.09.2008, 12:57
Hallo allerseits.
Habe schon einiges hier im Forum gelesen und leider gesehen, dass ich letztes Wochenende in Bielefeld wohl gefehlt habe. :p
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.09.2008, 13:08
Bsp:




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

McNugget
29.09.2008, 13:16
Wow.

Das war mal schnell.

Vielen Dank.

McNugget

McNugget
29.09.2008, 13:51
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[i];
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.09.2008, 14:00
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.09.2008, 14:14
Vielen Dank Zotos,

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

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.09.2008, 14:33
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.09.2008, 14:45
Also ich würde das so machen:



durchschnitt:=(durchschnitt*10+messwert)/11;


ist etwas übersichtlicher und schneller.

McNugget
29.09.2008, 14:49
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[i];
END_FOR
durchschnitt := durchschnitt/10;
j := j + 1;


Bin begeistert von der Anteilnahme. ;-)

Gruss

McNugget

drfunfrock
29.09.2008, 14:52
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.09.2008, 14:56
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.09.2008, 15:33
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.09.2008, 15:42
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.09.2008, 15:43
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.09.2008, 15:45
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.09.2008, 16:10
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.09.2008, 16:37
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.09.2008, 17:06
@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.09.2008, 17:15
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.09.2008, 17:49
@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.09.2008, 07:21
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. :mrgreen:

Gruss

McNugget

drfunfrock
30.09.2008, 07:29
@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.09.2008, 07:32
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.09.2008, 07:55
@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.09.2008, 08:43
@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

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.10.2008, 16:43
[quote=McNugget;157602]

ist das noch ein thema oder erledigt ?

McNugget
28.10.2008, 17:42
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.10.2008, 18:13
ja prima ... hätte sonst noch was für dich gehabt

vierlagig
28.10.2008, 19:01
ja prima ... hätte sonst noch was für dich gehabt

kannste ja trotzdem mal herzeigen

McNugget
28.10.2008, 20:28
... Genau.. Da gebe ich 4L Recht.

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

Gruss

McNugget

stricky
29.10.2008, 07:46
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.10.2008, 08:31
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.10.2008, 08:43
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.

Aber das ist ja schnell gebastelt.

Cool.

Vielen Dank!
***************


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


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;


gruß

RobiHerb
10.11.2008, 16:50
Also ich würde das so machen:



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

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.