Minuterie de fonction pour contrôleur industriel Simatic S7-1200

Même pour les séries S7-300 et S7-400, à l'étape 7, les versions classiques des temporisateurs proposés au développeur étaient suffisantes - ce sont des temporisateurs CEI standard, mis en œuvre en tant que blocs fonctionnels, et des temporisateurs S5 (qui, soit dit en passant, existent toujours pour la série S7- 1500). Cependant, dans certains cas, le développeur n'a pas utilisé d'outils standard et a implémenté ses propres temporisateurs, le plus souvent sous forme de fonctions. De telles fonctions de temporisation étaient nécessaires avec une approche «informatique» de la programmation, dans laquelle elles fonctionnaient non pas avec des instances distinctes des blocs fonctionnels de l'équipement technologique, avec la liaison correspondante des entrées et des sorties, mais avec des tableaux de structures. Par exemple, un tableau d'une structure de type d'entrée discrète. Ou un tableau d'une structure agrégée. Cette approche de la programmation a le droit d'exister, car elle vous permet d'économiser sérieusement la mémoire de travail du CPU, mais,d'autre part, rend le code du programme difficile à lire. Un programmeur tiers et avec un simple regard sur un programme CONT peut difficilement le comprendre tout de suite, mais des tas d'indices, de tableaux et de fonctions pour les traiter sont hors de question; ici, sans documentation pour le logiciel (et sans un demi-litre, bien sûr), nulle part.

Ces tableaux de structures étaient généralement traités dans des fonctions. En principe, rien n'empêchait le traitement des blocs fonctionnels, mais il y avait toujours une question importante - comment travailler avec des temporisateurs dans ces cas? Les temporisateurs standard supposent soit un nombre (S5) soit une instance d'un bloc fonctionnel (CEI). Je vous rappelle qu'il s'agit de traiter des tableaux de structures pour les automates Simatic classiques et de «tordre» les nombres de temporisations dans ces structures, et plus encore, les instances sont soit difficiles, soit tout simplement impossibles.

Pour cette raison, nous avons créé notre propre fonction de minuterie en tant que fonction. En principe, pour le fonctionnement de n'importe quel minuteur, vous devez connaître seulement quelques éléments - l'état de l'entrée, le réglage de l'heure et le temps écoulé depuis l'activation.

