Pengatur waktu dan pemicu CODESYS. Langkah Arduino lain menuju PLC klasik


Ini terjadi pada pengontrol program (PLC) di lingkungan CODESYS. Setiap orang yang telah berurusan dengan sistem ini tahu bahwa dalam proyek apa pun ada pustaka Standard.lib, di mana pengatur waktu dasar, pemicu, penghitung, dan sejumlah fungsi dan blok fungsi lainnya diimplementasikan. Banyak dari unit ini yang secara konstan digunakan dalam program PLC. Dan perpustakaan itu sendiri, seperti bahasa pemrograman CODESYS, adalah perwujudan dari standar IEC 61131-3, yaitu dirancang untuk membantu pemrograman tugas-tugas PLC klasik.

Salah satu fitur dari program PLC adalah bahwa siklus utama dari program harus dijalankan tanpa penundaan yang signifikan, tidak boleh memiliki siklus internal dengan waktu keluar yang tidak ditentukan atau panggilan sinkron dari fungsi "bijaksana", terutama yang berkaitan dengan komunikasi melalui saluran lambat. Gambar input dan output dari proses diperbarui hanya pada batas loop utama, dan semakin lama kita “duduk” di dalam satu iterasi dari loop, semakin sedikit kita akan tahu tentang keadaan sebenarnya dari objek kontrol, akhirnya pengawas dari loop overflow akan bekerja. Banyak yang mungkin keberatan dengan saya, mengatakan bahwa PLC modern ambigu, ada dukungan untuk gangguan hardware. Saya setuju, tetapi berbicara tentang sistem seperti itu tidak termasuk dalam rencana saya, tetapi saya ingin berbicara tentang (kuasi, pseudo-pilih) sebuah PLC dari implementasi tugas tunggal (tanpa gangguan) berdasarkan pada platform mikroprosesor Arduino, di mana hanya ada satu loop utama. Ngomong-ngomong, tidak akan salah tempat untuk mengatakan bahwa artikel CONTROLLINO PLC yang kompatibel dengan Arduino, bagian 1 tentang upaya untuk mengimplementasikan Arduino dalam perangkat keras mempromosikan saya untuk menulis catatan ini. PLC

Beberapa kata tentang Arduino. Dari sudut pandang programmer PLC, Arduino adalah pengontrol tipikal dengan satu loop yang sangat cepat atau, sebaliknya, loop yang sangat lambat (). Tidak ada batasan pada waktu pelaksanaan siklus, dan dapat bekerja baik satu dan beberapa kali - sesuai dengan rencana programmer. Ketika program ini sederhana dan mengurangi untuk melakukan operasi berurutan, pengontrol, tanpa kejadian paralel, itu cukup untuk operasi bergantian dengan siklus pemeriksaan kondisi bersarang tak berujung dan keterlambatan sinkron dari jenis penundaan (). Langkah-langkah berurutan dari program semacam itu akan dieksekusi secara harfiah baris demi baris, sederhana dan logis. Tetapi, segera setelah kebutuhan untuk pemrograman operasi paralel, perlu untuk mengubah paradigma program.

Dalam sistem tugas tunggal, paralelisme yang terlihat hanya dapat dicapai dengan pemindaian sekuensial status paralel yang sangat cepat, tanpa berlama-lama untuk setiap panggilan fungsi atau pemeriksaan kondisi. Tidak ada masalah dengan input dan output fisik, fungsi bekerja cukup cepat, tetapi delay () menjadi rem yang tidak dapat dibenarkan. Dan di sini timer non-blocking masuk, yang paling klasik dalam pemrograman PLC. Intinya adalah bahwa mereka menggunakan penghitung waktu milidetik, dan semua tindakan terkait dengan nilai-nilai penghitung global ini.

Dan sekarang mari kita ingat Standard.lib yang sama dari CODESYS. Itu hanya menerapkan timer non-blocking IEC. Saya menganggapnya sebagai dasar dan porting fungsi timer dan pemicu ke kode perpustakaan Arduino (C ++). Yaitu mencoba membawa Arduino lebih dekat ke PLC klasik.

Di bawah ini saya akan memberikan deskripsi singkat tentang blok fungsi porting (FB) dari CODESYS dan analog mereka di perpustakaan plcStandardLib saya, semua diagram waktu tepat untuk perpustakaan Arduino baru. Penjelasan lebih rinci tentang blok sumber dapat ditemukan, misalnya, dalam bantuan berbahasa Rusia di CODESYS.

TON - blok fungsi "timer dengan delay on"


TON(IN, PT, Q, ET) 

