# kann ich dass so machen?



## xinix (2 April 2011)

Moin moin,

ich hoffe mit diesr Frage nicht wieder einen "Threadtsunami" auszulösen aber ich hätte gern mal eine grundsätzliche Einschätzung...

Viele kenne ja bereits das Prog. oder Grundgerüst aus den vorhergegangenen Threads. Ich habe es auf Grund div. Anregungen und um es für mich übersichtlicher zu machen etwas umgebaut.

Kann ich das eigenlich mit RS Gliedern schreiben oder ist auch davon mal wieder abzuraten?

Danke vorab...


```
(*    Initialisierung      *)

RS_Start(SET:= xStart_wab , RESET1:=xStop_wab );
RS_Init(SET:= nLevel_t1<nLevel_t1_min AND nLevel_t2<nLevel_t2_min , RESET1:=F_TRIG_Start.Q );
xStartVorgang:= RS_Init.Q1;

(*    Startvorgang        *)

IF RS_Start.Q1 AND RS_Init.Q1 AND xStop_wab=FALSE THEN

    RS_P1_Start(SET:=nLevel_t1<(nLevel_t1_max-100) , RESET1:=nLevel_t1>nLevel_t1_max );            (* P1     | T1 auffüllen                *)
    xP1:= RS_P1_Start.Q1;

    RS_GWP_Start(SET:=nLevel_t1>(nLevel_t1_min+400) , RESET1:=nLevel_t1>nLevel_t1_max );        (* GWP     | T1 auffüllen                *)
    xGWP:= RS_GWP_Start.Q1;

    TOF_StaFilt(IN:=nLevel_t1>nLevel_Filt_Start , PT:=tStartDauer , Q=> , ET=> );                (* Filt    | Start Filtrierungszyklus     *)
    xFilt:= TOF_StaFilt.Q;

    RS_P4_Start(SET:=nLevel_t2>nLevel_StaP4 , RESET1:=RS_Start.Q1=FALSE AND RS_Init.Q1=FALSE );    (* P4    | Start P4                     *)
    xP4:= RS_P4_Start.Q1;

    RS_MV2_Start(SET:=nLevel_t2>(nLevel_t1+100) , RESET1:=nLevel_t2>nLevel_t2 );                (* MV2    | Start Magnetventiel 2        *)
    xMV2:= RS_MV2_Start.Q1;

    F_TRIG_Start(CLK:=TOF_StaFilt.Q , Q=> );                                                    (* Rücksetzen Startvorgang          *)

ELSE

    xStop_wab:= TRUE

END_IF

(*    Betrieb        *)

IF RS_Start.Q1 AND RS_Init.Q1=FALSE AND xStop_wab=FALSE THEN

    RS_P4_Betrieb(SET:=nLevel_t2>(nLevel_t2_min+100) , RESET1:=nLevel_t2>nLevel_t2_min );        (* P4    | Start P4                     *)
    xP4:= RS_P4_Betrieb.Q1;

    RS_P1_Betrieb(SET:=nLevel_t1<1200 , RESET1:=nLevel_t1>nLevel_t1_max );                        (* P1     | T1 auffüllen                *)
    xP1:= RS_P1_Betrieb.Q1;

    RS_GWP_Betrieb(SET:=nLevel_t1>1500 , RESET1:=nLevel_t1>nLevel_t1_max );                        (* GWP     | T1 auffüllen                *)
    xGWP:= RS_GWP_Betrieb.Q1;

    RS_Filt(SET:=nLevel_t2<(nLevel_t2_min+100) , RESET1:=nLevel_t2>nLevel_t2_max OR nLevel_t1<nLevel_t1_min );    (* ZwischenFiltrierung         *)
    xFilt:= RS_Filt.Q1;

ELSE

    xStop_wab:= TRUE

END_IF
```


----------



## StructuredTrash (2 April 2011)

Da Du keine Funktionsbeschreibung der Anlage veröffentlicht hast, nehme ich an, dass Deine Frage mehr auf den Stil als auf die Funktion abzielt. Da wird es wohl wieder sehr verschiedene Meinungen geben.

