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.
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.
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
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.
Commissioning Procedure and Verification
- 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
TABLEparameter (it must point to the header word of the FIFO, not the first data element). - Open the DB in the online view and confirm that
DBW 0 = 400andDBW 2 = 0. Manually write 400 toDBW 0if STEP 7 has overwritten the initial value during download. - In VAT or a watch table, force a test REAL (e.g.
MD 200 = 1.0e+02) and call FC4 once. Verify thatDBW 2increments by 2 and thatDBW 6..DBW 9now contain the IEEE-754 bit pattern of 100.0 (hex 42C80000 → high word 42C8, low word 0000). - Call FC5 once and confirm that the popped REAL in
MD 220equals 1.0e+02, thatDBW 2decrements by 2, and thatRET_VALof both FC85 calls is 0. - 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_OVFis set. - 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. - 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.
- 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.