Temporizador de funciones para el controlador industrial Simatic S7-1200

Incluso para las series S7-300 y S7-400, en el Paso 7, las versiones clásicas de los temporizadores ofrecidos al desarrollador eran suficientes: estos son temporizadores IEC estándar, implementados como bloques de funciones y temporizadores S5 (que, por cierto, todavía existen para la serie S7 1500). Sin embargo, en algunos casos, el desarrollador no usó herramientas estándar e implementó sus propios temporizadores, la mayoría de las veces en forma de funciones. Tales funciones de temporizadores eran necesarias con un enfoque de "TI" para la programación, en el que operaban no con instancias separadas de los bloques funcionales de equipos tecnológicos, con el enlace correspondiente de entradas y salidas, sino con conjuntos de estructuras. Por ejemplo, una matriz de una estructura de tipo de entrada discreta. O una matriz de una estructura agregada. Este enfoque de programación tiene derecho a existir, ya que le permite guardar seriamente la memoria de trabajo de la CPU, pero,por otro lado, hace que el código del programa sea difícil de leer. Un programador de terceros y con un simple aspecto de un programa en LAD no puede resolverlo de inmediato, pero no se puede encontrar un montón de índices, matrices y funciones para procesarlos; aquí, sin documentación para el software (y sin medio litro, por supuesto), en ninguna parte.

Estas matrices de estructuras se procesaban típicamente en funciones. En principio, nada impedía el procesamiento de bloques de funciones, pero siempre había una pregunta importante: ¿cómo trabajar con temporizadores en estos casos? Los temporizadores estándar suponen un número (S5) o una instancia de un bloque de funciones (IEC). Les recuerdo que se trata de procesar matrices de estructuras para PLC Simatic clásicos y de "torcer" números de temporizador en estas estructuras, y aún más, las instancias son difíciles o simplemente imposibles.

Por esta razón, creamos nuestra propia funcionalidad de temporizador como función. En principio, para el funcionamiento de cualquier temporizador, solo necesita saber algunas cosas: el estado de la entrada, la configuración de tiempo y cuánto tiempo ha pasado desde la activación.

Para las series 300 y 400, había dos formas de determinar esta vez. El primero es observar el tiempo de ejecución del OB1 principal (hay una variable correspondiente en el propio OB1) u OB cíclicos y aumentar el acumulador de tiempo interno con cada llamada del temporizador, siempre que se ingrese la "verdad". No es una buena opción, ya que esta vez es diferente para OB1 y OB cíclicos. El segundo método es la función del sistema TIME_TCK, que, con cada llamada, devuelve un valor único: el contador interno de milisegundos del procesador central.

imagen

Por lo tanto, para un temporizador del tipo TON (en retraso), el algoritmo de operación fue el siguiente:

  • en el borde ascendente de la solicitud de respuesta, restablezca la salida y recuerde el valor actual del temporizador del sistema TIME_TCK
  • «» , ( , TIME_TCK 0 (2 ^ 31 — 1), ). , . , «», — «»
  • «»,

Con el advenimiento de la serie "milésima", la situación ha cambiado un poco. El hecho es que la línea S7-1500 heredó el soporte para la llamada al sistema TIME_TCK, y los amantes del enfoque "de pie y en una hamaca" (¿de qué otra manera puede llamar a un programa que solo hace lo que procesa matrices de estructuras, mientras opera con índices espeluznantes?) tranquilamente continúe usando sus mejores prácticas.

La serie S7-1200 de controladores base se basa en una arquitectura diferente y tiene varias diferencias con respecto al S7-1500. Incluyendo la falta de una llamada al sistema TIME_TCK. En las filas de desarrolladores que no tienen suficiente flexibilidad de pensamiento, la insatisfacción ha desaparecido: es imposible ejecutar copias / pastas de programas antiguos. Sin embargo, la tarea de determinar cuánto tiempo ha pasado desde la llamada anterior se puede realizar utilizando la función de tiempo de ejecución.

