Funktionszeitgeber für Simatic S7-1200 Industriesteuerung

Selbst für die Serien S7-300 und S7-400 waren unter Schritt 7 die klassischen Versionen der dem Entwickler angebotenen Timer völlig ausreichend - dies sind Standard-IEC-Timer, die als Funktionsblöcke implementiert sind, und S5-Timer (die übrigens für die S7-Serie noch existieren) 1500). In einigen Fällen verwendete der Entwickler jedoch keine Standardtools und implementierte seine eigenen Timer, meist in Form von Funktionen. Solche Timer-Funktionen waren bei einem „IT“ -Ansatz für die Programmierung erforderlich, bei dem sie nicht mit getrennten Instanzen der Funktionsblöcke technologischer Geräte, mit der entsprechenden Bindung von Ein- und Ausgängen, sondern mit Arrays von Strukturen arbeiteten. Zum Beispiel ein Array einer diskreten Eingabetypstruktur. Oder ein Array einer Aggregatstruktur. Dieser Ansatz zur Programmierung hat ein Existenzrecht, da Sie damit den Arbeitsspeicher der CPU ernsthaft sparen können, aberAuf der anderen Seite ist der Programmcode schwer zu lesen. Ein Programmierer von Drittanbietern und mit einem einfachen Blick auf ein LAD-Programm kann es kaum sofort herausfinden, aber jede Menge Indizes, Arrays und Funktionen zu ihrer Verarbeitung kommen nicht in Frage - hier ohne Dokumentation für die Software (und natürlich ohne einen halben Liter) nirgendwo.

Diese Arrays von Strukturen wurden typischerweise in Funktionen verarbeitet. Im Prinzip hat nichts die Verarbeitung von Funktionsblöcken verhindert, aber es gab immer eine wichtige Frage - wie man in diesen Fällen mit Timern arbeitet? Standard-Timer nehmen entweder eine Nummer (S5) oder eine Instanz eines Funktionsblocks (IEC) an. Ich erinnere Sie daran, dass es darum geht, Arrays von Strukturen für klassische Simatic-SPS zu verarbeiten und Timer-Nummern in diese Strukturen zu „verdrehen“, und noch mehr, Instanzen sind entweder schwierig oder einfach unmöglich.

Aus diesem Grund haben wir als Funktion unsere eigene Timer-Funktionalität erstellt. Grundsätzlich müssen Sie für den Betrieb eines Timers nur wenige Dinge wissen - den Status der Eingabe, die Zeiteinstellung und wie viel Zeit seit der Aktivierung vergangen ist.

Für die Serien 300 und 400 gab es zwei Möglichkeiten, diese Zeit zu bestimmen. Die erste besteht darin, die Laufzeit des Haupt-OB1 (es gibt eine entsprechende Variable in OB1 selbst) oder der zyklischen OBs zu betrachten und den internen Zeitakkumulator bei jedem Timer-Aufruf zu erhöhen, vorausgesetzt, die „Wahrheit“ wird eingegeben. Keine gute Option, da diese Zeit für OB1 und zyklische OBs unterschiedlich ist. Die zweite Methode ist die Systemfunktion TIME_TCK, die bei jedem Aufruf einen einzelnen Wert zurückgibt - den internen Millisekundenzähler des Zentralprozessors.

Bild

Somit war für einen Zeitgeber vom TON-Typ (bei Verzögerung) der Operationsalgorithmus wie folgt:

  • Setzen Sie bei steigender Flanke der Antwortanforderung den Ausgang zurück und merken Sie sich den aktuellen Wert des System-Timers TIME_TCK
  • «» , ( , TIME_TCK 0 (2 ^ 31 — 1), ). , . , «», — «»
  • «»,

Mit dem Aufkommen der "tausendsten" Serie hat sich die Situation ein wenig geändert. Tatsache ist, dass die S7-1500-Leitung die Unterstützung für den Systemaufruf TIME_TCK und Liebhaber des Ansatzes „Stehen und in einer Hängematte“ geerbt hat (wie kann man sonst ein Programm aufrufen, das nur Arrays von Strukturen verarbeitet, während es mit gruseligen Indizes arbeitet?) Verwenden Sie ruhig weiterhin ihre Best Practices.

Die Basissteuerungen der S7-1200-Serie basieren auf einer anderen Architektur und weisen eine Reihe von Unterschieden zur S7-1500 auf. Einschließlich des Fehlens eines TIME_TCK-Systemaufrufs. In den Reihen der Entwickler, die nicht genügend Flexibilität beim Denken haben, ist die Unzufriedenheit verschwunden - es ist unmöglich, Kopien / Pasten alter Programme auszuführen. Die Aufgabe, zu bestimmen, wie viel Zeit seit dem vorherigen Aufruf vergangen ist, kann jedoch mithilfe der Laufzeitfunktion ausgeführt werden.

