In industrial automation, it is often necessary to have a temporary buffer for structured data: traceability records, production events, messages, test results, or machine state snapshots. In many PLC projects, this need is solved with ad hoc arrays, manual indexing, and duplicated logic. It works, but it scales poorly.
To address this problem, I developed a library of generic FIFO/LIFO buffers for Siemens S7-1500 in TIA Portal, written in SCL and designed for real reuse across projects. The strong point is not just implementing a queue or a stack, but doing so in a generic way, allowing the storage of any application data type, typically represented as a UDT.
The full source code of this library is publicly available on GitHub: github.com/EidoAut/Fifo_Lifo .
1) The problem with project-specific buffers
A typical implementation usually starts with an array of a specific application structure. From a functional point of view it works, but from an architectural point of view the queue becomes coupled to a single data model.
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
As soon as the next customer needs a different traceability structure, or when the same buffer must store another type of event, the logic has to be rewritten. The consequence is familiar: duplication, worse maintainability, and a library that is not really a library.
Strong coupling
The buffer logic directly depends on the stored type.
Low reusability
The same solution ends up being rewritten project after project.
2) Design goal
The library was designed with four clear goals:
- to decouple the buffer structure from the stored data type,
- to reuse the same logic across different projects,
- to keep a clean and natural design within TIA Portal,
- to provide real support for traceability and asynchronous buffering use cases.
3) Generic payload model
The key piece is UDT_Eido_AnyItem, which represents the element actually stored. Conceptually, the model is this:
TYPE UDT_Eido_AnyItem :
STRUCT
Len : UINT;
TypeId : UINT;
Payload : ARRAY[0..511] OF BYTE;
END_STRUCT
END_TYPE
| Field | Function |
|---|---|
Len |
Valid length of the serialized payload. |
TypeId |
Application-defined type identifier. |
Payload |
Raw serialized data as a byte array. |
From the buffer point of view, all data has the same shape. From the application point of view, each payload may correspond to a different UDT.
4) Architecture: public facade + internal core
Another important decision was to separate the public API from the internal logic. The library is divided into two layers.
FB_Eido_Fifo
FB_Eido_Lifo
Clean interface for the application: commands, status, and configuration.
FB_Eido_Fifo_Core
FB_Eido_Lifo_Core
Serialization, storage, indexing, validation, and error handling.
The facade receives high-level commands such as Enq, Deq, Push, Pop, and Clear. Internally, those commands are converted into one-cycle pulses and passed to the core. This simplifies usage from OB1 and avoids mixing application logic with internal storage details.
Cfg → configuration parameters
Cmd → operation commands
Status → state, diagnostics, and result
Store → internal storage
5) FIFO: circular buffer
The FIFO is intended for event queues, traceability records, telegram buffering, and decoupling data generation from data processing. The implementation uses a circular buffer, avoiding array shifting and keeping constant time cost for the main operations.
Head advances. On an enqueue, the new element enters at Tail and this index rotates to the next available slot. 6) LIFO: reusable stack
Although it shares the same payload model, the LIFO solves a different pattern. It is useful for short histories, undo actions, context management, or nested sequences. The advantage is that the application uses the same philosophy, but with stack semantics.
7) Real usage example in SCL
One of the advantages of the public facade is that the block integrates cleanly into the main cycle. A simplified example would be:
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
// Enqueue a new traceability record
TraceCmd.Enq := NewPartCompleted;
TraceCmd.InTypeId := 1001;
FifoTrace(
Cfg := TraceCfg,
Cmd := TraceCmd,
Status := TraceStatus,
Store := TraceStore,
DataIn := TraceRecord
);
// Consume a record when the downstream system is available
IF DbChannelReady AND NOT TraceStatus.Empty THEN
TraceCmd.Deq := TRUE;
TraceCmd.ExpectedTypeId := 1001;
END_IF;
This pattern is especially useful when data generation and data consumption do not occur at the same rate.
8) Traceability: a particularly strong use case
Each customer usually has its own UDT with its own fields: part identifier, timestamp, station, recipe, results, flags, or error codes. That data is not stable across projects, but the need to buffer it is.
TYPE UDT_CustomerTraceability :
STRUCT
PartId : STRING[32];
Timestamp : DATE_AND_TIME;
StationId : UINT;
RecipeId : UINT;
TestResult : BOOL;
ErrorCode : UINT;
END_STRUCT
END_TYPE
That UDT can be serialized, stored as payload, and recovered later without modifying the buffer. This fits especially well when the network temporarily fails, when database transfer is slower than the machine cycle, or when acquisition and transmission must be decoupled.
9) Optional validation with TypeId
The payload is generic, but that does not mean losing control. The library allows optional validation through TypeId. If enabled, when an element is extracted from the buffer, the expected type is checked against the stored type.
IF #Cfg.EnforceTypeId THEN
IF #StoredItem.TypeId <> #Cmd.ExpectedTypeId THEN
#Status.Error := TRUE;
#Status.ErrorId := 5; // Type mismatch
END_IF;
END_IF;
This provides an additional layer of safety when several payload types share the same infrastructure or when misuse of the library must be detected explicitly.
10) Real advantages of the approach
- Reusability: one implementation serves many projects.
- Decoupling: the buffer logic does not depend on the specific UDT.
- Scalability: new data types do not require rewriting the queue.
- Clean architecture: public facade, internal core, and clear diagnostics.
- Practical use: especially valuable for traceability, events, and asynchronous communication.
Conclusion
A queue or a stack in PLC software is nothing new. What is interesting here is the way they are built: generic payload, serialization, optional type validation, and modular architecture. That turns a recurring plant need into a truly reusable library.
In other words: it is not just a FIFO/LIFO; it is a small infrastructure layer for transporting application data inside the PLC with more order, more reuse, and less coupling.