Ich selbst verwende in ST keine RS-Bausteine. Es ist nun mal eine textbasierte Sprache und ich halte etwas wie dieses

```
IF Stop
THEN
ELSIF Start
THEN
END_IF
```
für besser lesbar. Aber das ist Geschmackssache.
Was ich gar nicht gut finde, sind die immer noch vorhandenen bedingten Aufrufe von FB's aus der Standardbibliothek wie TOF oder F_TRIG. Diese FB's sind dafür gemacht, in jedem Programmzyklus aufgerufen zu werden. Bei bedingtem Aufruf können sie ein anderes Verhalten zeigen. Selbst ein bewusstes Ausnutzen dieses Umstandes halte ich für schlechten Stil.

Und bevor ich es vergesse: Du hast nach wie vor getrennte Merker für "Init" und "Start". Warum? Die sollen doch niemals beide True sein, oder sehe ich das nicht richtig?


----------



## xinix (2 April 2011)

> Da Du keine Funktionsbeschreibung der Anlage veröffentlicht hast, nehme ich an, dass Deine Frage mehr auf den Stil als auf die Funktion abzielt.


Ja genau! nur um eine Meinung zu erfahren ob ich mal wider total auf dem Holzwege bin, wie es ja nun wieder scheint... 



> Was ich gar nicht gut finde, sind die immer noch vorhandenen bedingten Aufrufe von FB's aus der Standardbibliothek wie TOF oder F_TRIG. Diese FB's sind dafür gemacht, in jedem Programmzyklus aufgerufen zu werden. Bei bedingtem Aufruf können sie ein anderes Verhalten zeigen. Selbst ein bewusstes Ausnutzen dieses Umstandes halte ich für schlechten Stil.


Ich weiß jetzt nicht so ganz was Du mit bedingtem Aufruf meinst. 



> Du hast nach wie vor getrennte Merker für "Init" und "Start". Warum? Die sollen doch niemals beide True sein, oder sehe ich das nicht richtig?


Die sollen sogar biede TRUE sein. In RS_Start will ich die Ganze Anlage mit einer Art Start und Stop "TASTER" bedienen. In RS_Ini regle ich den Start oder Betriebszustand... 

Meine Hauptfrage ist immer noch: Wie schupse ich eine Startsequenz an, die auf Grund eines momentanen Zustands der Füllstände ermittelt wird. Diese soll dann aber für z.B. 60min laufen. Dabei ist zu beachten, dass die Füllstande schnell den Zustand annehmen die für das anschupsen des Betriebszustandes wären. Dieser soll aber grundlegend erst nach ablauf der Startsequenz beginnen..

Da wäre ich um ein Beispiel super dankbar...


----------



## StructuredTrash (2 April 2011)

xinix schrieb:


> Ich weiß jetzt nicht so ganz was Du mit bedingtem Aufruf meinst.


Ich meine den TOF_StaFilt und den F_TRIG_Start. Die stehen doch hinter einem IF...THEN. Was macht ein Timer, dessen Zeit gerade abläuft, wenn er auf einmal nicht mehr aufgerufen wird? Wird der Zeitablauf unterbrochen oder läuft die Zeit weiter ab? Ich habe es noch nie ausprobiert. Was der Trigger-FB macht, wenn er nach dem Zyklus, in dem sein Ausgang True geworden ist, nicht mehr aufgerufen wird, weiss ich dagegen schon. Der Ausgang bleibt dann True. 



xinix schrieb:


> Die sollen sogar biede TRUE sein. In RS_Start will ich die Ganze Anlage mit einer Art Start und Stop "TASTER" bedienen. In RS_Ini regle ich den Start oder Betriebszustand...


Sorry, das habe ich übersehen. Das war im ersten Programm noch anders.



xinix schrieb:


> Meine Hauptfrage ist immer noch: Wie schupse ich eine Startsequenz an, die auf Grund eines momentanen Zustands der Füllstände ermittelt wird. Diese soll dann aber für z.B. 60min laufen. Dabei ist zu beachten, dass die Füllstande schnell den Zustand annehmen die für das anschupsen des Betriebszustandes wären. Dieser soll aber grundlegend erst nach ablauf der Startsequenz beginnen..


