En automatización industrial es frecuente necesitar un buffer temporal de datos estructurados: registros de trazabilidad, eventos de producción, mensajes, resultados de test o snapshots de estado de máquina. En muchos proyectos PLC esta necesidad se resuelve con arrays ad hoc, índices manuales y lógica duplicada. Funciona, pero escala mal.
Para atacar ese problema desarrollé una librería de buffers FIFO/LIFO genéricos para Siemens S7-1500 en TIA Portal, escrita en SCL y pensada para reutilización real entre proyectos. El punto fuerte no es solo implementar una cola o una pila: es hacerlo de forma genérica, permitiendo almacenar cualquier tipo de dato de aplicación, normalmente representado como un UDT.
El código fuente completo de esta librería está disponible públicamente en GitHub: github.com/EidoAut/Fifo_Lifo .
1) El problema de los buffers específicos de proyecto
Una implementación típica suele partir de un array de una estructura concreta de aplicación. Desde el punto de vista funcional sirve, pero desde el punto de vista arquitectónico la cola queda acoplada a un único modelo de datos.
TYPE UDT_Traceability :
STRUCT
PartId : STRING[32];
StationId : UINT;
RecipeId : UINT;
TestOk : BOOL;
END_STRUCT
END_TYPE
VAR
Buffer : ARRAY[0..99] OF UDT_Traceability;
Head : INT;
Tail : INT;
Count : INT;
END_VAR
En cuanto el siguiente cliente necesita otra estructura de trazabilidad, o cuando el mismo buffer debe almacenar otro tipo de evento, toca volver a escribir la lógica. La consecuencia es conocida: duplicación, mantenimiento peor y una librería que en realidad no es librería.
Acoplamiento fuerte
La lógica del buffer depende directamente del tipo almacenado.
Poca reutilización
La misma solución acaba reescribiéndose en proyecto tras proyecto.
2) Objetivo de diseño
La librería se diseñó con cuatro objetivos claros:
- desacoplar la estructura del buffer del tipo de dato almacenado,
- poder reutilizar la misma lógica en proyectos distintos,
- mantener un diseño limpio y natural dentro de TIA Portal,
- dar soporte real a casos de trazabilidad y buffering asíncrono.
3) Modelo de payload genérico
La pieza clave es UDT_Eido_AnyItem, que representa el elemento realmente almacenado. Conceptualmente, el modelo es este:
TYPE UDT_Eido_AnyItem :
STRUCT
Len : UINT;
TypeId : UINT;
Payload : ARRAY[0..511] OF BYTE;
END_STRUCT
END_TYPE
| Campo | Función |
|---|---|
Len |
Longitud válida del payload serializado. |
TypeId |
Identificador de tipo definido por la aplicación. |
Payload |
Datos serializados en bruto como array de bytes. |
Desde el punto de vista del buffer, todos los datos tienen la misma forma. Desde el punto de vista de la aplicación, cada payload puede corresponder a un UDT distinto.
4) Arquitectura: fachada pública + core interno
Otra decisión importante fue separar la API pública de la lógica interna. La librería se divide en dos capas.
FB_Eido_Fifo
FB_Eido_Lifo
Interfaz limpia para la aplicación: comandos, estado y configuración.
FB_Eido_Fifo_Core
FB_Eido_Lifo_Core
Serialización, almacenamiento, índices, validaciones y errores.
La fachada recibe comandos de nivel como Enq, Deq, Push, Pop y Clear. Internamente esos comandos se convierten a pulsos de un ciclo y se entregan al core. Esto simplifica el uso desde OB1 y evita mezclar la lógica de aplicación con detalles internos del almacenamiento.
Cfg → parámetros de configuración
Cmd → órdenes de operación
Status → estado, diagnóstico y resultado
Store → almacenamiento interno
5) FIFO: buffer circular
El FIFO está pensado para colas de eventos, registros de trazabilidad, buffering de telegramas y desacoplar generación y procesado de datos. La implementación usa un circular buffer, evitando desplazar arrays y manteniendo coste constante en las operaciones principales.
Head avanza. En un enqueue nuevo elemento entra en Tail y este índice rota al siguiente hueco disponible. 6) LIFO: pila reutilizable
Aunque comparte el mismo modelo de payload, el LIFO resuelve un patrón distinto. Es útil para históricos cortos, deshacer acciones, gestionar contextos o secuencias anidadas. La ventaja es que la aplicación consume la misma filosofía de uso, pero con semántica de pila.
7) Ejemplo de uso real en SCL
Una de las ventajas de la fachada pública es que el bloque se integra de forma limpia en el ciclo principal. Un ejemplo simplificado sería:
VAR
FifoTrace : FB_Eido_Fifo;
TraceCfg : UDT_Eido_Fifo_Cfg;
TraceCmd : UDT_Eido_Fifo_Cmd;
TraceStatus : UDT_Eido_Fifo_Status;
TraceStore : UDT_Eido_Fifo_Store;
TraceRecord : UDT_CustomerTraceability;
END_VAR
// Encolar un nuevo registro de trazabilidad
TraceCmd.Enq := NewPartCompleted;
TraceCmd.InTypeId := 1001;
FifoTrace(
Cfg := TraceCfg,
Cmd := TraceCmd,
Status := TraceStatus,
Store := TraceStore,
DataIn := TraceRecord
);
// Consumir un registro cuando el sistema aguas abajo esté disponible
IF DbChannelReady AND NOT TraceStatus.Empty THEN
TraceCmd.Deq := TRUE;
TraceCmd.ExpectedTypeId := 1001;
END_IF;
Este patrón es especialmente útil cuando la generación del dato y su consumo no ocurren al mismo ritmo.
8) Trazabilidad: un caso de uso especialmente fuerte
Cada cliente suele tener su propio UDT con sus propios campos: identificador de pieza, timestamp, estación, receta, resultados, flags o códigos de error. Ese dato no es estable entre proyectos, pero la necesidad de bufferizarlo sí lo es.
TYPE UDT_CustomerTraceability :
STRUCT
PartId : STRING[32];
Timestamp : DATE_AND_TIME;
StationId : UINT;
RecipeId : UINT;
TestResult : BOOL;
ErrorCode : UINT;
END_STRUCT
END_TYPE
Ese UDT puede serializarse, almacenarse como payload y recuperarse más tarde sin modificar el buffer. Esto encaja muy bien cuando la red cae temporalmente, cuando el envío a base de datos es más lento que el ciclo de máquina o cuando hay que desacoplar adquisición y transmisión de datos.
9) Validación opcional con TypeId
El payload es genérico, pero eso no implica perder control. La librería permite validación opcional por TypeId. Si se activa, al extraer un elemento del buffer se comprueba que el tipo esperado coincide con el almacenado.
IF #Cfg.EnforceTypeId THEN
IF #StoredItem.TypeId <> #Cmd.ExpectedTypeId THEN
#Status.Error := TRUE;
#Status.ErrorId := 5; // Type mismatch
END_IF;
END_IF;
Esto aporta una capa adicional de seguridad cuando varios tipos de payload comparten infraestructura o cuando se quiere detectar de forma explícita un mal uso de la librería.
10) Ventajas reales del enfoque
- Reutilización: una sola implementación sirve para muchos proyectos.
- Desacoplamiento: la lógica del buffer no depende del UDT concreto.
- Escalabilidad: nuevos tipos de datos no exigen reescribir la cola.
- Arquitectura limpia: fachada pública, core interno y diagnóstico claro.
- Aplicación práctica: muy útil para trazabilidad, eventos y comunicación asíncrona.
Conclusión
Una cola o una pila en PLC no son algo nuevo. Lo interesante aquí es la forma de construirlas: payload genérico, serialización, validación opcional de tipo y arquitectura modular. Eso convierte una necesidad recurrente de planta en una librería reusable de verdad.
En otras palabras: no es solo un FIFO/LIFO; es una pequeña infraestructura para transportar datos de aplicación dentro del PLC con más orden, más reutilización y menos acoplamiento.