Siemens S7 DBD Format FIFO: Storing REAL Data with FC84/FC85

David Krause15 min read
S7-300SiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Problem Definition: Why Standard FC85 Cannot Hold a REAL

Siemens S7-300 and S7-400 CPUs ship a standard library that contains table management functions FC84 ("ATT" – Add to Table) and FC85 ("Table" – FIFO read with reorganize). The two functions are widely used for trend logging, alarm history, recipe sequencing, and shift registers, but they are fundamentally 16-bit WORD-oriented. The first two bytes of the source data block entry are interpreted as a WORD. If the application passes a pointer to a REAL (32-bit IEEE-754) the upper word is silently truncated and the lower word is written; the value is destroyed and the FIFO pointer arithmetic becomes corrupt on the next call.

Process data such as temperature (°C), pressure (bar), flow (m³/h), valve position (%), level (m), and conductivity (µS/cm) are almost always REAL. An engineer who tries to log a 100-sample trace of a 6-loop process needs 600 REAL entries, not 600 WORD entries. This article documents a robust technique for implementing a DBD-format FIFO on S7-300/400 using the standard FC84/FC85 system functions and a word-split architecture, then expands the technique with cycle-time analysis, overflow detection, and a parallel-array variant that scales to long-term archiving.

Compatibility scope: The code below targets STEP 7 V5.x and the S7-300/400 instruction set. It compiles on S7-1500 only inside an SCL source with explicit casting because the 1500 series already ships a native FIFO/ATT block in the IEC library. The SCL variant is included in the final section.

Architecture: Mapping One DBD to Two DBW Entries

Every REAL occupies exactly 4 bytes = 2 WORDs = 1 DWORD. The conversion is loss-free because the IEEE-754 single-precision layout is purely numeric. The strategy is to define the FIFO storage array as ARRAY[1..N] OF WORD with N = 2 × (number of REAL values) and to commit two slots per REAL in a fixed order (high word first, then low word). The wrapper FC that adds an entry splits the input REAL, calls FC84 twice in sequence, and increments the entry counter by two. The read-side wrapper calls FC85 twice, recombines the two words with the standard AD/COMB pattern, and returns the restored REAL.

REAL Array (conceptual view) REAL[1] REAL[2] REAL[3] REAL[4] FIFO Storage ARRAY[1..8] OF WORD (physical layout) W1 W2 W3 W4 W5 W6 W7 W8 W1..W2 = REAL[1] (high, low) W3..W4 = REAL[2] W5..W8 = REAL[3..4]

The advantage of a single physical array (with the high/low word pairing inside the array) over two parallel FIFOs is deterministic synchronization. Twin-table designs where one FC85 holds all high words and a second FC85 holds all low words will desynchronize the moment a call is skipped (OB1 cycle overrun, restart, online edit, or a single FC85 underflow with ENO = 0). The combined-array design guarantees that one FC84 or FC85 call always consumes exactly one REAL; the entry counter is a strict even number.

S7-300/400 Byte Order and the LAR1 P##Data Pattern

S7-300/400 store multi-byte numeric types in big-endian (Motorola) order inside a data block. For a REAL located at DB100.DBD10 the high word occupies DBW10 and the low word occupies DBW12. The original STL snippet uses the system macro LAR1 P##Data to load the address of a TEMP variable so that the two halves of the REAL can be addressed symbolically. The result is that the same T D[AR1,P#0.0] instruction writes the high word into Data[1] and T D[AR1,P#2.0] writes the low word into Data[2] automatically.

The reverse operation requires care. Never read the two words into two different DBWs and then load them with two separate L DBWxx instructions; that will produce a value in which the words are swapped. The correct pattern is:

NETWORK 1    ; Load high word into accumulator-1 high
  L   DB100.DBW[head_pointer]     ; high word of stored REAL
  T   LW10                       ; store in local high word
  L   DB100.DBW[head_pointer + 2] ; low word
  T   LW12                       ; store in local low word
  L   DBD 10                     ; reload as 32-bit REAL
  T   MD20                       ; output REAL to flag word