Warum kannst Du denn nicht nach den 60 Min in den normalen Betriebszustand übergehen?


----------



## xinix (3 April 2011)

> Ich meine den TOF_StaFilt und den F_TRIG_Start. Die stehen doch hinter einem IF...THEN. Was macht ein Timer, dessen Zeit gerade abläuft, wenn er auf einmal nicht mehr aufgerufen wird?



1000 Dank für diesen Hinweis! 

Grundsätzlich ist der Einsatz von TOF, TON, F- und R-Trig aber schon legetim oder auch nicht? Wie sehe denn ein Timer alternativ in ST aus?



> Warum kannst Du denn nicht nach den 60 Min in den normalen Betriebszustand übergehen?



Das möchte ich ja auch! Ich weiß halt nur nicht, welchen weg ich da einschlagen soll...


----------



## Blockmove (3 April 2011)

xinix schrieb:


> Grundsätzlich ist der Einsatz von TOF, TON, F- und R-Trig aber schon legetim oder auch nicht?



Selbstverständlich.
Nur ist es von Vorteil, wenn diese Funktionen nicht bedingt aufgerufen werden. Also ein Aufruf nach einer IF-Bedingung kann zu viel Ärger und seltsamen Verhalten führen. Daher die Zeitbausteine unbedingt aufrufen und nur die Ergebnisse in einer IF-Bedingung abfragen

Gruß
Dieter


----------



## xinix (3 April 2011)

StructuredTrash;323697 aus einem älteren Beitrag schrieb:
			
		

> CASE OF bietet sich für so etwas meistens an. Ich würde allerdings die Werte für die Betriebsarten als Konstanten oder als Enumeration deklarieren. Die Wahrscheinlichkeit, dass durch Flüchtigkeitsfehler undefinierte Werte zustandekommen, wird damit geringer.


 
Magst Du mir mal erleutern, was Du mit Werte als Konstante oder als Enumeration deklarieren meinst...

Danke!


----------



## StructuredTrash (3 April 2011)

xinix schrieb:


> Magst Du mir mal erleutern, was Du mit Werte als Konstante oder als Enumeration deklarieren meinst...
> 
> Danke!



Das habe ich bereits, und zwar in Antwort #50 im selben Thread. Dort allerdings nur als Konstanten-Version. Deshalb hier noch mal mit einer Enumeration:

```
TYPE enBetriebsarten :
(
   Aus,
   Init,
   Auto
);
END_TYPE

VAR
   Betriebsart:enBetriebsarten;
END_VAR

CASE Betriebsart OF
   Aus:
   Init:
   Auto:
END_CASE;
```


----------



## drfunfrock (4 April 2011)

Wunderbarer Code! Als Ergänzung:


```
TYPE enBetriebsarten :
(
   Aus,
   Init,
   Auto
);
END_TYPE

VAR
   Betriebsart:enBetriebsarten;
  (* Timer wie TON TOF *)
  timerOnButton : TON;
END_VAR

timerOnButton(...);
(* Weitere FB *)

CASE Betriebsart OF
   Aus:
     IF timerOnButton.Q THEN 
        ... 
     END_IF;
   Init: 
    
   Auto:
END_CASE;
```
Ich habe immer die FB vor das Case-Konstrukt gestellt, weils sonst unübersichtlich wird. Man kann es natürlich auch anders machen, aber die Gefahr, dass etwas schiefläuft ist gross.


----------



## xinix (4 April 2011)

Jo, daraus erkenne ich dass ich drei Betriebsarten steuern kann. 
Jetzt muss ich doch nur noch mal nachfragen, wie bekommen ich die Eigenschaft z.B. iTankLevel_T1<iTankLevel_Start auf die Enumeration Init gelegt. Wenn diese dann aktiviert wurde soll diese dann auch noch für 60min aktiv bleiben obwohl iTankLevel_T1 währen dessen schon lange größer als iTankLevel_Start.

Danke!


----------



## StructuredTrash (4 April 2011)

Hier ein möglicher Ansatz, ohne die weitere Umgebung zu berücksichtigen:

