# Frage bez. FOR-Schleife (Beckhoff CX9000-001)



## apmIng (3 Mai 2011)

Hallo zusammen!

Seit gestern sitze ich an einer FOR-Schleife, die einfach nicht funktionieren will. Da ich bei SPS erst seit Kurzem dabei bin, weiss ich nicht mehr Rat. 

Aufgabe: in einer FOR-Schleife soll eine beliebige Anzahl von kurzen Flashes (mit irgendeiner Lampe o.ä.) generiert werden. Ich übergebe der Schleife eine positive ganze Zahl und erwarte, dass die Schleife mir eine entspr. Anzahl von kurzen (1s) Blinks liefert, mit 1s Pause zwischen den Blinks.

Die Schleife sieht so aus:

FOR (counter := 1 TO myNumber BY 1) DO
    timer (IN:= TRUE, PT:=T#1s);
    IF (timer.Q = TRUE) THEN
        greenLightControl := TRUE;
    END_IF;

    timer (IN:= TRUE, PT:=T#1s);
    IF (timer.Q = TRUE) THEN
        greenLightControl := FALSE;
    END_IF;
END_FOR;

Komischerweise wird bei der Abarbeitung der Schleife der counter sofort auf myNumber gesetzt und die Pausen, die in der Schleife mit dem timer generiert werden sollten, werden ignoriert. Für sich alleine, liefert der timer aber schon eine Pause, die ja auch bei zyklischem Ablauf berücksichtigt werden sollte. 

Habe ich da was falsch verstanden? Alles andere klappt einwandfrei.

Danke für Eure Hilfe!


----------



## Schnick und Schnack (3 Mai 2011)

Hi

Eine FOR -Schleife bleibt im Zylkus nicht stehen. Das macht Dir WHILE.
Die FOR-Schleife hat in jedem Zyklus hochgezähtl daher bist Du sofort auf dem Sollwert gelandet.

Ich würds damit probieren:


```
IF counter <= mynumber THEN
timer (IN:= TRUE AND NOT timer2.Q, PT:=T#1s);
IF (timer.Q = TRUE) THEN
greenLightControl := TRUE;
END_IF;

timer2 (IN:= timer.Q, PT:=T#1s);
IF (timer2.Q = TRUE) THEN
greenLightControl := FALSE;
END_IF;

trig(CLK:=timer2.Q );

IF trig.Q THEN
COUNTER := COUNTER +1;
END_IF

END_IF
```
Timer : TON;
Timer2 : TOF;
trig : F_trig;

Ist nur schnell gebastelt, kannste noch verbessern. 
Sollte aber funktionieren.

Du hast für Pause und Betrieb beide Mal den gleichen Timer verwendet. 


Gruss Anis


----------



## Larry Laffer (3 Mai 2011)

... ich würde nicht innerhalb einer Schleife und auch nicht innerhalb einer Abfrage einen Timer-Aufruf unterbringen.
Ich kann jetzt nicht für Beckhoff & Co. sprechen aber innerhalb einer S7 würde in SCL dieser Aufruf unter den genannten Bedingungen auch nicht funktionieren.

Gruß
Larry


----------



## apmIng (3 Mai 2011)

Schnick und Schnack schrieb:


> Hi
> Eine FOR -Schleife bleibt im Zylkus nicht stehen. Das macht Dir WHILE.
> Die FOR-Schleife hat in jedem Zyklus hochgezähtl daher bist Du sofort auf dem Sollwert gelandet.


Danke für die Info. 

> Eine FOR -Schleife bleibt im Zylkus nicht stehen.

Was ich nicht verstehe - die FOR-Schleife läuft doch in jedem SPS-Zyklus komplett durch - bis sie beendet ist und erst dann läuft SPS-Zyklus weiter. Oder irre ich mich? Weil - wie kann eine FOR-Schleife nicht stehen bleiben (in diesem Fall für die o.g. Sekunden) wenn sie doch durch eben den timer dazu gezwungen wird? Zwingt etwa ein SPS-Zyklus die FOR-Schleife dazu, den Anweisungsblock zu ignorieren, wenn dessen Ausführung länger dauert, als ein SPS-Zyklus?

Bei WHILE/REPEAT-Schleife bekomme ich immer (auch bei billigsten Beispielen) einen SPS-Timeout und muss die CPU resetten, da diese sonst weder im Config, noch im Runmode startet.

Komme aus der C++/Java - Welt und hatte bisher leider recht wenig mit zyklischer Programmausführung zu tun. Habe hier sechs Bücher über SPS-Programmierung liegen (u.a. von Tiegelkamp, Kaftan, Wellenreuther und Krätzig) - nirgendwo wird es auf das Problem eingegangen - daher vermute ich einen DAU.


----------



## apmIng (3 Mai 2011)

Larry Laffer schrieb:


> ... ich würde nicht innerhalb einer Schleife und auch nicht innerhalb einer Abfrage einen Timer-Aufruf unterbringen.



Vielen Dank für die Info.


----------



## Ralle (3 Mai 2011)

Larry Laffer schrieb:


> ... ich würde nicht innerhalb einer Schleife und auch nicht innerhalb einer Abfrage einen Timer-Aufruf unterbringen.
> Ich kann jetzt nicht für Beckhoff & Co. sprechen aber innerhalb einer S7 würde in SCL dieser Aufruf unter den genannten Bedingungen auch nicht funktionieren.
> 
> Gruß
> Larry



Yep, genau.
Und selbst wenn du die For-Schleife nutzen willst/sollst, z.Bsp. aus Übungsgründen, dann mußt du das Hochzählen des Counters auch in irgendeiner Form mit dem Timer verknüpfen, denn Counter wird bei jedem Zyklusdurchgang einfach hochgezählt und ist somit ruckzuck durch.


----------



## Schnick und Schnack (3 Mai 2011)

@ LL



> ich würde nicht innerhalb einer Schleife und auch nicht innerhalb einer Abfrage einen Timer-Aufruf unterbringen


Stimmt. Geb ich Dir Recht. Funzen tuts, is aber ned unbedingt schön.

Beispiel:

```
IF counter <= mynumber THEN
do_job := TRUE;
ELSE
do_job := FALSE;
END_IF
```
@ampIng

Du hast Deine Frage selber beantwortet. Die FOR-Schleife läuft durch. 

Wie Ralle gesagt hat:


> ....denn Counter wird bei jedem Zyklusdurchgang einfach hochgezählt und ist somit ruckzuck durch.



Die WHILE-Schleife bleibt solange hängen, bis sie "beendet" ist.
Da Du innerhalb Deiner WHILE-Schleife zwei Timer aufrufst, mit einer gesamten Wartezeit von 2 Sekunden dauert die WHILE-Schleife auch *mindestens* 2 Sekunden. Somit bleibt der Zyklus für diese Zeit auch in der Schleife stehen. 
Ergebnis:
Dein Zyklus dauert mind. 2 Sekunden was eine Zykluszeitüberschreitung zur Folge hat und den WatchDog auslöst.


Anis


----------



## gloeru (3 Mai 2011)

Du bist wohl noch nicht ganz in der Echtzeit-Welt angekommen. (In diesem Sinne Willkommen!  :smile: )

Wenn du im TwinCAT eine Zykluszeit von 1ms hast, hast du nur wenige us Zeit, um die Berechnungen zu erledigen. Vergiss die WHILE Schleife! Den Timeout Fehler erhälst du, wenn dein Programm nicht innerhalb von den paar us abgearbeitet wird! Für deine Verzögerung von 2 Sekunden musst du eine andere Lösung finden. (Siehe oben)


----------



## Larry Laffer (3 Mai 2011)

@TE:
das Stichwort ist hier "zyklische Programm-Bearbeitung" was hier heißt : "es wird immer alles abgearbeitet" und "der Zyklus muß irgendwann beendet sein".

Die Lösung für dich könnte sein, dass du die Timer direkt zu einem Taktgeber verschaltest und dessen Impulse nach einem Start-Impuls zählst.
Ist der Zähler = Wunschwert löscht du den Start wieder und damit die Freigabe dafür die Impulse auf einen Ausgang (oder ähnlich) zu geben ...

Gruß
Larry


----------



## Schnick und Schnack (3 Mai 2011)

@ LL

Was ja eigentlich (sofern ich Dich richtig verstehe) etwa dem entspricht,
was ich bereits als Lösung (Beitrag #2) im Code vorgeschlagen habe.
Inkl. der Verbesserung nach Deinem berechtigten Einwand (Beitrag #7).

Oda??

Gruss


----------



## Larry Laffer (3 Mai 2011)

Schnick und Schnack schrieb:


> Was ja eigentlich (sofern ich Dich richtig verstehe) etwa dem entspricht,
> was ich bereits als Lösung (Beitrag #2) im Code vorgeschlagen habe.


 
Stimmt ... da hast du Recht - das hatte ich gar nicht mehr auf dem Schirm


----------



## StructuredTrash (3 Mai 2011)

apmIng schrieb:


> Was ich nicht verstehe - die FOR-Schleife läuft doch in jedem SPS-Zyklus komplett durch - bis sie beendet ist und erst dann läuft SPS-Zyklus weiter. Oder irre ich mich? Weil - wie kann eine FOR-Schleife nicht stehen bleiben (in diesem Fall für die o.g. Sekunden) wenn sie doch durch eben den timer dazu gezwungen wird?


Da hast Du ein grundsätzliches Verständnisproblem. Die Schleife wird schon in jedem SPS-Zyklus komplett durchlaufen, aber beim Aufruf eines Timers bleibt das Programm nicht an dieser Stelle stehen, bis der Timer abgelaufen ist. Beim Aufruf wird nur geprüft, ob die Zeit abgelaufen ist, und in diesem Fall Timer.Q=True. Wenn das nicht im aktuellen SPS-Zyklus der Fall ist, dann vielleicht im Nächsten.


----------



## Schnick und Schnack (3 Mai 2011)

Dann fass ich es nochmals zusammen. So müsst es gehen:


```
VAR
    Enable                    : BOOL;
    myNumber               : INT;
    Time_On                 :TON;
    Time_Off                 :TOF;
    Counter                  : INT;
    Trig                        : F_TRIG;
    greenLightControl     : BOOL;
    Cmd_DoJob             : BOOL;
END_VAR


IF Counter < mynumber THEN
    Cmd_DoJob  := TRUE;
ELSE
    Cmd_DoJob  := FALSE;
END_IF


Time_On (IN:=Enable AND Cmd_DoJob AND NOT Time_Off.Q, PT:=T#1s);
Time_Off (IN:= Time_On.Q, PT:=T#1s);

greenLightControl :=  Enable AND NOT Time_Off.Q;


Trig(CLK:=Time_Off.Q );

IF Trig.Q THEN
    Counter := Counter +1;
END_IF

IF NOT Cmd_DoJob THEN
    Enable := FALSE;
END_IF
```



Achtung, je nach Verwendung muss Enable ein In/Out sein.


----------



## Schnick und Schnack (3 Mai 2011)

Eine Frage habe ich aber noch.....

Falls er die FOR-Schleife für die Schule oder so verwenen muss....
Ralle hat etwas gesagt vonwegen das Hochzählen mit einem Timer verknüpfen...

Wie kann denn der Counter einr FOR-Schleife beeinflusst werden?
Da bin ich jetzt ehrlichgesagt überfragt. Dafür verwende ich FOR/WHILE
zu selten, zumindest in zyklischen SPS-Programmen.

Gruss


----------



## Larry Laffer (3 Mai 2011)

Eine FOR- oder WHILE-Schleife würde ich hier gar nicht hernehmen sondern exakt es so machen, wie in deinem Code :
	
	



```
IF Trig.Q THEN
    Counter := Counter +1;
END_IF ;
```
... allerdings bei näherem hinsehen würde ich das Ganze noch mit einer Flanke würzen, damit nicht die x-Takte gleich mit den ersten Trig.Q zu Stande kommen ...

Gruß
Larry


----------



## Schnick und Schnack (3 Mai 2011)

Larry Laffer schrieb:


> ... allerdings bei näherem hinsehen würde ich das Ganze noch mit einer Flanke würzen, damit nicht die x-Takte gleich mit den ersten Trig.Q zu Stande kommen ...
> 
> Gruß
> Larry



Ich steh aufm Schlauch...
Kannste das erläutern??
Die Flanke hab ich doch....


----------



## Larry Laffer (3 Mai 2011)

Oops - Schande über mich ... 
Trig ist ja gar nicht der Timer - sorry, da hatte ich (nochmals) nicht richtig hingesehen ...


----------



## Commander_Titte (3 Mai 2011)

Schnick und Schnack schrieb:


> ```
> IF Trig.Q THEN
> Counter := Counter +1;
> END_IF
> ```



Den Timer zurücksetzten sollte nicht vernachlässigt werden. Wenn z.B. Enable:=False ist.


MFG Christoph


----------



## Schnick und Schnack (3 Mai 2011)

Larry Laffer schrieb:


> Oops - Schande über mich ...



Ach was....

War doch jetzt ne nette Ablenkung frei nach....



Lipperlandstern schrieb:


> Signatur : Ich habe manchmal so lange ein Motivationsproblem, bis ich ein Zeitproblem hab


----------



## PN/DP (4 Mai 2011)

Schnick und Schnack schrieb:


> Ach was....
> 
> War doch jetzt ne nette Ablenkung frei nach....


... und weil es nur eine nette Ablenkung war muß man sich wohl auch keine Mühe geben ... 

Was mich generell an diesem ST stört:
Es verführt unerfahrene Programmierer unheimlich zu ereignisorientierter Programmierung mit überwiegend bedingten Zuweisungen, was zu lauter solchen undurchdachten und unvollständigen Programmen mit logischen Fehlern führt, wie hier und in anderen Threads leider immer wieder zu sehen ist. Es wird ohne Plan einfach losgetippt - "nur schnell gebastelt" - der Anwender wird zum ewigen Betatester, weil diese Programme aus dem Entwurfsstadium nie herauskommen - auch wenn noch so viel nachgeflickt wird. 

Harald


----------



## PN/DP (4 Mai 2011)

*Erzeugung einer vorgegebenen Pulsanzahl*

Wenn ein Vorwärtszähler vorgeschrieben ist, dann würde ich das Programm so schreiben:

```
VAR_INPUT
    Enable            : BOOL;
    myNumber          : INT;
END_VAR
VAR
    En_Trig           : R_TRIG;
    Timer_Puls        : TON;
    Timer_Pause       : TON;
    Counter           : INT;
    greenLightControl : BOOL;
END_VAR


En_Trig (CLK := Enable);

IF En_Trig.Q THEN
    Counter := 0;
END_IF;

Timer_Puls (IN := Enable AND (Counter < MyNumber) AND NOT Timer_Pause.Q, PT := T#1s);
Timer_Pause (IN := Timer_Puls.Q, PT := T#1s);

IF Timer_Pause.Q THEN
    Counter := Counter + 1;
END_IF;

greenLightControl := Enable AND (Counter < MyNumber) AND NOT Timer_Puls.Q;
```
Vorwärtszählen hat die Besonderheit, daß sich der Vergleichswert myNumber über den ganzen Impulsausgabezyklus nicht ändern darf, es sei denn, man will absichtlich noch während laufendem Impulsausgabezyklus nachträglich die Impulszahl ändern. Wenn myNumber sich ungewünscht ändern kann, dann muß man beim Impulszyklus-Start myNumber in eine Kopie speichern und dann mit dieser Kopie arbeiten.


Dieses Problem umgeht man elegant, wenn man mit einem Rückwärtszähler arbeitet, da wird myNumber im Counter gespeichert und danach nicht mehr beachtet:

```
VAR_INPUT
    Enable            : BOOL;
    myNumber          : INT;
END_VAR
VAR
    En_Trig           : R_TRIG;
    Timer_Puls        : TON;
    Timer_Pause       : TON;
    Counter           : INT;
    greenLightControl : BOOL;
END_VAR


En_Trig (CLK := Enable);

IF En_Trig.Q THEN
    Counter := myNumber;
END_IF;

IF NOT Enable THEN
    Counter := 0;
END_IF;

Timer_Puls (IN := (Counter <> 0) AND NOT Timer_Pause.Q, PT := T#1s);
Timer_Pause (IN := Timer_Puls.Q, PT := T#1s);

IF Timer_Pause.Q AND (Counter <> 0) THEN
    Counter := Counter - 1;
END_IF;

greenLightControl := (Counter <> 0) AND NOT Timer_Puls.Q;
```
Ein weiterer Vorteil des Rückwärtszählers ist, daß man den Zählerstand direkt zur Anzeige eines Countdowns benutzen kann.


Wenn Pulszeit und Pausezeit gleich lang sind, dann braucht man nur 1 Timer. Mein Favorit ist in dem Fall ein Rückwärtszähler, der die Pulse und die Pausen zählt und die Pulsausgabe wird nur bei ungeraden Zählerständen TRUE:

```
VAR_INPUT
    Enable            : BOOL;
    myNumber          : INT;
END_VAR
VAR
    En_Trig           : R_TRIG;
    Timer             : TON;
    Counter           : INT;
    greenLightControl : BOOL;
END_VAR


En_Trig (CLK := Enable);

IF En_Trig.Q AND (myNumber > 0) THEN
    Counter := myNumber + myNumber - 1;
END_IF;

IF NOT Enable THEN
    Counter := 0;
END_IF;

Timer (IN := (Counter <> 0) AND NOT Timer.Q, PT := T#1s);

IF Timer.Q AND (Counter <> 0) THEN
    Counter := Counter - 1;
END_IF;

greenLightControl := WORD_TO_BOOL(INT_TO_WORD(Counter) AND 1);
[SIZE="1"]
(* greenLightControl := Counter.0; darf man in ST wahrscheinlich nicht schreiben? *)[/SIZE]
```

Harald


----------



## soma (4 Mai 2011)

Ich würds so machen:
FUNCTION_BLOCK _BLINK
VAR_INPUT
    BLINK_COUNT:INT;
    BLINK_ON_TIME:TIME;
    BLINK_OFFTIME:TIME;
END_VAR
VAR_OUTPUT
    THE_BLINKER:BOOL;
END_VAR
VAR
    TIMER:TON;
END_VAR


IF BLINK_COUNT>0 THEN
         IF NOT TIMER.IN THEN
                 IF THE_BLINKER THEN
                         TIMER.PT:=BLINK_ON_TIME;
                 ELSE
                         TIMER.PT:=BLINK_OFFTIME;
                 END_IF;
         END_IF;
         TIMER(IN:=NOT TIMER.Q);
         IF TIMER.Q THEN
                 THE_BLINKER:=NOT THE_BLINKER;
         IF NOT THE_BLINKER THEN
             BLINK_COUNT:=BLINK_COUNT-1;
         END_IF;
    END_IF;
ELSE
    TIMER(IN:=FALSE);
    THE_BLINKER:=FALSE;
END_IF;


----------



## PN/DP (4 Mai 2011)

soma schrieb:


> Ich würds so machen:


Ist das in ST so üblich, daß man VAR_INPUT wie interne Variablen benutzt (Beschreiben und Werte für den nächsten Zyklus merken)? 
Da darf man ja die Inputs gar nicht beschalten und braucht 2 verschiedene Aufrufe des Bausteins oder muß zum Starten außerhalb des Aufrufs den Input-Parameter BLINK_Instanz.BLINK_COUNT beschreiben. Das finde ich gar nicht schön.

Zusätzlich wäre es besser, wenn das Programm sofort mit der Impulsausgabe beginnen würde statt mit der Impulspause.

Tip:
Für bessere Lesbarkeit kann man Programmcode in eine Code-Box einfügen. Das erreicht man mit den Code-Tags *[CODE]* und *[/CODE]* bzw. im Beitragseditor mit dem #-Button. Dein Code könnte dann so aussehen:

```
FUNCTION_BLOCK _BLINK
VAR_INPUT
    BLINK_COUNT:INT;
    BLINK_ON_TIME:TIME;
    BLINK_OFFTIME:TIME;
END_VAR
VAR_OUTPUT
    THE_BLINKER:BOOL;
END_VAR
VAR
    TIMER:TON;
END_VAR


IF BLINK_COUNT>0 THEN
    IF NOT TIMER.IN THEN
        IF THE_BLINKER THEN
            TIMER.PT:=BLINK_ON_TIME;
        ELSE
            TIMER.PT:=BLINK_OFFTIME;
        END_IF;
    END_IF;
    TIMER(IN:=NOT TIMER.Q);
    IF TIMER.Q THEN
        THE_BLINKER:=NOT THE_BLINKER;
        IF NOT THE_BLINKER THEN
            BLINK_COUNT:=BLINK_COUNT-1;
        END_IF;
    END_IF;
ELSE
    TIMER(IN:=FALSE);
    THE_BLINKER:=FALSE;
END_IF;
```

Harald


----------



## Larry Laffer (5 Mai 2011)

PN/DP schrieb:


> Ist das in ST so üblich, daß man VAR_INPUT wie interne Variablen benutzt (Beschreiben und Werte für den nächsten Zyklus merken)?
> Da darf man ja die Inputs gar nicht beschalten und braucht 2 verschiedene Aufrufe des Bausteins oder muß zum Starten außerhalb des Aufrufs den Input-Parameter BLINK_Instanz.BLINK_COUNT beschreiben. Das finde ich gar nicht schön.


 
... das sehe ich genau so - unabhängig ob ST oder SCL.
Vor Allem auch deswegen, da ich bei solchen Bausteinen die IN-Parameter auch gerne mal mit einer Konstante (also einem Festwert) beschalte - das entspräche hier ja sogar auch dem Sinn des Bausteins ...

Gruß
Larry


----------



## Larry Laffer (5 Mai 2011)

PN/DP schrieb:


> ... und weil es nur eine nette Ablenkung war muß man sich wohl auch keine Mühe geben ...
> 
> Was mich generell an diesem ST stört:
> Es verführt unerfahrene Programmierer unheimlich zu ereignisorientierter Programmierung mit überwiegend bedingten Zuweisungen, was zu lauter solchen undurchdachten und unvollständigen Programmen mit logischen Fehlern führt, wie hier und in anderen Threads leider immer wieder zu sehen ist. Es wird ohne Plan einfach losgetippt - "nur schnell gebastelt" - der Anwender wird zum ewigen Betatester, weil diese Programme aus dem Entwurfsstadium nie herauskommen - auch wenn noch so viel nachgeflickt wird.


 
Das sehe ich nicht so ...
Natürlich sollte man sich Mühe geben - da hast du Recht. Auf der anderen Seite geht es hier aber nicht darum "dem Anderen seine Arbeit zu machen" sondern aus meiner Sicht eher "dem Anderen einen Start zu ermöglichen". Dazu ist kein 100% fehlerfreies Code-Beispiel oder am Besten schon die ganze erledigte Arbeit notwendig. Das würde im Gegenteil dem Anderen auch gar nicht helfen, da es niemanden wirklich hilft, mit etwas zu arbeiten das man nicht wirklich verstanden hat.
Dem entsprechend sehe ich meine Tätigkeit hier (im Forum) unter der Prämisse des Helfens verstehen zu lernen.

Gruß
Larry


----------



## trinitaucher (5 Mai 2011)

Schnick und Schnack schrieb:


> Wie kann denn der Counter einr FOR-Schleife beeinflusst werden?
> Da bin ich jetzt ehrlichgesagt überfragt. Dafür verwende ich FOR/WHILE
> zu selten, zumindest in zyklischen SPS-Programmen.


mit "EXIT":


```
VAR
  i: INT;
  Limit : INT := 50;
END_VAR

FOR i := 0 TO 100 DO
  IF i > Limit  THEN
    [B]EXIT[/B];
  END_IF
END_FOR
```
Hier würde die FOR-Schleife bei i = 51 abgebrochen werden


----------



## apmIng (5 Mai 2011)

PN/DP schrieb:


> Es verführt unerfahrene Programmierer unheimlich zu ereignisorientierter Programmierung mit überwiegend bedingten Zuweisungen



Klar, wenn man 19 Jahre C++ programmiert, ist es schwer, sich davon zu lösen


----------



## Dummy (5 Mai 2011)

apmIng schrieb:


> Klar, wenn man 19 Jahre C++ programmiert, ist es schwer, sich davon zu lösen


 
Wenn man einmal verstanden hat wie eine SPS arbeitet, kann man mit jeder Sprache verständliche und funktionierende Programme schreiben.

In FUP oder AWL kann man genauso schlecht oder gut programmieren.

Dein Problem kann man auch ohne verwendung von Timern und Bedingungen programmieren.


```
counter :=(counter + 1)MOD NumberOfCycles;
boOut:= boOut XOR (counter <= 0  );
```
 
Gruß

dummy


----------

