الموقتات والمشغلات CODESYS. خطوة أخرى من Arduino نحو PLC الكلاسيكي


يحدث ذلك لوحدات تحكم البرنامج (PLCs) في بيئة CODESYS. يعرف كل من تعامل مع هذا النظام أنه في أي مشروع توجد مكتبة Standard.lib ، يتم فيها تنفيذ الموقتات ، والمحفزات ، والعدادات ، وعدد من الوظائف والكتل الأخرى. يتم استخدام العديد من هذه الوحدات باستمرار في برامج PLC. والمكتبة نفسها ، مثل لغات برمجة CODESYS ، هي تجسيد لمعيار IEC 61131-3 ، أي مصمم للمساعدة في برمجة مهام PLC الكلاسيكية.

تتمثل إحدى ميزات برامج PLC في أنه يجب تشغيل الدورة الرئيسية للبرنامج دون تأخير كبير ، ويجب ألا يكون لها دورات داخلية بوقت خروج غير محدد أو مكالمات متزامنة للوظائف "المدروسة" ، خاصة فيما يتعلق بالاتصالات عبر القنوات البطيئة. يتم تحديث صور المدخلات والمخرجات للعملية فقط عند حدود الحلقة الرئيسية ، وكلما "جلسنا" لفترة أطول داخل تكرار واحد من الحلقة ، كلما قل معرفتنا بالحالة الحقيقية لكائن التحكم ، وفي النهاية ستعمل المراقبة على تجاوز الحلقة. قد يعترض الكثير عليّ ، قائلين إن PLC الحديثة غامضة ، وهناك دعم لمقاطعات الأجهزة. أوافق ، لكن الحديث عن مثل هذه الأنظمة غير مدرج في خططي ، لكنني أريد أن أتحدث عن (شبه ، اختيار زائف) PLC لتنفيذ مهمة واحدة (بدون مقاطعات) استنادًا إلى منصة المعالج الصغري Arduino ، حيث يوجد حلقة رئيسية واحدة فقط. بالمناسبة ، لن يكون من غير المناسب أن نقول أن مقالة PLC CONTROLLINO المتوافقة مع Arduino ، الجزء الأول حول محاولة تنفيذ Arduino في الأجهزة قد شجعتني على كتابة هذه المذكرة. PLC

بضع كلمات عن اردوينو. من وجهة نظر مبرمج PLC ، Arduino هي وحدة تحكم نموذجية مع دورة حلقة واحدة سريعة جدًا أو ، على العكس ، بطيئة جدًا (). لا توجد قيود على وقت تنفيذ الدورة ، ويمكن أن تعمل مرة واحدة وعدد لا نهائي من المرات - وفقًا لنية المبرمج. عندما يكون البرنامج بسيطًا ويقلل من أداء العمليات المتسلسلة ، وحدات التحكم ، بدون أحداث متوازية ، يكفي استبدال العمليات بدورات فحص حالة متداخلة لا نهاية لها وتأخير متزامن لتأخير النوع (). سيتم تنفيذ الخطوات المتتالية لمثل هذا البرنامج حرفيا سطرا بسطر ، ببساطة ومنطقية. ولكن بمجرد ظهور الحاجة لبرمجة العمليات المتوازية ، من الضروري تغيير نموذج البرنامج.

في نظام المهام الفردية ، لا يمكن تحقيق التوازي المرئي إلا من خلال مسح متسلسل سريع جدًا للحالات المتوازية ، دون البقاء لفترة طويلة في كل استدعاء دالة أو فحص حالة. لا توجد مشاكل مع المدخلات والمخرجات المادية ، وتعمل الوظائف بسرعة كافية ، ولكن التأخير () يصبح مكابح غير مبررة. وهنا تأتي الموقتات غير المحظورة ، وهي تلك الكلاسيكية في برمجة PLC. خلاصة القول هي أنهم يستخدمون عداد الوقت بالمللي ثانية ، وترتبط جميع الإجراءات بقيم هذا العداد العالمي.

والآن دعنا نتذكر نفس Standard.lib من CODESYS. لقد قامت بتطبيق مؤقتات IEC-s غير المحجوبة. لقد أخذت ذلك كأساس وقمت بنقل وظائف المؤقتات والمشغلات إلى رمز مكتبة Arduino (C ++). على سبيل المثال حاول تقريب اردوينو من المجلس التشريعي الكلاسيكي.