```
VAR
   InitTime:TON;
   InitDone:BOOL;
END_VAR

IF (* Alles aus *)
THEN
   Betriebsart:=Aus;
   InitDone:=FALSE;
ELSIF  (iTankLevel_T1<iTankLevel_Start) (* AND weitere Bedingungen für den Start von Init *)
THEN
   Betriebsart:=Init;
ELSIF (iTankLevel_T1>=iTankLevel_Start) AND InitDone (* AND weitere Bedingungen für den Start von Auto *)
THEN
   Betriebsart:=Auto;
END_IF;

InitTime(In:=Betriebsart=Init,
             Pt:=t#60min);
IF InitTime.Q
THEN
   InitDone:=TRUE;
END_IF;
```

Und noch zwei weitere Gedanken
1) Statt des Hilfsmerkers "InitDone" könnte man einen weiteren Betriebsart-Enumeratinswert (AutoBereit) oder wie auch immer deklarieren und die Betriebsart nach Ablauf von "InitTime" auf diesen Wert setzen. Ob das der Übersicht dient, wirst Du anhand des Umfangs Deiner Aufgabe besser wissen als ich.
2) Wenn Dir die IF THEN ELSIF-Konstruktion für den Wechsel der Betriebsarten über den Kopf wächst, nimm auch dafür CASE OF.


----------



## drfunfrock (5 April 2011)

Streich Merker, wenn du schon eine Case-Struktur hast. Jedem Case entspricht einem Zustand. Z.B, bei der Ampel sind es die Zustände Rot, Rot-Gelb, Gelb, Grün. 

Übertragen kann man das auf eine Maschine: 


```
PowerUp
Init
InitReady
Run
...
...
```
Das hat den Vorteil, dass man mit den Merkern nicht durcheinanderkommt und es braucht nicht diese Menge an IF-THEN-ELSE . Du kannst jederzeit einen Zustand einfügen, wenn du einen vergessen hast.



Jetzt das Bsp: 


```
CASE Zustand OF
   PowerUp:
  Alarmlicht := True;
  Zustand := Init;
   Init:
  Betriebsart = Drehschalter;
  Zustand := InitReady;
InitReady:
  IF timerStartupReady.Q THEN
    Zustand := Auto;
  END_IF
   Auto:
blablabla
END_CASE;
```


----------



## xinix (5 April 2011)

Das sind zwei super Ansätze! Vielen Dank sage ich auch für Eure Mühe mir die einzelheiten zu erläutern!! 

Eine Frage bleibt noch zu folgenden Beispiel:



drfunfrock schrieb:


> ```
> CASE Zustand OF
> PowerUp:
> Alarmlicht := True;
> ...



Wenn ich das richtig verstanden habe, sind "PowerUp, Init, InitReady usw. die Merker. Nur wie gebe ich den Merkern vor dem CASE die inhaltliche Bedingung? 

Etwa so?


```
IF iTankLevel_T1<iTankLevel_Start THEN
PowerUp := FALSE
Init :=TRUE
InitReady:= FALSE
AUS:= FALSE
ELSE_IF iTankLevel_T1>iTankLevel_Start THEN
PowerUp := TRUE
Init :=FALSE
InitReady:= FALSE
AUS:= FALSE
ELSE
PowerUp := FALSE
Init :=FALSE
InitReady:= FALSE
AUS:= TRUE
END_IF
```
Aber irgendwie frage ich mich warum der ganze aufwand? Wenn ich auf diese Art die Bedingungen an die Merker knöpfe, kann ich doch gleich den jeweiligen CODE unter die IF-Anweisung schreiben?

Vielen Dank noch mal!


----------



## drfunfrock (5 April 2011)

Du brauchst nur die Zustandvar. 


```
TYPE 
  tBetriebsarten : (   Init, PowerUp,   Auto ); 
END_TYPE
VAR
  Zustand : tBetriebsart := Init;
END_VAR

CASE Zustand OF
Init:
    AUS := False;
    if iTankLevel_T1>iTankLevel_Start then
      Zustand := PowerUp;
    end_if;
