Siemens S7 FC ANY Pointer: Why DB Number Changes Between Calls

David Krause18 min read
S7-300SiemensTechnical Reference
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

Siemens S7 FC ANY Pointer: Why DB Number Changes Between Calls

A Siemens S7-300/400 Function (FC) is observed to initialise several data blocks. The same FC is called multiple times from the OB1 cycle, each call handing in a different DB number through an ANY input parameter. The DB number "changing" in the watch table is not the FC mutating any value; it is the call site overwriting the FC's input L-stack frame on every CALL. This article disassembles the snippet L P#0.0 / L DW#16#84000000 / OW / T LD [AR2, P#6.0], explains the area-byte code 0x84, shows the same logic in clean TIA Portal SCL, and provides a step-by-step diagnostic to confirm the call site is the actual source of the change.

1. Background: FBs, FCs, and the L-Stack

Siemens STEP 7 (Classic) and TIA Portal both pass block parameters through a temporary local data (L) frame that the system allocates at the call site. For an FC, the input area starts at L 0.0. For an FB, the system uses two frames: a DI (instance DB) for the static part, and a temporary L area for the dynamic part. The compiled code accesses input parameters by absolute L offsets that the compiler recomputes when the interface is edited.

AR2 is the corner stone of S7 indirect access. The CPU populates AR2 automatically:

  • At the start of an FB, AR2 = address of byte 0 of the current instance's DI block.
  • At the start of an FC, AR2 = address of byte 0 of the input interface in the FC's L-frame (the call site has copied the inputs there).
  • Inside an OB, AR2 retains the value it had on entry (typically the value set by the last FC/FB that completed).

See the Siemens STEP 7 Programming and Operating Manual, section on parameter passing and AR2.

AR2 is not preserved across OB calls unless the code explicitly stores and restores it. The STEP 7 compiler emits a save/restore around every FC/FB that uses AR2.

2. The Two Parameter Types Relevant Here

Type Size Use
POINTER 6 bytes Points to a single variable. 32-bit area-internal pointer (byte.bit) plus a DB number. Legacy from S5.
ANY 10 bytes Generic descriptor: type, repetition, DB number, area, byte offset, bit offset. Describes scalars, arrays, or whole DBs.

The FC has a parameter named _Ziel (German for "target") declared as ANY. The caller passes the address of a target area; the FC uses the descriptor to initialise the area.

3. Why the DB Number Appears to Change

A common observation when single-stepping a STEP 7 program in the watch table:

  1. First scan: the FC reads DB 506.
  2. Next scan: the same FC reads DB 508.
  3. Third scan: the FC reads DB 502.

The naive interpretation is "the FC changed the DB number". The correct interpretation is that the OB1 calls the FC three times, each with a different target. The L-stack of the FC is rebuilt on every CALL from the values supplied by the caller. Bytes 4–5 of the ANY (the DB number field) flip between 502, 506, and 508 because the call site toggles between them.

This is code reusability: one FC, many data blocks, identical initialisation. The pattern is canonical in S7-300/400 codebases with many parameter-driven data structures.

4. Anatomy of an S7 ANY Pointer (10 Bytes)