Esta función devuelve el tiempo transcurrido desde su llamada anterior, en segundos, como un número real de doble precisión LREAL. Los detalles se describen en la ayuda. Para fines internos, se requiere una variable MEM adicional (también del tipo LREAL).

Daré las fuentes de la primera aproximación de la función, y daré algunas notas.

Declaración de función:

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

Con las entradas / salidas, todo está claro: IN, Q y PT. Configuré el ajuste del tiempo en forma real, son segundos. Solo quería (pero en vano, pero más sobre eso a continuación). Más sobre las variables del área InOut. Como tenemos una función, no tenemos un área STAT, no hay variables que conserven su valor durante la próxima llamada a la función, y se necesitan tales variables:

INPrv - para determinar el borde positivo de la solicitud

MEM - variable auxiliar para la llamada al sistema

Tiempo de ejecución TimeACC - acumulador de tiempo , que almacenará el número de microsegundos del retraso actual.

Las variables TimeACC, udiCycle y udiPT se especifican en formato UDINT, un entero sin signo, 4 bytes. A pesar de que especifiqué el tiempo como real, y la función de tiempo de ejecución devuelve real tanto como la precisión doble, prefiero realizar operaciones simples de suma y comparación con operandos enteros para ahorrar tiempo de procesador. El tiempo en mi caso se tiene en cuenta al microsegundo. La razón es simple: si reduce el tiempo a milisegundos, entonces con el OB1 casi vacío (por ejemplo, si solo se llama a un temporizador en todo el programa del controlador y nada más), son posibles "saltos" de ciclos, el programa a veces se ejecuta durante 250 μs. Pero en este caso, el valor máximo permitido del acumulador de tiempo será 4,294 segundos, casi 4,295 (2 ^ 32 - 1 = 4,294,967,295). No hay nada que hacer, tal "optimización" requiere sacrificio.

Texto de función

#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

Las primeras dos líneas son el recálculo de la configuración del temporizador desde el número de segundos especificado en el formato REAL hasta el número de microsegundos. También se determina el tiempo en microsegundos transcurrido desde la llamada de bloque de programa anterior.

Además, el algoritmo es el siguiente, y ya lo he dado:

  • en el flanco ascendente de la entrada IN, restablezca la salida Q y restablezca el acumulador de tiempo
  • si la "verdad" continúa siendo ingresada, aumentamos el tiempo acumulado por el valor udiCycle ya conocido y lo comparamos con la configuración de tiempo. Si se excede el ajuste de tiempo, el temporizador ha funcionado, dé la salida "verdadero", de lo contrario, dé la salida "falso"
  • en el caso de aplicar una entrada falsa a la entrada IN, restablezca la salida Q y restablezca el acumulador de tiempo.

Al final de la función, para determinar el borde de la entrada IN, recuerde su valor anterior. También proporcione a la salida ENO (cuando se utiliza una función en lenguajes gráficos, como LAD) el valor de la salida Q.

Nos aseguramos de que la función esté funcionando, después de lo cual resulta interesante evaluar su velocidad y, si es necesario, mejorar (ya parece a primera vista que varios cálculos van a inactivo y desperdiciando tiempo de CPU en vano). Para evaluar el rendimiento, declaro una matriz de 1000 estructuras de datos del temporizador.

Declaración de la estructura. Sus campos duplican las variables de entrada y salida de la función del temporizador.

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

END_TYPE

Se declara una serie de estructuras en el bloque de datos global de TortureTON:

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

El siguiente código se ejecuta en el bloque organizativo 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;

Anunció 1000 "instancias" de temporizadores, cada uno de ellos con un tiempo de 10 segundos. Los 1000 temporizadores comienzan a contar el tiempo por el valor de la variable del marcador de inicio.

Comienzo las funciones de diagnóstico del controlador (S7-1214C DC / DC / DC, versión FW 4.4, versión Step7 - V16) y miro el tiempo de ciclo de exploración del controlador. En "inactivo" (cuando "falso" llega a la entrada de los temporizadores), el millar completo se procesa en promedio durante 36-42 milisegundos. Durante la cuenta regresiva de diez segundos, esta lectura crece unos 6-8 milisegundos y, a veces, se desplaza durante 50 ms.

