Extracting S7-1200 Variant Pointer Components in TIA Portal SCL

David Krause12 min read
SiemensTechnical ReferenceTIA Portal
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: The VARIANT Data Type in S7-1200 and S7-1500

The VARIANT data type is a typed pointer (reference) to another data object, used in the SIMATIC S7-1200 and S7-1500 families. According to the official Siemens documentation Overview of the VARIANT data type (S7-1200, S7-1500), a VARIANT does not store data itself - it points to the original data object and identifies its data type. The same TIA Portal reference confirms the key property that distinguishes VARIANT from the older ANY pointer used on S7-300/400: VARIANT is typified, meaning you can read the referenced data type at runtime.

Because the pointer is typified, the engineering question "how do I extract the components of a Variant?" is not a single-step dereference. Component extraction is mediated through a fixed set of system instructions. The S7-1200 system manual (revision 04/2012) lists the topic in section 4.4.9.3 "Variant pointer data type", and the current TIA Portal documentation Variant pointer data type documents that the VARIANT can point to structures and to individual structural components while occupying no space in instance memory.

Memory Model and Pointer Footprint

A VARIANT parameter on a function block occupies zero bytes in the instance DB. The runtime resolves the pointer to the actual operand only when an instruction that accepts a VARIANT input is executed. The runtime footprint of the pointer itself is implementation-internal, but for the application engineer the rule is: storing a VARIANT in an FB does not create a shadow copy of the data being pointed to.

Property VARIANT (S7-1200/1500) ANY (S7-300/400 legacy) POINTER (S7-300/400 legacy)
Typified at runtime Yes No No (typed by declaration only)
Can point to single tag Yes No (must be array/struct) Yes
Can point to ARRAY element Yes No Yes (with offset arithmetic)
Size in instance data 0 bytes (no shadow copy) 10 bytes 6 bytes
TypeOf / TypeOfElements supported Yes No No
S7-1200/1500 firmware required Firmware >= V4.0 (S7-1200), all S7-1500 Limited support Limited support

Because the VARIANT occupies no data memory, attempting to copy one with a plain assignment will not work - you must always pass it as an InOut or feed it to a system block that accepts a VARIANT reference.

Why Direct Component Access Is Not Exposed

The S7-1200/1500 runtime does not expose the dereferenced components of a VARIANT as plain identifiers inside SCL. The pointer metadata (target DB number, byte offset, data type code) is held in a runtime-internal structure that the application cannot inspect directly. The TIA Portal manual states that the VARIANT can point to a structure or to an individual structural component - but the only sanctioned way to read the pointed-to bytes is through the instructions in the following sections.

The Standard Extraction Toolbox

The TIA Portal instruction set provides a small, well-defined set of operations that take a VARIANT as input and produce useful information or copies. The most important are summarized below; all are documented in the TIA Portal "Basic instructions" and "Extended instructions" manuals.

Instruction Input Output Purpose
TypeOf VARIANT INT (data type code) Returns the data type code of the referenced object
TypeOfElements VARIANT INT (element type code) Returns the element type for an ARRAY
VariantGet VARIANT Static destination Copies the referenced data into a typed destination
VariantPut VARIANT Static source Copies data from a typed source into the referenced object
CountOfElements VARIANT UDInt Returns the array length for an ARRAY reference
MOVE_BLK_VARIANT Source/dest VARIANT Status Block copy between two variant references
Serialize / Deserialize VARIANT ARRAY of BYTE Marshals data through a byte stream
BLKMOV / PEK / PEL VARIANT-aware variants Status Block-move helpers

These are the only operations the runtime supports for inspecting or moving data through a VARIANT. The set is small by design - it lets the runtime enforce type safety even though the pointer itself is opaque to the programmer.

Extracting an Array via VariantGet

The most common use case is: a caller passes an array to an FB through an InOut VARIANT parameter, and the FB needs to read or modify elements. The standard pattern is to declare a typed local variable, test the type, and then call VariantGet.

FUNCTION_BLOCK FB_ArrayProcessor
VAR_IN_OUT
    ipData : VARIANT;        // caller passes an ARRAY[0..99] OF REAL
END_VAR
VAR
    arrReal : ARRAY[0..99] OF REAL;   // typed shadow
    i       : INT;
    iType   : INT;
    iSum    : REAL;
END_VAR

iType := TypeOf(in := ipData);
IF iType = 5 THEN   // 5 = REAL in S7-1200/1500 type table (single element)
    // Caller passed a single REAL, not an array - error path
    RETURN;
END_IF;

// For an array, use TypeOfElements
IF TypeOfElements(in := ipData) = 5 THEN
    VariantGet(src := ipData, dst := arrReal);
    FOR i := 0 TO 99 DO
        iSum := iSum + arrReal[i];
    END_FOR;
END_IF;

Notes on this pattern:

  • VariantGet requires the destination type to match the pointed-to type. A mismatch produces a runtime error and sets ENO := FALSE.
  • The local arrReal is a true ARRAY in instance data; it is what the FB logic can index. The variant itself is not indexable in SCL.
  • You must size the local array to the worst-case expected length, or guard the copy with CountOfElements.

