kann ich dass so machen?

xinix

Level-1
Beiträge
46
Reaktionspunkte
1
Zuviel Werbung?
-> Hier kostenlos registrieren
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...

Code:
(*    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
 
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
Code:
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?
 
Zuviel Werbung?
-> Hier kostenlos registrieren
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... :confused:

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. :rolleyes:

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...
 
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.

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.

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?
 
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...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
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
 
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!
 
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:
Code:
TYPE enBetriebsarten :
(
   Aus,
   Init,
   Auto
);
END_TYPE

VAR
   Betriebsart:enBetriebsarten;
END_VAR

CASE Betriebsart OF
   Aus:
   Init:
   Auto:
END_CASE;
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wunderbarer Code! Als Ergänzung:

Code:
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.
 
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!
 
Hier ein möglicher Ansatz, ohne die weitere Umgebung zu berücksichtigen:
Code:
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.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
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:

Code:
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:

Code:
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;
 
Zuletzt bearbeitet:
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:

Code:
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;

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?

Code:
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!
 
Du brauchst nur die Zustandvar.

Code:
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.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
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:

Code:
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!
 
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 :D
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
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.
 
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.
 
Zurück
Oben