أدناه سأقدم وصفًا موجزًا ​​لمجموعات الوظائف المنقولة (FB) لـ CODESYS ونظائرها في مكتبة plcStandardLib ، جميع الرسوم البيانية للتوقيت صحيحة لمكتبة Arduino الجديدة. يمكن العثور على وصف أكثر تفصيلاً للكتل المصدر ، على سبيل المثال ، في مساعدة اللغة الروسية على CODESYS.

TON - كتلة الوظائف "مؤقت مع تأخير"


TON(IN, PT, Q, ET) 

المدخلات IN و PT لأنواع BOOL و TIME على التوالي. المخرجات Q و ET متشابهة لأنواع BOOL و TIME. بينما IN هي FALSE ، الإخراج Q = FALSE ، الإخراج ET = 0. بمجرد أن يصبح IN TRUE ، يبدأ العد التنازلي (بالمللي ثانية) على الإخراج ET بقيمة تساوي PT. علاوة على ذلك ، لا يزيد العداد. Q هي TRUE عندما تكون IN TRUE و ET هي PT ، وإلا فإن FALSE. لذا
وبالتالي ، يتم ضبط الإخراج Q بتأخير PT من حافة الإدخال IN.

في Arduino IDE:


خيارات الإعلان:

 TON TON1(); TON TON1(unsigned long PT); //     PT 

حالات الاستخدام:

 Q = TON1.Run(boolean IN); //  "  " TON1.IN = IN; TON1.Run(); Q = TON1.Q; 

مخطط توقيت تشغيل TON:


TOF - فدرة الوظائف "مؤقت مع إيقاف التشغيل المتأخر"


 TOF(IN, PT, Q, ET) 

المدخلات IN و PT لأنواع BOOL و TIME على التوالي. المخرجات Q و ET متشابهة لأنواع BOOL و TIME. إذا كانت IN تساوي TRUE ، فإن الناتج هو Q = TRUE والإخراج ET = 0. وبمجرد انتقال IN إلى FALSE ، يبدأ العد التنازلي (بالمللي ثانية) للإخراج ET. عند بلوغ المدة المحددة ، يتوقف العد التنازلي. الناتج Q هو FALSE إذا كان IN هو FALSE و ET هو PT ، وإلا فإن TRUE. وبالتالي ، تتم إعادة ضبط خرج Q مع تأخير PT من انخفاض دخل IN.

في Arduino IDE:


تشبه إلى حد كبير TON ، باختصار:

 TOF TOF1(unsigned long PT); //     PT Q = TOF1.Run(boolean IN); //  "  " 

مخطط توقيت TOF:


TP - فدرة وظيفة مؤقت الدفع


 TP(IN, PT, Q, ET) 

المدخلات IN و PT لأنواع BOOL و TIME على التوالي. المخرجات Q و ET متشابهة لأنواع BOOL و TIME. بينما IN هو FALSE ، الإخراج Q = FALSE ، الإخراج ET = 0. عندما يتم تبديل IN إلى TRUE ، يتم تعيين الإخراج Q على TRUE ويبدأ المؤقت بالعد (بالمللي ثانية) على الإخراج ET حتى يتم الوصول إلى المدة المحددة بواسطة PT. علاوة على ذلك ، لا يزيد العداد. وبالتالي ، يولد الإخراج Q نبضة مدة PT على طول حافة الإدخال IN.

في Arduino IDE:


تشبه إلى حد كبير TON ، باختصار:

 TP TP1(unsigned long PT); //     PT Q = TP1.Run(boolean IN); //  "  " 

مخطط توقيت تشغيل TP:


R_TRIG - فدرة الوظائف "كاشف أمامي"


تولد كتلة وظيفة R_TRIG نبضة على الحافة المرتفعة لإشارة الإدخال. الناتج Q هو FALSE طالما أن الإدخال CLK هو FALSE. بمجرد حصول CLK على TRUE ، يتم تعيين Q على TRUE. في المرة التالية التي يتم فيها استدعاء كتلة الوظائف ، تتم إعادة تعيين الإخراج إلى FALSE. وبالتالي ، تعطي الكتلة دفعة واحدة عند كل انتقال CLK من FALSE إلى TRUE.

مثال CODIDEYS في ST:

 RTRIGInst : R_TRIG ; RTRIGInst(CLK:= VarBOOL1); VarBOOL2 := RTRIGInst.Q; 

في Arduino IDE:


إعلان:

 R_TRIG R_TRIG1; 

