Cela arrive aux automates programmables (API) dans un environnement CODESYS. Tous ceux qui ont traité ce système savent que dans n'importe quel projet, il existe une bibliothèque Standard.lib, dans laquelle les temporisateurs, déclencheurs, compteurs de base et un certain nombre d'autres fonctions et blocs fonctionnels sont implémentés. Beaucoup de ces unités sont constamment utilisées dans les programmes PLC. Et la bibliothèque elle-même, comme les langages de programmation CODESYS, est un mode de réalisation de la norme CEI 61131-3, c'est-à-dire Conçu pour aider à la programmation des tâches API classiques.
L'une des caractéristiques des programmes PLC est que le cycle principal du programme doit être exécuté sans retards importants, il ne doit pas avoir de cycles internes avec un temps de sortie indéfini ou d'appels synchrones de fonctions «réfléchies», en particulier en ce qui concerne les communications sur des canaux lents. Les images d'entrée et de sortie du processus ne sont mises à jour qu'à la limite de la boucle principale, et plus nous nous «asseyons» longtemps à l'intérieur d'une itération de la boucle, moins nous en saurons sur l'état réel de l'objet de contrôle, éventuellement le chien de garde du débordement de la boucle fonctionnera. Beaucoup peuvent s'opposer à moi, disant que les automates modernes sont ambigus, il y a un support pour les interruptions matérielles. Je suis d'accord, mais parler de tels systèmes n'est pas inclus dans mes plans, mais je veux parler (quasi, pseudo-choisir) d'un PLC d'une implémentation à tâche unique (sans interruption) basée sur la plate-forme de microprocesseur Arduino, dans laquelle il n'y a qu'une seule boucle principale. Soit dit en passant, il ne sera pas hors de propos de dire que l'article
CONTROLLINO PLC compatible Arduino, partie 1 sur la tentative d'implémentation d'Arduino dans le matériel m'a encouragé à écrire cette note. PLC
Quelques mots sur Arduino. Du point de vue du programmeur PLC, Arduino est un contrôleur typique avec une boucle très rapide ou, inversement, une boucle très lente (). Il n'y a aucune restriction sur le temps d'exécution du cycle, et il peut fonctionner à la fois une et un nombre infini de fois - selon le plan du programmeur. Lorsque le programme est simple et réduit à l'exécution d'opérations séquentielles, contrôleurs, sans événements parallèles, il suffit d'alterner les opérations avec des cycles de vérification de condition imbriqués sans fin et des retards synchrones de type retard (). Les étapes séquentielles d'un tel programme seront exécutées littéralement ligne par ligne, simplement et logiquement. Mais, dès que le besoin se fait sentir de programmer des opérations parallèles, il faut changer le paradigme du programme.
Dans un système à tâche unique, le parallélisme visible ne peut être atteint que par un balayage séquentiel très rapide des états parallèles, sans s'attarder longtemps sur chaque appel de fonction ou vérification de condition. Il n'y a aucun problème avec les entrées et sorties physiques, les fonctions fonctionnent assez rapidement, mais delay () devient un frein injustifié. Et ici les temporisateurs non bloquants entrent en jeu, ceux-là mêmes qui sont des classiques dans la programmation PLC. L'essentiel est qu'ils utilisent un compteur de temps en millisecondes, et toutes les actions sont liées aux valeurs de ce compteur global.
Et maintenant, rappelons-nous ce même Standard.lib de CODESYS. Il vient de mettre en œuvre les temporisateurs non bloquants de la CEI. Je l'ai pris comme base et j'ai porté les fonctions des minuteries et des déclencheurs sur le code de la bibliothèque Arduino (C ++). C'est-à-dire essayé de rapprocher Arduino de l'API classique.
Ci-dessous, je vais donner une brève description des blocs fonctionnels portés (FB) de CODESYS et de leurs analogues dans ma bibliothèque
plcStandardLib , tous les chronogrammes sont corrects pour la nouvelle bibliothèque Arduino. Une description plus détaillée des blocs sources peut être trouvée, par exemple, dans l'aide en russe sur CODESYS.
TON - bloc fonction "temporisateur avec retard activé"
TON(IN, PT, Q, ET)
Entrées IN et PT de types BOOL et TIME respectivement. Les sorties Q et ET sont similaires aux types BOOL et TIME. Alors que IN est FAUX, la sortie Q = FAUX, la sortie ET = 0. Dès que IN devient VRAI, le compte à rebours (en millisecondes) sur la sortie ET démarre jusqu'à une valeur égale à PT. De plus, le compteur n'augmente pas. Q est VRAI lorsque IN est VRAI et ET est PT, sinon FAUX. Alors
ainsi, la sortie Q est réglée avec un retard de PT à partir du bord de l'entrée IN.
Dans l'IDE Arduino:
Options d'annonces:
TON TON1(); TON TON1(unsigned long PT);
Cas d'utilisation:
Q = TON1.Run(boolean IN); // " " TON1.IN = IN; TON1.Run(); Q = TON1.Q;
Diagramme de synchronisation des opérations TON:
TOF - bloc fonctionnel «minuterie avec arrêt différé»
TOF(IN, PT, Q, ET)
Entrées IN et PT de types BOOL et TIME respectivement. Les sorties Q et ET sont similaires aux types BOOL et TIME. Si IN est VRAI, alors la sortie Q = VRAI et la sortie ET = 0. Dès que IN passe à FAUX, le compte à rebours (en millisecondes) à la sortie ET démarre. Lorsque la durée définie est atteinte, le compte à rebours s'arrête. La sortie Q est FAUX si IN est FAUX et ET est PT, sinon VRAI. Ainsi, la sortie Q est réinitialisée avec un retard PT à partir du déclin de l'entrée IN.
Dans l'IDE Arduino:
Très similaire à TON, pour faire court:
TOF TOF1(unsigned long PT);
Diagramme de temps de TOF:
TP - bloc fonction temporisateur d'impulsion
TP(IN, PT, Q, ET)
Entrées IN et PT de types BOOL et TIME respectivement. Les sorties Q et ET sont similaires aux types BOOL et TIME. Alors que IN est FAUX, la sortie Q = FAUX, la sortie ET = 0. Lorsque IN est réglé sur VRAI, la sortie Q est définie sur VRAI et le temporisateur commence à compter (en millisecondes) sur la sortie ET jusqu'à ce que la durée spécifiée par PT soit atteinte. De plus, le compteur n'augmente pas. Ainsi, la sortie Q génère une impulsion de durée PT le long du bord de l'entrée IN.
Dans l'IDE Arduino:
Très similaire à TON, pour faire court:
TP TP1(unsigned long PT);
Chronogramme des opérations TP:
R_TRIG - bloc fonction "détecteur avant"
Le bloc fonction R_TRIG génère une impulsion sur le front montant du signal d'entrée. La sortie Q est FAUX tant que l'entrée CLK est FAUX. Dès que le CLK devient VRAI, Q est défini sur VRAI. Lors du prochain appel du bloc fonction, la sortie est réinitialisée sur FAUX. Ainsi, le bloc émet une seule impulsion à chaque transition CLK de FALSE à TRUE.
Exemple CODEDESYS en ST:
RTRIGInst : R_TRIG ; RTRIGInst(CLK:= VarBOOL1); VarBOOL2 := RTRIGInst.Q;
Dans l'IDE Arduino:
Annonce:
R_TRIG R_TRIG1;
Cas d'utilisation:
Q = R_TRIG1.Run(boolean CLK);
F_TRIG - unité de fonction "détecteur de baisse"
Le bloc fonctionnel F_TRIG génère une impulsion sur le front arrière du signal d'entrée.
La sortie Q est FALSE jusqu'à ce que l'entrée CLK soit TRUE. Dès que CLK est défini sur FALSE, Q est défini sur TRUE. Lors du prochain appel du bloc fonction, la sortie est réinitialisée sur FAUX. Ainsi, le bloc émet une seule impulsion à chaque transition CLK de TRUE à FALSE.
Dans l'IDE Arduino:
F_TRIG F_TRIG1; Q = F_TRIG1.Run(boolean CLK);
RS_TRIG - bloc fonctionnel RS-trigger / SR_TRIG - bloc fonctionnel SR-trigger
Interrupteur avec l'interrupteur dominant désactivé, RS-trigger:
Q1 = RS (SET, RESET1)
Commutateur avec dominante sur:
Q1 = SR (SET1, RESET)
Les variables d'entrée SET et RESET1 sont identiques à la variable de sortie Q1 de type BOOL.
Dans l'IDE Arduino:
RS_TRIG RS_TRIG1; Q = RS_TRIG1.Run(boolean SET, boolean RESET); // " "
SR_TRIG SR_TRIG1; Q = SR_TRIG1.Run(boolean SET, boolean RESET); // " "
Code source et exemple
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);
Par exemple, pour filtrer le bavardage des contacts du bouton (lors de l'ouverture aussi!), Ce code suffit:
FiltredButtonIn = TON1.Run(digitalRead(ButtonIn))
En conclusion: voici à quoi ressemble CODESYS comme un générateur d'impulsions basé sur une chaîne de temporisateurs TON et TP. Au début, TON est couvert par une rétroaction avec inversion, et à partir de lui un générateur d'impulsions unique est généré, qui démarre le fonctionnement du générateur d'impulsions TP. Dans mon exemple Arduino, un analogue de ceci ressemble à ceci:
digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q)))