Critical reminder: After loading the two words, the REAL must be re-interpreted by loading the same memory location as a DBD. STEP 7 performs the endian conversion internally; a manual CAW or SLW/SRW sequence on the words will corrupt the IEEE-754 mantissa.

Data Block Definition and Capacity Planning

Define a single data block to host both the FIFO control word and the storage array. The control word is identical to the format expected by FC84/FC85: a WORD at offset 0 holding the table length, and a WORD at offset 2 holding the current number of entries. The original DB100 design used INT counters; this is functional but a WORD is the canonical type because FC84/FC85 operate on memory pointers and read the first two bytes as length. The following DB declaration covers 200 REAL values = 400 WORD entries:

DATA_BLOCK DB 100
TITLE =REAL_FIFO_BUFFER
VERSION : 0.1
  STRUCT
    FIFO : STRUCT
      iTableLength    : INT  := 400;   // length in WORDs, MUST be even
      iNumberOfEntries: INT  := 0;     // current fill in WORDs, MUST be even
      Data            : ARRAY[1..400] OF WORD;
    END_STRUCT;
  END_STRUCT;
BEGIN
    FIFO.iTableLength     := 400;
    FIFO.iNumberOfEntries := 0;
END_DATA_BLOCK

Capacity planning rule:

Symbol Meaning Formula
Nreal Number of REAL samples to buffer Application requirement
L FIFO length in WORDs (table length) L = 2 × Nreal
F Fill level in WORDs (number of entries) 0 ≤ F ≤ L, F is always even
M Memory footprint in bytes M = 4 + 2 × L

For 200 REALs the DB consumes 4 + 2 × 400 = 804 bytes. The default DB register area of an S7-315-2 DP is 8 KB, so a 200-sample buffer is well within budget. For 1000 REALs the DB reaches 4004 bytes, still safe on any modern S7-300 CPU. The S7-314 IFM, with its 4 KB of DB, maxes out at roughly 2000 REALs before the DB must be split into a second area.

Implementing the REAL FIFO Add Wrapper (FC4)

The wrapper takes a single REAL input, splits it into two WORDs using the address-of-temporary pattern, and calls FC84 twice. The original code from the forum thread is reproduced below with the comments expanded and the FC84/FC85 parameter names matched to the official Siemens "System Software for S7-300/400 – System and Standard Functions" reference manual:

FUNCTION FC 4 : VOID
TITLE =REAL FIFO - Add Entry
VERSION : 0.2
VAR_INPUT
    rData : REAL;                    // value to be appended
END_VAR
VAR_TEMP
    Data  : ARRAY[1..2] OF WORD;     // word-pair view of the REAL
