# ST while loop



## Seppe (3 Februar 2020)

Hallo,

habe ein ST Programm erstellt mit folgendem Code:

WHILE Speichervariable <= 1000 DO
    Speichervariable:= Speichervariable + Testprg.Testvariable; 
END_WHILE

IF Speichervariable >= 1000 THEN
    Speichervariable:=0;
END_IF

Wenn ich einen Breakpoint setze und manuell durchdebugge macht es genau das was es soll. Sobald ich es jedoch durchlaufen lasse (Task=100ms) steht in der Speichervariable permanent 0.
Hat jemand eine Idee woran das liegen könnte?

Grüße


----------



## oliver.tonn (3 Februar 2020)

Das die Variable ständig 0 ist stimmt so nicht, nur ändert sich ihr Wert so schnell, das Du nichts siehst.
Ich denke ich weiß was Du erwartest, aber das wirst Du mit Deinem Programm nicht erreichen.
Du erwartest vermutlich, dass sich der Wert von Speichervariable alle 100ms um 1 erhöht, doch das geht so nicht.


----------



## Seppe (3 Februar 2020)

Danke für die schnelle Antwort!
Muss ich dann das Programm extern z.B. mit einem ton alle 100ms aufrufen oder gibt es etwas wie eine sleep() Funktion wie in c, das ich in die while loop integrieren kann?


----------



## PN/DP (3 Februar 2020)

Aus Deiner WHILE-Schleife kommt das Programm erst heraus, wenn die Bedingung "Speichervariable <= 1000" nicht mehr erfüllt ist, also wenn Speichervariable > 1000 geworden ist (oder wenn das so lange dauert, daß die Zykluszeitübertwachung Deine SPS in STOP schickt).
Das "IF Speichervariable >= 1000 THEN" direkt nach der Schleife ist dann immer erfüllt, wodurch Speichervariable immer auf 0 gesetzt wird.

Harald


----------



## Heinileini (3 Februar 2020)

Seppe schrieb:


> Muss ich dann das Programm extern z.B. mit einem ton alle 100ms aufrufen oder gibt es etwas wie eine sleep() Funktion wie in c, das ich in die while loop integrieren kann?


Geht es hier wirklich um SPS-Programmierung?
Wofür willst Du denn die ProgrammSchleife einsetzen?


----------



## Seppe (3 Februar 2020)

PN/DP schrieb:


> Aus Deiner WHILE-Schleife kommt das Programm erst heraus, wenn die Bedingung "Speichervariable <= 1000" nicht mehr erfüllt ist, also wenn Speichervariable > 1000 geworden ist (oder wenn das so lange dauert, daß die Zykluszeitübertwachung Deine SPS in STOP schickt).
> Das "IF Speichervariable >= 1000 THEN" direkt nach der Schleife ist dann immer erfüllt, wodurch Speichervariable immer auf 0 gesetzt wird.
> 
> Harald



Danke, hatte einen Denkfehler mit der while...


----------



## PN/DP (3 Februar 2020)

Du solltest verstehen wie eine SPS größtenteils funktioniert: das Programm wird zyklisch wiederholt ausgeführt. Dadurch braucht man fast nie WHILE-Schleifen, um auf ein Ergebnis zu warten, sondern fragt einfach (zyklisch) ab, ob das Ergebnis mittlerweile eingetreten ist:

```
Speichervariable:= Speichervariable + Testprg.Testvariable; 

IF Speichervariable >= 1000 THEN
  Speichervariable:=0;
END_IF
```
Wenn nur alle 100ms die Addieranweisung ausgeführt werden soll, dann darf der Programmcode nur alle 100ms ausgeführt werden, z.B. durch Programmcode in einer Task, welche nur alle 100ms ausgeführt wird, oder durch einen TON:

```
TON_1(IN:= xHilfsVar, PT:=T#100MS);
xHilfsVar := NOT TON_1.Q;

IF TON_1.Q THEN
  Speichervariable:= Speichervariable + Testprg.Testvariable; 
END_IF

IF Speichervariable >= 1000 THEN
  Speichervariable:=0;
END_IF
```

