TIA Alternierungsbaustein beliebig vieler pumpen

mista

Level-2
Beiträge
108
Reaktionspunkte
6
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo community das hier ist mein erster Beitrag. Ich würde gerne ein Baustein bauen der mir beliebig viele pumpen alterniert.

Mein Ansatz ist ein array von Beispiel 5 pumpen zu nehmen, pumpen[0..4].
Ein pumpensortiert[0..4] und ein freigaben[0..4] die Freigabe ist je nach Wasserstand. Alle pumpen sind gleich. Und sollen nach max. Laufzeit z. B. 10min wechseln solange nicht alle 5 laufen müssen weil Freigaben[4] = 1 ist.

Ich initialisieren das pumepnsortiert mit pumpen und dann sortiere ich mit einem selection sort das pumepnsortiert array nach Anläufe oder Betriebsstunden. So dass in pumpensortiert[0] der index der Pumpe steht mit den wenigsten Betriebsstunden/Anläufen.

Beispiel Pumpe 3
Pumepnsortiert[0] =2

Dann gehe ich in einer for Schleife pumpen durch setze die Freigabe :

For Index := 0 to 4 do
Pumpen[pumpensortiert[Index]] := freigaben[index];
End_for;

Das klappt auch soweit. Aber wenn die Pumpe ihre maximale Laufzeit von 10min erreicht hat soll diese stoppen und die nächst freie anlaufen. Doch wenn Beispiel 2 laufen darf die 2. Die ihre Zeit noch nicht um ist nicht ausschalten. Also muss ich die Pumpen sortieren nur wenn alle pumpen aus sind oder eine gestört oder ausgeht.

Beim initialisieren der Betreibstunden im array pumpensortiert schreibe ich eine 7ffffff rein wenn die gestört oder gesperrt ist, damit sie nicht am Anfang sortiert wird.

Mein Problem ist dass wenn ich die for Schleife mit Haltepunkt beobachte ist der Index nicht immer der gleiche.

Ich hoffe das war verständlich. Würdet ihr auch Arrays nehmen oder einen total anderen Ansatz wählen?
 
Ich hab sowas vor einigen Jahren für Kompressoren entwickelt. Eines der größten Probleme war, ein System zu finden, was einfach zu kapieren und
im Status sauber zu diagnostizieren ist.
Nachdem ich auch lange probiert hab und es nicht so 100% funktionierte, hab ich alles auf Mapping-Bausteine umgestellt. Das hab ich bis heute so
durchgezogen und find es die beste Lösung!

Dabei macht man folgendes:
- du steuerst in deinem Steuerungsprogramm 4 'virtuelle' Pumpen. Du schaltest dann immer nach der Reihe virtP1 ein, dann virtP2, virtP3, virP4 ein.
Ausschalten geht in umgekerter Reihenfolge. Du musst dich also nicht drum kümmern welche Pumpe als erstes dran ist!

- nun müssen die virutellen Pumpen auf die Realen geschaltet werden:
-- dazu brauchst du 4xMapping-Bausteine, und eine MappingTable[1..4] OF INT, die MappingTable enthält die Pumpen-Nummern {1..4}
-- um die Reihenfolge der Pumpen zu ändern, musst die die Pumpenummern in der MappingTable vertauschen/umsortieren, das ist alles!
-- nicht bereite Pumpen oder solche mit den meisten Starts/Betriebstunden wandern nach hinten. (das geht OnTheFly)

Der Mapping Baustein ist im Prinzip ein 4-fach Überkreuzschalter. Du hast 4-Eingänge und 4 Ausgänge. Die Zuordnung der
Eingaenge zu den Ausgaengen übernimmt die MappingTable

----------------
S1 -| I1 Q1 |- (S1)
S2 -| I2 Q2 |- (S2)
S3 -| I3 Q3 |- (S3)
S4 -| I4 Q4 |- (S4)
1 -| MAP1 |
2 -| MAP2 |
3 -| MAP3 |
4 -| MAP3 |
--------------


4=>1, 2=>2, 1=>3, 3=4
--------------
S1 -| I1 Q1 |- (S4)
S2 -| I2 Q2 |- (S2)
S3 -| I3 Q3 |- (S1)
S4 -| I4 Q4 |- (S3)
4 -| MAP1 |
2 -| MAP2 |
1 -| MAP3 |
3 -| MAP3 |
--------------

Das hat ich noch vergessen, man kann damit auch überlappend schalten.
in dem Beispiel kommt am Ausgang Q1 & Q2 das Signal S1 raus
--------------
S1 -| I1 Q1 |- (S1)
S2 -| I2 Q2 |- (S1)
S3 -| I3 Q3 |- (S2)
S4 -| I4 Q4 |- (S3)
1 -| MAP1 |
1 -| MAP2 |
2 -| MAP3 |
3 -| MAP3 |
--------------
 
Zuletzt bearbeitet:
OK so richtig habe ich das Prinzip nicht verstanden. Ich hab ja auch die virtuellen pumpen. Das Freigaben array, dann das sortierte mit den Index der pumpen plus Betriebsstunden und Anläufe und die eigentlichen pumpen..
 
Würdet ihr auch Arrays nehmen ...?
Array ja, sehr gut. Noch besser 1 Array of struct.
Wenn ich das richtig sehe, hast Du 4 SortierKriterien (unterschiedlicher Priorität bzw. unabhängig voneinander):
1. sortiert nach absolvierten BetriebsStunden
2. sortiert nach Anzahl absolvierter Anläufe
3. sortiert nach 'ungesperrt' bzw. 'gesperrt'
4. sortiert nach einem Kriterium, welche Pumpe beim Abschalten Vorrang hat. Z.B. abgelaufene Zeit, seit die Pumpe zuletzt abgeschaltet wurde.
Ob es genügt, für das Abschalten einfach die umgekehrte EinschaltReihenfolge zu nehmen ... ich weiss es nicht. Über das Abschalten wird man sich wohl noch den Kopf zerbrechen müssen.
Macht es Sinn, wenn z.B. zwei Pumpen [fast] gleichzeitig gestartet wurden und bei denen folglich [fast] gleichzeitig die "10 Minuten" ablaufen würden, diese überhaupt abzuschalten, wenn die Folge wäre, dass beide oder auch nur eine davon sofort wieder gestartet würde[n]??? Ich finde, dafür müsste noch ein Kriterium gefunden werden, wenn ich auch noch nicht weiss, welches.