Diese Funktion gibt die seit ihrem vorherigen Aufruf verstrichene Zeit in Sekunden als reelle Zahl LREAL mit doppelter Genauigkeit zurück. Details sind in der Hilfe beschrieben. Für interne Zwecke ist eine zusätzliche MEM-Variable (ebenfalls vom Typ LREAL) erforderlich.

Ich werde die Quellen der ersten Annäherung der Funktion angeben und einige Anmerkungen machen.

Funktionsdeklaration:

FUNCTION "PerversionTON" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      IN : Bool;   //  
      PT : Real;   //    
   END_VAR

   VAR_OUTPUT 
      Q : Bool;   //  
   END_VAR

   VAR_IN_OUT 
      INPrv : Bool;
      MEM : LReal;
      TimeACC : UDInt;
   END_VAR

   VAR_TEMP 
      udiCycle : UDInt;
      udiPT : UDInt;
   END_VAR

Mit den Ein- / Ausgängen ist alles klar: IN, Q und PT. Ich stelle die Zeiteinstellung in Form einer echten ein, es sind Sekunden. Ich wollte nur (aber vergebens, aber mehr dazu weiter unten). Weitere Informationen zu Variablen des InOut-Bereichs. Da wir eine Funktion haben, haben wir keinen STAT-Bereich, es gibt keine Variablen, die ihren Wert beim nächsten Funktionsaufruf beibehalten, und solche Variablen werden benötigt:

INPrv - um die positive Flanke der Anforderung

MEM zu bestimmen - Hilfsvariable für den Systemaufruf zur Laufzeit

RuntimeACC - Zeitakkumulator , die die Anzahl der Mikrosekunden der aktuell laufenden Verzögerung speichert.

Die Variablen TimeACC, udiCycle und udiPT werden im UDINT-Format angegeben, einer vorzeichenlosen Ganzzahl mit 4 Bytes. Trotz der Tatsache, dass ich die Zeit als real angegeben habe und die Laufzeitfunktion real bis zur doppelten Genauigkeit zurückgibt, bevorzuge ich einfache Operationen zum Summieren und Vergleichen mit ganzzahligen Operanden, um Prozessorzeit zu sparen. Die Zeit in meinem Fall wird in Mikrosekunden berücksichtigt. Der Grund ist einfach: Wenn Sie die Zeit auf Millisekunden vergröbern, ist bei fast leerem OB1 (z. B. wenn nur ein Timer im gesamten Steuerungsprogramm aufgerufen wird und nichts weiter) ein „Überspringen“ von Zyklen möglich, das Programm läuft manchmal für 250 μs. In diesem Fall beträgt der maximal zulässige Wert des Zeitakkumulators 4.294 Sekunden, fast 4.295 (2 ^ 32 - 1 = 4.294.967.295). Es gibt nichts zu tun, eine solche „Optimierung“ erfordert Opfer.

Funktionstext.

#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
#udiPT := REAL_TO_UDINT(#PT * 1000000); //   