Harald


----------



## Seppe (3 Februar 2020)

Verstehe, vielen Dank.

Was mir dahingehnd aber noch unklar ist: Wie beeinflussen die Taskeinstellungen den Programmablauf? Was macht es für einen Unterschied wenn der Task zyklisch mit 50ms oder 200ms aufgerufen wird?


----------



## PN/DP (3 Februar 2020)

Die Task-Zeit (Task-Frequenz) bestimmt die Reaktionszeit des SPS-Programms auf Zustände und ob ein externer Zustand überhaupt wahrgenommen wird.
z.B. wenn das Programm auf das Drücken einer STOP-Taste reagieren soll, dann muß die STOP-Taste mindestens so lange gedrückt sein wie die Task-Aufrufzeit, damit der "gedrückt"-Zustand garantiert erkannt wird, und im ungünstigsten Fall reagiert die SPS erst nach der Task-Zeit auf das Drücken des STOP-Tasters.

Harald


----------



## Seppe (3 Februar 2020)

Bitte nochmals um Erläuterung:
Habe das Programm mit folgenden Einstellungen laufen lassen:

TON_1(IN:= xHilfsVar, PT:=T#100MS);
xHilfsVar := NOT TON_1.Q;

IF TON_1.Q THEN
  Speichervariable:= Speichervariable + 1; 
END_IF

Ergebnis nach 1min:
Task 50ms: Ergebnis Speichervariable: 300
Task 10ms: Ergebnis Speichervariable:500
Task 1ms: Ergebnis Speichervariable:600

Meiner Meinug nach müsste das Ergebnis allerdings bei allen 3 Taskeinstellungen 600 betragen?

Gruß


----------



## Heinileini (3 Februar 2020)

Ich würde zunächst mal die beiden ersten Befehle tauschen und den TON gegen einen TOF:


Seppe schrieb:


> ```
> xHilfsVar := NOT TOF_1.Q;
> TOF_1(IN:= xHilfsVar, PT:=T#100MS);
> ```


Die Abstände zwischen 2 aufeinanderfolgenden Starts des Timers ist >= 100 ms.
Frühestens, wenn die 100 ms abgelaufen sind, wird der Timer neu gestartet.
Der Zeitpunkt des Startens hängt aber davon ab, um wie viele ms die Befehle *nach* dem Ablaufen des Timers die Befehle durchlaufen werden, d.h. bei 50 ms ZyklusZeit kann dadurch locker eine nutzlos vertane WarteZeit von 49 ms eingefügt bzw. an die Timer LaufZeit angehängt werden.
Je grösser die ZyklusZeit, desto mehr wird das Ergebnis abweichen. Abweichen heisst, der Timer wird nicht so zügig neu gestartet und damit der Zähler nicht so häufig hochgezählt.


----------



## PN/DP (3 Februar 2020)

Bei einem sich selbst neustartenden TON wie im Beispiel wird Q nur alle "PT + 1 Zyklus"-Zeit TRUE, weil TON benötigt in dem Beispiel noch einen Zyklus zum Rücksetzen der abgelaufenen Zeit auf 0. Ich weiß nicht wie das bei Wago ist, möglicherweise benötigt der TON zum Start auch eine steigende/0-1-Flanke. Nachdem die Zeit PT abgelaufen ist (Zeit >= PT vergangen ist), wird TON für 1 Zyklus mit IN = 0 aufgerufen, erst im nächsten Zyklus wird die Zeit wieder gestartet. (Möglicherweise vergeht sogar noch ein weiterer Zyklus, bevor der TON auf >= PT kommt?)

Um diesen zusätzlichen Reset-Zyklus zu eliminieren, müsste man den TON nach Ablauf (Q = 1) sofort einmal mit IN = 0 aufrufen. Oder man stellt PT auf "Sollzeit" - 1x Taskzeit ein.

```
TON_1(IN:= TRUE, PT:=T#100MS); //ggf. IN:= xNotFirstCycle

IF TON_1.Q THEN
  TON_1(IN:= FALSE); //den TON einmal mit IN = 0 aufrufen --> Reset des TON
...
```

Ich weiß nicht, wie bei Wago die TON reagieren, wenn schon im ersten RUN-Zyklus der SPS der IN von TON = 1 ist, und ob es in der Wago-SPS eine FirstCycle-Variable gibt --> ausprobieren!

Möglicherweise verbessert sich die Situation, wenn man anstatt TON einen TOF nimmt, wie von Heinileini gezeigt. (ich kenne die Wago nicht)

Wenn man ein exaktes 100ms Raster braucht, dann könnte man anstatt TON/TOF auch einen Zyklenzähler verwenden: in jedem Zyklus eine Variable incrementieren, bis die 100ms erreicht sind und dann sofort wieder auf 0 setzen. Oder die letzte Zyklusdauer addieren bis >= 100ms erreicht sind.

Harald


----------



## PN/DP (3 Februar 2020)

Heinileini schrieb:


> ```
> xHilfsVar := NOT TOF_1.Q;
> TOF_1(IN:= xHilfsVar, PT:=T#100MS);
> ```


Ich meine, auch bei dieser TOF-Konstruktion wird ein Pulsabstand (Periodendauer) "PT + 1 Zyklus" erzeugt, weil die TOF-Zeit erst läuft, wenn IN = 0
Zusätzlich verwirrend: der TOF-Timer ist abgelaufen bei Q = 0, die nachfolgende Logik muß das Q negiert abfragen.

(Implementationsabhängigkeit! Bei Siemens SCL 1200/1500: hinter dem TOF-Aufruf wird der Code fast immer TOF_1.Q = 1 sehen. Der TOF-Ausgang Q kann nicht weiter verwendet werden - es kann aber xHilfsVar verwendet werden)

Harald


----------



## PN/DP (3 Februar 2020)

Seppe schrieb:


> Ergebnis nach 1min:
> Task 50ms: Ergebnis Speichervariable: 300
> Task 10ms: Ergebnis Speichervariable:500
> Task 1ms: Ergebnis Speichervariable:600





PN/DP schrieb:


> Um diesen zusätzlichen Reset-Zyklus zu eliminieren [...] Oder man stellt PT auf "Sollzeit" - 1x Taskzeit ein.


Damit auch noch leichte Programmausführungszeit-Schwankungen vom Anfang der Task bis zum Programmcode mit dem TON eliminiert werden, kann man noch weitere 10..50% der Task-Zeit vom Wert für PT abziehen, dann sorgt das Task-Aufruf-Raster dafür, daß sich ein tatsächlicher Pulsabstand von 100ms einstellt, und auch die 10ms-Task bzw die 50ms-Task auf 600 kommen:

```
TON_1(IN:= xHilfsVar, PT:=T#87MS); //10ms-Task / für 50ms-Task sollte ein Wert T#40MS funktionieren
xHilfsVar := NOT TON_1.Q;

IF TON_1.Q THEN
  Speichervariable := Speichervariable + 1;
END_IF
```

Man kann auch einfach bis 10 bzw. 2 zählen ...

Harald


----------



## .:WAGO::0100146:. (4 Februar 2020)

Zusätzlich zu der Abblaufzeit des Timers kommt ein Zyklusdurchlauf, in dem der Timer zurückgesetzt wird.
Das führt zu einer Durchlaufzeit von ca.
Task 50ms: 150ms
Task 10ms: 110ms
Task 1ms: 101ms

In so einem Fall wäre es also besser auf eine Globale Zeit des Controllers zu schauen und den Wert daran zu synchronisieren.
Hier bietet sich die Funktion FuGetLongTime der Bibliothek WagoAppTime an.


----------