Passing an Array as InOut Without a Variant

If the only requirement is "pass an array to an FB and let the FB read or write elements", a typed InOut on the array element type is usually the cleaner choice. TIA Portal fully supports passing an ARRAY[*] or a fully bounded ARRAY[lo..hi] as an InOut to an FB; the caller's full array is referenced (no copy).

FUNCTION_BLOCK FB_Sum
VAR_IN_OUT
    arrValues : ARRAY[*] OF REAL;   // variable-length array
END_VAR
VAR
    i   : UDInt;
    rSum : REAL;
END_VAR

rSum := 0.0;
FOR i := 0 TO (DINT_TO_UDINT(UDINT_TO_DINT(CountOfElements(in := arrValues)) - 1)) DO
    rSum := rSum + arrValues[i];
END_FOR;

This pattern is preferable to a VARIANT whenever the FB does not need to operate on multiple, type-varying inputs. The trade-offs are:

Criterion Typed ARRAY InOut VARIANT InOut
Type safety at compile time Full None (errors at runtime)
Can accept multiple element types No (one FB per type) Yes (one FB, dispatch on TypeOf)
Indexer on parameter Yes No - must VariantGet to local first
DB number / offset introspection Not possible Not possible at user level
Code size Smaller Larger (dispatch ladder)

Using a PLC Data Type (UDT) as the InOut

The most flexible production pattern is to define a PLC data type (UDT) that wraps the array plus a type tag, and pass the UDT by reference. The UDT must be compiled before the FB can use it as an InOut type. Example:

TYPE "UDT_Payload"
    STRUCT
        eDataType : INT;            // 1=REAL, 2=INT, 3=BOOL...
        aReal     : ARRAY[0..99] OF REAL;
        aInt      : ARRAY[0..99] OF INT;
    END_STRUCT;
END_TYPE
FUNCTION_BLOCK FB_PayloadHandler
VAR_IN_OUT
    stPayload : "UDT_Payload";
END_VAR
BEGIN
    CASE stPayload.eDataType OF
        1: // REAL payload path
            ;
        2: // INT payload path
            ;
    END_CASE;
END_FUNCTION_BLOCK

The wrapper approach gives full static type checking and is indexable inside the FB. It loses one feature of the pure VARIANT pattern - it cannot accept an arbitrary user-defined structure without expanding the UDT - but it is almost always the right choice for a fixed set of known payload shapes.

Dispatching on TypeOf: The Multi-Type Variant Pattern

When the FB must accept any of several element types, the canonical solution is a dispatch on TypeOf / TypeOfElements with a typed local for each branch. The data type codes used by S7-1200/1500 are documented in the TIA Portal help for the TypeOf instruction. The most common codes:

Code Type Code Type
1 BOOL 5 REAL
2 BYTE 6 LREAL
3 CHAR 7 TIME
4 INT 8 DINT

Always verify the active code table against the firmware documentation; the values are stable across current S7-1200 (V4.x) and S7-1500 firmware, but new element types (e.g. LWORD, USINT) extend the table.

LAD vs SCL: What the Editor Allows

In LAD/FBD, the only operations on a VARIANT are the system boxes (drag from the instruction catalog). There is no LAD coil that dereferences a VARIANT. In SCL, the language accepts TypeOf, TypeOfElements, CountOfElements, and the conversion functions, and supports implicit VARIANT typing in FB/FC parameter lists, but the syntax does not permit writing expressions such as ipData^.field or ipData[3]. The pointer must be resolved into a typed variable first. This restriction is intentional - it keeps the runtime in control of type checking.

Step-by-Step: Implement a Type-Aware Array FB

  1. Declare a new FB in the TIA Portal project tree. Right-click "Program blocks" > "Add new block" > "Function block".
  2. Add an InOut parameter named ipData of type VARIANT. The compiler accepts this directly.
  3. In the Static section, declare one local array per supported element type, sized to the maximum expected length. Example: arrReal : ARRAY[0..1023] OF REAL;.
  4. Insert the TypeOfElements instruction. Branch on the result code with a CASE statement in SCL.
  5. In each branch, call VariantGet to copy the source into the matching local array.
  6. Index the local array normally. Apply the algorithm.
  7. If the FB must write back, perform the algorithm into the local array, then call VariantPut at the end with the same ipData reference.
  8. Monitor the ENO output of VariantGet / VariantPut; on a type mismatch or length overflow, ENO is reset and the local diagnostics word receives a status code.

Verification Procedure

