يحدث ذلك لوحدات تحكم البرنامج (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);
حالات الاستخدام:
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);
مخطط توقيت 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);
مخطط توقيت تشغيل 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);
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 #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);
على سبيل المثال ، لتصفية ثرثرة جهات اتصال الزر (عند الفتح أيضًا!) ، هذا الرمز يكفي:
FiltredButtonIn = TON1.Run(digitalRead(ButtonIn))
في الختام: هذه هي الكيفية التي تبدو بها CODESYS كمولد نبضي يعتمد على سلسلة من أجهزة ضبط الوقت TON و TP. في البداية ، يتم تغطية TON من خلال التغذية المرتدة بالعكس ، ومنه يتم إنشاء مولد نبض واحد ، والذي يبدأ تشغيل مولد النبض TP. في مثال اردوينو الخاص بي ، يبدو هذا التناظرية مثل هذا:
digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q)))