Pour les séries 300 et 400, il y avait deux façons de déterminer cette heure. La première consiste à examiner le temps d'exécution de l'OB1 principal (il existe une variable correspondante dans l'OB1 lui-même) ou des OB cycliques et d'augmenter l'accumulateur de temps interne à chaque appel de temporisateur, à condition que la «vérité» soit entrée. Pas une bonne option, car ce temps est différent pour l'OB1 et les OB cycliques. La deuxième méthode est la fonction système TIME_TCK, qui, à chaque appel, renvoyait une valeur unique - le compteur interne en millisecondes du processeur central.

image

Ainsi, pour un temporisateur de type TON (on delay), l'algorithme de fonctionnement était le suivant:

  • sur le front montant de la demande de réponse, réinitialisez la sortie et mémorisez la valeur actuelle du temporisateur système TIME_TCK
  • «» , ( , TIME_TCK 0 (2 ^ 31 — 1), ). , . , «», — «»
  • «»,

Avec l'avènement de la "millième" série, la situation a un peu changé. Le fait est que la ligne S7-1500 a hérité de la prise en charge de l'appel système TIME_TCK et des amateurs de l'approche «debout et dans un hamac» (comment pouvez-vous appeler un programme qui traite uniquement des tableaux de structures tout en fonctionnant avec des index effrayants?) calmement continuer à utiliser leurs meilleures pratiques.

La série de contrôleurs de base S7-1200 est basée sur une architecture différente et présente un certain nombre de différences par rapport au S7-1500. Y compris l'absence d'un appel système TIME_TCK. Dans les rangs des développeurs qui n'ont pas une flexibilité de pensée suffisante, l'insatisfaction a disparu - il est impossible d'exécuter des copies / pâtes d'anciens programmes. Cependant, la tâche de déterminer combien de temps s'est écoulé depuis l'appel précédent peut être effectuée à l'aide de la fonction d' exécution.

Cette fonction renvoie le temps écoulé depuis son appel précédent, en secondes, sous la forme d'un nombre réel double précision LREAL. Les détails sont décrits dans l'aide. À des fins internes, une variable MEM supplémentaire (également de type LREAL) est requise.

Je donnerai les sources de la première approximation de la fonction, et je donnerai quelques notes.

Déclaration de fonction:

FUNCTION "PerversionTON" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      IN : Bool;   //  
      PT : Real;   //    
   END_VAR

   VAR_OUTPUT 
      Q : Bool;   //  
   END_VAR

   VAR_IN_OUT 
      INPrv : Bool;
      MEM : LReal;
      TimeACC : UDInt;
   END_VAR

   VAR_TEMP 
      udiCycle : UDInt;
      udiPT : UDInt;
   END_VAR

Avec les entrées / sorties, tout est clair: IN, Q et PT. J'ai mis le réglage de l'heure sous la forme d'un vrai, c'est des secondes. Je voulais juste (mais en vain, mais plus sur cela ci-dessous). Plus d'informations sur les variables de la zone InOut. Étant donné que nous avons une fonction, nous n'avons pas de zone STAT, aucune variable ne conserve sa valeur lors du prochain appel de fonction, et de telles variables sont nécessaires:

INPrv - pour déterminer le bord positif de la demande

MEM - variable auxiliaire pour l'appel système au travail runtime

TimeACC - accumulateur de temps , qui stockera le nombre de microsecondes du retard en cours d'exécution.

Les variables TimeACC, udiCycle et udiPT sont spécifiées au format UDINT, un entier non signé, 4 octets. Malgré le fait que j'ai spécifié l'heure comme réelle et que la fonction d'exécution renvoie autant que la double précision, je préfère effectuer des opérations simples de sommation et de comparaison avec des opérandes entiers pour gagner du temps sur le processeur. Le temps dans mon cas est pris en compte à la microseconde près. La raison est simple - si vous grossissez le temps en millisecondes, puis avec un OB1 presque vide (par exemple, si un seul temporisateur est appelé dans tout le programme du contrôleur et rien de plus), des «sauts» de cycles sont possibles, le programme s'exécute parfois pendant 250 μs. Mais dans ce cas, la valeur maximale autorisée de l'accumulateur de temps sera de 4 294 secondes, soit près de 4 295 (2 ^ 32 - 1 = 4 294 967 295). Il n'y a rien à faire, une telle «optimisation» nécessite des sacrifices.

Texte de fonction.

#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
#udiPT := REAL_TO_UDINT(#PT * 1000000); //   

IF (#IN AND (NOT #INPrv)) THEN //         
    #TimeACC := 0;
    #Q := FALSE;
ELSIF (#IN AND #INPrv) THEN //     ""
    #TimeACC += #udiCycle; //     "    "
    IF #TimeACC >=  #udiPT THEN //      
        #Q := TRUE; //  ""
        #TimeACC := #udiPT; //  
    ELSE //      
        #Q := FALSE; // 
    END_IF;
ELSE //    -      
    #Q := FALSE;
    #TimeACC := 0;
END_IF;

#INPrv := #IN; //  

ENO := #Q; // ENO         LAD  FBD

Les deux premières lignes sont le recalcul du réglage de la minuterie du nombre de secondes spécifié dans le format REAL au nombre de microsecondes. Le temps en microsecondes écoulé depuis l'appel de bloc de programme précédent est également déterminé.

De plus, l'algorithme est le suivant, et je l'ai déjà donné:

  • sur le front montant de l'entrée IN, réinitialiser la sortie Q et réinitialiser l'accumulateur de temps
  • si la «vérité» continue d'être saisie, nous augmentons l'accumulateur de temps de la valeur udiCycle déjà connue et le comparons avec le réglage de l'heure. Si le réglage de l'heure est dépassé, la minuterie a fonctionné, donnez la sortie «vrai», sinon, donnez la sortie «faux»
  • en cas d'application d'une fausse entrée à l'entrée IN, réinitialiser la sortie Q et réinitialiser l'accumulateur de temps.

A la fin de la fonction, afin de déterminer le bord de l'entrée IN, rappelez-vous sa valeur précédente. Donnez également à la sortie ENO (lors de l'utilisation d'une fonction dans des langages graphiques, comme LAD) la valeur de la sortie Q.

Nous nous assurons que la fonction fonctionne, après quoi il devient intéressant d'évaluer sa vitesse et, si nécessaire, de l'améliorer (il apparaît déjà à première vue qu'un certain nombre de calculs sont effectués inactif et perdre du temps CPU en vain). Pour évaluer les performances, je déclare un tableau de 1000 structures de données de temporisation.

Déclaration de la structure. Ses champs dupliquent les variables d'entrée et de sortie de la fonction timer.

TYPE "typePervTONdata"
VERSION : 0.1
   STRUCT
      IN : Bool;   //  
      PT : Real;   //   
      Q : Bool;   //  
      INPrv : Bool;   //    
      MEM : LReal;   //    
      TimeACC : UDInt;   //  
   END_STRUCT;

END_TYPE

Un tableau de structures est déclaré dans le bloc de données global TortureTON:

TONs : Array[0..999] of "typePervTONdata";

Le code suivant est exécuté dans le bloc d'organisation OB1:

FOR #i := 0 TO 999 DO
    "TortureTON".TONs[#i].IN := "startton";
    "PerversionTON"(IN := "TortureTON".TONs[#i].IN,
                    PT := "TortureTON".TONs[#i].PT,
                    Q := "TortureTON".TONs[#i].Q,
                    INPrv := "TortureTON".TONs[#i].INPrv,
                    MEM := "TortureTON".TONs[#i].MEM,
                    TimeACC := "TortureTON".TONs[#i].TimeACC);
END_FOR;

Annonce de 1 000 «instances» de minuteries, chacune fixant un temps de 10 secondes. Tous les 1000 temporisateurs commencent à compter le temps par la valeur de la variable de marqueur de startton.

Je lance les fonctions de diagnostic du contrôleur (S7-1214C DC / DC / DC, version FW 4.4, version Step7 - V16) et je regarde le temps de cycle de balayage du contrôleur. À «inactif» (lorsque «faux» arrive à l'entrée des temporisateurs), le millier entier est traité en moyenne pendant 36 à 42 millisecondes. Pendant le décompte de dix secondes, cette lecture augmente d'environ 6 à 8 millisecondes et se prolonge parfois pendant 50 ms.

Nous regardons ce qui peut être amélioré dans le code de fonction. Tout d'abord, les lignes au tout début du bloc de programme:

#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
#udiPT := REAL_TO_UDINT(#PT * 1000000); //   

Ils sont toujours appelés, que le chronomètre compte le temps, ne compte pas ou ait déjà compté. Un gros gaspillage d'argent est de charger le processeur peu puissant de la série 1200 avec des calculs impliquant des matériaux à double précision. Il est raisonnable de transférer les deux lignes dans la partie du code qui traite le compte à rebours (si la «vérité» continue de se manifester). Il est également nécessaire de dupliquer le calcul udiCycle en un code qui traite un front positif à l'entrée du temporisateur. Cela devrait soulager le «fonctionnement à vide» du temporisateur lorsque la valeur d'entrée est fausse. En pratique, les temporisateurs des contrôleurs logiques programmables fonctionnent le plus souvent «au ralenti». Par exemple, le temps de filtrage du rebond de contact est de dizaines de millisecondes. L'impulsion de commande d'une sortie discrète est de quelques centaines de millisecondes, généralement de 0,5 à 1,0 seconde.Le temps de surveillance de l'exécution de la commande de l'unité (par exemple, l'heure à laquelle la vanne s'ouvre complètement) est de quelques dizaines à plusieurs minutes. Le PLC en production fonctionne 24 heures sur 24 et 365 jours (et parfois plus!) Par an. Autrement dit, le plus souvent, l'entrée du minuteur est soit «zéro», et le minuteur ne compte rien, soit une «unité» arrive depuis longtemps, et le minuteur a déjà tout compté. Pour décharger le processeur du deuxième étage inactif (le minuteur a déjà compté), il est nécessaire de vérifier au stade "l'entrée continue de recevoir la vérité" - si le minuteur a déjà tout le temps compté et de régler la sortie sur vrai. Dans ce cas, aucun calcul ne doit être effectué.le plus souvent, l'entrée du minuteur est soit «zéro», et le minuteur ne compte rien, soit une «unité» arrive depuis longtemps, et le minuteur a déjà tout compté. Pour décharger le processeur du deuxième étage inactif (le minuteur a déjà compté), il est nécessaire de vérifier au stade "l'entrée continue de recevoir la vérité" - si le minuteur a déjà tout le temps compté et de régler la sortie sur vrai. Dans ce cas, aucun calcul ne doit être effectué.le plus souvent, l'entrée du minuteur est soit «zéro», et le minuteur ne compte rien, soit une «unité» arrive depuis longtemps, et le minuteur a déjà tout compté. Pour décharger le processeur du deuxième étage inactif (le minuteur a déjà compté), il est nécessaire de vérifier au stade "l'entrée continue de recevoir la vérité" - si le minuteur a déjà tout le temps compté et de régler la sortie sur vrai. Dans ce cas, aucun calcul ne doit être effectué.

Pour effectuer ces modifications, il est nécessaire de transférer la sortie du temporisateur Q de la zone OUTPUT vers la zone IN_OUT, et la valeur de sortie sera stockée dans des variables externes (dans cet exemple, dans un tableau de structures). Après raffinement, le code de fonction entier, y compris la déclaration, est le suivant:

FUNCTION "PerversionTON" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      IN : Bool;   //  
      PT : Real;   //    
   END_VAR

   VAR_IN_OUT 
      Q : Bool;   //  
      INPrv : Bool;
      MEM : LReal;
      TimeACC : UDInt;
   END_VAR

   VAR_TEMP 
      udiCycle : UDInt;
      udiPT : UDInt;
   END_VAR


BEGIN
	IF (#IN AND (NOT #INPrv)) THEN //         
	    #TimeACC := 0;
	    #Q := FALSE;
	    #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); // " "  
	ELSIF (#IN AND #INPrv) THEN //     ""
	    IF (NOT #Q) THEN
	        #udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000); //     
	        #udiPT := REAL_TO_UDINT(#PT * 1000000); //   
	        #TimeACC += #udiCycle; //     "    "
	        IF #TimeACC >= #udiPT THEN //      
	            #Q := TRUE; //  ""
	            #TimeACC := #udiPT; //  
	        END_IF;
	    END_IF;
	ELSE //    -      
	    #Q := FALSE;
	    #TimeACC := 0;
	END_IF;
	
	#INPrv := #IN; //  
	
	ENO := #Q; // ENO         LAD  FBD
END_FUNCTION

Après cela, le temps d'exécution s'améliore: le traitement du temps d'inactivité des temporisateurs est de 23 ms, avec un temps de filtrage de travail de 37 à 40 ms.

Ce code de fonction ne vérifie pas une valeur non valide du réglage de la minuterie - une valeur négative (si le matériau est converti en un entier non signé, le réglage sera déformé) ou une valeur supérieure à 4294,9 secondes (le réglage de l'heure débordera et se déformera). Vous devez soit contrôler la valeur de la valeur PT dans le code, soit confier la tâche de vérification de la plage de réglage de l'heure (de 0 à 4294,9 secondes) au système opérateur de niveau supérieur. La vérification de la plage au moyen du programme PLC augmente le temps de traitement à environ 45-46 ms (et, en général, la manière la plus correcte est de régler le temps de la minuterie non pas au format REAL, mais au format UDINT en millisecondes et pour faire des bêtises).

Le projet d'application avec une minuterie pour l'environnement TIA Portal Step 7 version 16 est disponible ici .

All Articles