Offset Bytes Field Example (P#DB506.DBX0.0 BYTE 1)
0 0–1 Syntax ID 0x10 0x02 (W#16#1002, "ANY with 10-byte descriptor")
2 2 Data type 0x02 (BYTE)
3 3 Repetition factor 0x01 (1 element)
4 4–5 DB number 0xFA 0x01 = 506 (little-endian)
6 6 Memory area 0x84 (DB)
7 7 Bit offset in last byte 0x00 (byte-aligned)
8 8–9 Byte offset within area 0x00 0x00 (byte 0)

Note the mixed byte ordering. The first two bytes (syntax ID) and the DB number are little-endian words. The area/byte/bit pointer is the legacy S5 area-internal format, where bits 7–4 of byte 6 encode the area, bits 3–0 of byte 7 encode the bit offset, and bytes 8–9 are the byte offset.

4.1 Memory-Area Codes (Bits 7–4 of the Area Byte)

Hex Area Bit syntax Byte syntax Notes
0x80 P (periphery) P#Px.y P#Px Direct I/O read; bypasses process image
0x81 I (inputs) P#Ix.y P#Ix Process image of inputs
0x82 Q (outputs) P#Qx.y P#Qx Process image of outputs
0x83 M (merker / flag) P#Mx.y P#Mx Bit memory
0x84 DB P#DBx.DBXy.z P#DBx.DBBy Global / shared data block
0x85 DI P#DIx.DIXy.z P#DIx.DIBy Instance data block
0x86 L (local data) P#Lx.y P#Lx Temp L-stack of the current block
0x87 V P#Vx.y P#Vx Older S5-compatible area

Source: SIMATIC S7-300/400 STL Reference Manual.

5. Decoding the Snippet Line by Line

L   P#0.0
L   DW#16#84000000
OW
T   LD [AR2, P#6.0]
  1. L P#0.0 loads the constant 0x00000000 (byte 0, bit 0) into ACCU1. The previous ACCU1 moves to ACCU2 (discarded by step 2). The intent is to start from a known zero.
  2. L DW#16#84000000 loads the double-word constant 0x84000000 into ACCU1; the value from step 1 moves to ACCU2 (also discarded). The high byte 0x84 is the DB area code.
  3. OW ORs the low word of ACCU1 (0x0000) with the low word of ACCU2. Since both are zero, the result is still 0x84000000. The OR is defensive: if P#0.0 had a non-zero byte offset (for example P#10.0), the OR would preserve the lower three bytes (10.0) while setting the area byte to 0x84.
  4. T LD [AR2, P#6.0] transfers ACCU1 (a double word) to the L-stack at the address formed by AR2 + 6.0 bytes. Because AR2 points to the start of the FC's input interface, byte 6 is the start of the 32-bit area pointer inside the input ANY parameter _Ziel.

After the four lines run, the ANY descriptor in the FC's input interface reads:

[0-1]  syntax ID   = 0x1002   (preserved from call site)
[2]    type        = (preserved from call site)
[3]    rep         = (preserved from call site)
[4-5]  DB number   = (preserved from call site, e.g., 506)
[6-9]  area+offset = 0x84000000 (forced: DB byte 0, bit 0)

The caller's DB number and the area-byte injection coexist. The FC then uses the fully-assembled ANY to do its work, for example SFC 20 BLKMOV into the area.

6. The 0x84000000 Constant — A Closer Look

The constant is a 32-bit area-internal pointer in big-endian representation:

0x84 0x00 0x00 0x00
 |   |   |   |
 |   |   |   +-- bit 0 of byte 0
 |   |   +------ byte 0 (low byte of byte offset)
 |   +---------- byte 0 (high byte of byte offset)
 +------------- memory area = 0x84 = DB

Equivalent in classic notation: P#DB0.DBX0.0. The lower three bytes are zero, so the pointer resolves to "DB byte 0, bit 0" once a DB number is supplied separately (or, in a POINTER, by bytes 4–5 of the descriptor).

Why OR and not load? Because the call site may have placed a non-zero byte offset in the lower three bytes. A direct load of 0x84000000 would clobber that offset. The OR preserves the offset while forcing the area byte to 0x84.

7. AR2 in an FC — The Input Frame Pointer

The STEP 7 compiler emits a prologue for every FC that uses AR2 indirectly. The prologue saves the caller's AR2 onto the L-stack, then sets AR2 to point to the FC's input interface. The epilogue restores the caller's AR2 on BEA (block end).

Concretely, the FC sees:

AR2 --> L 0  : input parameter 0 (first byte)
         L 1  : input parameter 0 (second byte)
         ...
         L 6  : depends on parameter order (see below)
         L 7
         L 8
         L 9
         ...

For an FC with signature VAR_INPUT Mode : INT; Ziel : ANY, the input offsets in the L-stack are:

L offset Size Parameter
0.0 2 bytes Mode (INT)
2.0 10 bytes _Ziel (ANY)

The 32-bit pointer field within the ANY starts at L 8.0 (offset 2.0 + 6.0). So the snippet T LD [AR2, P#6.0] actually targets L 6.0 — i.e., the DB number field (bytes 4–5) of the ANY — provided the input ordering is exactly Mode then _Ziel. If the order were reversed, the offsets shift. The original author was using a relative offset to make the code robust against interface re-ordering in the same FC: a hard-coded T LD 8 would break if someone added a new input parameter before _Ziel.

When a new parameter is added to the FC interface, the absolute L offsets of all subsequent inputs shift. Using AR2-relative addressing makes the code immune to this; using absolute L offsets makes it brittle. Check the L offsets in the FC's compiled interface before assuming any byte number is correct.

8. How the FC Is Actually Called

A representative call chain in OB1 or a higher-level FC:

// Rcv buffer A
CALL  "INIT_RCV"
     Mode    := 1
     Ziel    := P#DB502.DBX0.0 BYTE 200

// Rcv buffer B
CALL  "INIT_RCV"
     Mode    := 1
     Ziel    := P#DB506.DBX0.0 BYTE 200

// Rcv buffer C
CALL  "INIT_RCV"
     Mode    := 1
     Ziel    := P#DB508.DBX0.0 BYTE 200

Each CALL line:

  1. Pushes the L-stack frame for the FC.
  2. Copies 1 into L 0..1 (Mode).
  3. Copies the 10-byte ANY P#DB502.DBX0.0 BYTE 200 into L 2..11 (Ziel).
  4. Sets AR2 = address of L 0.
  5. Jumps into the FC's code.

When the FC inspects bytes 4–5 of the ANY, it sees 502. The next CALL overwrites the L-stack with 506. The next with 508. The "logical connection" is just the OB calling the same FC three times with three targets.

9. Code Reusability — Why This Pattern Is So Common in S7

STEP 7 supports polymorphism only through explicit parameter passing. The FC + ANY pattern is the S7 way of writing "a function that operates on any data block". Benefits:

  • One body of code to maintain; no per-DB copy-paste.
  • Runtime selection of the data block, with the DB number decided by the caller.
  • Memory savings — one FC, many DBs, no instance-DB overhead.

Trade-offs:

  • Hand-built ANY pointers are error-prone. A bit mistake in the area byte yields SFC 20/21 error codes like W#16#80A1, W#16#80B1, or W#16#80C3.
  • Watch-table debugging is harder because the FC's behaviour depends on the call site.
  • Portability to TIA Portal requires a rewrite using Variant or POINTER with AT overlay.

10. Cleaner Implementations

10.1 Variant A — Pass a Complete ANY, Walk It with an AT Overlay

FUNCTION FC 100 : VOID
VAR_INPUT
   Ziel : ANY;
   Mode : INT;
END_VAR
VAR_TEMP
   info AT Ziel : STRUCT
       sID    : WORD;
       tCode  : BYTE;
       rep    : BYTE;
       dbNr   : WORD;
       area   : BYTE;
       bit    : BYTE;
       offset : DWORD;
   END_STRUCT;
END_VAR
BEGIN
   IF info.area <> 16#84 THEN RETURN; END_IF;
   // info.dbNr holds the DB number the caller passed
   // info.offset holds the byte offset within that DB
   // info.rep * sizeof(info.tCode) holds the byte length
END_FUNCTION

The AT overlay gives field access without manual pointer arithmetic. The compiler keeps the original ANY bytes intact; reading info.dbNr reads the same memory the caller wrote.

10.2 Variant B — SFC 20 BLKMOV with a Caller-Supplied ANY

CALL  SFC  20
     SRCBLK := P#MySource.Byte 0
     RET_VAL := #retVal
     DSTBLK  := #Ziel

SFC 20 reads the ANY at DSTBLK, extracts the DB number, area, and offset, and copies the source bytes into the target. The FC no longer needs to inspect the descriptor.

10.3 Variant C — Pass DB Number and Offset as Scalars

FUNCTION FC 100 : VOID
VAR_INPUT
   iDB  : INT;
   iOff : INT;
   iLen : INT;
END_VAR
BEGIN
   AUF  DB [#iDB];
   // Direct symbolic access from here on
END_FUNCTION

Loses the polymorphism of the ANY but is the most readable. Useful when the target is always a DB and the offset is always known.

11. Why Hand-Built ANY Pointers Were Common (And Why They Are Fading)

Hand-built ANYs in STL were the only way to realise "FC operates on caller-supplied DB" before the ANY parameter type was introduced in STEP 7 V3.x (1994). Many S7-300/400 programs migrated from S5 carry the pattern forward as legacy. The original author of the snippet probably inherited it from older code.

On S7-1200 and S7-1500 (TIA Portal), the compiler does the pointer work. Relevant changes:

  • Block interfaces can use Variant, a type-safe super-type of ANY that the compiler tracks symbolically.
  • The PEEK and POKE instructions in SCL give direct byte access through a Variant without manual area-byte assembly.
  • Optimised block access removes the absolute L-stack layout. Symbolic names are resolved at compile time and do not depend on offsets.

The same logic in SCL for S7-1500:

FUNCTION "INIT_RCV" : VOID
{ S7_Optimized_Access := 'TRUE' }
VAR_INPUT
   Ziel : Variant;
END_VAR
BEGIN
   IF IS_DB(#Ziel) THEN
       // Use VariantGet / VariantPut for typed access
   END_IF;
END_FUNCTION

See the TIA Portal – S7-1200/1500 Programming Reference for Variant semantics.

12. Common Pitfalls When Hand-Building ANY Pointers

Symptom Cause Fix
BLKMOV returns W#16#80A1 DB number in ANY is 0 or out of range Check bytes 4–5 of the input ANY at the call site
Data shifted by 1 byte Bit field of the area pointer set to non-zero Ensure bits 3–0 of byte 6 are zero
Works for DB506, fails for DB508 DB508 not loaded into the CPU; check SZL list 0x11 / 0x12 Load the DB in the S7 project; recompile the blocks
ACCU1 overwritten before the T LD [AR2, P#6.0] Wrong order of L / T operations; AR2 no longer points to the input area Keep all L / OW / T together; do not call any FB/FC that uses AR2 in between
Watch table shows the area byte as 0x00 The OW was forgotten, or the constant was loaded as a byte (B#) instead of DW Use DW#16#84000000, not B#16#84
FC reads the wrong parameter Input interface was edited; absolute L offsets shifted Use AR2-relative access (the original author's approach)
DB number cycles 506 → 508 → 502 in the watch table Normal — multiple CALL sites; not a bug Confirm the call sites in OB1 / calling FC
SFC 20 writes to the wrong area Area byte was 0x83 (M) instead of 0x84 (DB) Verify the constant: 0x84 for DB, 0x83 for M
OPN DB error W#16#80B1 DB number exceeds CPU work memory range Reduce DB number; verify SZL 0x11 / 0x12 for valid DB range
SFC 20 RET_VAL = W#16#8091 Source and destination ANY overlap Adjust one of the areas so they do not collide

13. Diagnostic Procedure

  1. Open the watch table that contains the FC and a few DBs.
  2. Right-click the FC and select "Monitor/Modify". The dialog shows the input parameters, including the ANY descriptor for _Ziel.
  3. Read the DB number from bytes 4–5 of the ANY. Note the value.
  4. Open a second watch table with OB1 (or the calling block) and single-scan. Each CALL line shows the actual arguments, including the ANY literal.
  5. Compare the DB number seen inside the FC to the DB number at the most recent CALL. They will match.
  6. To capture the value at the moment of the call, add the following diagnostic line inside the FC right after the OR:
L   W [AR2, P#4.0]    // bytes 4-5 of the input ANY
T   MW 200            // visible in the watch table

Reload and observe. MW 200 will track the active call's DB number, which is a 1:1 mirror of the call site's ANY.

14. Verification — How to Confirm the FC Is Correct

  1. Static check: open the FC's interface in STEP 7 and confirm _Ziel is ANY, not POINTER or INT.
  2. Cross-reference: in the symbol table or cross-reference (Ctrl+Alt+F7), search for the FC. The "Caller" column lists every call site. For each, double-click and inspect the input ANY.
  3. Online watch: monitor MW 200. It should cycle 502 → 506 → 508 as the OB runs through the three calls.
  4. Forced call: use a one-shot trigger in OB1 to call the FC only once with a known DB, then observe. The watch table will show that DB number stably until the trigger is removed.

15. Migration Path to TIA Portal

If the project is being ported from STEP 7 Classic to TIA Portal on S7-1500:

  1. Create the FC in TIA Portal. Mark it as "Optimised block access".
  2. Replace the ANY input with a Variant input. The compiler tracks the variant symbolically; the FC no longer needs to disassemble a 10-byte descriptor.
  3. Inside the FC, use IS_DB and TypeOf to validate the variant. Use VariantGet / VariantPut for typed reads and writes.
  4. Delete the hand-built OR line. The compiler in TIA Portal rejects untyped pointer arithmetic; keeping it requires switching off optimised access, which defeats the purpose of the migration.

If the project must stay on S7-300/400 (no S7-1500 hardware), keep the FC in Classic STEP 7. Apply Variant A from §10 to remove the hand-built OR while preserving the polymorphic interface.

16. Field-Proven Caveats

  • Watch-table cadence: the OB scan in S7-300/400 runs in OBs 1, 35, 100, etc. The watch table samples on a different cycle. A 1-second observation window can show the DB number "flipping" if multiple calls are packed into a single OB1 scan.
  • AR2 is not preserved across OB boundaries: if the FC is called directly from an OB, the compiler inserts a save/restore of AR2 around the call. If the FC is called from another FC, the caller must compile with AR2 enabled.
  • The OW is a no-op only when the lower three bytes of P#0.0 are zero: with P#0.0 this is always true. If the original author had loaded P#10.0 instead, the OR would keep the offset 10 and only force the area byte to 0x84.
  • AR1 vs AR2: AR1 is the "any-pointer" scratch register; AR2 is the "instance / input" pointer. Mixing them in an FC causes the compiler to flag the access as "AR2 not initialised" or generate incorrect code.
  • Optimised access: on S7-1500 with optimised blocks, AR2 is not used at all. The compiler resolves all symbolic access statically. Migrating an S7-300/400 FC that uses LD [AR2, P#x.y] to optimised S7-1500 access requires a rewrite of the indirect access.
  • SFC 20 RET_VAL codes: W#16#80A1 = source area error, W#16#80A2 = destination area error, W#16#80B1 = source DB does not exist, W#16#80B2 = destination DB does not exist, W#16#80C3 = source/destination overlap, W#16#8091 = nested depth exceeded. See the STEP 7 System Software manual.

17. Glossary

Term Definition
AR2 Address register 2. Holds the address of the current instance (FB) or input interface (FC).
ANY 10-byte pointer descriptor in STEP 7. Describes a data area (type, length, DB, offset).
POINTER 6-byte pointer in STEP 7. Points to a single variable. Legacy from S5.
Variant TIA Portal super-type of ANY. Type-checked at compile time.
L-stack Temporary local data area, used for block parameters and intermediate results.
DI Instance data block. Holds the static data of an FB.
SFC 20 BLKMOV. Copies a block of bytes from a source area to a destination area, both described by ANYs.
SFC 21 FILL. Fills a destination area (ANY) with a source pattern.
BLKMOV error codes W#16#80A1, 80A2, 80B1, 80B2, 80C3, 8091, 8092. See the STEP 7 System Software manual.

18. FAQ

Why does the DB number in the FC change between 506, 508, and 502?

The FC is a generic initializer called from multiple call sites. Each CALL pushes a different ANY (with a different DB number) onto the L-stack. The FC does not change the DB number; the caller supplies it. Watch the L-stack at the start of the FC — bytes 4–5 of the input ANY will show whatever DB the most recent caller passed.

What does the constant DW#16#84000000 do in the snippet?

It is the area-internal pointer prefix for the DB memory area. The high byte 0x84 selects "data block" in the area code, and the remaining three bytes are zero (byte 0, bit 0). OR-ing it with P#0.0 sets the area byte to 0x84 without disturbing the lower bytes. If the caller had passed P#DB506.DBX10.0, the OR would preserve the offset 10 and force the area byte to 0x84.

What is the role of AR2 in T LD [AR2, P#6.0]?

AR2 is the system-maintained register that, in an FC, points to the start of the input interface area on the L-stack. Adding P#6.0 offsets to byte 6 of the input interface. The actual relative offset to the area/offset field of the ANY depends on the order of parameters in the FC interface: with Mode : INT followed by Ziel : ANY, the area/offset field is at L 8.0 (offset 2.0 + 6.0). The original author may have used P#6.0 to target the DB-number field instead. Verify by inspecting the compiled interface.

Is the OR line necessary, or could the FC just use absolute addressing?

It is not strictly necessary. The author uses the OR pattern to make the code robust against FC interface re-ordering: if the offset of _Ziel changes because the interface is edited, the AR2-relative access still works. With absolute LD addressing (e.g., T LD 8) the code would silently break on interface edits. The OR itself only sets the area byte to 0x84; the offset and DB number come from the caller.

Can the same FC be reused for many DBs in TIA Portal on S7-1500?

Yes, and the recommended pattern is to declare the input as Variant and let the compiler generate the descriptor. S7-1500 symbolic access replaces the hand-built ANY with a checked descriptor, and the VariantGet / VariantPut instructions read/write the data. There is no need to manually OR the area byte.

How do I confirm in the watch table that the DB number is supplied by the call site?

Add L W [AR2, P#4.0] / T MW 200 inside the FC. Reload and watch MW 200. Then single-step through the calling OB and observe that MW 200 changes to match the DB number in each CALL's input ANY. The mapping is 1:1: the FC sees exactly what the caller passed.

What happens if the caller passes a wrong area code, for example 0x83 (M) instead of 0x84 (DB)?

The OR in the snippet forces the area byte to 0x84 regardless. The caller's area code is overwritten. If the FC was supposed to operate on M, the FC author would have used 0x83 instead. The hard-coded constant is intentional: it tells the FC "always treat the input ANY as a DB pointer". If the caller passes a different area, the FC will mis-interpret it.

Back to blog