IF (#IN AND (NOT #INPrv)) THEN //         
    #TimeACC := 0;
    #Q := FALSE;
ELSIF (#IN AND #INPrv) THEN //     ""
    #TimeACC += #udiCycle; //     "    "
    IF #TimeACC >=  #udiPT THEN //      
        #Q := TRUE; //  ""
        #TimeACC := #udiPT; //  
    ELSE //      
        #Q := FALSE; // 
    END_IF;
ELSE //    -      
    #Q := FALSE;
    #TimeACC := 0;
END_IF;

#INPrv := #IN; //  

ENO := #Q; // ENO         LAD  FBD

Die ersten beiden Zeilen sind die Neuberechnung der Timer-Einstellung von der im REAL-Format angegebenen Anzahl von Sekunden auf die Anzahl von Mikrosekunden. Die Zeit in Mikrosekunden, die seit dem vorherigen Programmblockaufruf vergangen ist, wird ebenfalls bestimmt.

Ferner ist der Algorithmus wie folgt und ich habe ihn bereits gegeben:

  • Setzen Sie bei steigender Flanke des Eingangs IN den Ausgang Q zurück und setzen Sie den Zeitspeicher zurück
  • Wenn die „Wahrheit“ weiterhin eingegeben wird, erhöhen wir den Zeitakkumulator um den bereits bekannten udiCycle-Wert und vergleichen ihn mit der Zeiteinstellung. Wenn die Zeiteinstellung überschritten wird, hat der Timer funktioniert, geben Sie die Ausgabe "wahr", andernfalls geben Sie die Ausgabe "falsch".
  • Wenn Sie einen falschen Eingang an den IN-Eingang anlegen, setzen Sie den Q-Ausgang zurück und setzen Sie den Zeitspeicher zurück.

Erinnern Sie sich am Ende der Funktion an den vorherigen Wert, um die Flanke des Eingangs IN zu bestimmen. Geben Sie der Ausgabe ENO (bei Verwendung einer Funktion in grafischen Sprachen wie LAD) auch den Wert der Ausgabe Q an.

Wir stellen sicher, dass die Funktion funktioniert. Danach wird es interessant, ihre Geschwindigkeit zu bewerten und gegebenenfalls zu verbessern (es scheint bereits auf den ersten Blick, dass eine Reihe von Berechnungen durchgeführt werden) Leerlauf und vergebliche Verschwendung von CPU-Zeit). Um die Leistung zu bewerten, deklariere ich ein Array von 1000 Timer-Datenstrukturen.

Erklärung der Struktur. Seine Felder duplizieren die Eingabe- und Ausgabevariablen der Timerfunktion.

TYPE "typePervTONdata"
VERSION : 0.1
   STRUCT
      IN : Bool;   //  
      PT : Real;   //   
      Q : Bool;   //  
      INPrv : Bool;   //    
      MEM : LReal;   //    
      TimeACC : UDInt;   //  
   END_STRUCT;

END_TYPE

Im globalen Datenblock TortureTON wird ein Array von Strukturen deklariert:

TONs : Array[0..999] of "typePervTONdata";

Der folgende Code wird im Organisationsblock OB1 ausgeführt:

FOR #i := 0 TO 999 DO
    "TortureTON".TONs[#i].IN := "startton";
    "PerversionTON"(IN := "TortureTON".TONs[#i].IN,
                    PT := "TortureTON".TONs[#i].PT,
                    Q := "TortureTON".TONs[#i].Q,
                    INPrv := "TortureTON".TONs[#i].INPrv,
                    MEM := "TortureTON".TONs[#i].MEM,
                    TimeACC := "TortureTON".TONs[#i].TimeACC);
END_FOR;

Ankündigung von 1000 "Instanzen" von Timern, die jeweils eine Zeit von 10 Sekunden einstellen. Alle 1000 Timer beginnen, die Zeit nach dem Wert der Startton-Markierungsvariablen zu zählen.

Ich starte die Diagnosefunktionen der Steuerung (S7-1214C DC / DC / DC, Version FW 4.4, Version Step7 - V16) und beobachte die Scan-Zykluszeit der Steuerung. Bei "Leerlauf" (wenn "Falsch" am Eingang der Timer eintrifft) werden die gesamten Tausend durchschnittlich 36-42 Millisekunden lang verarbeitet. Während des Countdowns von zehn Sekunden wächst dieser Messwert um etwa 6-8 Millisekunden und rollt manchmal für 50 ms.

Wir schauen uns an, was im Funktionscode verbessert werden kann. Erstens die Zeilen ganz am Anfang des Programmblocks:

#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
#udiPT := REAL_TO_UDINT(#PT * 1000000); //   

Sie werden immer aufgerufen, unabhängig davon, ob der Timer die Zeit zählt, nicht zählt oder bereits gezählt hat. Eine große Geldverschwendung besteht darin, die nicht sehr leistungsstarke CPU der 1200er-Serie mit Berechnungen zu beladen, bei denen Materialien mit doppelter Genauigkeit verwendet werden. Es ist sinnvoll, beide Zeilen auf den Teil des Codes zu übertragen, der den Countdown verarbeitet (wenn die „Wahrheit“ weiterhin eingeht). Es ist auch erforderlich, die udiCycle-Berechnung in einen Code zu duplizieren, der eine positive Flanke am Eingang des Timers verarbeitet. Dies sollte den "Leerlaufbetrieb" des Timers entlasten, wenn der Eingabewert falsch ist. In der Praxis arbeiten Timer in speicherprogrammierbaren Steuerungen meistens im Leerlauf. Beispielsweise beträgt die Filterzeit des Kontaktsprungs einige zehn Millisekunden. Der Steuerimpuls eines diskreten Ausgangs beträgt einige hundert Millisekunden, normalerweise 0,5 bis 1,0 Sekunden.Die Zeit für die Überwachung der Ausführung des Einheitenbefehls (z. B. die Zeit, in der das Ventil vollständig geöffnet wird) beträgt einige zehn Sekunden bis einige Minuten. SPS in der Produktion arbeitet 24 Stunden am Tag und 365 (und manchmal mehr!) Tage im Jahr. Das heißt, meistens ist die Eingabe des Timers entweder "Null" und der Timer zählt nichts oder eine "Einheit" kommt für eine lange Zeit an und der Timer hat bereits alles gezählt. Um den CPU-Leerlauf der zweiten Stufe zu entladen (der Timer hat bereits gezählt), muss in der Stufe überprüft werden, ob der Eingang weiterhin die Wahrheit empfängt. Wenn der Timer bereits die ganze Zeit gezählt hat, muss der Ausgang auf true gesetzt werden. In diesem Fall sollten keine Berechnungen durchgeführt werden.Meistens ist der Eingang des Timers entweder "Null" und der Timer zählt nichts oder eine "Einheit" kommt für eine lange Zeit an und der Timer hat bereits alles gezählt. Um den CPU-Leerlauf der zweiten Stufe zu entladen (der Timer hat bereits gezählt), muss in der Phase überprüft werden, ob die Wahrheit weiterhin eingeht. Wenn der Timer bereits die ganze Zeit gezählt hat, muss der Ausgang auf true gesetzt werden. In diesem Fall sollten keine Berechnungen durchgeführt werden.Meistens ist der Eingang des Timers entweder "Null" und der Timer zählt nichts oder eine "Einheit" kommt für eine lange Zeit an und der Timer hat bereits alles berechnet. Um den CPU-Leerlauf der zweiten Stufe zu entladen (der Timer hat bereits gezählt), muss in der Stufe überprüft werden, ob der Eingang weiterhin die Wahrheit empfängt. Wenn der Timer bereits die ganze Zeit gezählt hat, muss der Ausgang auf true gesetzt werden. In diesem Fall sollten keine Berechnungen durchgeführt werden.

Um diese Änderungen vorzunehmen, muss die Ausgabe des Q-Timers vom OUTPUT-Bereich in den IN_OUT-Bereich übertragen werden, und der Ausgabewert wird in externen Variablen gespeichert (in diesem Beispiel in einem Array von Strukturen). Nach der Verfeinerung lautet der gesamte Funktionscode einschließlich der Deklaration wie folgt:

FUNCTION "PerversionTON" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      IN : Bool;   //  
      PT : Real;   //    
   END_VAR

   VAR_IN_OUT 
      Q : Bool;   //  
      INPrv : Bool;
      MEM : LReal;
      TimeACC : UDInt;
   END_VAR

   VAR_TEMP 
      udiCycle : UDInt;
      udiPT : UDInt;
   END_VAR


BEGIN
	IF (#IN AND (NOT #INPrv)) THEN //         
	    #TimeACC := 0;
	    #Q := FALSE;
	    #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); // " "  
	ELSIF (#IN AND #INPrv) THEN //     ""
	    IF (NOT #Q) THEN
	        #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
	        #udiPT := REAL_TO_UDINT(#PT * 1000000); //   
	        #TimeACC += #udiCycle; //     "    "
	        IF #TimeACC >= #udiPT THEN //      
	            #Q := TRUE; //  ""
	            #TimeACC := #udiPT; //  
	        END_IF;
	    END_IF;
	ELSE //    -      
	    #Q := FALSE;
	    #TimeACC := 0;
	END_IF;
	
	#INPrv := #IN; //  
	
	ENO := #Q; // ENO         LAD  FBD
END_FUNCTION

Danach verbessert sich die Ausführungszeit: Die Leerlaufzeitverarbeitung der Timer beträgt 23 ms bei einer Arbeitsfilterzeit von 37-40 ms.

Dieser Funktionscode prüft nicht auf einen ungültigen Wert der Timer-Einstellung - einen negativen Wert (wenn das Material in eine vorzeichenlose Ganzzahl konvertiert wird, wird die Einstellung verzerrt) oder einen Wert größer als 4294,9 Sekunden (die Zeiteinstellung läuft über und verzerrt sich). Sie müssen entweder den Wert des PT-Werts im Code steuern oder die Aufgabe, den Bereich der Zeiteinstellung (von 0 bis 4294,9 Sekunden) zu überprüfen, dem Betriebssystem der obersten Ebene übertragen. Das Überprüfen des Bereichs mit dem SPS-Programm erhöht die Verarbeitungszeit auf ungefähr 45-46 ms (und im Allgemeinen ist es am korrektesten, die Timerzeit nicht im REAL-Format, sondern im UDINT-Format in Millisekunden einzustellen und Unsinn zu machen).

Das Anwendungsprojekt mit einem Timer für die Umgebung von TIA Portal Step 7 Version 16 finden Sie hier .

All Articles