حالات الاستخدام:

 Q = R_TRIG1.Run(boolean CLK); //  "  " R_TRIG1.CLK = CLK; R_TRIG1.Run(); Q = R_TRIG1.Q; 

F_TRIG - الوحدة الوظيفية "كاشف الرفض"


تولد فدرة الوظائف F_TRIG نبضة على الحافة اللاحقة لإشارة الإدخال.
الإخراج Q هو FALSE حتى إدخال CLK TRUE. بمجرد تعيين CLK على FALSE ، يتم تعيين Q على TRUE. في المرة التالية التي يتم فيها استدعاء كتلة الوظائف ، تتم إعادة تعيين الإخراج إلى FALSE. وبالتالي ، تعطي الكتلة دفعة واحدة عند كل انتقال CLK من TRUE إلى FALSE.

في Arduino IDE:


 F_TRIG F_TRIG1; Q = F_TRIG1.Run(boolean CLK); //  "  " 

RS_TRIG - كتلة الوظائف RS- الزناد / SR_TRIG - كتلة الوظائف SR-الزناد


التبديل مع إيقاف تشغيل المسيطر ، الزناد RS:

 Q1 = RS (SET, RESET1) 

التبديل مع المسيطر على:

 Q1 = SR (SET1, RESET) 

متغيرات الإدخال SET و RESET1 هي نفس متغير الإخراج Q1 من نوع BOOL.

في 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); //  "  " 

كود المصدر والمثال


plcStandardLib_1.h
 /* * plcStandardLib_1.h * * Created on: 01.01.2017 * Author: Admin */ #ifndef PLCSTANDARDLIB_1_H_ #define PLCSTANDARDLIB_1_H_ #if ARDUINO >= 100 #include <Arduino.h> #else #include <WProgram.h> #endif /* ------------------- TON ------------------- */ 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; }; /* ------------------- TOF ------------------- */ 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; }; /* ------------------- TP ------------------- */ 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; }; /* ------------------- R_TRIG ------------------- */ class R_TRIG //    { public: R_TRIG(); boolean Run(boolean CLK); boolean CLK; //   boolean Q; //   private: boolean _M; //   }; /* ------------------- F_TRIG ------------------- */ class F_TRIG //    { public: F_TRIG(); boolean Run(boolean CLK); boolean CLK; //   boolean Q; //   private: boolean _M; //   }; /* ------------------- RS_TRIG ------------------- */ class RS_TRIG //    { public: RS_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; //   boolean RESET; //   boolean Q; //   //private: }; /* ------------------- SR_TRIG ------------------- */ class SR_TRIG //    { public: SR_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; //   boolean RESET; //   boolean Q; //   //private: }; #endif /* PLCSTANDARDLIB_H_ */ 


plcStandardLib_1.cpp
 /* * plcStandardLib_1.h * * Created on: 01.01.2017 * Author: Admin */ #include "plcStandardLib_1.h" /* ------------------- TON ------------------- */ 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::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::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::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::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::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); //   , 500. TON TON2(1000); //   , 1000. TOF TOF1(500); //   , 500. TP TP1(300); //   , 300. TP TP2(200); //   , 200. R_TRIG R_TRIG1; //      void setup() { pinMode(ButtonIn, INPUT_PULLUP); pinMode(LED, OUTPUT); } void loop() { digitalWrite(LED, TP1.Run(R_TRIG1.Run(TON1.Run(digitalRead(ButtonIn))))); // TON1 -    // R_TRIG1 -    // TP1 -     digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q))); //     TON  TP // TON2.Run(!TON2.Q)) -    // TP2 -     digitalWrite(LED, TOF1.Run(TON1.Run(digitalRead(ButtonIn)))); //     } 


على سبيل المثال ، لتصفية ثرثرة جهات اتصال الزر (عند الفتح أيضًا!) ، هذا الرمز يكفي:

 FiltredButtonIn = TON1.Run(digitalRead(ButtonIn)) 

في الختام: هذه هي الكيفية التي تبدو بها CODESYS كمولد نبضي يعتمد على سلسلة من أجهزة ضبط الوقت TON و TP. في البداية ، يتم تغطية TON من خلال التغذية المرتدة بالعكس ، ومنه يتم إنشاء مولد نبض واحد ، والذي يبدأ تشغيل مولد النبض TP. في مثال اردوينو الخاص بي ، يبدو هذا التناظرية مثل هذا:

 digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q))); 

Source: https://habr.com/ru/post/ar402315/


All Articles