Input IN dan PT dari masing-masing tipe BOOL dan TIME. Output Q dan ET mirip dengan tipe BOOL dan TIME. Sementara IN adalah FALSE, output Q = FALSE, output ET = 0. Begitu IN menjadi BENAR, hitungan mundur (dalam milidetik) pada output ET dimulai dengan nilai yang sama dengan PT. Selanjutnya, penghitung tidak bertambah. Q BENAR ketika IN BENAR dan ET adalah PT, jika tidak SALAH. Jadi
dengan demikian, output Q diatur dengan penundaan PT dari tepi input IN.

Dalam Arduino IDE:


Opsi Iklan:

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

Gunakan kasing:

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

Diagram Waktu Operasi TON:


TOF - blok fungsi "timer dengan shutdown tertunda"


 TOF(IN, PT, Q, ET) 

Input IN dan PT dari masing-masing tipe BOOL dan TIME. Output Q dan ET mirip dengan tipe BOOL dan TIME. Jika IN adalah BENAR, maka outputnya adalah Q = BENAR dan outputnya adalah ET = 0. Begitu IN masuk ke FALSE, hitungan mundur (dalam milidetik) dari output ET dimulai. Ketika durasi yang ditetapkan tercapai, hitungan mundur berhenti. Output Q adalah FALSE jika IN adalah FALSE dan ET adalah PT, jika tidak BENAR. Dengan demikian, output Q diatur ulang dengan penundaan PT dari penurunan input IN.

Dalam Arduino IDE:


Sangat mirip dengan TON, singkatnya:

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

Diagram waktu TOF:


TP - blok fungsi impuls-waktu


 TP(IN, PT, Q, ET) 

Input IN dan PT dari masing-masing tipe BOOL dan TIME. Output Q dan ET mirip dengan tipe BOOL dan TIME. Ketika IN adalah FALSE, output Q = FALSE, output ET = 0. Ketika IN beralih ke TRUE, output Q diatur ke TRUE dan timer mulai menghitung (dalam milidetik) pada output ET sampai durasi yang ditentukan oleh PT tercapai. Selanjutnya, penghitung tidak bertambah. Dengan demikian, output Q menghasilkan pulsa berdurasi PT di sepanjang tepi input IN.

Dalam Arduino IDE:


Sangat mirip dengan TON, singkatnya:

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

Bagan waktu operasi TP:


R_TRIG - blok fungsi "detektor depan"


Blok fungsi R_TRIG menghasilkan pulsa di tepi naik dari sinyal input. Output Q FALSE selama input CLK FALSE. Segera setelah CLK mendapatkan TRUE, Q diatur ke TRUE. Kali berikutnya blok fungsi dipanggil, output di-reset ke FALSE. Dengan demikian, blok memberikan impuls tunggal pada setiap transisi CLK dari FALSE ke TRUE.

Contoh CODEDESYS di ST:

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

Dalam Arduino IDE:


Pengumuman:

 R_TRIG R_TRIG1; 

Gunakan kasing:

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

F_TRIG - unit fungsi "tolak detektor"


Blok fungsi F_TRIG menghasilkan pulsa di tepi trailing dari sinyal input.
Output Q adalah SALAH sampai input CLK BENAR. Segera setelah CLK diatur ke FALSE, Q diatur ke TRUE. Kali berikutnya blok fungsi dipanggil, output di-reset ke FALSE. Dengan demikian, blok memberikan impuls tunggal pada setiap transisi CLK dari TRUE ke FALSE.

Dalam Arduino IDE:


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

RS_TRIG - blok fungsi RS-trigger / SR_TRIG - blok fungsi SR-trigger


Beralih dengan sakelar dominan yang dimatikan, pemicu RS:

 Q1 = RS (SET, RESET1) 

Beralih dengan dominan pada:

 Q1 = SR (SET1, RESET) 

Variabel input SET dan RESET1 sama dengan variabel output Q1 dari tipe BOOL.

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

Kode Sumber dan Contoh


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)))); //     } 


Misalnya, untuk memfilter obrolan kontak tombol (saat membuka juga!), Kode ini cukup:

 FiltredButtonIn = TON1.Run(digitalRead(ButtonIn)) 

Kesimpulannya: ini adalah bagaimana CODESYS terlihat seperti generator pulsa berdasarkan rantai timer TON dan TP. Pada awalnya, TON ditutupi oleh umpan balik dengan inversi, dan darinya generator pulsa tunggal dihasilkan, yang memulai operasi generator pulsa TP. Dalam contoh Arduino saya, analog dari ini terlihat seperti ini:

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

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


All Articles