Resolving Siemens FC103 Correlated Data Table Errors on S7-300

David Krause15 min read
S7-300SiemensTroubleshooting
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

Overview: Siemens FC103 Correlated Data Table

FC103 is delivered as part of the Standard Library - TI-S7 Converting Functions for the S7-300 and S7-400 PLC families. It implements a classical 1-D correlated lookup where a single input value is matched against an ordered array (IN_TBL) and the corresponding entry from a parallel output array (OUT_TBL) is returned. The block is intended for piecewise-linear approximations, characteristic curve linearisation, valve curves, transducer scaling, and small recipe-driven reference tables where interpolation between two adjacent breakpoints is acceptable.

Although FC103 is widely used in field installations running STEP 7 V5.5 as well as legacy blocks imported into TIA Portal, it has well documented behaviour patterns that produce silent data corruption rather than a STATUS or RET_VAL fault. Three failure modes recur in service: (1) RET_VAL = 0 while OUT is decoupled from OUT_TBL, (2) OUT equals a constant when IN matches no entry, and (3) OUT returns an arbitrary element of IN_TBL regardless of the value of IN. Each of these failures is traceable to how FC103 evaluates the ANY pointers that are passed at the formal interface.

The same lookup function is not provided in the S7-1200 or S7-1500 instruction set, and Siemens support confirms there is no direct replacement inside the basic S7-1200 operations. Migration of an FC103 application to a newer controller therefore requires either a user-written FC (using the STL templates below) or a re-implementation in SCL with a FOR loop and indexed access to the array.

FC103 Interface and Parameter Specification

The block signature in STEP 7 V5.5 and TIA Portal's "Extended Instructions - S7-300/S7-400" library is identical:

Parameter Declaration Data Type Description
IN_TBL INPUT ANY (pointer to WORD-aligned data) Input array. First element MUST contain the array length (number of breakpoints). Matchable values start at element 2.
OUT_TBL INPUT ANY (pointer to WORD-aligned data) Output array. First element MUST contain the array length. Output values start at element 2.
IN INPUT INT / DINT / REAL Lookup key.
OUT OUTPUT INT / DINT / REAL Resulting value. Mirrors the type of IN when no interpolation occurs.
E_TYPE INPUT BYTE Element type selector. Valid codes listed below.
RET_VAL OUTPUT INT Error word. W#16#0000 = success; W#16#80A1 = invalid E_TYPE; W#16#80A2 = mismatched table lengths; W#16#80A3 = IN out of range.

E_TYPE Encoding

Code Element Type Pointer Byte-Offset Increment
B#16#05 INT (16-bit) P#2.0 per loop step
B#16#07 DINT (32-bit) P#4.0 per loop step
B#16#08 REAL (32-bit) P#4.0 per loop step
Note: The element-type code is interpreted by FC103 only as a stride length. Any data type that consumes 2 bytes (WORD/INT) or 4 bytes (DWORD/DINT/REAL) on a word boundary can be used with the corresponding stride. The block compares signed integers when E_TYPE = B#16#05; a DBD/REAL comparison follows IEEE-754 bit-wise semantics when E_TYPE = B#16#08.

Failure Modes Observed in Service

Symptom 1 - RET_VAL = 0 but OUT Is Wrong

The most subtle failure pattern is one in which RET_VAL reports success while OUT has no correspondence to either IN_TBL or OUT_TBL. In practice the value returned is whatever happens to live at the byte offset corresponding to the loop count, which frequently lands inside IN_TBL itself rather than OUT_TBL. The diagnostic signature is that OUT varies with changes to IN_TBL but is unaffected by edits to OUT_TBL.

Symptom 2 - OUT Is Constant for All IN

When the first element of either table is not the declared length, the loop counter is initialised from an arbitrary value and the block completes without ever loading from the lookup arrays. OUT is then either zero (default MW/DBW initial value) or the value latched at the previous cycle.

Symptom 3 - OUT Tracks IN_TBL, Not OUT_TBL

