Frage zu TON Timern in Codesys

Sps_Guy

Level-1
Beiträge
20
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

ich habe mir ein paar Tutorials angeschaut und auch die Doku in Codesys zu den Timern und da steht echt nicht viel. Daher möchte ich mein Problem hier diskutieren.

Der TON besitzt die Eingänge IN für das antriggern des Timers und PT für die Timerzeit. Der Timer liefert außerdem die Ausgänge ET für die bis Zeitpunkt x gezählte Zeit und Q für das Signalisieren, dass der Timer abgelaufen ist.

Wenn der Timer definiert ist (z.B. codesysTimer : TON;) startet man diesen somit wie folgt:
codesysTimer(IN:= True, PT := T#4s);

Ich habe dabei gemerkt, dass ET immer dann aktualisiert wird, wenn die Zeile codesysTimer(IN:= True, PT := T#4s); ausgeführt wird.

Jetzt habe ich ein eigenes kleines Beispielprojekt geschrieben, wo der Timer alle x ms (z.B. 100 ms) ablaufen soll und einen Wert aktualisieren soll.
Dies soll solange geschehen, bis ein zweiter Timer y (z.B. 5s) abläuft. In dieser Zeit sollte der Wert 50-mal (5000ms/100ms) aktualisiert werden, stattdessen wird dieser nur 41-mal aktualisiert. Kann sich dieses Phänomen einer erklären?

Der Code:

tDurX:= T#100MS;
tDurY:= T#5S;

IF NOT timeExpired THEN

timerY(IN := TRUE, PT := tDurY);
timerX(IN := TRUE, PT := tDurX);

END_IF

IF timerX.Q THEN

CNT := CNT + 1;
value := value + 100 * (TIME_TO_REAL(tDurX)/(TIME_TO_REAL(tDurY)));
timerX(IN := FALSE);

END_IF

IF timerY.Q THEN

timerY(IN := FALSE);
timeExpired := TRUE;

END_IF
 
In dem SPS-Zyklus, in dem timerX.Q true wird, setzt Du den Timer zurück. Wieder anlaufen tut er jedoch erst mit dem Aufruf timerX(IN:=TRUE,PT:=tDurX) im folgenden Zyklus. Die Zeit zwischen den timerX.Q-Triggern ist also tDurX + 1 SPS-Zyklus.
Schreib mal hinter dem timerX(IN:=FALSE) noch ein timerX(IN:=TRUE), um den Timer noch im selben Zyklus wieder zu starten.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Unabhängig vom Problem: Timer nur bedingt aufzurufen, ist eine ganz gefährliche Sache.
Auch mit der o.a. korrekten Antwort wird es passieren, dass es nicht zwingend 50 mal aktualisiert wird sondern weniger. Abhängig von der Zykluszeit kann es vorkommen, dass beim einen Programm-Durchlauf der Timer gerade erst 99ms erreicht hat, also noch nicht ganz fertig ist, im nächsten Durchgang aber dann schon bei 105ms angekommen ist. Das ergibt dann einen Fehler, der sich aufsummiert.
 
Ok das habe ich auch spekuliert und es funktioniert auch. Aber was mir verunsichert ist, dass ich dann trotzdem nicht auf die 50 komme, sondern auf 49. Der Fehler ist auch nicht konstant, sondern schwankt, wenn man tDurX und tDurY ändert. Das heißt die Funktion verhält sich einfach nicht genau so, wie ich es möchte. Kannst du mir erklären woran das liegt?
 
Unabhängig vom Problem: Timer nur bedingt aufzurufen, ist eine ganz gefährliche Sache.
Auch mit der o.a. korrekten Antwort wird es passieren, dass es nicht zwingend 50 mal aktualisiert wird sondern weniger. Abhängig von der Zykluszeit kann es vorkommen, dass beim einen Programm-Durchlauf der Timer gerade erst 99ms erreicht hat, also noch nicht ganz fertig ist, im nächsten Durchgang aber dann schon bei 105ms angekommen ist. Das ergibt dann einen Fehler, der sich aufsummiert.
Was genau bedeutet timer bedingt aufrufen?
 
Das heißt die Funktion verhält sich einfach nicht genau so, wie ich es möchte. Kannst du mir erklären woran das liegt?
Das hat Oberchefe doch erklärt. Im Programm wird der Timer alle x ms abgefragt/bearbeitet, also können beim abfragen nur Werte von n * x ms gelesen werden. n ist dabei die Anzahl der Zykluszeiten. x ist die (durchschnittliche!) Länge der Zykluszeit. Die Zykluszeit kann durchaus schwanken.
Was genau bedeutet timer bedingt aufrufen?
Das bedeutet, man soll den Timer "unbedingt" in jedem Zyklus aufrufen und nur über den Zustand am IN-Eingang beeinflussen.

Edit:
Ruft man den Timer nur dann auf, wenn man es für nötig hält, dann kann es passieren, dass der Timer die FlankenWechsel des IN-Signals gar nicht "sehen" kann.
 
Das hat Oberchefe doch erklärt. Im Programm wird der Timer alle x ms abgefragt/bearbeitet, also können beim abfragen nur Werte von n * x ms gelesen werden. n ist dabei die Anzahl der Zykluszeiten. x ist die (durchschnittliche!) Länge der Zykluszeit. Die Zykluszeit kann durchaus schwanken.

Das bedeutet, man soll den Timer "unbedingt" in jedem Zyklus aufrufen und nur über den Zustand am IN-Eingang beeinflussen.

Edit:
Ruft man den Timer nur dann auf, wenn man es für nötig hält, dann kann es passieren, dass der Timer die FlankenWechsel des IN-Signals gar nicht "sehen" kann.
Aus der Eklärung von Oberchefe und dir verstehe ich.
Wenn ich die Timer ohne Bedingung immer laufen lasse, kann ich nur "hoffen", dass die Zeile, in der der Timer gesetzt wird, alle 100 ms auftaucht, wenn sie aber mal in 105 ms, mal in 104 ms und mal in 110 ms auftauchen würde, würde ich schon einen Fehler um die 5+4+10 ms haben?

Gibt es die Timer auch interrupt basiert?

Also das sie parallel zum Code laufen und dann ausgelöst werden, wenn wirklich die Zeit abgelaufen ist?

Edit:
Gäbe es nicht dasselbe Problem bei der Flankenerkennung? Man drückt ja den Button nich genau dann, wenn der Code an der Zeile der Flankenerkennung ankommt. Wie funktioniert das denn dann?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe den Code mal verbessert:

tDurX:= T#100MS;
tDurY:= T#20S;

timerY(IN := timerYStart, PT := tDurY);
timerX(IN := timerXStart, PT := tDurX);

IF NOT timeExpired THEN

timerXStart := TRUE;
timerYStart := TRUE;

END_IF

IF timerX.Q THEN

CNT := CNT + 1;
value := value + 100 * (TIME_TO_REAL(tDurX)/(TIME_TO_REAL(tDurY)));
timerX(IN := FALSE);
timerX(IN := TRUE);

END_IF

IF timerY.Q THEN

timerYStart := FALSE;
timerXStart := FALSE;
timeExpired := TRUE;

END_IF

Aus vorangegangener Erklärung verstehe ich, dass auh durch diese Verbesserung keine 50-mal erreicht werden können.
 
Gibt es die Timer auch interrupt basiert?

Also das sie parallel zum Code laufen und dann ausgelöst werden, wenn wirklich die Zeit abgelaufen ist?
Das "Auslösen" wäre also ein "rein theoretischer" Vorgang, der wirkunglos bleibt, weil das Abfragen des Erreichens (eigentlich des Überschreitens) der Zeit nach wie vor im Zyklus passiert. Vom ZeitPunkt des theoretischen Auslösens bis zum praktischen Abfragen im Programm werden MilliSekunden dennoch und weiterhin nutzlos verstreichen.
Laufzeiten von Timern, die nur unwesentlich länger oder sogar kürzer sind, als die ZyklusZeit, führen unweigerlich zu starken prozentualen Schwankungen.
Die Schwankungen der ZyklusZeit sollte man auch im Auge behalten.

Aus vorangegangener Erklärung verstehe ich, dass auh durch diese Verbesserung keine 50-mal erreicht werden können.
So ist es. Falls man beim Testen feststellt, dass Zeiten von sagen wir mal 48 ms genauer reproduzierbar zu tatsächlichen Pausen von 50 ms führen, dann könnte man 48 ms programmieren statt 50 ms.
 
In welcher Task willst Du die "genaue" Zeitmessung machen? Wenn das eine zyklische Task ist, kann man auch die Task-Durchläufe zählen.

Gäbe es nicht dasselbe Problem bei der Flankenerkennung? Man drückt ja den Button nich genau dann, wenn der Code an der Zeile der Flankenerkennung ankommt. Wie funktioniert das denn dann?
Das Signal muß mindestens so lange wie die Zykluszeit anstehen, dann wird es auch in mindestens einem Zyklus erfasst.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Danke für die Hilfe.
Das Signal muß mindestens so lange wie die Zykluszeit anstehen, dann wird es auch in mindestens einem Zyklus erfasst.

Also geht man davon aus, dass die SPS so schnell ist, das ein kurzer Tastendruck aufjedenfall in mindestens einem Taskzyklus erfasst wird.

Danke an alle für die Unterstützung.

Das war wirklich sehr Hilfreich.
 
Ich kenne keine Codesys-Steuerung, die Interrupt-Tasks bietet. Wenn Du genaue Zeitmessungen auf Basis der TIME()-Funktion machen willst ( wie z. B. mit einem TON), dann musßt Du Dein Programm in einem festen Zykluszeitraster laufen lassen. Am besten mit einer Zykluszeit von 1 ms, denn das ist die Auflösung vom Datentyp TIME.
 
Gäbe es nicht dasselbe Problem bei der Flankenerkennung? Man drückt ja den Button nich genau dann, wenn der Code an der Zeile der Flankenerkennung ankommt. Wie funktioniert das denn dann?
Das funktioniert dank der ProzessAbbilder der Eingänge (und Ausgänge). Das BetriebsSystem kopiert zu 1 Zeitpunkt den Zustand aller Eingänge ins ProzessAbbild und das Programm greift (normalerweise) nur auf das ProzessAbbild zu.
Dadurch entsteht ein gewisses, aber beschränktes Mass an Entprellung.
Je länger die ZyklusZeit, desto stärker die Entprellung. ABER: eine "zu starke Entprellung" führt dazu, dass sehr kurze Signale vom Programm gar nicht mehr zuverlässig "gesehen" werden.

Also geht man davon aus, dass die SPS so schnell ist, das ein kurzer Tastendruck aufjedenfall in mindestens einem Taskzyklus erfasst wird.
Ja. Die Bediener sind i.A. auch nicht sooo schnell. Das klappt auch sehr gut, solange man für die ZyklusZeit nicht so wahnsinnig lange Zeiten zulässt.
Auch hier macht es Sinn, eine FlankenErkennung nicht bedingt aufzurufen, sondern in jedem Zyklus.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich kenne keine Codesys-Steuerung, die Interrupt-Tasks bietet. Wenn Du genaue Zeitmessungen auf Basis der TIME()-Funktion machen willst ( wie z. B. mit einem TON), dann musßt Du Dein Programm in einem festen Zykluszeitraster laufen lassen. Am besten mit einer Zykluszeit von 1 ms, denn das ist die Auflösung vom Datentyp TIME.
meinst du mit Zykluszeit die Wartezeit zwischen 2 zyklen? Auch wenn ich diese auf 1 ms stelle bleibt der Fehler.
 
Das funktioniert dank der ProzessAbbilder der Eingänge (und Ausgänge). Das BetriebsSystem kopiert zu 1 Zeitpunkt den Zustand aller Eingänge ins ProzessAbbild und das Programm greift (normalerweise) nur auf das ProzessAbbild zu.
Dadurch entsteht ein gewisses, aber beschränktes Mass an Entprellung.
Je länger die ZyklusZeit, desto stärker die Entprellung. ABER: eine "zu starke Entprellung" führt dazu, dass sehr kurze Signale vom Programm gar nicht mehr zuverlässig "gesehen" werden.


Ja. Die Bediener sind i.A. auch nicht sooo schnell. Das klappt auch sehr gut, solange man für die ZyklusZeit nicht so wahnsinnig lange Zeiten zulässt.
Auch hier macht es Sinn, eine FlankenErkennung nicht bedingt aufzurufen, sondern in jedem Zyklus.
Gilt das auch für alle anderen Variablen?

Weil dann stelle ich mir das so vor:
Zyklus 1:
Speicherung aller Variablen

Zyklus 2:
High-Signal an Variable 1 durch externen Tastendruck oder internes ändern des Bool Werts
Aufruf der Funktion R_TRIG
Vergleich mit aktuellen Variablen mit Abbild aus vor Zustand --> Flankenerkennung: TRUE

Wäre das so in etwa der Prozess?

Dann verstehe ich, dass wenn die Zeit zwischen zwei Tasks so groß ist, dass in dieser Zeit eine Variable den Zustand von LOW auf HIGH und wieder LOW wechselt, die SPS das überhaupt nicht merkt.

Ist das soweit korrekt?
 
Die Zykluszeit ist das Zeitraster in welchen die Task aufgerufen wird.
Bei Codesys kann man wählen zwischen:
Freilaufend -> hier wird der Task aufgerufen wenn der Controller Zeit hat
Ereignisgesteuert -> Aufruf bei bestimmten Ereignis
Zeitraster -> z.B.50ms -> Hier wird die Task alle 50ms aufgerufen
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Die einfachste Lösung wäre tatsächlich die zyklische Task.

Wenn es unbedingt in der freialufende Task passieren soll, sollte hier nur ein Timer verwendet werden.

Deklaration:
Code:
    meinTimer: TON;
    diZeitwert: DINT;
    diSchritt: DINT;
    diSchrittAlt: DINT;
    CNT: DINT;
    value: REAL;
    tDurY: TIME;
    diAnzahl: DINT;
    tDurX: TIME;

und Code:

Code:
diAnzahl := 50;
tDurX:= T#100ms;
tDurY:= tDurX*REAL_TO_DINT((DINT_TO_REAL(diAnzahl)+1.5));

meinTimer(PT:= tDurY, IN:= TRUE);
diZeitwert := TIME_TO_DINT(meinTimer.ET);
diSchritt := (diZeitwert - (diZeitwert MOD TIME_TO_DINT(tDurX))) / TIME_TO_DINT(tDurX);
IF diSchritt <> diSchrittAlt AND diSchritt<>0 AND diSchritt <=diAnzahl THEN
    CNT := CNT + 1;
    value := value + 100 * (TIME_TO_REAL(tDurX)/(TIME_TO_REAL(tDurY)));
END_IF;
diSchrittAlt := diSchritt;
 
Ach ok

Ich kenne keine Codesys-Steuerung, die Interrupt-Tasks bietet. Wenn Du genaue Zeitmessungen auf Basis der TIME()-Funktion machen willst ( wie z. B. mit einem TON), dann musßt Du Dein Programm in einem festen Zykluszeitraster laufen lassen. Am besten mit einer Zykluszeit von 1 ms, denn das ist die Auflösung vom Datentyp TIME.
Aber zu deiner ursprünglichen Aussage: Trotz der Umstellung der Zeit auf 1 ms, bekomme ich einen kleinen Fehler.
 
Zurück
Oben