يحدث ذلك لوحدات تحكم البرنامج (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)))