This is the failure traced specifically to the constraint that IN_TBL and OUT_TBL must reside in the same Data Block. The release version of FC103 opens only one DB (or DI) and computes its byte offset against the input table; once that offset is found it is reused against the same DB number rather than the table-specific DB number supplied via the ANY pointer. As a consequence, when the two tables are placed in different DBs the output pointer is silently ignored and the value copied into OUT is taken from the address offset within the input DB.

Root Cause Analysis

ANY Pointer Layout

Every ANY pointer occupies 10 bytes in the local stack or DB interface and is laid out as follows:

Byte 0..1  : ID for syntax check (always 16#10)
Byte 2..3  : Data type code (16#05=INT, 16#07=DINT, 16#08=REAL, ...)
Byte 4..5  : Length (count or number of elements - 1 depending on context)
Byte 6..7  : DB number (16#0000 for non-DB areas)
Byte 8..9  : Area + byte offset (bits 24..31 = area, bits 0..23 = byte offset)

FC103 reads bytes 6..7 to OPN DB[...] or OPN DI[...]. The substitution that breaks the output path is that the block uses the same byte position from BOTH ANY pointers but only opens one DB with it. Implementation details of how the block accesses bytes 4..7 of IN_TBL vs OUT_TBL differ between revision families, and that is the source of the documented "must be same DB" limitation in the Siemens Knowledge Base and on SiePortal.

Length Initialisation Trap

FC103 interprets the first element of each table as the lookup length. The HELP text states "When creating the tables (IN_TBL and OUT_TBL) you must initialise the first entry (Table-length) in each case." However, the help omits the fact that the length field is the initial (start-up) value, not the actual (load-time) value, and that online edits that change the first element do not propagate through the lookup. Engineers who configured the table with the correct length but then re-edited it after download often saw the FIRST element not updated, which made a previously-correct lookup behave as if the length were zero.

Always cross-check with L DBLG (loads the length of the currently opened DB) or with a manual byte-count in the DB editor to confirm that the array length matches the number of actually-populated breakpoints plus one (for the length field itself).

Diagnostic Procedure

  1. Confirm DB assignment. Open the FC103 call site online. Place the cursor on the IN_TBL formal parameter and select Go To > Address in DB (Ctrl+Shift+F5 in STEP 7 V5.5). Repeat for OUT_TBL. Verify that both pointers resolve to the same DB number. If they differ, that is the root cause of any wrong-constant output.
  2. Verify the length field. Monitor the first word of each table online using Monitor/Modify > Display. The value must equal the number of actual breakpoints. Note that Monitor shows the actual value while the editor shows the initial value - they may differ after online edits.
  3. Verify the element type. Compare E_TYPE with the actual data type stored. Mismatches (for example E_TYPE = B#16#05 set against a REAL table) cause FC103 to interpret the same bytes as INT, producing nonsense.
  4. Verify IN value range. When the lookup key sits below the lowest breakpoint, OUT is clamped to the smallest OUT_TBL value. When it sits above the highest, OUT is clamped to the largest. RET_VAL remains 0 in both cases. Check that the breakpoints were saved in ascending order - FC103 performs a simple linear search and does not sort, so an unordered table yields undefined results.
  5. Check the symbol table. When symbolic display is enabled, IN_TBL addresses that fall on byte 0 of a symbol appear as the symbolic name and not as P#DBxx.DBX... notation. Switching View > Display With > Symbol Information off and back on does not change the underlying parameter. The parameter value is correct as long as the address is byte-aligned. Test by deliberately entering P#DB10.DBX0.5 - STEP 7 will refuse to compile, confirming that the pointer is interpreted.
  6. Capture RET_VAL. RET_VAL is only updated when a true error occurs (invalid E_TYPE, mismatched lengths, or IN out of range). RET_VAL = 0 is not a guarantee of correctness - it only means no error was detected.
Critical: If the application stores IN_TBL and OUT_TBL in different DBs, neither editing the values nor patching RET_VAL will correct the lookup behaviour. The block must be replaced.

STL Replacement - INT Variant

The following FC was written to replace FC103 in S7-300/400 STEP 7 V5.5 or TIA Portal classic. It accepts ANY pointers for both tables so the length parameter passes through the pointer header, removing the need for a separate length field in the data block. It performs an exact (==I) match and returns an error flag when no match is found.

FUNCTION FC 200 : VOID
TITLE = 'Linear Lookup INT - exact match, dual ANY pointer'
VAR_INPUT
  InputTable  : ANY;     // P#DBxx.DBXy.0 BYTE n ; n = (count-1)*2
  OutputTable : ANY;     // P#DByy.DBXz.0 BYTE m ; m = (count-1)*2
  InputValue  : INT;
END_VAR
VAR_OUTPUT
  OutputValue : INT;
  Error       : BOOL := TRUE; // default to error - cleared on match
END_VAR
VAR_TEMP
  Length     : WORD;
  DataBlock  : WORD;
  Index      : WORD;
  PointerFmt : DWORD;
END_VAR
BEGIN
  CLR   := #Error;
  //------- Resolve InputTable pointer -------
  L     P##InputTable;           // push ANY pointer onto AR1
  LAR1  ;
  L     W [AR1,P#2.0];           // element length bytes (2 for INT)
  SRW   1;                       // divide by 2 = element count
  T     #Length;
  L     W [AR1,P#4.0];           // DB number
  T     #DataBlock;
  OPN   DB [#DataBlock];
  L     D [AR1,P#6.0];           // area + byte offset (DBxx.DBXy.0)
  LAR1  ;
  //------- Resolve OutputTable pointer -------
  L     P##OutputTable;
  LAR2  ;
  L     W [AR2,P#4.0];           // DB number (allowed to differ from input!)
  T     #DataBlock;
  OPN   DI [#DataBlock];
  L     D [AR2,P#6.0];           // area + byte offset
  LAR2  ;
  //------- Loop through input table -------
  L     #Length;
Top: T     #Index;
  L     DBW [AR1,P#0.0];         // current input entry
  L     #InputValue;
  ==I   ;
  JC    calc;                     // exit loop on match
  +AR1  P#2.0;                   // step 2 bytes forward in input
  L     #Index;
  LOOP  Top;                      // decrement & loop if non-zero
  //------- No-match path -------
  SET   := #Error;                // signal no match
  BEU   ;
  //------- Match-found path -------
calc:L     #Length;
  L     #Index;
  -I    ;
  L     P#2.0;
  *D    ;
  TAR2  ;                        // AR2 holds output base address
  +D    ;                        // add byte offset
  LAR2  ;
  L     DIW [AR2,P#0.0];         // fetch output element
  T     #OutputValue;
END_FUNCTION

Walk-through

  • L P##InputTable ; LAR1 loads the address of the first byte of the temporary copy of the ANY parameter. The four subsequent reads (W [AR1,P#2.0], W [AR1,P#4.0], D [AR1,P#6.0]) extract element-byte size, DB number, and intra-DB byte offset.
  • SRW 1 divides the element-byte count (e.g. 50 for 25 INT elements) by 2 to obtain the element count for the loop counter.
  • OPN DB [#DataBlock] opens the input DB. L D [AR1,P#6.0] ; LAR1 now loads AR1 with the byte offset within that DB so that DBW [AR1,P#0.0] reads the first breakpoint.
  • The same sequence is repeated for the output table, but with OPN DI. Because a separate DB number is opened for the output, IN_TBL and OUT_TBL may now live in completely different DBs. This is the key behavioural fix over FC103.
  • The loop uses the LOOP instruction which decrements the low byte of ACCU1 and jumps if non-zero. The accumulator word Index therefore counts down from N to 1.
  • On match (JC calc) the remaining iterations are computed as Length - Index which gives the index of the matched element. Multiplying by the stride (P#2.0 = 2 bytes) yields the byte offset into the output DB. The output pointer AR2 is offset and the corresponding element is loaded.

STL Replacement - DINT Variant

For DINT, REAL, DWORD or any 32-bit element the only changes are: (1) replace SRW 1 with SRW 2 in the length calculation, (2) replace DBW and DIW with DBD and DID, (3) change the stride to +AR1 P#4.0, and (4) declare InputValue : DINT; OutputValue : DINT;. The behaviour for exact match becomes comparison-of-32-bits; if the platform requires REAL semantics, declare both as REAL and replace ==I with the equivalent unsigned compare on the IEEE bit pattern.

FUNCTION FC 201 : VOID
TITLE = 'Linear Lookup DINT / REAL - exact match, dual ANY pointer'
VAR_INPUT
  InputTable  : ANY;
  OutputTable : ANY;
  InputValue  : DINT;
END_VAR
VAR_OUTPUT
  OutputValue : DINT;
  Error       : BOOL := TRUE;
END_VAR
VAR_TEMP
  Length     : WORD;
  DataBlock  : WORD;
  Index      : WORD;
END_VAR
BEGIN
  CLR   := #Error;
  //------- Resolve InputTable pointer -------
  L     P##InputTable;
  LAR1  ;
  L     W [AR1,P#2.0];
  SRW   2;                       // 4 bytes per element
  T     #Length;
  L     W [AR1,P#4.0];
  T     #DataBlock;
  OPN   DB [#DataBlock];
  L     D [AR1,P#6.0];
  LAR1  ;
  //------- Resolve OutputTable pointer -------
  L     P##OutputTable;
  LAR2  ;
  L     W [AR2,P#4.0];
  T     #DataBlock;
  OPN   DI [#DataBlock];
  L     D [AR2,P#6.0];
  LAR2  ;
  //------- Loop through input table -------
  L     #Length;
Top: T     #Index;
  L     DBD [AR1,P#0.0];
  L     #InputValue;
  ==D   ;                         // exact match on 32 bits
  JC    calc;
  +AR1  P#4.0;
  L     #Index;
  LOOP  Top;
  //------- No-match path -------
  SET   := #Error;
  BEU   ;
  //------- Match-found path -------
calc:L     #Length;
  L     #Index;
  -D    ;                         // DINT arithmetic
  L     P#4.0;
  *D    ;
  TAR2  ;
  +D    ;
  LAR2  ;
  L     DID [AR2,P#0.0];
  T     #OutputValue;
END_FUNCTION

REAL Match Considerations

For REAL tables the exact-match semantics are fragile because an input converted from a measurement rarely produces an identical IEEE-754 bit pattern. Production systems should use the INT/DINT variant to look up the lower breakpoint and the next-higher breakpoint, then perform a one-shot linear interpolation in SCL or STL. The INT code above is intentionally an exact-match engine so that it can be re-used as a recipe selector, dip-switch decoder, or grayscale/encoder map without modification.

ANY Pointer Format Reference

The ANY pointer argument to FC200/FC201 above is entered in STEP 7 in the form:

P#<DB or Process Area><Byte>.<Bit> <ElementType> <Length>

Examples that have been verified against the S7-300/400 firmware:

Pointer Literal Interpretation Length Field Returned
P#DB100.DBX0.0 BYTE 100 100 bytes of raw data 16#0064 (100)
P#DB100.DBX0.0 WORD 100 100 words = 200 bytes 16#0128 (300 = bytes? actually doubled for length code 4; see note)
P#DB100.DBX0.0 DWORD 100 100 dwords = 400 bytes 16#0258 (600)
P#DB200.DBX40.0 BYTE 50 50 bytes = 25 INTs 16#0032 (50)
P#M 100.0 WORD 10 10 words starting at MW100 16#0014 (20 bytes)
Note: The length field in the ANY header stores the total byte count for data-type codes BYTE/WORD/DWORD, not the element count. The FC above divides by the per-element byte size (SRW 1 for INT, SRW 2 for DINT/REAL) to recover the element count for the LOOP instruction. If you pass the symbol name of a STRUCT or ARRAY declared in a DB, STEP 7 will automatically construct the correct ANY pointer at call time.

Migration to S7-1200 and S7-1500

FC103 is not present in the S7-1200/1500 instruction set; the only Siemens-published substitute is the "Table" instruction found under Extended Instructions > Comparison functions in TIA Portal V17 and later. That instruction is limited to DINT lookup keys and is functionally equivalent to a one-shot SCL FOR loop. For full control over the lookup, write an SCL block:

FUNCTION_BLOCK FB_CDT
VAR_INPUT
  InTbl   : ARRAY[*] OF DINT;
  OutTbl  : ARRAY[*] OF DINT;
  InVal   : DINT;
END_VAR
VAR_OUTPUT
  OutVal  : DINT;
  Matched : BOOL;
END_VAR
VAR
  i : DINT;
END_VAR
BEGIN
  Matched := FALSE;
  OutVal  := 0;
  FOR i := 0 TO TO_DINT(UPPER_BOUND(ARR := InTbl)) DO
    IF InTbl[i] = InVal THEN
      OutVal  := OutTbl[i];
      Matched := TRUE;
      EXIT;
    END_IF;
  END_FOR;
END_FUNCTION_BLOCK

For ordered (ascending) tables the search can be reduced from O(N) to O(log N) using a binary-search variant of the same template. The IEC standard does not specify a CDT primitive, so each implementation must be validated against the relevant safety class before deployment in SIL-rated applications.

Verification and Commissioning Checklist

  1. Build a test data block with a known set of input/output pairs. Include boundary cases (lowest, second-lowest, highest, second-highest).
  2. Place an OB1 call to the FC and force IN to every input value. Verify that OUT matches the expected output for each.
  3. Force a value that is NOT in the input table. Confirm that Error rises and OutputValue is unchanged from the previous successful match.
  4. Cycle the CPU power and confirm that the arrays retain their actual values (initial-value vs actual-value semantics).
  5. Add a programming-error OB (OB121) if not already installed - a CPU STOP on an uninitialised OUT pointer may otherwise go unnoticed.
  6. Document in the project README that the FC does not behave like FC103: it does not interpolate, it returns an error on no-match, and IN_TBL/OUT_TBL may live in distinct DBs.

Frequently Asked Questions

Why does FC103 return a wrong value with RET_VAL = 0?

The block opens only one DB from the IN_TBL pointer and reuses the resulting byte offset to index OUT_TBL. If IN_TBL and OUT_TBL reside in different DBs the output address points back into the input DB and the value returned corresponds to the input breakpoint, not the output. RET_VAL stays 0 because no parameter-validation error was detected. Place both tables in a single DB or migrate to the STL replacement above.

How do I migrate an FC103 lookup to an S7-1200?

FC103 does not exist in the S7-1200 instruction set. Port the lookup either as an SCL FOR-loop scanning an ARRAY[*] OF DINT, or as a call to the TIA Portal "Table" instruction under Extended Instructions. Test every breakpoint including the boundaries and the no-match path before relying on the result.

Why does FC103 ignore changes to the first entry of IN_TBL?

FC103 reads the length from the FIRST element of the table on every call. The HELP text requires this entry to be initialised at configuration time. If you edit the length online, the change is visible immediately, but if you re-download the block with a different initial value the actual value at runtime may not match unless you perform a complete CPU restart or a Reset to factory settings followed by re-download.

What is the maximum table size for an ANY pointer passed to FC200?

For S7-300/400 the ANY length field is a 16-bit unsigned count, so the theoretical maximum is 65 535 elements for any per-element size that fits in two bytes. Practical limits are imposed by available DB memory (see L DBLG) and by the cycle-time budget - a single LOOP-based scan is O(N) and consumes approximately N * 2 us of CPU time on an S7-314, so a 1024-element array adds ~2 ms per call.

Can FC200 perform interpolation between two adjacent breakpoints?

The reference implementation performs an exact match. To add linear interpolation, return both the lower-matched index and the next-higher index, then compute Out = OutLow + (In - InLow) * (OutHigh - OutLow) / (InHigh - InLow) in SCL or with MUL/DIV instructions in STL. Test with degenerate cases where In equals exactly InLow or InHigh to avoid divide-by-zero faults.

Back to blog