Vemos qué se puede mejorar en el código de función. En primer lugar, las líneas al principio del bloque del programa:

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

Siempre se llaman, independientemente de si el temporizador cuenta el tiempo, no cuenta o ya ha contado. Una gran pérdida de dinero es cargar la CPU no muy potente de la serie 1200 con cálculos que involucran materiales de doble precisión. Es razonable transferir ambas líneas a la parte del código que procesa la cuenta regresiva (si la "verdad" continúa entrando). También es necesario duplicar el cálculo de udiCycle en un código que procese un borde positivo en la entrada del temporizador. Esto debería aliviar la "operación inactiva" del temporizador cuando el valor de entrada es falso. En la práctica, los temporizadores en los controladores lógicos programables suelen funcionar "inactivos". Por ejemplo, el tiempo de filtrado del rebote de contacto es de decenas de milisegundos. El pulso de control de una salida discreta es de unos pocos cientos de milisegundos, generalmente de 0,5 a 1,0 segundos.El tiempo para monitorear la ejecución del comando de la unidad (por ejemplo, el tiempo que la válvula se abre por completo) es de decenas de segundos a varios minutos. El PLC en producción funciona las 24 horas del día y los 365 (¡y a veces más!) Días al año. Es decir, la mayoría de las veces la entrada del temporizador es "cero", y el temporizador no cuenta nada, o llega una "unidad" durante mucho tiempo, y el temporizador ya ha contado todo. Para descargar la CPU inactiva de la segunda etapa (el temporizador ya ha contado), es necesario verificar en la etapa "la entrada continúa recibiendo la verdad", si el temporizador ya ha contado todo el tiempo y ha configurado la salida como verdadera. En este caso, no se deben realizar cálculos.la mayoría de las veces la entrada del temporizador es "cero", y el temporizador no cuenta nada, o llega una "unidad" durante mucho tiempo, y el temporizador ya ha contado todo. Para descargar la CPU inactiva de la segunda etapa (el temporizador ya ha contado), es necesario verificar en la etapa "la verdad sigue entrando", si el temporizador ya ha contado todo el tiempo y ha configurado la salida como verdadera. En este caso, no se deben realizar cálculos.la mayoría de las veces la entrada del temporizador es "cero", y el temporizador no cuenta nada, o llega una "unidad" durante mucho tiempo, y el temporizador ya ha contado todo. Para descargar la CPU inactiva de la segunda etapa (el temporizador ya ha contado), es necesario verificar en la etapa "la entrada continúa recibiendo la verdad", si el temporizador ya ha contado todo el tiempo y ha configurado la salida como verdadera. En este caso, no se deben realizar cálculos.

Para realizar estos cambios, es necesario transferir la salida del temporizador Q desde el área de SALIDA al área de IN_OUT, y el valor de salida se almacenará en variables externas (en este ejemplo, en una matriz de estructuras). Después del refinamiento, el código de función completo, incluida la declaración, es el siguiente:

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

Después de eso, el tiempo de ejecución mejora: el tiempo de inactividad del procesamiento de los temporizadores es de 23 ms, con un tiempo de filtrado de trabajo de 37-40 ms.

Este código de función no verifica un valor no válido de la configuración del temporizador: un valor negativo (cuando el material se convierte en un entero sin signo, la configuración se distorsionará) o un valor mayor de 4294.9 segundos (la configuración de tiempo se desbordará y distorsionará). Debe controlar el valor del valor de PT en el código, o confiar la tarea de verificar el rango del ajuste de tiempo (de 0 a 4294.9 segundos) al sistema de operador de nivel superior. La verificación del rango mediante el programa PLC aumenta el tiempo de procesamiento a aproximadamente 45-46 ms (y, en general, la forma más correcta es establecer el tiempo del temporizador no en formato REAL, sino en formato UDINT en milisegundos y sin sentido).

El proyecto de aplicación con un temporizador para el entorno TIA Portal Step 7 versión 16 está disponible aquí .

All Articles