1. Problem Overview
Engineers porting code from S7-300/400 or S7-1500 to the S7-1200 often rely on the AT-overlay trick that slices an ANY pointer into SyntaxID, DataType, Length, DBNumber, and StartAddress. On the S7-1200, the same trick applied to a VARIANT returns garbage because the S7-1200 runtime stores a VARIANT as a tagged pointer, not as the flat 10/12-byte ANY record used by S7-300/400.
Siemens ships a first-class solution: the VARIANT_TO_DB_ANY and DB_ANY_TO_VARIANT conversion instructions. They are the only sanctioned way to read the DB number from a VARIANT (or to build a VARIANT from a DB number) on S7-1200 firmware 4.x and newer. The SCL example in this article shows both directions with full error handling, EN/ENO propagation, and a commissioning verification block.
2. Why the Classic ANY Trick Fails on S7-1200
ANYTest : ANY;
ptrAny AT ANYTest : STRUCT
SyntaxID : BYTE; // B#16#10
DataType : BYTE; // 02h = BYTE, 05h = INT ...
Length : INT; // count in bits/bytes/words
DBNumber : INT; // 0 for non-DB areas
StartAddress : DWORD; // P#DBX x.y encoded
END_STRUCT;
On the S7-1200, VARIANT is implemented as a tagged pointer whose first bytes are a type tag, not the S7-300 ANY header. The result of an AT overlay is therefore not the DB number but a scrambled mix of type tag and the first bytes of the address payload. This is why the source's author observed nonsense values on FW 3.0 and could not get a deterministic answer even on a fresh FW 4.0 project without using the official instructions.
| Property | S7-300/400 ANY | S7-1200/1500 VARIANT |
|---|---|---|
| Storage | 10 bytes (12 with extended area) | Tagged pointer, size depends on payload |
| DB number offset | Byte 4-5 (INT) | Not byte-addressable; must be queried via instruction |
| Type tag | Byte 1 (constant table) | First 1-2 bytes, type-tag specific |
| Bit-accessible? | Yes (with restrictions) | No at SCL level |
| Querying DB number | Direct read from ptrAny.DBNumber
|
VARIANT_TO_DB_ANY instruction |
3. The Official Toolset: DB_ANY and Variant Conversion Instructions
Siemens exposes two complementary instructions in the TIA Portal "Basic instructions > Conversion operations > Variant conversion" catalog:
-
VARIANT_TO_DB_ANY - Returns the DB number (as a
DB_ANY) referenced by a VARIANT. Use this to answer the original question "extract the DB number from a VARIANT". -
DB_ANY_TO_VARIANT - The inverse: takes a
DB_ANYand produces a VARIANT. Use this to programmatically build a VARIANT from a known DB number (this is the modern replacement for the legacy ANY construction pattern).
Both instructions live in the "Basic instructions" catalog under TIA Portal V15.1 and later and are available in the S7-1200 program editor without any additional library install.
4. VARIANT_TO_DB_ANY - Parameter Reference
| Parameter | Direction | Type | Description |
|---|---|---|---|
| IN | Input | VARIANT | The VARIANT pointer to inspect. Can be a tag, an IN/OUT/STAT parameter, or a multi-instance. |
| RET_VAL | Output | DB_ANY | DB number of the referenced data block. 0 = NULL pointer, no DB associated, or the VARIANT does not point into a DB area. |
| ENO | Output | BOOL | FALSE if the instruction cannot resolve the DB (corrupt VARIANT, version mismatch). TRUE on success. |
Return-value semantics that matter in the field:
- If the VARIANT points at a tag inside a non-optimized DB, RET_VAL returns the DB number 1..65535.
- If the VARIANT points at a tag inside an optimized DB, the instruction still returns the DB number - optimization affects symbol access, not DB number resolution.
- If the VARIANT is a literal
NULL, RET_VAL = 0 and ENO = FALSE. - If the VARIANT points at a local stack (Temp), RET_VAL = 0 and ENO = FALSE. The DB number is not exposed for local tags.
- If the VARIANT points at a PI/PQ area, RET_VAL = 0 and ENO = FALSE.
5. DB_ANY_TO_VARIANT - Parameter Reference
| Parameter | Direction | Type | Description |
|---|---|---|---|
| DB | Input | DB_ANY | The DB number to wrap into a VARIANT. Use a literal (e.g. 5) or a tag typed as DB_ANY. |
| RET_VAL | Output | VARIANT | The resulting VARIANT pointing at the start of the referenced DB. |
| ENO | Output | BOOL | FALSE on invalid DB number (out-of-range, not loaded). TRUE on success. |
Use this instruction when the original problem of "construct a VARIANT programmatically" needs to be solved without resorting to AT-overlay manipulation of an ANY. The resulting VARIANT always points at offset 0 of the DB; dereference sub-fields with VARIANT_GET / VARIANT_PUT.
6. Step-by-Step SCL Implementation
6.1 Prerequisites
- CPU: S7-1200, firmware 4.0 or newer (verify in online > diagnostics > CPU information). S7-1500 is supported on FW 1.0+.
- Software: TIA Portal V15.1 or newer. V17+ is recommended for fully consistent help content.
- The DB whose number you want to read must be known to the block as a typed reference (e.g. as an IN parameter of type VARIANT). The instruction cannot reverse-engineer a DB number from a raw pointer.
6.2 Extract the DB number from a VARIANT input
- Declare an FB with one
INPUTof typeVARIANT(e.g.IN_vRef : VARIANT). - Declare a
Temptag of typeDB_ANY(e.g.tDBno : DB_ANY). - Drag
VARIANT_TO_DB_ANYfrom the catalog into the SCL body. - Wire
INtoIN_vRef, captureRET_VALintotDBno. - Use
tDBnoas you would a normal DB number (e.g. as input toDB_ANY_TO_VARIANTor to index an array of DBs).
// SCL fragment - S7-1200 / S7-1500, FW 4.0+
FUNCTION_BLOCK "fbExtractDBno"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
inVariant : VARIANT; // pointer to be inspected
END_VAR
VAR_OUTPUT
outDBno : DINT; // 0 = none, >0 = DB number
outValid : BOOL; // ENO mirror for downstream logic
END_VAR
VAR_TEMP
tDBany : DB_ANY;
tENO : BOOL;
END_VAR
BEGIN
// Step 1: query the DB number via the official instruction
tDBany := VARIANT_TO_DB_ANY(IN := #inVariant,
ENO => #tENO);
// Step 2: surface the result. ENO FALSE means no DB to report.
IF #tENO THEN
#outDBno := DWORD_TO_DINT(tDBany);
#outValid := TRUE;
ELSE
#outDBno := 0;
#outValid := FALSE;
END_IF;
END_FUNCTION_BLOCK
6.3 Build a VARIANT from a known DB number
- Declare an
INPUTof typeDB_ANY(e.g.inDBno : DB_ANY). - Declare a
TemporSTATtag of typeVARIANTto receive the wrapped pointer. - Call
DB_ANY_TO_VARIANT; check ENO; use the resulting VARIANT withVARIANT_GET/VARIANT_PUTor pass it to other FBs.
// SCL fragment - construct VARIANT from a DB number
FUNCTION_BLOCK "fbWrapDBasVariant"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
inDBno : DB_ANY; // e.g. 5 for DB5
END_VAR
VAR_OUTPUT
outVariant : VARIANT; // wrapped pointer for downstream use
outOK : BOOL;
END_VAR
VAR_TEMP
tENO : BOOL;
END_VAR
BEGIN
#outVariant := DB_ANY_TO_VARIANT(DB := #inDBno,
ENO => #tENO);
#outOK := #tENO;
END_FUNCTION_BLOCK
6.4 Full round-trip block (extract then rewrap)
The combined use case is common: a generic block receives a VARIANT, logs the DB number, and later needs to pass the same DB into another generic block that takes a VARIANT. The clean way is to keep the original VARIANT as a STAT and rewrap the DB number only when the receiving block cannot accept a VARIANT directly.
// SCL fragment - round-trip: VARIANT -> DB number -> VARIANT
FUNCTION_BLOCK "fbVariantRoundTrip"
VERSION : 0.1
VAR_INPUT
inVariant : VARIANT;
END_VAR
VAR_OUTPUT
outVariant : VARIANT;
outDBno : DINT;
outEqual : BOOL; // did the rewrap point at the same source?
END_VAR
VAR_TEMP
tDBany : DB_ANY;
tENO1 : BOOL;
tENO2 : BOOL;
END_VAR
BEGIN
tDBany := VARIANT_TO_DB_ANY(IN := #inVariant,
ENO => #tENO1);
IF #tENO1 THEN
#outDBno := DWORD_TO_DINT(tDBany);
ELSE
#outDBno := 0;
END_IF;
#outVariant := DB_ANY_TO_VARIANT(DB := tDBany,
ENO => #tENO2);
#outEqual := #tENO1 AND #tENO2;
END_FUNCTION_BLOCK
7. Field-Proven Caveats and Edge Cases
| Symptom | Likely cause | Resolution |
|---|---|---|
| RET_VAL always 0 on a tag inside DB100 | VARIANT was created from a literal NULL or from a Temp variable |
Verify the call site is passing the actual DB tag symbol, not NULL
|
| ENO = FALSE on every call | CPU is on FW 3.0 or older; instruction symbol is unresolved | Upgrade to FW 4.0 minimum; recompile the project |
| DB number is correct but the rewrapped VARIANT points at offset 0 | DB_ANY_TO_VARIANT always points at the start of the DB | Use VARIANT_GET / VARIANT_PUT with explicit field names after the rewrap |
| Compiler error "Instruction not found" | TIA Portal version too old or library not installed | Open the project in TIA Portal V15.1+ and refresh the catalog |
| RET_VAL is non-zero but the symbolic name does not resolve in the project tree | DB is in a different program block group / library | Right-click the project tree and use "Update block consistency" |
| Optimized vs non-optimized DB confusion | Optimized DBs hide the byte-level offset | Use symbolic access throughout; the conversion instructions handle both storage models transparently |
8. Firmware and Version Compatibility Matrix
| CPU family | FW minimum for VARIANT_TO_DB_ANY | FW minimum for DB_ANY_TO_VARIANT | Notes |
|---|---|---|---|
| S7-1200 | 4.0 | 4.0 | Source thread reports FW 3.0 does not work. FW 4.0 and newer confirmed. |
| S7-1200 G2 (second gen) | 1.0 | 1.0 | Inherits the S7-1500 instruction set; no FW gate |
| S7-1500 | 1.0 | 1.0 | All FW versions since launch |
| ET 200SP CPU | 1.0 | 1.0 | Same instruction set as S7-1500 |
Cross-portability: an SCL FB written for the S7-1500 and using these two instructions can be downloaded unmodified to an S7-1200 FW 4.0+. The block-interface types (VARIANT, DB_ANY) are identical between the two families.
9. Alternative Approaches and When to Use Them
9.1 Type-of-system functions
For granular inspection (element name, data type, length), use the TypeOf family: TypeOf, TypeOfDB, TypeOfElements, TypeOfDBNumber. TypeOfDBNumber returns the DB number of a VARIANT's referenced tag and is the most direct fit for the original problem on S7-1500. On S7-1200 FW 4.0+ the same function is available but the conversion instructions are preferred because they expose ENO for control flow.
9.2 Direct AT-overlay on a VARIANT
Not supported on S7-1200. The runtime stores the tagged pointer in an opaque format. Any attempt to AT-overlay a VARIANT into bytes is a misread and will return corrupt values. Use the official instructions only.
9.3 Legacy ANY on S7-300/400
Still the cleanest way to inspect an ANY pointer on the legacy families. The AT overlay from the source code is correct for those CPUs. If you need to consume the same logic on S7-1200, rewrite the consuming side to take a VARIANT and use the instructions above.
10. Verification and Commissioning Checklist
- Compile the project. The compiler will reject
VARIANT_TO_DB_ANYif the instruction is missing from the active TIA Portal version or the library. - Download to the CPU. The CPU must be in STOP if the change touches the OB1 call structure; otherwise RUN is fine.
- Place a watchpoint on
outDBnoand call the FB with a known DB tag (e.g."DB100".myTag). ConfirmoutDBno = 100andoutValid = TRUE. - Repeat the test with a literal
NULL. ConfirmoutDBno = 0andoutValid = FALSE. - Repeat with a Temp tag. Confirm
outDBno = 0andoutValid = FALSE(Temp has no DB). - Repeat with a PI/PQ operand. Confirm
outDBno = 0andoutValid = FALSE. - If the FB will be reused on S7-1500, repeat step 3 there. Results must be identical.
11. Common Diagnostic Questions
Q: Why does the S7-1200 reject the AT-overlay approach that works on S7-300/400?
A: The S7-1200 implements VARIANT as a tagged pointer, not a flat ANY record. The bytes you read out of an AT overlay are the type tag and address payload, not the S7-300 ANY header. The only sanctioned way to read the DB number is the VARIANT_TO_DB_ANY instruction.
Q: Can the DB number be obtained on S7-1200 FW 3.0?
A: No. The runtime API for VARIANT inspection was not completed on S7-1200 until FW 4.0. Upgrade the CPU or, if a CPU swap is not feasible, restrict the block to platforms that do support the query (S7-1500, ET 200SP CPU).
Q: Does the optimization attribute of the DB affect the DB number returned?
A: No. Optimization affects symbol access performance, not the underlying DB number. The instruction returns the same DB number for an optimized and a non-optimized DB.
Q: Can I use the result of VARIANT_TO_DB_ANY as a literal for the DB block parameter?
A: Yes, as long as the target instruction accepts a DB_ANY parameter. Most modern Siemens blocks (e.g. MOVE_BLK_VARIANT, Serialize, Deserialize) do. Pass a literal DB number with the prefix DB when wiring the parameter.
How do I get the DB number from a VARIANT on the S7-1200?
Call the VARIANT_TO_DB_ANY instruction (TIA Portal catalog: Basic instructions > Conversion operations > Variant conversion). Wire the VARIANT to the IN input and capture the RET_VAL output, which is typed as DB_ANY. RET_VAL = 0 means no DB (NULL, Temp, or PI/PQ). Requires S7-1200 firmware 4.0 or newer.
Does the S7-1200 support programmatic VARIANT construction like S7-300/400?
Not via the legacy AT-overlay trick on an ANY. The recommended replacement is DB_ANY_TO_VARIANT, which takes a DB_ANY DB number and returns a VARIANT pointing at offset 0 of that DB. The resulting VARIANT can then be used with VARIANT_GET / VARIANT_PUT for element-level access.
What is the minimum S7-1200 firmware for VARIANT DB-number queries?
Firmware 4.0. On firmware 3.0 the instruction is not present in the runtime and the AT-overlay returns garbage. S7-1500 has supported both VARIANT_TO_DB_ANY and DB_ANY_TO_VARIANT since firmware 1.0.
Why does RET_VAL = 0 even though the VARIANT points at a real DB tag?
Three common causes: (1) the call site passed a literal NULL, (2) the operand is a Temp or local-static tag (no DB), (3) the operand is in the PI/PQ process-image area. Inspect the call site and confirm the VARIANT was constructed from an actual DB tag symbol, not from a Temp or from a process I/O address.
Can the SCL block be reused on S7-1500 and S7-1200 without changes?
Yes. The block interface types VARIANT and DB_ANY are identical between the two families, and both conversion instructions exist in the shared instruction set from S7-1200 FW 4.0 onward. The same FB downloads and runs on both without recompilation, provided the firmware gate is met.