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:
-
VariantGetrequires the destination type to match the pointed-to type. A mismatch produces a runtime error and setsENO := FALSE. - The local
arrRealis a trueARRAYin 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
- Declare a new FB in the TIA Portal project tree. Right-click "Program blocks" > "Add new block" > "Function block".
- Add an
InOutparameter namedipDataof typeVARIANT. The compiler accepts this directly. - In the
Staticsection, declare one local array per supported element type, sized to the maximum expected length. Example:arrReal : ARRAY[0..1023] OF REAL;. - Insert the
TypeOfElementsinstruction. Branch on the result code with aCASEstatement in SCL. - In each branch, call
VariantGetto copy the source into the matching local array. - Index the local array normally. Apply the algorithm.
- If the FB must write back, perform the algorithm into the local array, then call
VariantPutat the end with the sameipDatareference. - Monitor the
ENOoutput ofVariantGet/VariantPut; on a type mismatch or length overflow,ENOis reset and the local diagnostics word receives a status code.
Verification Procedure
After compiling and downloading, perform these checks in the online view:
- Watch table: create a tag of type
ARRAY[0..9] OF REALin a global DB. In a watch table, set theipDatainput to"MyDB".MyArrayusing the absolute operand entry - do not pass a literal, theVARIANTmust point to an actual tag. - Force a single cycle with the variant unset. Confirm the FB does not crash and the
VariantGetreturnsENO := FALSE(or that theTypeOfresult is 0, which means "invalid/nil"). - Set the variant to point to a single
REALtag (not an array). TheTypeOfElementsresult for a non-array is the same as its own data type code; verify the FB rejects it if the design requires an array. - Set the variant to a too-small array (e.g.
ARRAY[0..4] OF REAL).CountOfElementsmust return 5, and the localVariantGetmust complete only the first five elements of the localarrRealor trigger a length error - test both behaviors against the design. - 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 |
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 TInOut. Smallest code, full compile-time checks, indexes directly. - Single element type, variable length: use
ARRAY[*] OF TInOutwithCountOfElements. 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
VARIANTInOut, dispatch onTypeOf/TypeOfElements, and resolve withVariantGet/VariantPut. Heaviest code, but most flexible. - Generic library block reused across projects: use a
VARIANTInOutso 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.