After compiling and downloading, perform these checks in the online view:

  1. Watch table: create a tag of type ARRAY[0..9] OF REAL in a global DB. In a watch table, set the ipData input to "MyDB".MyArray using the absolute operand entry - do not pass a literal, the VARIANT must point to an actual tag.
  2. Force a single cycle with the variant unset. Confirm the FB does not crash and the VariantGet returns ENO := FALSE (or that the TypeOf result is 0, which means "invalid/nil").
  3. Set the variant to point to a single REAL tag (not an array). The TypeOfElements result for a non-array is the same as its own data type code; verify the FB rejects it if the design requires an array.
  4. Set the variant to a too-small array (e.g. ARRAY[0..4] OF REAL). CountOfElements must return 5, and the local VariantGet must complete only the first five elements of the local arrReal or trigger a length error - test both behaviors against the design.
  5. Use the PLC online > "Block consistency" check to confirm no type warnings on the FB call sites.

Common Errors and Edge Cases

Symptom Likely Cause Remediation
ENO = FALSE on first VariantGet call Variant was never assigned (initial value is a NIL pointer) Check EN at the calling block; gate the FB with an explicit enable input
Compiler rejects "ARRAY[lo..hi] OF LREAL" in a UDT used as InOut UDT was not compiled before FB Compile the PLC data type first, then reopen the FB interface
FB indexes past the end of caller array Local copy is sized larger than the actual call Bound the loop with CountOfElements and use the smaller of the two lengths
Type code 0 returned unexpectedly Variant points to a TEMP variable that has gone out of scope Only pass variants to tags in global DBs, instance DBs, or I/O
VariantGet succeeds but values look wrong on a 1500 CPU byte order differs from S7-1200 for multi-byte types in some firmware versions Check the "Optimized block access" attribute and use the same attribute for source and destination
Cannot connect a Variant input in the editor The connected tag is a literal constant, not a variable Wire the input to a variable in a DB, instance, or I/O area
Safety note: Never assume a VARIANT parameter is valid. NIL or stale variants are legal runtime states and VariantGet / VariantPut will set ENO := FALSE without raising a diagnostic interrupt. Always gate the algorithm with an explicit enable and check the TypeOf return value before dereferencing.

Interoperability Notes Across Firmware

S7-1200 VARIANT support requires firmware V4.0 or later. The TypeOf / TypeOfElements / CountOfElements / VariantGet / VariantPut instructions were extended across the V4.x firmware line; MOVE_BLK_VARIANT and Serialize/Deserialize are reliable on V4.2 and later, with full coverage on all current S7-1500 CPUs. If the project must run on legacy S7-1200 V4.0, restrict the design to VariantGet / VariantPut / TypeOf / TypeOfElements and verify the helper block list in the TIA Portal help filter for that firmware.

S7-1500 and ET 200SP CPUs implement the same instruction set as the S7-1200 V4.4+; VARIANT semantics, type codes, and instance-memory behavior are identical. A library FB that targets VARIANT parameters can be reused across both families without modification.

Decision Flowchart for Selecting the Parameter Style

Use the following logic when designing the FB interface:

  • Single element type, fixed at design time: use a typed ARRAY[lo..hi] OF T InOut. Smallest code, full compile-time checks, indexes directly.
  • Single element type, variable length: use ARRAY[*] OF T InOut with CountOfElements. Compile-time type check, runtime length check.
  • Multiple element types, fixed set: define a UDT wrapper with a type tag and one array per supported type. Pass the UDT by InOut. Compile-time checks on the wrapper shape, runtime branch on the type tag.
  • Multiple element types, open set: use a VARIANT InOut, dispatch on TypeOf / TypeOfElements, and resolve with VariantGet / VariantPut. Heaviest code, but most flexible.
  • Generic library block reused across projects: use a VARIANT InOut so the library is independent of any caller-specific UDT.

Frequently Asked Questions

Can I index a VARIANT directly in SCL, the same way I index a typed ARRAY InOut?

No. SCL does not expose indexing on a VARIANT. You must call VariantGet to copy the referenced data into a typed local variable and then index that local. The runtime does not support ipData[i] syntax on a variant.

What is the difference between TypeOf and TypeOfElements for an ARRAY?

TypeOf returns the data type code of the operand itself; for an array the result is the code for the array construct (not the element type). TypeOfElements returns the data type code of the array elements, which is the value you want when dispatching on element type. TypeOfElements on a non-array returns the type code of the operand itself.

Why does the editor refuse to let me pass a constant to a VARIANT input?

A VARIANT is a typed pointer to a tag. Constants have no addressable location, so the pointer cannot be formed. Wire the input to a variable in a global DB, instance DB, or I/O area, or use a temporary tag in a higher-level block.

Do I have to declare a separate typed local array for every supported element type in my dispatch FB?

Yes, if you want to use VariantGet. The destination of VariantGet must be statically typed and the runtime enforces a strict type match. A common pattern is one local array per type, each sized to the worst-case length, with a CASE on the TypeOfElements result to select the right copy.

Does the VARIANT occupy any space in the FB instance DB?

No. The TIA Portal documentation states that the VARIANT pointer does not occupy memory in instance data; the pointer is resolved at runtime by the instructions that consume it. This is one of the main reasons VARIANT is preferred over the legacy ANY for S7-1200/1500 design.

Back to blog