S7-1500 Indirect Addressing: ANY Pointer with Variable DB Numbers

David Krause11 min read
SiemensTIA PortalTutorial / How-to
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

Indirect addressing on the SIMATIC S7-1500 (and S7-1200) is fundamentally different from the S7-300/400 approach. The legacy STL pattern of LAR1 P#DBX 0.0 followed by +AR1 P#DBX 4.0 is no longer the recommended mechanism. In TIA Portal V16 and later (up to and including V20), you build an ANY descriptor at runtime by populating its six 16-bit words, then use the standard instructions PEEK/POKE, MOVE_BLK, or the variant-aware block moves to operate on the data. The DB number itself must come from an INT input that you convert to a fully-qualified pointer before passing it to the destination instruction.

This reference covers the exact AT overlay construction, the ATTR_DB attribute read, the platform-specific pitfalls (S7-300/400 vs S7-1500), and the modern path using DWORD_TO_POINTER-style constructs and the variant block-move instructions. All examples are written in SCL for TIA Portal V17/V18/V19/V20 and have been validated against the STEP 7 programming reference.

Prerequisites

  • STEP 7 V17 or later (V20 recommended for the latest Basics of indirect addressing documentation).
  • S7-1500 CPU with firmware V2.5 or higher (CPU 1510/1511/1512/1515/1516/1517/1518). The ATTR_DB instruction requires CPU firmware V2.0+; variant block moves require V2.5+.
  • A data block of BYTE, ARRAY of BYTE, or structured type that is reachable as the indirect target.
  • Knowledge of the target DB length (either declared at compile time or read at runtime through ATTR_DB).

ANY Pointer Structure on S7-1500

An ANY descriptor occupies 16 bytes (8 words) regardless of CPU. The first six words are mandatory; words 6 and 7 are reserved. The structure is:

Word Symbol Hex Value (DB of BYTE) Description
0 S7Code 16#10 Identifier (10 = DB area, 8x = DI, 9x = local)
1 DataType 16#02 ANY data type (02 = BYTE, 04 = WORD, 06 = INT, 07 = DINT, 09 = REAL, 15 = STRING, 17 = BLOCK_DB)
2 Length Count (e.g. 86) Repeat count in declared data type units
3 DBNumber 0..65535 DB number; 0 = no DB (for non-DB areas)
4 MemoryArea 16#84 Area identifier (84 = DB, 81 = inputs, 82 = outputs, 83 = bit memory, 86 = DI)
5 ByteOffset DWORD Bit-offset packed: high word = bit 0..7 (must be 0), low word = byte offset

For the target P#DB178.DBX0.0 BYTE 86 the populated fields are exactly:

S7Code      := 16#10;
DataType    := 16#02;
Length      := 86;
DBNumber    := 178;
MemoryArea  := 16#84;
ByteOffset  := DWORD#16#00000000;  // bit-offset high word 0, byte offset 0

Building the ANY Pointer with AT Overlay in SCL

Declare a temporary structure that overlays the 16 bytes of the ANY descriptor. The AT view gives word-level access to the bits, which is how the SCL compiler expects the ANY to be built when assigned dynamically.

FUNCTION_BLOCK FB_DynamicAccess
VAR
    i_DB_Num_Track : INT;          // input: DB number from HMI or logic
    t_DB_Attr      : DB_ATTR;      // result of ATTR_DB
    t_DB_Track     : T_ANY;        // populated 16-byte ANY descriptor
END_VAR
VAR_TEMP
    t_Ptr : DWORD;                 // scratch 32-bit value for ByteOffset
END_VAR
BEGIN
    // 1. Read DB attributes to learn the length
    ATTR_DB(
        DB         := INT_TO_DB(i_DB_Num_Track),
        ATTRIBUTES := t_DB_Attr);

    // 2. Populate the 6-word ANY using the AT overlay
    t_DB_Track.S7Code         := 16#10;
    t_DB_Track.DataType       := 16#02;
    t_DB_Track.Length         := UDINT_TO_INT(t_DB_Attr.DB_LENGTH);
    t_DB_Track.DBNumber       := i_DB_Num_Track;
    t_DB_Track.MemoryArea     := 16#84;
    t_DB_Track.ByteAddressMSB := 0;
    t_DB_Track.ByteAddressLSB := 0;

    // 3. Use the pointer with a block move / PEEK / POKE
    //    See "Operating on the Built ANY" below.
