Dies geschieht mit Programmierern (SPS) in einer CODESYS-Umgebung. Jeder, der sich mit diesem System befasst hat, weiĂ, dass es in jedem Projekt eine Standard.lib-Bibliothek gibt, in der grundlegende Timer, Trigger, ZĂ€hler und eine Reihe anderer Funktionen und Funktionsblöcke implementiert sind. Viele dieser GerĂ€te werden stĂ€ndig in SPS-Programmen eingesetzt. Und die Bibliothek selbst ist wie die CODESYS-Programmiersprachen eine AusfĂŒhrungsform des IEC 61131-3-Standards, d. H. Entwickelt, um bei der Programmierung klassischer SPS-Aufgaben zu helfen.
Eines der Merkmale von SPS-Programmen ist, dass der Hauptprogrammzyklus ohne signifikante Verzögerungen ausgefĂŒhrt werden sollte, keine internen Zyklen mit einer undefinierten Austrittszeit oder synchronen Aufrufen von âdurchdachtenâ Funktionen, insbesondere im Hinblick auf die Kommunikation ĂŒber langsame KanĂ€le. Die Eingabe- und Ausgabebilder des Prozesses werden nur am Rand der Hauptschleife aktualisiert. Je lĂ€nger wir in einer Iteration der Schleife âsitzenâ, desto weniger wissen wir ĂŒber den tatsĂ€chlichen Zustand des Steuerobjekts, und schlieĂlich funktioniert der Watchdog des SchleifenĂŒberlaufs. Viele mögen EinwĂ€nde gegen mich erheben und sagen, dass moderne SPSen nicht eindeutig sind und Hardware-Interrupts unterstĂŒtzt werden. Ich stimme zu, aber ĂŒber solche Systeme zu sprechen ist nicht in meinen PlĂ€nen enthalten, aber ich möchte ĂŒber eine SPS einer Single-Task-Implementierung (ohne Unterbrechungen) auf der Basis der Arduino-Mikroprozessorplattform sprechen (quasi Pseudo-Choose), in der es nur eine Hauptschleife gibt. Ăbrigens wird es nicht
unangebracht sein zu sagen, dass der Artikel
Arduino-kompatible SPS CONTROLLINO, Teil 1 ĂŒber den Versuch, Arduino in Hardware zu implementieren, mich dazu veranlasst hat, diesen Hinweis zu schreiben. SPS
Ein paar Worte zu Arduino. Aus Sicht des SPS-Programmierers ist Arduino eine typische Steuerung mit einer sehr schnellen oder umgekehrt sehr langsamen loop () - Schleife. Es gibt keine EinschrĂ€nkungen fĂŒr die AusfĂŒhrungszeit des Zyklus und er kann sowohl ein als auch unendlich oft funktionieren - gemÀà dem Plan des Programmierers. Wenn das Programm einfach ist und sich darauf beschrĂ€nkt, sequentielle Operationen, Controller, ohne parallele Ereignisse auszufĂŒhren, reicht es aus, Operationen mit endlosen ĂberprĂŒfungszyklen fĂŒr verschachtelte ZustĂ€nde und synchronen Verzögerungen vom Typ delay () zu wechseln. Die aufeinander folgenden Schritte eines solchen Programms werden buchstĂ€blich Zeile fĂŒr Zeile einfach und logisch ausgefĂŒhrt. Sobald jedoch die Notwendigkeit besteht, parallele Operationen zu programmieren, muss das Programmparadigma geĂ€ndert werden.
In einem Single-Tasking-System kann eine sichtbare ParallelitĂ€t nur durch einen sehr schnellen sequentiellen Scan paralleler ZustĂ€nde erreicht werden, ohne bei jedem Funktionsaufruf oder jeder ZustandsprĂŒfung lange zu verweilen. Es gibt keine Probleme mit physischen Ein- und AusgĂ€ngen, Funktionen funktionieren schnell genug, aber delay () wird zu einer ungerechtfertigten Bremse. Und hier kommen nicht blockierende Timer ins Spiel, die Klassiker der SPS-Programmierung sind. Unter dem Strich verwenden sie einen Millisekunden-ZeitzĂ€hler, und alle Aktionen sind an die Werte dieses globalen ZĂ€hlers gebunden.
Und jetzt erinnern wir uns an dieselbe Standard.lib von CODESYS. Es wurden gerade die nicht blockierenden Timer der IEC implementiert. Ich nahm es als Grundlage und portierte die Funktionen von Timern und Triggern auf den Arduino-Bibliothekscode (C ++). Das heiĂt, versuchte, Arduino nĂ€her an die klassische SPS heranzufĂŒhren.
Im Folgenden werde ich eine kurze Beschreibung der portierten Funktionsblöcke (FB) von CODESYS und ihrer Analoga in meiner
plcStandardLib- Bibliothek geben. Alle
Zeitdiagramme sind fĂŒr die neue Arduino-Bibliothek korrekt. Eine detailliertere Beschreibung der Quellblöcke finden Sie beispielsweise in der russischsprachigen Hilfe zu CODESYS.
TON - Funktionsbaustein "Timer mit Verzögerung ein"
TON(IN, PT, Q, ET)
EingÀnge IN und PT vom Typ BOOL bzw. TIME. Die AusgÀnge Q und ET Àhneln den Typen BOOL und TIME. WÀhrend IN FALSE ist, ist Ausgang Q = FALSE, Ausgang ET = 0. Sobald IN TRUE wird, startet der Countdown (in Millisekunden) am Ausgang ET auf einen Wert gleich PT. Ferner erhöht sich der ZÀhler nicht. Q ist TRUE, wenn IN TRUE und ET PT ist, andernfalls FALSE. Also
somit wird der Ausgang Q mit einer Verzögerung von PT von der Flanke des Eingangs IN gesetzt.
In der Arduino IDE:
Anzeigenoptionen:
TON TON1(); TON TON1(unsigned long PT);
AnwendungsfÀlle:
Q = TON1.Run(boolean IN); // " " TON1.IN = IN; TON1.Run(); Q = TON1.Q;
TON-Betriebszeitdiagramm:
TOF - Funktionsbaustein âTimer mit verzögerter Abschaltungâ
TOF(IN, PT, Q, ET)
EingĂ€nge IN und PT vom Typ BOOL bzw. TIME. Die AusgĂ€nge Q und ET Ă€hneln den Typen BOOL und TIME. Wenn IN TRUE ist, ist der Ausgang Q = TRUE und der Ausgang ET = 0. Sobald IN auf FALSE geht, startet der Countdown (in Millisekunden) am Ausgang ET. Wenn die eingestellte Dauer erreicht ist, stoppt der Countdown. Der Ausgang Q ist FALSE, wenn IN FALSE und ET PT ist, andernfalls TRUE. Somit wird der Ausgang Q mit einer Verzögerung PT von der Abnahme des Eingangs IN zurĂŒckgesetzt.
In der Arduino IDE:
Sehr Àhnlich zu TON, kurz:
TOF TOF1(unsigned long PT);
Zeitdiagramm von TOF:
TP - Impuls-Timer-Funktionsblock
TP(IN, PT, Q, ET)
EingÀnge IN und PT vom Typ BOOL bzw. TIME. Die AusgÀnge Q und ET Àhneln den Typen BOOL und TIME. WÀhrend IN FALSE ist, ist Ausgang Q = FALSE, Ausgang ET = 0. Wenn IN auf TRUE geschaltet wird, wird Ausgang Q auf TRUE gesetzt und der Timer beginnt am Ausgang ET zu zÀhlen (in Millisekunden), bis die von PT angegebene Dauer erreicht ist. Ferner erhöht sich der ZÀhler nicht. Somit erzeugt der Ausgang Q einen Impuls der Dauer PT entlang der Flanke des Eingangs IN.
In der Arduino IDE:
Sehr Àhnlich zu TON, kurz:
TP TP1(unsigned long PT);
TP-Betriebszeitdiagramm:
R_TRIG - Funktionsbaustein "Frontdetektor"
Der Funktionsblock R_TRIG erzeugt einen Impuls bei der ansteigenden Flanke des Eingangssignals. Ausgang Q ist FALSE, solange Eingang CLK FALSE ist. Sobald der CLK TRUE wird, wird Q auf TRUE gesetzt. Beim nĂ€chsten Aufruf des Funktionsbausteins wird der Ausgang auf FALSE zurĂŒckgesetzt. Somit gibt der Block bei jedem CLK-Ăbergang von FALSE nach TRUE einen einzelnen Impuls aus.
Beispiel CODEDESYS in ST:
RTRIGInst : R_TRIG ; RTRIGInst(CLK:= VarBOOL1); VarBOOL2 := RTRIGInst.Q;
In der Arduino IDE:
AnkĂŒndigung:
R_TRIG R_TRIG1;
AnwendungsfÀlle:
Q = R_TRIG1.Run(boolean CLK);
F_TRIG - Funktionseinheit "Ablehnungsdetektor"
Der Funktionsbaustein F_TRIG erzeugt einen Impuls an der Hinterflanke des Eingangssignals.
Ausgang Q ist FALSE, bis Eingang CLK TRUE ist. Sobald der CLK auf FALSE gesetzt ist, wird Q auf TRUE gesetzt. Beim nĂ€chsten Aufruf des Funktionsbausteins wird der Ausgang auf FALSE zurĂŒckgesetzt. Somit gibt der Block bei jedem CLK-Ăbergang von TRUE nach FALSE einen einzelnen Impuls aus.
In der Arduino IDE:
F_TRIG F_TRIG1; Q = F_TRIG1.Run(boolean CLK);
RS_TRIG - Funktionsbaustein RS-Trigger / SR_TRIG - Funktionsbaustein SR-Trigger
Schalten Sie bei ausgeschaltetem Dominanten, RS-Trigger:
Q1 = RS (SET, RESET1)
Schalten Sie mit Dominant ein:
Q1 = SR (SET1, RESET)
Die Eingangsvariablen SET und RESET1 sind die gleichen wie die Ausgangsvariablen Q1 vom Typ BOOL.
In der Arduino IDE:
RS_TRIG RS_TRIG1; Q = RS_TRIG1.Run(boolean SET, boolean RESET); // " "
SR_TRIG SR_TRIG1; Q = SR_TRIG1.Run(boolean SET, boolean RESET); // " "
Quellcode und Beispiel
plcStandardLib_1.h #ifndef PLCSTANDARDLIB_1_H_ #define PLCSTANDARDLIB_1_H_ #if ARDUINO >= 100 #include <Arduino.h> #else #include <WProgram.h> #endif class TON { public: TON(); TON(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class TOF { public: TOF(); TOF(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class TP { public: TP(); TP(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class R_TRIG // { public: R_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class F_TRIG // { public: F_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class RS_TRIG // { public: RS_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; class SR_TRIG // { public: SR_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; #endif
plcStandardLib_1.cpp #include "plcStandardLib_1.h" TON::TON() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TON::TON(unsigned long PT) { IN = false; TON::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TON::Run(boolean IN) { TON::IN = IN; if (!TON::IN) { Q = false; ET = 0; _M = false; } else { if (!_M) { _M = true; // _StartTime = millis(); // ET = 0; // = 0 } else { if (!Q) ET = millis() - _StartTime; // } if (ET >= PT) Q = true; } return Q; } TOF::TOF() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TOF::TOF(unsigned long PT) { IN = false; TOF::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TOF::Run(boolean IN) { TOF::IN = IN; if (TOF::IN) { Q = true; ET = 0; _M = true; } else { if (_M) { _M = false; // _StartTime = millis(); // ET = 0; // = 0 } else { if (Q) ET = millis() - _StartTime; // } if (ET >= PT) Q = false; } return Q; } TP::TP() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TP::TP(unsigned long PT) { IN = false; TP::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TP::Run(boolean IN) { TP::IN = IN; if (!_M) { if (TP::IN) { _M = true; // _StartTime = millis(); if (ET < PT) Q = true; } } else { if (Q) { ET = millis() - _StartTime; // if (ET >= PT) Q = false; } else { if (!TP::IN) { _M = false; ET = 0; } } } return Q; } R_TRIG::R_TRIG() { CLK = false; _M = false; Q = false; } boolean R_TRIG::Run(boolean CLK) { R_TRIG::CLK = CLK; Q = R_TRIG::CLK && !_M; _M = R_TRIG::CLK; return Q; } F_TRIG::F_TRIG() { CLK = false; _M = true; Q = false; } boolean F_TRIG::Run(boolean CLK) { F_TRIG::CLK = CLK; Q = !F_TRIG::CLK && !_M; _M = !F_TRIG::CLK; return Q; } RS_TRIG::RS_TRIG() { SET = false; RESET = false; Q = false; } boolean RS_TRIG::Run(boolean SET, boolean RESET) { RS_TRIG::SET = SET; RS_TRIG::RESET = RESET; Q = !RESET and (SET or Q); return Q; } boolean RS_TRIG::Run() { Q = !RESET and (SET or Q); return Q; } SR_TRIG::SR_TRIG() { SET = false; RESET = false; Q = false; } boolean SR_TRIG::Run(boolean SET, boolean RESET) { SR_TRIG::SET = SET; SR_TRIG::RESET = RESET; Q = SET or (!RESET and Q); return Q; } boolean SR_TRIG::Run() { Q = SET or (!RESET and Q); return Q; }
plcStandardLib_1_example.ino #include "plcStandardLib_1.h" #define LED 13 #define ButtonIn 7 TON TON1(500);
Um beispielsweise das Rattern der Kontakte der Taste zu filtern (auch beim Ăffnen!), Reicht dieser Code aus:
FiltredButtonIn = TON1.Run(digitalRead(ButtonIn))
Fazit: So sieht CODESYS aus wie ein Impulsgenerator, der auf einer Kette von TON- und TP-Timern basiert. Zu Beginn wird TON durch RĂŒckkopplung mit Inversion abgedeckt, und daraus wird ein einzelner Impulsgenerator erzeugt, der den Betrieb des TP-Impulsgenerators startet. In meinem Arduino-Beispiel sieht ein Analogon dazu folgendermaĂen aus:
digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q)))