PowerUp:
    AUS := False;
    if iTankLevel_T1>iTankLevel_Start then
      Zustand := Auto;
    end_if;
 Auto: 
   AUS := True;
   if alles_aus or (iTankLevel_T1<iTankLevel_Start) then
     Zustand := Init;
   end_if
END_CASE
```
Der Zustand PowerUp ist nicht notwendig. Und deine 4 Merker sind viel zu kompliziert.


----------



## xinix (5 April 2011)

drfunfrock schrieb:


> Du brauchst nur die Zustandvar.



Ach soooooooo!!!

Das habe ich ja dann vorher immer ganz anders versrtanden... 

und die Aktionen kommen dann auch in die jeweilige CASE ? 

So zum beispiel:


```
CASE Zustand OF
Init:
    AUS := False;
    if iTankLevel_T1>iTankLevel_Start then
      Zustand := PowerUp;
    end_if;
    xPumpe1:=TRUE
    xPumpe2:=FALSE
PowerUp:
    AUS := False;
    if iTankLevel_T1>iTankLevel_Start then
      Zustand := Auto;
    end_if;
    xPumpe1:=FALSE
    xPumpe2:=TRUE
 Auto: (* usw.... *)
```

1000 Dank!


----------



## drfunfrock (6 April 2011)

Genau so! Das lässt sich auch leicht Debuggen, weil du den Zustand anzeigen lassen kannst, um dann nur diese Codezeilen zu betrachten. Vergess nicht die Initialisierung der Zustands-var im VAR-Teil


Viel Spass


----------



## xinix (6 April 2011)

Danke ! :-d


----------



## StructuredTrash (6 April 2011)

Wenn Du die Steuerzúng des Zustandswechsels und die bei den Zuständen notwendigen Aktionen in das selbe CASE OF-Konstrukt packst, entsteht folgende Sondersituation: In dem Zyklus, in dem Du den Zustand wechselst, werden trotzdem noch die Aktionen entsprechend dem bisherigen Zustand ausgeführt. Die Aktionen hinken immer um einen Zyklus hinter den Zustandswechseln her. Das mag sich bei einem einfachen Programm nicht nach aussen hin auswirken, kann aber bei Erweiterungen zum Fallstrick werden, denn wirklich gewollt ist das ja eigentlich nicht.
Ich würde deshalb zwei CASE OF schreiben. Das Erste nur für die Zustandswechsel, das Zweite für die Aktionen.


----------



## drfunfrock (6 April 2011)

Ich würde es anders sagen:
Der Automat hier braucht immer einen Zyklus um auf Eingangsänderungen zu reagieren. Wenn man es vermeiden kann, sollte man es so belassen, weil der Code dann übersichtlicher wird. 

Wenn man in einen Zyklus eine Ausnahme machen will, so kann man die Ausgänge auch in IF-Zweig des Zustandswechsels schalten. Wenn das allerdings zur Regel wird, empfiehlt sich tatsächlich ein zweiter Case-Zweig, in dem die Zustände mit den Eingängen vernüpft werden, um den Ausgang zu ermitteln.


----------



## StructuredTrash (6 April 2011)

drfunfrock schrieb:


> Wenn man in einen Zyklus eine Ausnahme machen will, so kann man die Ausgänge auch in IF-Zweig des Zustandswechsels schalten.



Dann erfolgen die Ausgangszuweisungen aber nur im Zyklus des Zustandswechsels. Geht also nur für Ausgänge, die für die Dauer des neuen Zustandes ihren Status nicht mehr ändern sollen.


----------



## drfunfrock (7 April 2011)

StructuredTrash schrieb:


> Dann erfolgen die Ausgangszuweisungen aber nur im Zyklus des Zustandswechsels. Geht also nur für Ausgänge, die für die Dauer des neuen Zustandes ihren Status nicht mehr ändern sollen.



Der neue Zustand sollte natürlich auch die neuen Ausgangszustände beinhalten, das ist aber kein Muss. Damit hat man die Ausgangszustände an 2 verschiedenen Positionen und das ist unübersichtlicher. Ich setze lieber eine schnellere SPS ein, als das so zu machen. 

Es gibt aber Situationen, in denen es nicht anders möglich ist. Bei der Steuerung der Montrac-Monorail-Bahn hatte ich am Anfang das Problem, wenn 2 Wagen gleichzeitig an den 2 Spuren einer Weiche ankamen, dass dann der Automat die jeweils andere Spur als leer detektiert hat  Die Folge war, das die Weiche umschaltete  während ein Wagen auf der Weiche war. 3000Eu rastem dem Boden entgegen. Ich war gezwungen, den Automat so zu bauen, dass Änderungen am Eingang sofort übernommen wurden.


----------



## xinix (7 April 2011)

Also die Funktion habe ich ja nun verstanden, jedoch komme ich mit der einrichtung der TYPE nicht ganz klar...

Also bei CoDeSys habe ich nun unter Karteireiter DATENTYPEN links unten 
das Objekt tBetriebsart angelegt.

Da steht dann:


```
TYPE
    tBetriebsarten :