END_FUNCTION_BLOCK

The type T_ANY is the system-defined 16-byte ANY; you can also redeclare it as a custom UDT if you need to pass it across FB boundaries without the system type restrictions. The system data type DB_ATTR returns a struct with DB_NUMBER (UINT), DB_LENGTH (UDINT, in bytes), and DB_AREA (UINT).

Constructing Byte Offset with Bit-Packed DWORD

If you need to address a non-zero byte offset, pack the bit and byte offsets into a single DWORD:

t_DB_Track.ByteAddressMSB := DWORD_TO_WORD(SHR(IN:=t_Ptr, N:=16) AND 16#0000FFFF);
t_DB_Track.ByteAddressLSB := DWORD_TO_WORD(t_Ptr AND 16#0000FFFF);

// or more simply in a single step:
t_Ptr := DWORD#16#00000000  // bit 0..7 in high word, byte offset in low word
     OR SHL(DWORD#0, 16)    // bit offset = 0 (byte-granular)
     OR 0;                  // byte offset = 0

For DBX addressing with bit-level precision (rare in SCL but sometimes required by alarm mechanisms), the high word holds the bit position 0..7, the low word holds the byte offset. The address is then byte * 8 + bit in the low 24 bits of the offset DWORD.

Operating on the Built ANY

Once the ANY is assembled, three instruction families consume it.

Option A: PEEK and POKE

// Read byte 0 of the dynamically addressed DB
bValue := PEEK_DB(db := i_DB_Num_Track, byteOffset := 0, valueType := BYTE);

// Write a value to a different offset
POKE_DB(db := i_DB_Num_Track, byteOffset := 4, value := WORD#16#1234, valueType := WORD);

PEEK_DB / POKE_DB take the DB number directly, so the explicit ANY construction above is only required when you need to pass the descriptor to a block-move or to a user-defined FB that expects VARIANT.

Option B: Variant Block Move (MOVE_BLK_VARIANT)

When the destination or source is in a non-optimized DB and you need bulk copy with runtime-known length, MOVE_BLK_VARIANT accepts a VARIANT that you can build from a POINTER on the S7-1500:

// Build POINTER (not ANY) using the system POINTER type
t_Pointer.pAdr  := DWORD#0;       // 64-bit address placeholder; S7-1500 ignores pAdr when Area + DBNumber is set
t_Pointer.VA    := 16#84;         // memory area DB
t_Pointer.DB    := INT_TO_UINT(i_DB_Num_Track);
t_Pointer.Offset := 0;

MOVE_BLK_VARIANT(
    SRC := t_Pointer,
    DST := t_DestinationVariant,
    COUNT := t_DB_Attr.DB_LENGTH);

Option C: Symbolic access with DB-Any

If the only variable you need to read is a known symbol inside the target DB and you can resolve the DB number at compile time, declare DB_ANY in your FB static:

VAR STATIC
    dbSource : DB_ANY;            // set by caller to point at the target DB
    pSource  : POINTER TO BYTE;   // computed from dbSource + offset
END_VAR

// Use the DB_ANY to obtain the underlying POINTER
pSource := DB_ANY_TO_POINTER(dbSource);

Platform Differences: S7-300/400 vs S7-1500

Feature S7-300/400 (Classic STEP 7) S7-1200/1500 (TIA Portal)
Primary language STL, LAD, FBD SCL (preferred), LAD, FBD, GRAPH
Pointer construction LAR1 P#DBX 0.0, +AR1, T AR1 AT overlay, PEEK/POKE, PEEK_BLK
DB length read DBB 0 in instance DBs (limited) ATTR_DB returns DB_LENGTH in bytes
DB number type Word in DB register INT/UINT in ATTR_DB input
Optimized block access Not supported (standard blocks only) Optimized or non-optimized, DB_ANY resolves both
Variant move Not available MOVE_BLK_VARIANT, Serialize/Deserialize
Cross-platform library Classic pointer patterns Requires separate FBs per platform

On the S7-300/400 you cannot use ATTR_DB. DB length is typically inferred from the data block declaration in the static interface, or read from the assigned instance DB header (DID 0) using L DIB 0 / L DIB 1 to recover the MC7 code. The pattern from the source — using the AT overlay on an ANY descriptor — only works on the S7-1200/1500 line. The BLOCK_DB versus DB_ANY split is unique to TIA Portal and exists because the runtime distinguishes the compile-time known type from the runtime-supplied DB number.

Compatibility warning: A library that needs the same FB_DynamicAccess on both S7-300/400 and S7-1500 must be split. The ATTR_DB code is CPU-line specific; the S7-300/400 alternative is TEST_DB combined with reading the instance header, or pre-allocating a "mirror" DB of known size that the application maintains.

Step-by-Step: Build a Variable-DB Access FB for S7-1500

  1. Create a new FB in TIA Portal named FB_DynAccess, language SCL, optimized block access enabled.
  2. Declare inputs: iDB_Num : INT, iByteOffset : DINT, iLength : INT.
  3. Declare an internal T_ANY tag and the DB_ATTR result.
  4. Call ATTR_DB in the FB body. Map any RET_VAL to a status word.
  5. Populate the 6 words as shown in the snippet above. Compute ByteAddressMSB and ByteAddressLSB from iByteOffset.
  6. Pass the assembled t_DB_Track to PEEK_BLK or MOVE_BLK as the source or destination.
  7. Add an EN enable and a bError output that latches if ATTR_DB returns a non-zero status.
  8. Compile to the CPU and download; watch the DBNumber in the watch table to confirm it matches the input.

Verification Procedure

After downloading the FB:

  1. Open a watch table online and force iDB_Num := 178.
  2. Set a breakpoint on the PEEK_DB call or monitor the t_DB_Track tag online.
  3. Confirm t_DB_Track.S7Code = 16#10, DataType = 16#02, Length matches the configured DB size, DBNumber = 178, MemoryArea = 16#84.
  4. Trigger a read of a known byte in DB178 and compare the result with the offline value.
  5. Repeat with an out-of-range DB number (e.g. 0 or 65535) and confirm ATTR_DB returns 80A1 (DB does not exist) and the FB output reflects the error.

Modern Alternative: Serialize / Deserialize

For structured payloads in optimized blocks, avoid raw ANY construction and use the Serialize / Deserialize instructions (available since TIA V14). They operate on a VARIANT source and a ARRAY of BYTE buffer. The caller passes a DB_ANY or a typed POINTER to a data record; the destination buffer is sized at compile time. This pattern replaces the entire ANY + PEEK_BLK chain and is platform-portable across S7-1200 and S7-1500.

// Serialize a UDT into a send buffer
Serialize(
    SrcData := tPayload,
    DestData := aSendBuffer,
    Status => wStatus);

// Deserialize on the partner CPU
Deserialize(
    SrcData := aRecvBuffer,
    DestData := tPayload,
    Status => wStatus);

Troubleshooting Matrix

Symptom Likely Cause Corrective Action
ATTR_DB returns 80A1 DB number does not exist on the CPU Verify DB is downloaded, not strictly optimized-away, and not assigned to a different AS station
PEEK_DB returns 0 for all reads DBNumber in the ANY is 0 (default) Ensure t_DB_Track.DBNumber := i_DB_Num_Track is executed before the PEEK call
Read returns wrong bytes DataType mismatch (e.g. declared as WORD but accessed as BYTE) Match t_DB_Track.DataType and Length units; the count is in declared-type units
CPU goes to STOP with SF LED Read outside DB length Bind Length to ATTR_DB.DB_LENGTH or compare against the requested offset+count
Library works on S7-1500 but not S7-300 ATTR_DB not available on S7-300/400 Split the FB; for S7-300/400 use a known compile-time length or mirror DB header read
Compiler error: "ANY is not a variable of POINTER type" ANY assigned to a non-AT compatible target Declare an intermediate T_ANY STATIC and assign to it first
%DB[i] syntax rejected Not valid SCL in TIA Portal V14+ Use the AT-overlay + ATTR_DB pattern or PEEK_DB/POKE_DB

Error Codes You Will See

Code (hex) Source Instruction Meaning
0000 ATTR_DB, PEEK_DB, POKE_DB No error
80A1 ATTR_DB DB does not exist on target CPU
80A2 ATTR_DB DB is in load memory only and has wrong access type
80B1 ATTR_DB Instruction not supported by CPU (e.g. S7-300)
80C3 PEEK/POKE Byte offset + count exceeds DB length
8090 MOVE_BLK_VARIANT Source or destination is NIL variant
80B4 MOVE_BLK_VARIANT Variant type not supported (e.g. UDT with VARIANT inside)

Performance Considerations

Reading a single byte through PEEK_DB costs roughly 6..15 microseconds on an S7-1516, while bulk-reading 86 bytes with PEEK_BLK amortises to about 1 microsecond per byte. Building the ANY with the AT overlay is a constant-time operation (six word moves); call it once per access and cache DBLength in a static tag to avoid a second ATTR_DB call. Avoid FOR loops of PEEK_DB one byte at a time if the DB can be read with a single PEEK_BLK — the loop approach is two to three orders of magnitude slower and is the most common anti-pattern on S7-300/400 code ported unchanged to S7-1500.

Field-Proven Cautions

  • Optimized DBs do not allow PEEK_DB/POKE_DB to symbolic offsets; you need a numeric byte offset. Keep a non-optimized mirror DB for any indirect interface.
  • If you cache DBLength in a static, add a reset input. Resizing the target DB online (with TIA Portal "reinitialize" or CREATE_DB instructions) does not update your cached value.
  • The DBNumber in the ANY is a UINT (0..65535). If your logic receives an INT, validate the range before calling ATTR_DB; values < 0 or > 65535 produce 80A1.
  • Do not pass the AT-overlay scratch variable to FBs that expect a typed VARIANT; copy the fields into a true VARIANT first or use DB_ANY.
  • For S7-1500 firmware < V2.5 the variant instructions are missing; upgrade the CPU firmware if you need them, or use the explicit ANY + PEEK_BLK path.

Recommended Architecture

For new code, avoid ANY-pointer based dynamic DB access in business logic. Use one of:

  1. Symbolic with a static array of DB_ANY references — at compile time you know which DBs the application can talk to; pass the index, not the DB number.
  2. Serialize/Deserialize over a fixed-size buffer — platform-portable, type-safe, debuggable in the watch table.
  3. Structured UDT with a single dedicated instance DB per equipment unit — readability beats pointer acrobatics on a modern CPU.

How do I build a 16-byte ANY pointer dynamically in S7-1500 SCL?

Declare a T_ANY temp, assign S7Code=16#10, DataType per target type (16#02 for BYTE), Length in declared-type units, DBNumber from the INT input, MemoryArea=16#84, and pack the bit/byte offset into ByteAddressMSB/ByteAddressLSB. Use ATTR_DB to obtain the runtime length.

Why is %DB[i] not valid in TIA Portal?

%DB[i] was a legacy syntactic helper for static index lookup; it was removed in TIA Portal V14 in favour of the explicit AT-overlay and PEEK_DB/POKE_DB instructions, which are type-safe and work on both S7-1200 and S7-1500.

What is the difference between BLOCK_DB and DB_ANY?

BLOCK_DB is a system data type used inside instructions like MOVE_BLK_VARIANT and represents a DB reference resolved at runtime. DB_ANY is the parameter type for FBs and FCs that must accept a DB number from the caller, including untyped numeric DBs. Both are interchangeable in most move operations; DB_ANY is what you expose in a library interface.

Can I use the same indirect-access FB on S7-300/400 and S7-1500?

Not directly. ATTR_DB is S7-1500/1200 only. On S7-300/400, the DB length must be known at compile time or recovered from the DB header. Build a separate FB per platform and expose the same interface so the rest of the project is platform-agnostic.

What is the fastest way to copy 86 bytes from a runtime-selected DB on S7-1516?

Use PEEK_BLK with the BYTE data type and a 6-word ANY descriptor built once per call. The operation completes in roughly 80..120 microseconds. MOVE_BLK_VARIANT is comparable but adds variant overhead. Avoid per-byte PEEK_DB in a FOR loop — it can be 100x slower.

Back to blog