END_VAR
BEGIN
NETWORK 1   // Build word-pair view of input REAL
TITLE =Split REAL into two WORDs
    LAR1  P##Data;                   // AR1 -> temp array base
    L     #rData;                    // load REAL into ACCU1
    T     D [AR1,P#0.0];             // write ACCU1 (4 bytes) to Data[1]..Data[2]
NETWORK 2   // Add high word (Data[1] = DBW at lower address)
TITLE =FC84 call for high word
    CALL  FC84 (
        DATA    := #Data[1],
        RET_VAL := MW100,             // return code from FC84
        TABLE   := DB100.DBX 0.0);    // DB100 header = pointer target
    NOP   0;
NETWORK 3   // Add low word (Data[2] = DBW at higher address)
TITLE =FC84 call for low word
    CALL  FC84 (
        DATA    := #Data[2],
        RET_VAL := MW102,             // return code from FC84
        TABLE   := DB100.DBX 0.0);
    NOP   0;
END_FUNCTION

The RET_VAL output of FC84 is documented in the Siemens reference manual. 0 means the entry was added; a non-zero value means the table is already full and the new word was rejected. With a paired design both calls must succeed, so the wrapper must also verify that the second call did not reject. A practical implementation writes both RET_VALs into adjacent MWs (MW100, MW102) and exposes them in the FC's output interface, or sets a global bit FIFO_FULL that the calling OB interrogates before every push.

Implementing the REAL FIFO Remove Wrapper (FC5)

The remove side mirrors the add side. FC85 ("Table") returns the oldest entry in the data block pointed to by the TABLE input and reorganises the remaining entries so that the second-oldest becomes the new oldest. Calling FC85 twice retrieves both halves of the oldest REAL; the two words must be combined into a single REAL by re-interpretation:

FUNCTION FC 5 : REAL
TITLE =REAL FIFO - Pop Oldest Entry
VERSION : 0.2
VAR_OUTPUT
    rValue : REAL;                   // oldest REAL, 0.0 if empty
END_VAR
VAR_TEMP
    Data  : ARRAY[1..2] OF WORD;
    wRet  : WORD;
END_VAR
BEGIN
NETWORK 1   // Refuse pop if fewer than 2 entries present
TITLE =Underflow guard
    L     DB100.DBW 2;               // current number of entries
    L     2;
    <I                                // ACCU2 < ACCU1?
    JC    EMPTY;
NETWORK 2   // Pop high word
TITLE =FC85 call for high word
    CALL  FC85 (
        DATA    := #Data[1],
        RET_VAL := #wRet,
        TABLE   := DB100.DBX 0.0);
NETWORK 3   // Pop low word
TITLE =FC85 call for low word
    CALL  FC85 (
        DATA    := #Data[2],
        RET_VAL := #wRet,
        TABLE   := DB100.DBX 0.0);
NETWORK 4   // Recombine into REAL via temp DBD
TITLE =Combine word-pair into REAL
    LAR1  P##Data;
    L     D [AR1,P#0.0];
    T     #rValue;
    JU    DONE;
EMPTY: L     0.000000e+000;
    T     #rValue;
DONE: NOP   0;
END_FUNCTION

The underflow guard is essential. If the FIFO is empty and FC85 is called, it returns an error in RET_VAL and does not modify the table, so the next call still pops the original oldest entry – effectively corrupting the table. By short-circuiting with the <I comparison we ensure FC85 is only called when at least two entries exist.

SCL and LAD Equivalents

For serviceability many plants require the code in ladder (LAD) or in SCL. The SCL equivalent of the add wrapper is two lines and uses the IEC standard WORD_TO_BLOCK_WORD / DWORD casts. The SCL version also makes the byte-order decision explicit:

FUNCTION "F_REAL_FIFO_PUSH" : VOID
TITLE ='REAL FIFO - Push entry'
VAR_INPUT
    rData : REAL;
END_VAR
VAR_TEMP
    dwSplit : DWORD;   // word-pair view of the REAL
    wHi     : WORD;    // high word (DBW at lower address)
    wLo     : WORD;    // low word
END_VAR
BEGIN
    dwSplit := DWORD_OF(#rData);
    wHi := WORD_OF(BLOCK_DB(DB100).DW[0]);  // dummy to keep interface
    wHi := DWORD_TO_WORD(dwSplit AND 16#FFFF0000) / 16#10000;
    wLo := DWORD_TO_WORD(dwSplit AND 16#0000FFFF);
    ATT(DATA := wHi, TABLE := DB100.FIFO);
    ATT(DATA := wLo, TABLE := DB100.FIFO);
END_FUNCTION

The LAD equivalent is straightforward: two networks, each containing a MOVE box for one of the two words into a TEMP WORD, followed by a CAL FC84 box. Most teams choose STL for the wrapper because the LAR1 P##Data pattern avoids the manual masking required in SCL and produces a smaller block (≈ 90 bytes compiled vs 240 bytes for the SCL version on a 315-2 DP).

Synchronization, Overflow, and Watchdog Considerations

Three failure modes dominate field experience and must be engineered against:

Failure mode Symptom Detection Mitigation
FIFO overflow FC84 RET_VAL ≠ 0 on second push Compare RET_VAL against 0 after every push; latch bit FIFO_OVF Drop oldest via FC85 before push, or stop logging and raise an alarm
FC85 underflow FC85 RET_VAL ≠ 0; table head pointer frozen Compare DBW 2 ≥ 2 before pop; check RET_VAL ≠ 0 Skip the pop; do not retry until the table is refilled
Cycle-time overrun OB1 scan > max OB1 time; subsequent cycles may skip FIFO ops Monitor OB1_PREV_CYCLE (OB1 system time) Decouple the FIFO from OB1; call the wrapper from a time-of-day OB (OB10..OB17) or a cyclic interrupt OB (OB30..OB38) at a fixed rate

A 400-entry FIFO (200 REALs) on a 315-2 DP executing FC84 twice per push and FC85 twice per pop takes approximately 1.4 ms per push and 1.6 ms per pop. Pushing once every 100 ms in OB35 (cyclic interrupt at 100 ms) consumes 1.4 % of the cyclic budget – negligible. A 4000-entry FIFO (2000 REALs) jumps to 14 ms per push; this must be moved to a slower cyclic OB (OB37 at 500 ms) or scheduled behind a watchdog.

Restart safety: The DB retains its fill level across STOP→RUN transitions but is reset to the initial values defined in the BEGIN section on a COLD RESTART or after a factory reset. For warm-restart resilience the application should reload the fill level from a non-volatile bit-cell (S7-300/400 retain area) at the start of OB100. On S7-315 and above the entire DB can be marked retentive in the hardware configuration under "Retentive Memory → Data Blocks".

Commissioning Procedure and Verification

  1. Compile the DB and both FCs; download to the CPU in RUN-P. Check the CPU diagnostic buffer for SF (system fault) – the most common cause is a mismatched ANY pointer in the FC84 TABLE parameter (it must point to the header word of the FIFO, not the first data element).
  2. Open the DB in the online view and confirm that DBW 0 = 400 and DBW 2 = 0. Manually write 400 to DBW 0 if STEP 7 has overwritten the initial value during download.
  3. In VAT or a watch table, force a test REAL (e.g. MD 200 = 1.0e+02) and call FC4 once. Verify that DBW 2 increments by 2 and that DBW 6..DBW 9 now contain the IEEE-754 bit pattern of 100.0 (hex 42C80000 → high word 42C8, low word 0000).
  4. Call FC5 once and confirm that the popped REAL in MD 220 equals 1.0e+02, that DBW 2 decrements by 2, and that RET_VAL of both FC85 calls is 0.
  5. Push 250 values (overfilling the 200-slot buffer) and confirm that the 201st push returns a non-zero RET_VAL on the second FC84 call and that FIFO_OVF is set.
  6. Pop the FIFO completely until DBW 2 = 0. Call FC5 a final time and verify the underflow guard returns 0.0 with RET_VAL = 0 and that the FC85 calls are not executed.
  7. Cycle the CPU STOP→RUN three times and verify that the DB retain flag is set, otherwise the fill level will be lost and the underflow guard will not function on the first scan.
  8. Run the plant for 24 hours with the HMI trend page active and verify that the maximum scan time remains below 80 % of the configured OB1 max (default 150 ms on a 315-2 DP).

Application Examples

The DBD-format REAL FIFO is the building block of three common plant scenarios:

  • Trend logging: Push one REAL per loop into the FIFO every 100 ms; the HMI reads the buffer through a WinCC flexible / TIA Portal trend control bound to the DB array. Set the FIFO length to 600 entries (300 REALs) to provide 30 seconds of history at 100 ms resolution for six loops.
  • Batch recording: Push the recipe set-point and the actual value of a critical variable on every recipe step. The SFC59 / SFC60 "RD_REC" / "WR_REC" system functions can then archive the entire DB to a recipe DB at the end of the batch, producing a complete audit trail.
  • Alarm history: Combine the REAL FIFO with a parallel INT FIFO holding the alarm timestamp (seconds since midnight). Push one REAL and one INT whenever an alarm becomes active. The HMI displays them as paired rows.

Troubleshooting Matrix

Symptom Likely cause Diagnostic Fix
FC84 RET_VAL = 1 (table full) DBW 0 was not set to the correct length, or the DB initial value was overwritten Online → DB100 → DBW 0 Re-download the DB and re-initialise in OB100
Popped REAL is high/low swapped Pop wrapper swapped the order of the two FC85 calls Push 1.0 and check that DBW6..DBW9 = 42C8 0000 Swap the calls so the high word is popped first
Popped REAL = 0.0 even when buffer is non-empty Underflow guard false-triggered by an off-by-one count Watch DBW 2 during the <I comparison Use <=I to allow pop when DBW 2 ≥ 2; do not use <> 0
SF (system fault) on CPU after download ANY pointer mismatch on the TABLE parameter CPU diagnostic buffer → open the SF event Re-open the FC and verify that TABLE points to DB100.DBX 0.0, not DB100.DBX 4.0
OB1 max cycle time exceeded FIFO is being called from OB1 with a very large L OB1 → "Stack" tab; check local stack usage Move the push/pop to a cyclic interrupt OB
HMI shows garbage Endian mismatch on the HMI side – some panels treat the array as little-endian Compare the HMI raw view with the DB online view Use the standard S7 endian; the HMI driver should match – re-import the tag list
Data lost after STOP→RUN DB not marked retentive HW Config → CPU → Retentive Memory → check DB100 Add DB100 to the retentive list and re-download the hardware configuration

Frequently Asked Questions

Can I store a REAL directly in an FC85 call on S7-300/400?

No. FC85 ("Table") on S7-300/400 expects a 16-bit WORD. If you pass a pointer to a REAL the upper word is truncated and the lower word is written, producing a corrupted value and desynchronising the FIFO. Use the word-split wrapper described above, or migrate to S7-1500 where the IEC 61131-3 "FIFO" / "LIFO" standard functions natively support REAL.

Should I use two parallel FIFOs (one for high word, one for low word) or one combined array?

Use one combined array. Two parallel FIFOs desynchronise whenever a single FC85 call is skipped (OB1 cycle overrun, restart, online edit, underflow). The combined-array design commits two slots per REAL inside one DB; one FC84 / FC85 call always consumes exactly one REAL, the entry counter stays even, and the underflow guard is a single comparison against 2.

What is the maximum FIFO length on a 315-2 DP?

A 315-2 DP has 8 KB of DB address space. Each FIFO entry consumes 2 bytes plus 4 bytes of header, so the practical maximum is about 4000 WORDs = 2000 REALs. Above 2000 REALs the DB should be split or moved into the load memory (SFC59 / SFC60 "RD_REC" / "WR_REC").

Why does the popped REAL show 0.0 even when DBW2 is non-zero?

Almost always an off-by-one error in the underflow guard. The check must be "DBW 2 ≥ 2" (use <=I with 2 in ACCU1), not "DBW 2 ≠ 0". A second common cause is that the pop wrapper swapped the order of the two FC85 calls; verify by pushing 1.0 and reading DBW6..DBW9 – the bit pattern must be 42C8 0000 (high word first, low word second).

How do I keep the FIFO data across a CPU restart?

Mark the FIFO data block as retentive in HW Config → CPU → Retentive Memory → Data Blocks, and reload the fill level from the retain area in OB100 (warm restart) or OB102 (cold restart). On S7-315 and above the entire DB can be retained; on S7-312/314 only the first N bytes are retentive – keep the table length and fill level at the top of the DB and limit the storage array to the first retained block.

Does this technique work on S7-1500 / TIA Portal?

The word-split technique works but is unnecessary. TIA Portal ships the IEC 61131-3 standard blocks "FIFO" and "LIFO" inside the "Basic Instructions" palette; they support every elementary data type including REAL, LREAL, DWORD, and STRUCT. The legacy FC84 / FC85 path is provided for compatibility – on a 1500 project prefer the standard blocks unless the application is being ported unchanged from a 300/400.

Back to blog