STRUCT
END_STRUCT
END_TYPE
```
dann habe ich im eigentlichen Programm unter dem "CASE Zustand OF" nun das erste CASE deklariert (in diesem Falle ja Init. Dann ging das Variablen Deklarationsfenster auf und ich habe unter TYPE "tBetriebsarten" ausgewählt...

jetzt steht das also unter VAR so:


```
VAR
Betrieb: tBetriebsarten;
Init: tBetriebsarten;
Start: tBetriebsarten;
END_VAR
```
das sieht ja nun son bisschen anders auch als bei Euch? Mache ich da was falsch?

Danke


----------



## xinix (7 April 2011)

habs gefunden....


----------



## drfunfrock (7 April 2011)

Schau dir mal die Hilfedatei an. Da sind zu jedem ST-Befehl auch in der Regel ein oder zwei Beispiele. Vergess die Initialisierung im Var-Block nicht


----------



## xinix (7 April 2011)

geht das eigentlich so?


```
VAR
startP2     :R_TRIG;
laufzeitP2     :TP;
VentielAUF    :TP;
ENDVAR

startP2     (CLK:=ZYKL=Filtern , Q=> );
laufzeitP2    (IN := startP2.Q, PT:= t_Filt_Zeit);
VentielAUF    (IN := [B]laufzeitP2.PT-t_Filt_VentStellZeit[/B], PT:= t_Filt_VentStellZeit);
```

Damit möchte ich erreichen, dass die Pumpe P2 läuft, solange das Ventiel (welches 5 sec. Fahrzeit hat) öffnet.

Wenn ich hinter dem namen des TP Bausteines ein"." eingebe bekomme ich die Auswahl mit ET, IN PT, Q und StartTime. Was ist denn StartTIME?
steht leider nicht in der Hilfe...

DANKE!


----------



## StructuredTrash (9 April 2011)

xinix schrieb:


> geht das eigentlich so?


Die Antwort gibt in diesem Fall schon der Compiler, nämlich Nein. PT.In ist doch vom Typ Bool.



xinix schrieb:


> Damit möchte ich erreichen, dass die Pumpe P2 läuft, solange das Ventiel (welches 5 sec. Fahrzeit hat) öffnet.


Ich würde dafür nicht mit mehreren Zeitbausteinen herumhantieren, einer reicht.

```
FilterZeitTimer(In:=ZYKL=Filtern,     (* FilterZeitTimer ist ein TON *)
                      PT:=t_Filt_Zeit);
Pumpe:=(ZYKL=Filtern) AND NOT FilterZeitTimer.Q;
Ventil:=Pumpe AND (FilterZeitTimer.ET<t_Filt_Zeit-t_Filt_VentStellzeit);
```



xinix schrieb:


> Wenn ich hinter dem namen des TP Bausteines ein"." eingebe bekomme ich die Auswahl mit ET, IN PT, Q und StartTime. Was ist denn StartTIME?


Das ist eine interne Variable des TP-Bausteins. CoDeSys erlaubt von aussen zumindest lesende Zugriffe auf solche Variablen, was aber eigentlich nicht sein dürfte.


----------

