Automation
2026-03-09

Generic FIFO/LIFO Buffer for Siemens S7-1500

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 .

Diagram 1 · Generic data flow
Application UDT
VARIANT
Serialize
Payload + TypeId
FIFO / LIFO
Deserialize

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.

Diagram 2 · Software architecture
Public facade
FB_Eido_Fifo
FB_Eido_Lifo

Clean interface for the application: commands, status, and configuration.

Internal core
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.

Diagram 3 · Circular FIFO example
Head = 2
Tail = 6
Count = 4
[0]
[1]
[2] ← HeadA
[3]B
[4]C
[5]D
[6] ← Tailnext
[7]
On a dequeue, A is removed and 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.

← ALL INSIGHTSHOME →
META
POST
SLUG
fifo_lifo_buffer
LANG
en
TAG
Automation
Edit the content in content/insights/fifo_lifo_buffer.en.md and run the build again.