Wenn also eine Pumpe gesperrt wird (wodurch auch immer, z.B. manuelles Eingreifen über eine Visu oder "Fallen" des zugehörigen MotorschutzSchalters), willst Du die über Monate oder Jahre mühsam aufgesammelten BetriebsStunden der Pumpe radikal überschreiben? Ich fürchte das wirst Du irgendwann bereuen. Je eine eigene Info für die verschiedenen SperrGründe vorsehen!
Die aktuelle Zeit, die eine Pumpen läuft, würde ich in einem eigenen Feld hochzählen und erst beim Abschalten auf die BetriebsStunden addieren. Und dieses Feld erst beim Einschalten der Pumpe löschen.

Du kannst ein Array "sortieren", ohne die zu sortierenden Daten im Array herumzuschieben. Einfach für jeden SortierModus im Array eine eigene Spalte anlegen, in der die Reihenfolge steht.
Beim Zugriff auf die Daten von Pumpe 1 indizierst Du wie gewohnt. Beim Zugriff auf die Pumpe, die als nächste eingeschaltet werden soll, indizierst Du ebenfalls mit Index 1, liest aber aus der Spalte 'Einschalten' den Index der Pumpe und greifst mit diesem Index auf die Daten zu.
Statt dieser zusätzlichen Spalten für die Indizes in dem einen Array kannst Du auch eigene Arrays anlegen. Aber wozu? Damit reisst Du die zusammengehörigen Daten unnötig auseinander.
Wenn Du Dein Array visualieren willst, dann stehen die Daten immer an derselben Stelle und die Reihenfolge für das Einschalten und für das Abschalten (etc.) lässt sich auch ablesen.
Für jede SortierReihenfolge, die Du benötigst, musst Du das Array sortieren, ABER dabei nur die Indizes in der zugehörigen Spalte neu fest legen - die Daten selbst (BetriebsStunden, Anzahl Anläufe u.s.w.) bleiben wo sie sind und hingehören, nämlich einer bestimmten Pumpe (=Zeile der Tabelle) zugeordnet.
Z.B. sortieren nach BetriebsStunden:
Vorrangig nach ungesperrt, wenn zwei oder mehr [un]gesperrt, dann nach BetriebsStunden, wenn zwei oder mehr mit gleichen BetriebsStunden, dann nach Anzahl Anläufe.
D.h. die Abfrage, ob die Indizes der beiden Datensätze getauscht werden müssen, ist mehrstufig, wenn mehrere Kriterien unterschiedlicher Priorität geprüft werden.
Jedes Kriterium kann einen Tausch vorschreiben oder verbieten oder zu dem Ergebnis kommen, dass weder vorschreiben noch verbieten angesagt ist, dann muss mit dem nächst geringer prioren Kriterium weitergeprüft werden u.s.w.. Sobald aber klar ist, dass getauscht werden muss oder nicht getauscht werden darf, ist der Vergleich beendet und Kriterien mit niedrigerer Priorität werden nicht mehr geprüft.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hier mal die Initialisierung, natürlich überschreibe ich nicht die Betriebsstunden bzw. Starts sondern schreibe dich in das PumpenSortiert Array of struct:

Code:
REGION Initialisierung der Betriebstunden und Starts für die Alternierung
        
        #tempAlternierung[0] := #Einstellungen.ALTERNIERUNG.P1_ALTERNIERUNG;
        #tempAlternierung[1] := #Einstellungen.ALTERNIERUNG.P2_ALTERNIERUNG;
        #tempAlternierung[2] := #Einstellungen.ALTERNIERUNG.P3_ALTERNIERUNG;
        #tempAlternierung[3] := #Einstellungen.ALTERNIERUNG.P4_ALTERNIERUNG;
        #tempAlternierung[4] := #Einstellungen.ALTERNIERUNG.P5_ALTERNIERUNG;
        
        FOR #statSortierung.i := 0 TO 4 DO
            IF
                //#Pumpenstarts.Pumpenstarts.startsAktuell[#statSortierung.i].QU
                //OR
                #Pumpen[#statSortierung.i].Status.ALARM
                OR #Pumpen[#statSortierung.i].Status.SAFE_OFF
                OR NOT #tempAlternierung[#statSortierung.i]
                OR #Schieber[#statSortierung.i].Status.ALARM
                OR #Pumpen[#statSortierung.i].Status.LOCK
                OR (#Anforderung_T_MAX_AUS[#statSortierung.i] AND #Schieber[#statSortierung.i].Status.IST_ZU)
            THEN
                #statPumpenSortiert[#statSortierung.i].hours := 16#7FFF_FFFF;
                #statPumpenSortiert[#statSortierung.i].starts := 16#7FFF_FFFF;
            ELSE
                #statPumpenSortiert[#statSortierung.i].hours := #Pumpen[#statSortierung.i].BS_elektrisch;
                #statPumpenSortiert[#statSortierung.i].starts := #Pumpenstarts.Pumpenstarts.startsGesamt[#statSortierung.i].CV;
            END_IF;
            
            #statPumpenSortiert[#statSortierung.i].pumpe := #statSortierung.i;
        END_FOR;
        
    END_REGION

Hier die Sortieerung:

Code:
IF #Einstellungen.ALTERNIERUNG.ALTERNIERUNG_NACH_STARTS THEN
            //Alternierung nach Starts
            FOR #statSortierung.i := 0 TO 4 BY 1 DO
                FOR #statSortierung.j := (#statSortierung.i + 1) TO 4 BY 1 DO
                    IF 
                        (#Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe] AND #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.IST_ZU ) OR
                        
                        (#statPumpenSortiert[#statSortierung.i].starts > #statPumpenSortiert[#statSortierung.j].starts) THEN
                        #tempPumpenSortiert := #statPumpenSortiert[#statSortierung.i];
                        #statPumpenSortiert[#statSortierung.i] := #statPumpenSortiert[#statSortierung.j];
                        #statPumpenSortiert[#statSortierung.j] := #tempPumpenSortiert;
                    END_IF;
                END_FOR;
            END_FOR;
        ELSE

Hier Die Freigaben: mit den auskommentierten Block der nicht klappte:
 #statNiveau[0].start := #Einstellungen.START_STOPP_NIVEAUS.STARTNIVEAU_1;
    #statNiveau[1].start := #Einstellungen.START_STOPP_NIVEAUS.STARTNIVEAU_2;
    #statNiveau[2].start := #Einstellungen.START_STOPP_NIVEAUS.STARTNIVEAU_3;
    #statNiveau[3].start := #Einstellungen.START_STOPP_NIVEAUS.STARTNIVEAU_4;
    #statNiveau[4].start := #Einstellungen.START_STOPP_NIVEAUS.STARTNIVEAU_5;
    
    #statNiveau[0].stopp := #Einstellungen.START_STOPP_NIVEAUS.STOPPNIVEAU_1;
    #statNiveau[1].stopp := #Einstellungen.START_STOPP_NIVEAUS.STOPPNIVEAU_2;
    #statNiveau[2].stopp := #Einstellungen.START_STOPP_NIVEAUS.STOPPNIVEAU_3;
    #statNiveau[3].stopp := #Einstellungen.START_STOPP_NIVEAUS.STOPPNIVEAU_4;
    #statNiveau[4].stopp := #Einstellungen.START_STOPP_NIVEAUS.STOPPNIVEAU_5;
    
    
    
    FOR #statSortierung.i := 0 TO 4 DO
        // Freigabe
        IF #füllstand >= #statNiveau[#statSortierung.i].start THEN
            #statFreigaben[#statSortierung.i].Pumpe := true;
            #statFreigaben[#statSortierung.i].Schieber := true;
        END_IF;
        
        // Stopp
        IF #füllstand < #statNiveau[#statSortierung.i].stopp THEN
            #statFreigaben[#statSortierung.i].Schieber := false;
            #statFreigaben[#statSortierung.i].Pumpe := false;
            
            // IF #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.IST_ZU THEN
            //     #statFreigaben[#statSortierung.i].Pumpe := false;
            
            //     IF #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe] THEN
            //         #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe] := false;
            //         #statT_LOCK[#statPumpenSortiert[#statSortierung.i].pumpe](IN := true,
            //                                                                   PT := t#30s);
            //         #statAlternierungNeuePumpe := true;
            //     ELSE
            //         #statAlternierungNeuePumpe := false;
            //     END_IF;
            // END_IF;
        END_IF;
               
    END_FOR;

Und hier der Output:

Code:
 // Leistungspumpen
            IF "Regelfreigabe" THEN
                FOR #statSortierung.i := 0 TO 4 DO
                    
                    
                    IF (#statFreigaben[#statSortierung.i].Pumpe OR #statWartungslauf[#statPumpenSortiert[#statSortierung.i].pumpe].TP_Blockierschutz.Q)
                        AND #Pumpen[#statPumpenSortiert[#statSortierung.i].pumpe].Status.AUTO AND NOT #Pumpen[#statPumpenSortiert[#statSortierung.i].pumpe].Status.SAFE_OFF
                        AND NOT #Pumpen[#statPumpenSortiert[#statSortierung.i].pumpe].Status.ALARM AND NOT #Pumpen[#statPumpenSortiert[#statSortierung.i].pumpe].Status.LOCK
                        AND NOT #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe]
                    THEN
                        #Output.Leistung[#statPumpenSortiert[#statSortierung.i].pumpe].FRG := true;
                        #statAlternierungNeuePumpe := false;
                    END_IF;
                    
                    // Freigabe der Pumpe entziehen
                    IF ((NOT #statFreigaben[#statSortierung.i].Pumpe OR #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe])
                        AND #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.IST_ZU)
                        OR (NOT #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.IST_ZU
                        AND #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.ALARM AND NOT #statFreigaben[#statSortierung.i].Pumpe)
                    THEN
                    
                        #Output.Leistung[#statPumpenSortiert[#statSortierung.i].pumpe].FRG := false;
                        
                        IF
                            #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe]
                            AND #Schieber[#statPumpenSortiert[#statSortierung.i].pumpe].Status.IST_ZU
                        THEN
                            #statAlternierungNeuePumpe := true;
                            #statT_LOCK[#statPumpenSortiert[#statSortierung.i].pumpe](IN := true,
                                                                                      PT := t#3s);
                            
                            #Anforderung_T_MAX_AUS[#statPumpenSortiert[#statSortierung.i].pumpe] := false;
                        END_IF;
                    END_IF;
END_FOR;

Wer mag kann sich die komplette SCL Quelle anschauen:
Anhang anzeigen Quelle.zip
 
OK so richtig habe ich das Prinzip nicht verstanden. Ich hab ja auch die virtuellen pumpen. Das Freigaben array, dann das sortierte mit den Index der pumpen plus Betriebsstunden und Anläufe und die eigentlichen pumpen..

Ok besser vergessen, ich hab das gerade bei mir mal probiert und festgestellt, dass meine Software nur aus Zufall funktioniert. Bei mir geht das, weil ich 2 Anlagen mit je 2 Kompressoren hab. Dort schalte ich dann über Kreuzschalter die Anlagen um und über weitere Kreuzschalter die 2 Kompressoren.
Wenn ich das mit 4 linear hintereinader geschalten Pumpen versuch, bricht das System zusammen. Der Grund ist mir mittlerweile klar: Es ist nicht ganz
so einfach wie ich mir das gedachte hatte:

Was man aber bei den Multipumpen Schaltungen in der Praxis wohl berücksichtigen muss ist folgendes:

1. Wird eine Pumpe getauscht, dann sollten die
Betriebsstunden und Starts der Pumpe vom Service
gelöscht werden.
Nun hat man eine Pumpe die praktisch 0 Starts/Betiebsstunden
hat im Vergleich zu den anderen Pumpen mit 1000den Starts!
Bei reiner Auswahl nach Starts/Betriebsstunden läuft jetzt
nur noch Pumpe 1, solange bis sie die Betriebszeit der anderen
aufgeholt hat! Ist das so gemwünscht ???

2. Jede Pumpe braucht wahrscheinlich irgendwann einen Aus/EIN/Auto
Schalter, so dass der Bediener vor Ort übersteuern kann.
Sei es Wartung, Ausfall irgendeines Bauteils ...

3. Hand eingeschaltete Pumpen müssen in der Automatik
mit berücksichtigt werden.

4. Laufen mehrere Pumpen und es muss eine abgeschaltet
werden, dann diejenige welche als erstes eingeschaltet wurde.
Bzw. die mit den meisten Betriebsstunden, sofern
eine Mindesteinschaltzeit der Pumpe abgelaufen ist.

5. Muss eine Pumpe zugeschaltet werden, dann diejenige welche
bisher die wenigsten Starts/Betiebsstunden hat.
Und evtl. eine Mindestausschaltzeit der Pumpe abgelaufen ist,
da sonst doch evtl. wieder die einschaltet, die als
letztes abgeschaltet wurde.

6. Fällt eine Pumpe aus, muss diese sofort durch eine
bereite Pumpe ersetzt werden. Hier gilt wieder 4.
Das gilt normalerweise auch für Pumpen, die per Hand
abgeschaltet werden!

7. Sonderfall: Asymetrische Laufzeit/Start-Verteilung
Ein Problem das wir bereits hatten!
Gibt es Serienfehler bei den Geräten. Z.B. Kupplungen,
dann gehen die oft nach etwa den gleichen Betriebsbedingungen
kaputt. Hat man dann bei 2 Geräten die Starts sehr symetrisch
verteilt, fällt die 2te mit dem gleichen Fehler aus, während man
noch auf das Ersatzteil wartet.

Ich geh mal davon aus, dass dies alles in eine Funktion gepackt nicht mehr wartbar ist!
 
Hab mir den Baustein in SCL nun mal runtergeladen und angesehen!

Das mag zwar gut und korrekt funktionern. Dafür Hut ab für die Leistung!

Aber!!! Wer soll das im Servicefall im Status diagnostizieren???
Da hast du einen Baustein der ist dann im Prinzip eine BlackBox.
Da würd ich jetzt als Servicetechniker davorstehen und nur mit den Schultern zucken!

Wenn du das an deine Inbetriebnehmer/Servicetechniker so rausgibst, schicken die dir ein Lynchkommando!!!

Bei allem Verständis für die Schwierigkeit der Problematik! Abers sowas bitte nicht machen, das kommt einem 'Verbrechen' gleich!!!

Wenn, dann solltest du das in einzelne Bausteine zerteilen und dann in KOP/FUP Aufrufen wieder zusammenbauen,
so dass man später eine Chance hat einzugreifen!

Ich versuch mal noch eine Demo mit der Mapping Tabelle zu machen. Ich geh davon aus, dass man sich das Sortieren
nach Betriebstunden komplett ersparen kann, da die Verteilung so schon gut funktioniert.
 
Zuletzt bearbeitet:
In der Tat habe ich eine Blackbox raus gebaut, und genau das mit dem Fehlerfall ist auch meine Sorge... Der Kunde ist vorerst aber zufrieden, mal schauen wie es nach der Inbetriebnahme sein wird.
Ich bin auf der Suche nach SPS-Entwurfsmuster oder wie man man gängige Probleme routiniert angeht. Mein Problem beim Programmieren der Steuerung ist, dass ich nicht einschätzen kann, wie lange ich für eine Aufgabe benötige, und dass ich immer wieder reinfuchsen muss um zu schauen, wie mache ich das nur...
 
... dass ich immer wieder reinfuchsen muss um zu schauen, wie mache ich das nur...
Und vorher schon reinfuchsen, welche Funktionalität sinnvoll ist bzw. was wirklich gebraucht wird. Wenn man auf Unklarheiten oder Widersprüche stösst, ruhig noch mal beim Kunden nachfragen.
Die "normale" Funktionalität ist zwar wichtig, aber noch wichtiger ist die Funktionalität in ExtremFällen, z.B. wenn eine oder mehrere Pumpen ausfallen.
Auch die eigentliche Strategie (möglichst gleichmässige Abnutzung der Pumpen) daraufhin prüfen, ob sie nicht geradewegs provoziert, dass mehrere Pumpen annähernd gleichzeitig schlappmachen.
Vielleicht wäre eine Strategie sinnvoller, die ein[ig]e Pumpe[n] "schont", um sie bei Bedarf als Reserve verfügbar zu haben.

PS:
Ich habe mich gefragt, wozu nach dem Kriterium 'absolvierte BetriebsStunden' UND nach dem Kriterium 'Anzahl Anläufe' zu entscheiden ist, wenn auch beachtet werden soll, dass nach 10 Minuten Betrieb eine Pumpe wieder gestoppt werden soll. Laufzeiten über 10 Minuten kommen dann nur vor, wenn die FörderMenge aller verfügbaren Pumpen benötigt wird. Und Laufzeiten von weniger als 10 Minuten kommen nur vor, wenn die FörderMenge einer Pumpe vor Ablauf ihrer 10 Minuten nicht mehr benötigt wird.
Ist evtl. der Quotient 'absolvierte BetriebsStunden' pro 'Anzahl Anläufe' das eigentliche Kriterium?
Um eine Pumpe in den "Schongang" zu versetzen, könnten man das Kriterium 'gesperrt' "aufweichen", so dass die "Schongang-Pumpen" zwischen den 'vorbehaltlos freigegebenen' und den 'bedingungslos gesperrten' einsortiert werden.
 
Zuletzt bearbeitet:
Ich würde gerne ein Baustein bauen der mir beliebig viele pumpen alterniert.

...
Aber wenn die Pumpe ihre maximale Laufzeit von 10min erreicht hat soll diese stoppen und die nächst freie anlaufen.
Zotos hat mal ein Motorenpendel für eine ähnliche Anwendung gepostet, welches sich ziemlich einfach auf Deine Bedürfnisse anpassen lassen sollte.
Ich hab' mal Zotos Motorenpendel als Beispiel an TIA S7-1500 und Deine Anforderungen angepasst:

Die Anzahl der Motoren wird anhand des übergebenen Motorarrays ermittelt.
Motoren im "off mode" (=0) sind immer aus, die im "on mode" (=1) immer an und die im "auto mode" (=2) bei Bedarf durch Anforderung von Req.
Wenn weniger Motoren an sind, als durch Req angefordert, wird der nichtlaufende (Auto-) Motor mit der geringsten Gesamtlaufzeit zuerst zugeschaltet.
Wenn mehr Motoren an sind, als durch Req angefordert, wird der laufende (Auto-) Motor mit der höchsten Gesamtlaufzeit zuerst abgeschaltet.
Sind mehr (Auto-) Motoren verfügbar als benötigt, greift die Laufzeitüberwachung für die max. zulässige Momentanlaufzeit. Wenn diese von einem (Auto-) Motor überschritten wird, schaltet dieser ab und der mit der verbleibenden niedrigsten Gesamtlaufzeit dafür zu. Der durch die Laufzeitüberwachung abgeschaltete Motor unterliegt einer Sperrzeit, bis er wieder in die Auswahl kommt.



Zunächst mal ein udt für die Motorendaten:
Code:
TYPE "typeMotorControl"TITLE = MOTOR CONTROL
VERSION : 0.1
   STRUCT
      State         : Struct                               // state
         Mode         : USInt;                               //     mode (0=off, 1=on, 2=auto)
         Enable         : Bool;                               //     enable
         Run         : Bool;                               //     output
      END_STRUCT;
      RT         : Struct                               // runtimes
         Total         : LTime;                               //     total runtime
         Current     : LTime;                               //     current runtime
         Lock         : LTime;                               //     lock time
      END_STRUCT;
   END_STRUCT;


END_TYPE
Die Laufzeiten hab' ich hier alle der Einfachheit halber in LTIME wie die ermittelte letzte Zykluszeit gespeichert.
Damit sind etwas über 106751 Tage, also mehr als 290 Jahre möglich. Sollte ja ausreichen.



Dann das Motorenpendel mit Laufzeitkontrolle:
Code:
FUNCTION "MotorControl" : VoidTITLE = FC MOTOR CONTROL
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : zotos/hucki
VERSION : 0.1
//automatic motor pendulum,
//controls a count of motors depending on requirements as well as total and maximum current runtime


   VAR_INPUT 
      Req         : DInt;                               // required motors
      RT_Limit         : Time;                               // maximum permissible current runtime
      LockTime         : Time;                               // lock time after running
   END_VAR


   VAR_IN_OUT 
      Motor         : Array
[*] of "typeMotorControl";                   // motors
   END_VAR


   VAR_TEMP 
      LastCycle         : LTime;                               // last cycle time
      Actual         : LTime;                               // shortest/longest runtime
      Bound         : Struct                               // array borders
         L         : DInt;                               //     first motor
         U         : DInt;                               //     last motor
      END_STRUCT;
      Count         : Struct                               // counts
         Run         : DInt;                               //     counts of running motors
         ModeOn         : DInt;                               //     counts of motors in on mode
         ModeAuto     : DInt;                               //     counts of motors in automatic mode
      END_STRUCT;
      RetVal         : Int;                                   // return value
      Sel         : DInt;                               // motor pointer
      i             : DInt;                               // count variable
   END_VAR


   VAR CONSTANT 
      INFO_MODE         : UInt := 25;                               // wanted information (25 = last cycle time)
      INFO_OB         : OB_ANY := 1;                               // selected OB
      MODE_OFF         : USInt := 0;                               // off mode
      MODE_ON         : USInt := 1;                               // on mode
      MODE_AUTO         : USInt := 2;                               // automatic mode
      RT_MIN         : LTime := LT#0ns;                           // shortest possible runtime
      RT_MAX         : LTime := LT#+106751d_23h_47m_16s_854ms_775us_807ns;           // longest possible runtime
   END_VAR




BEGIN
    REGION // initialization
        
        #RetVal := RT_INFO(MODE := #INFO_MODE, OB := #INFO_OB, INFO := #LastCycle);     // last cycle time
        #Bound.L := LOWER_BOUND(ARR := #Motor, DIM := 1);                               // lower bound of motor array
        #Bound.U := UPPER_BOUND(ARR := #Motor, DIM := 1);                               // upper bound of motor array
        #Count.Run := 0;                                                                // reset counter of running motors
        #Count.ModeOn := 0;                                    // reset counter of motors in on mode
        #Count.ModeAuto := 0;                                                           // reset counter of motors in automatic mode
        #Sel := #Bound.L - 1;                                                           // reset selection
        
    END_REGION
    
    
    REGION // operating data
        
        FOR #i := #Bound.L TO #Bound.U BY 1 DO                                          // loop over all motors
        IF #Motor[#i].State.Mode < #MODE_OFF                                        //      mode out of
                    OR #Motor[#i].State.Mode > #MODE_AUTO                                   //      the defined area?
            THEN
                    #Motor[#i].State.Mode := #MODE_OFF;                                     //          select off mode
            END_IF;
            #Count.ModeOn += BOOL_TO_INT(#Motor[#i].State.Mode = #MODE_ON);             //      raise counter of motors in on mode
            #Count.ModeAuto += BOOL_TO_INT(#Motor[#i].State.Mode = #MODE_AUTO);         //      raise counter of motors in automatic mode
            #Motor[#i].RT.Lock := MAX(IN1 := #Motor[#i].RT.Lock - #LastCycle,           //      lower lock time
                                      IN2 := #RT_MIN);                                  //      to zero
            IF #Motor[#i].State.Run                                                     //      motor is on?
            THEN
                #Motor[#i].RT.Current += #LastCycle;                                    //          raise current runtime
                #Motor[#i].RT.Total += #LastCycle;                                      //          raise total runtime
                IF #Motor[#i].State.Mode = #MODE_OFF                                    //          motor is in off mode?
                THEN
                    #Motor[#i].State.Run := FALSE;                                      //              switch off motor
                ELSE
                    #Count.Run += 1;                                                    //              raise counter of running motors
                END_IF;
            ELSE
                #Motor[#i].RT.Current := #RT_MIN;                                       //          reset current runtime
                IF #Motor[#i].State.Mode = #MODE_ON                                     //          motor is in on mode?
                THEN
                    #Motor[#i].State.Run := TRUE;                                       //              switch on motor
                    #Count.Run += 1;                                                    //              raise counter of running motors
                END_IF;
            END_IF;
        END_FOR;
        
    END_REGION
    
    
    REGION // automatic
        
        IF #Count.Run < #Req                                                            // to less motors?
        THEN
            #Actual := #RT_MAX;                                                         //      initialize to maximum possible runtime
            FOR #i := #Bound.L TO #Bound.U BY 1 DO                                      //      loop over all motors
                IF NOT #Motor[#i].State.Run                                             //         motor not running?
                    AND #Motor[#i].State.Enable                                         //         and enabled?
                    AND #Motor[#i].State.Mode = #MODE_AUTO                              //         and in automatic mode?
                    AND #Motor[#i].RT.Total < #Actual                                   //         and shorter total runtime? 
                THEN
                    #Actual := #Motor[#i].RT.Total;                                     //              transfer total runtime to actual
                    #Sel := #i;                                                         //              select motor
                END_IF;
            END_FOR;
            IF #Sel >= #Bound.L                                                         //      found a motor?
            THEN
                #Motor[#Sel].State.Run := TRUE;                                         //          switch on motor
            END_IF;
        ELSIF #Count.Run > #Req                                                         // to much motors?
        THEN
            #Actual := #RT_MIN;                                                         //      initialize to minimum possible runtime
            FOR #i := #Bound.L TO #Bound.U BY 1 DO                                      //      loop over all motors
                IF #Motor[#i].State.Run                                                 //          running?
                    AND #Motor[#i].State.Mode = #MODE_AUTO                              //          and automatic mode?
                    AND #Motor[#i].RT.Total > #Actual                                   //          and longer total runtime? 
                THEN
                    #Actual := #Motor[#i].RT.Total;                                     //              transfer total runtime to actual
                    #Sel := #i;                                                         //              select motor
                END_IF;
            END_FOR;
            IF #Sel >= #Bound.L                                                         //      found a motor?
            THEN
                #Motor[#Sel].State.Run := FALSE;                                        //          switch off motor 
            END_IF;
        END_IF;
        
    END_REGION
    
    
    REGION // runtime monitoring
        
        FOR #i := #Bound.L TO #Bound.U BY 1 DO                                          // loop over all motors
            IF #Count.ModeAuto + #Count.ModeOn > #Req                                   //      unused motors in automatic mode?
                AND #Motor[#i].State.Mode = #MODE_AUTO                                  //      and motor in automatic mode?
                AND #Motor[#i].RT.Current >= TIME_TO_LTIME(#RT_Limit)                   //      and maximum of current runtime reached?
                AND #Motor[#i].RT.Current > #Actual                                     //
            THEN
                #Motor[#i].State.Run := FALSE;                                          //          motor switch off
                #Motor[#i].State.Enable := FALSE;                                       //          disable motor
                #Motor[#i].RT.Lock := TIME_TO_LTIME(#LockTime);                         //           set lock time
            ELSIF #Motor[#i].RT.Current = #RT_MIN                                       //      current runtime reseted?
                AND #Motor[#i].RT.Lock <= #RT_MIN                                       //      and lock count down finished?
            THEN
                #Motor[#i].State.Enable := TRUE;                                        //          enable motor
            END_IF;
        END_FOR;
        
    END_REGION
END_FUNCTION



Und noch ein Test-DB zum Beschalten des FCs und Beobachten (und mit kurzen Zeitvorgaben zum Testen):
Code:
DATA_BLOCK "gdbMotorControl"{ S7_Optimized_Access := 'TRUE' }
AUTHOR : 'hucki'
VERSION : 0.1
NON_RETAIN
   VAR RETAIN
      Motor         : Array[1..4] of "typeMotorControl";                   // motor
      RT_Limit         : Time;                               // current runtime limit
      LockTime         : Time;                               // lock time
   END_VAR
   VAR 
      Req         : DInt;                               // required motors
   END_VAR




BEGIN
   RT_Limit := t#10s;
   LockTime := t#2s;


END_DATA_BLOCK

PS:
Hier noch die Quelle zum Download (einfach die Dateiendung .txt entfernen)
 

Anhänge

  • MotorControl.scl.txt
    10 KB · Aufrufe: 77
Zuletzt bearbeitet:
Ich hab' mal Zotos Motorenpendel als Beispiel an TIA S7-1500 und Deine Anforderungen angepasst:

Die Anzahl der Motoren wird anhand des übergebenen Motorarrays ermittelt.
Motoren im "off mode" (=0) sind immer aus, die im "on mode" (=1) immer an und die im "auto mode" (=2) bei Bedarf durch Anforderung von Req.
Wenn weniger Motoren an sind, als durch Req angefordert, wird der nichtlaufende (Auto-) Motor mit der geringsten Gesamtlaufzeit zuerst zugeschaltet.
Wenn mehr Motoren an sind, als durch Req angefordert, wird der laufende (Auto-) Motor mit der höchsten Gesamtlaufzeit zuerst abgeschaltet.
Sind mehr (Auto-) Motoren verfügbar als benötigt, greift die Laufzeitüberwachung für die max. zulässige Momentanlaufzeit. Wenn diese von einem (Auto-) Motor überschritten wird, schaltet dieser ab und der mit der verbleibenden niedrigsten Gesamtlaufzeit dafür zu. Der durch die Laufzeitüberwachung abgeschaltete Motor unterliegt einer Sperrzeit, bis er wieder in die Auswahl kommt.



Zunächst mal ein udt für die Motorendaten:
Code:
TYPE "typeMotorControl"TITLE = MOTOR CONTROL
VERSION : 0.1
   STRUCT
      State         : Struct                               // state
         Mode         : USInt;                               //     mode (0=off, 1=on, 2=auto)
         Enable         : Bool;                               //     enable
         Run         : Bool;                               //     output
      END_STRUCT;
      RT         : Struct                               // runtimes
         Total         : LTime;                               //     total runtime
         Current     : LTime;                               //     current runtime
         Lock         : LTime;                               //     lock time
      END_STRUCT;
   END_STRUCT;


END_TYPE
Die Laufzeiten hab' ich hier alle der Einfachheit halber in LTIME wie die ermittelte letzte Zykluszeit gespeichert.
Damit sind etwas über 106751 Tage, also mehr als 290 Jahre möglich. Sollte ja ausreichen.



Dann das Motorenpendel mit Laufzeitkontrolle:
Code:
FUNCTION "MotorControl" : VoidTITLE = FC MOTOR CONTROL
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : zotos/hucki
VERSION : 0.1
//automatic motor pendulum,
//controls a count of motors depending on requirements as well as total and maximum current runtime


   VAR_INPUT 
      Req         : DInt;                               // required motors
      RT_Limit         : Time;                               // maximum permissible current runtime
      LockTime         : Time;                               // lock time after running
   END_VAR


   VAR_IN_OUT 
      Motor         : Array
[*] of "typeMotorControl";                   // motors
   END_VAR


   VAR_TEMP 
      LastCycle         : LTime;                               // last cycle time
      Actual         : LTime;                               // shortest/longest runtime
      Bound         : Struct                               // array borders
         L         : DInt;                               //     first motor
         U         : DInt;                               //     last motor
      END_STRUCT;
      Count         : Struct                               // counts
         Run         : DInt;                               //     counts of running motors
         ModeOn         : DInt;                               //     counts of motors in on mode
         ModeAuto     : DInt;                               //     counts of motors in automatic mode
      END_STRUCT;
      RetVal         : Int;                                   // return value
      Sel         : DInt;                               // motor pointer
      i             : DInt;                               // count variable
   END_VAR


   VAR CONSTANT 
      INFO_MODE         : UInt := 25;                               // wanted information (25 = last cycle time)
      INFO_OB         : OB_ANY := 1;                               // selected OB
      MODE_OFF         : USInt := 0;                               // off mode
      MODE_ON         : USInt := 1;                               // on mode
      MODE_AUTO         : USInt := 2;                               // automatic mode
      RT_MIN         : LTime := LT#0ns;                           // shortest possible runtime
      RT_MAX         : LTime := LT#+106751d_23h_47m_16s_854ms_775us_807ns;           // longest possible runtime
   END_VAR




BEGIN
    REGION // initialization
        
        #RetVal := RT_INFO(MODE := #INFO_MODE, OB := #INFO_OB, INFO := #LastCycle);     // last cycle time
        #Bound.L := LOWER_BOUND(ARR := #Motor, DIM := 1);                               // lower bound of motor array
        #Bound.U := UPPER_BOUND(ARR := #Motor, DIM := 1);                               // upper bound of motor array
        #Count.Run := 0;                                                                // reset counter of running motors
        #Count.ModeOn := 0;                                    // reset counter of motors in on mode
        #Count.ModeAuto := 0;                                                           // reset counter of motors in automatic mode
        #Sel := #Bound.L - 1;                                                           // reset selection
        
    END_REGION
    
    
    REGION // operating data
        
        FOR #i := #Bound.L TO #Bound.U BY 1 DO                                          // loop over all motors
        IF #Motor[#i].State.Mode < #MODE_OFF                                        //      mode out of
                    OR #Motor[#i].State.Mode > #MODE_AUTO                                   //      the defined area?
            THEN
                    #Motor[#i].State.Mode := #MODE_OFF;                                     //          select off mode
            END_IF;
            #Count.ModeOn += BOOL_TO_INT(#Motor[#i].State.Mode = #MODE_ON);             //      raise counter of motors in on mode
            #Count.ModeAuto += BOOL_TO_INT(#Motor[#i].State.Mode = #MODE_AUTO);         //      raise counter of motors in automatic mode
            #Motor[#i].RT.Lock := MAX(IN1 := #Motor[#i].RT.Lock - #LastCycle,           //      lower lock time
                                      IN2 := #RT_MIN);                                  //      to zero
            IF #Motor[#i].State.Run                                                     //      motor is on?
            THEN
                #Motor[#i].RT.Current += #LastCycle;                                    //          raise current runtime
                #Motor[#i].RT.Total += #LastCycle;                                      //          raise total runtime
                IF #Motor[#i].State.Mode = #MODE_OFF                                    //          motor is in off mode?
                THEN
                    #Motor[#i].State.Run := FALSE;                                      //              switch off motor
                ELSE
                    #Count.Run += 1;                                                    //              raise counter of running motors
                END_IF;
            ELSE
                #Motor[#i].RT.Current := #RT_MIN;                                       //          reset current runtime
                IF #Motor[#i].State.Mode = #MODE_ON                                     //          motor is in on mode?
                THEN
                    #Motor[#i].State.Run := TRUE;                                       //              switch on motor
                    #Count.Run += 1;                                                    //              raise counter of running motors
                END_IF;
            END_IF;
        END_FOR;
        
    END_REGION
    
    
    REGION // automatic
        
        IF #Count.Run < #Req                                                            // to less motors?
        THEN
            #Actual := #RT_MAX;                                                         //      initialize to maximum possible runtime
            FOR #i := #Bound.L TO #Bound.U BY 1 DO                                      //      loop over all motors
                IF NOT #Motor[#i].State.Run                                             //         motor not running?
                    AND #Motor[#i].State.Enable                                         //         and enabled?
                    AND #Motor[#i].State.Mode = #MODE_AUTO                              //         and in automatic mode?
                    AND #Motor[#i].RT.Total < #Actual                                   //         and shorter total runtime? 
                THEN
                    #Actual := #Motor[#i].RT.Total;                                     //              transfer total runtime to actual
                    #Sel := #i;                                                         //              select motor
                END_IF;
            END_FOR;
            IF #Sel >= #Bound.L                                                         //      found a motor?
            THEN
                #Motor[#Sel].State.Run := TRUE;                                         //          switch on motor
            END_IF;
        ELSIF #Count.Run > #Req                                                         // to much motors?
        THEN
            #Actual := #RT_MIN;                                                         //      initialize to minimum possible runtime
            FOR #i := #Bound.L TO #Bound.U BY 1 DO                                      //      loop over all motors
                IF #Motor[#i].State.Run                                                 //          running?
                    AND #Motor[#i].State.Mode = #MODE_AUTO                              //          and automatic mode?
                    AND #Motor[#i].RT.Total > #Actual                                   //          and longer total runtime? 
                THEN
                    #Actual := #Motor[#i].RT.Total;                                     //              transfer total runtime to actual
                    #Sel := #i;                                                         //              select motor
                END_IF;
            END_FOR;
            IF #Sel >= #Bound.L                                                         //      found a motor?
            THEN
                #Motor[#Sel].State.Run := FALSE;                                        //          switch off motor 
            END_IF;
        END_IF;
        
    END_REGION
    
    
    REGION // runtime monitoring
        
        FOR #i := #Bound.L TO #Bound.U BY 1 DO                                          // loop over all motors
            IF #Count.ModeAuto + #Count.ModeOn > #Req                                   //      unused motors in automatic mode?
                AND #Motor[#i].State.Mode = #MODE_AUTO                                  //      and motor in automatic mode?
                AND #Motor[#i].RT.Current >= TIME_TO_LTIME(#RT_Limit)                   //      and maximum of current runtime reached?
                AND #Motor[#i].RT.Current > #Actual                                     //
            THEN
                #Motor[#i].State.Run := FALSE;                                          //          motor switch off
                #Motor[#i].State.Enable := FALSE;                                       //          disable motor
                #Motor[#i].RT.Lock := TIME_TO_LTIME(#LockTime);                         //           set lock time
            ELSIF #Motor[#i].RT.Current = #RT_MIN                                       //      current runtime reseted?
                AND #Motor[#i].RT.Lock <= #RT_MIN                                       //      and lock count down finished?
            THEN
                #Motor[#i].State.Enable := TRUE;                                        //          enable motor
            END_IF;
        END_FOR;
        
    END_REGION
END_FUNCTION



Und noch ein Test-DB zum Beschalten des FCs und Beobachten (und mit kurzen Zeitvorgaben zum Testen):
Code:
DATA_BLOCK "gdbMotorControl"{ S7_Optimized_Access := 'TRUE' }
AUTHOR : 'hucki'
VERSION : 0.1
NON_RETAIN
   VAR RETAIN
      Motor         : Array[1..4] of "typeMotorControl";                   // motor
      RT_Limit         : Time;                               // current runtime limit
      LockTime         : Time;                               // lock time
   END_VAR
   VAR 
      Req         : DInt;                               // required motors
   END_VAR




BEGIN
   RT_Limit := t#10s;
   LockTime := t#2s;


END_DATA_BLOCK

PS:
Hier noch die Quelle zum Download (einfach die Dateiendung .txt entfernen)


Hallo Hucki,

vielen Dank für die Erklärung erstmal,

ich hab versucht den Code in Tia Portal ( CPU 319-3 PN/DP) anzupassen, aber leider funktioniert nicht so werklich

könntest du mir bitte veraten wie ich von S7 umwandele oder überschreibe ??


vielen Dank im Voraus
 
Zuviel Werbung?
-> Hier kostenlos registrieren
@Aiman,
bitte kopiere einen so langen Post nicht komplett, wenn es für Deine Frage nicht unbedingt notwendig ist.
Wie Du ja selbst sehen kannst, macht das den Thread eher unübersichtlich.
;)


Nun zu Deiner Bitte:
könntest du mir bitte veraten wie ich von S7 umwandele oder überschreibe ??
Schau mal hier:
Zotos hat mal ein Motorenpendel für eine ähnliche Anwendung gepostet, welches sich ziemlich einfach auf Deine Bedürfnisse anpassen lassen sollte.
In diesem Link findest Du das Original von Zotos für eine S7-300.
Alles andere sind nur geringfügige Anpassungen auf die Bedürfnisse des TEs dieses Thread hier.


PS:
Einige Sachen, wie z.B. das Array unbestimmter Größe und das Bestimmen der Grenzen des übergebenen Arrays, gibt es bei der S7-300 auch so noch nicht.
 
Zuletzt bearbeitet:
Zurück
Oben