Indexing S7-1500 Static Areas with PEEK/POKE in TIA Portal V19
The TIA Portal V19 STL editor refuses to compile an indexed ANY pointer that targets a Static area of a Function Block on S7-1500 CPUs. The compiler accepts the same construct against an external Global DB, accepts a literal ANY on a Static area, and accepts an indexed DB number on a Global DB. The single combination that fails is P#DB[#dbnr] DBX0.0 WORD n referenced against a Static member. This article documents the exact compiler rule, explains the underlying CPU architecture, and provides PEEK/POKE and BLKMOV workarounds that compile and execute reliably on a CPU 1515-2 PN (and equivalent) with TIA Portal V19.
Problem Statement
An engineer working in Statement List (STL) on a CPU 1500 attempts to write:
CALL MOVE
SRCBLK := P#DB[#dbnr] DBX0.0 WORD 2
RET_VAL := #retVal
DSTBLK := P##staticBuffer
The TIA Portal STL parser returns "The ANY pointer can only point to a data block area in the local instance." The same engineer confirms that #dbnr is correctly populated from the input area (for example, loaded with L DINO after an OPN instruction). Replacing WORD 2 with BYTE 4, DWORD 1, or REAL 2 produces the same diagnostic. The construct compiles cleanly when P##staticBuffer is replaced by a Global DB symbol, confirming the restriction is scoped to the Static area of an Instance DB.
The same engineer compares the behaviour with STEP 7 V5.x on an S7-300 CPU. There the construct compiles and executes. The S7-1500 path is therefore a deliberate change in the TIA Portal V19 STL grammar, not a regression.
Background: ANY Pointer Grammar in TIA Portal V19
The ANY pointer in the S7-1500 world is a 10-byte structure with the layout shown below. The TIA editor enforces this structure at compile time, the CPU interprets it at runtime, and the two are not symmetric in the S7-1500 architecture.
| Byte | Content | Notes |
|---|---|---|
| 0..1 | Syntax ID (10h for ANY) | Always 10h |
| 2..3 | Internal length / padding | Reserved by compiler |
| 4..5 | DB number (0 for non-DB areas) | 0..65535 |
| 6 | Area code (0x84 = DB, 0x86 = DI, etc.) | Set by TIA |
| 7 | Reserved | Always 0 |
| 8..9 | Byte offset (DINT, only low 24 bits used) | Bit offset << 3 |
| 10..11 | Type code (BYTE, WORD, DWORD, INT, REAL...) | Type-safe |
| 12..13 | Length in elements (WORD) | Triggers the compiler rule |
The STL literal P#DB100 DBX10.0 BYTE 20 is a parser convenience that pre-fills the structure at edit time. When the DB number is replaced by an indexed variable (#dbnr), the TIA parser cannot resolve the structure at edit time and refuses to emit the literal. The CPU would happily execute the resulting 10-byte block at runtime, but the editor will not assemble it.
According to Siemens Industry Online Support, the rule is enforced because the S7-1500 CPU recognises the configured Transmit areas of its connected partners at compile time. Allowing the editor to assemble an indexed ANY on a Static area would create a runtime situation where the CPU accepts the move against a buffer whose actual length is unknown to the FW. Siemens opted to reject the construct at edit time instead of at runtime.
Root Cause Analysis
Three independent causes converge to block the syntax:
- Literal restriction on Static members. The TIA Portal V19 STL grammar disallows an indexed DB number in a P# literal when the destination is a Static area of an FB. The grammar allows the indexed DB number for a Global DB operand.
- Block optimisation default. The S7-1500 series uses optimised blocks by default. An optimised Instance DB hides its absolute address layout from the user, so the editor cannot generate a valid offset/length tuple for the ANY structure. The user must explicitly disable the "Optimized block access" attribute on the FB.
- Length-safety check. The length field in the ANY structure (bytes 12..13) must match a declared length in the Static area. When the indexed DB number is taken from an HMI tag or arithmetic expression, the editor cannot verify the length and refuses the literal outright.
None of these three checks are CPU limitations. The CPU firmware accepts an ANY pointer whose DB number is computed at runtime and whose length field is arbitrary. The editor is the gatekeeper.
Compiler Behaviour Matrix: S7-300 vs S7-1500
| Construct | STEP 7 V5.x / S7-300 | TIA V19 / S7-1500 | Notes |
|---|---|---|---|
P#DB100 DBX0.0 BYTE 20 on Global DB |
Compiles | Compiles | External DB, any length |
P#DB[#dbnr] DBX0.0 BYTE 20 on Global DB |
Compiles | Compiles | Indexed DB number, literal offset |
P#DB100 DBX0.0 BYTE 20 on Static |
Compiles | Compiles | Literal ANY, non-optimised block |
P#DB[#dbnr] DBX0.0 BYTE 20 on Static |
Compiles | Rejected | Compiler enforces static type |
PEEK on Static (byte granularity) |
Limited | Compiles (STL/SCL) | Recommended workaround |
BLKMOV with constructed ANY |
Compiles | Compiles | Use for length-aware block moves |
The Instance DB itself must be non-optimised for the workarounds below to function. The setting is found in the FB properties under Attributes > Optimized block access. Un-checking the box forces TIA to retain the absolute offset map. Siemens Industry Online Support documents this attribute in the S7-1500 system manual as the switch that exposes the address layout to pointer instructions.
Solution 1: PEEK and POKE for Byte-Granular Static Access
The S7-1500 base instruction set provides PEEK and POKE variants that bypass the P# literal grammar entirely. They accept a runtime-evaluated DB number and a byte offset, and they read/write single bytes (or BOOL with the _BOOL overloads) against the address.
STL Example: Indexed Write to Static Array
// FB Inputs:
// #dbnr : INT - target Global DB number
// #offset : DINT - byte offset into the target DB
// #value : BYTE - value to write
// FB Static:
// ARRAY[0..31] of BYTE
// ARRAY[0..31] of BYTE indexed by #dbnr (HMI/PLC tag)
L #value
POKE DB [#dbnr], #offset, #value // 16-bit extended STL POKE
L #value
POKE DB [#dbnr], #offset // Byte-granular write
Note the syntax DB [#dbnr] - the brackets denote a runtime DB number, identical to the legacy AUF DB [#dbnr] grammar. This is the only context in TIA V19 STL where a runtime DB number may appear inside a memory instruction.
STL Example: Byte-Wise Bulk Read into Static Buffer
// Loop over 32 bytes; copy from DB[#dbnr] at #offset to #staticBuffer[i]
L 0
next: T #i
L #i
ITD
+ #offset
SLW 1
LAR1
PEEK DB [#dbnr], AR1, #tmpByte
L #i
ITD
LAR1
POKE DI [#i], AR1, #tmpByte // DI is the instance DB
L #i
+ 1
T #i
L #i
L 32
<I
JC next
Limitations of PEEK/POKE
- Byte-granular only. Word or DWORD access requires multiple PEEK calls plus manual byte swapping on little-endian S7-1500.
- PEEK/POKE do not honour the optimised-block protection. A read against an optimised block returns 0, not a protection violation.
- The DB number passed to
PEEK DB [#dbnr]must be in the valid DB range (0..65535). Out-of-range values are silently treated as DB 0.
Solution 2: BLKMOV with Runtime-Constructed ANY
For length-aware bulk transfer, build the source ANY pointer in a TEMP structure, pass it to BLKMOV together with a compile-time generated destination ANY for the Static area. The TIA Portal compiler accepts P##staticBuffer as a destination ANY; the source ANY must be constructed at runtime.
STL Example: Copy N Bytes from Indexed DB into Static Buffer
// FB TEMP layout (10-byte ANY structure):
// #tempSrc.syntaxID : BYTE = 0x10
// #tempSrc.pad : BYTE = 0
// #tempSrc.length : INT = runtime
// #tempSrc.dbNumber : INT = #dbnr
// #tempSrc.areaCode : BYTE = 0x84 // DB
// #tempSrc.reserved : BYTE = 0
// #tempSrc.byteOffset : DINT = 0
// #tempSrc.typeCode : INT = 0x11 // BYTE
L B#16#10
T #tempSrc.syntaxID
L 0
T #tempSrc.pad
L #byteCount
T #tempSrc.length
L #dbnr
T #tempSrc.dbNumber
L B#16#84
T #tempSrc.areaCode
L 0
T #tempSrc.reserved
L L#0
T #tempSrc.byteOffset
L W#16#11
T #tempSrc.typeCode
CALL BLKMOV
SRCBLK := #tempSrc
RET_VAL := #retVal
DSTBLK := P##staticBuffer
The asymmetry is intentional. The source ANY lives in TEMP because the DB number is runtime-evaluated; the destination ANY is generated by TIA at compile time as P##staticBuffer because the Static address is fixed for the FB instance. The CPU copies length bytes from the source DB starting at byte 0 into the Static buffer. retVal is 0 on success; non-zero indicates a length mismatch or invalid DB number.
Common Error Codes Returned by BLKMOV
| retVal | Meaning | Resolution |
|---|---|---|
| 0 | Success | No action |
| 0x80B1 | Source length < destination length | Reduce byteCount or pad source |
| 0x80B2 | Destination length < source length | Verify Static buffer size |
| 0x80B5 | Source ANY area code invalid | Set areaCode to 0x84 for DB |
| 0x80C3 | DB does not exist (PLCSIM only) | Create target DB in PLCSIM |
| 0x80C4 | DB exists but wrong length | Resize or re-map target DB |
Solution 3: DPRD_DAT and DPWR_DAT for PROFINET Consistency
For PROFINET slots with a configured transmit area, the S7-1500 provides consistent-data read/write instructions that bypass the ANY grammar entirely.
- DPRD_DAT - read consistent data from a partner slot
- DPWR_DAT - write consistent data to a partner slot
The Instance DB Static area must be exactly the same size as the configured transmit area, otherwise TIA Portal raises "Area length mismatch" at compile time. Siemens Support confirms: "A 1500 CPU knows about the configured Transmit areas of its connected parts." This is why size mismatches are blocked at edit time instead of at runtime.
STL Example: 32-Byte PROFINET Slot Read
// FB Static:
// ARRAY[0..31] of BYTE - exactly 32 bytes
// Input:
// #hwId : INT - hardware identifier of the slot
CALL DPRD_DAT
LADDR := #hwId
RET_VAL := #status
RECORD := P##inputSlot32
The instruction returns 0 on success. Common error returns are 0x8090 (LADDR invalid) and 0x80A0 (negative acknowledgment from IO controller). The HW identifier is found in the device properties under System constants > HW Identifier.
Instance DB Configuration Checklist
| Setting | Required Value | Where to Find |
|---|---|---|
| Optimized block access | Disabled | FB Properties > Attributes |
| Data block write-protected in PLCSIM | Off (PLCSIM only) | PLCSIM Options |
| Accessible from HMI/OPC UA | Enabled if HMI reads | FB Properties > Attributes |
| Knows about connected Transmit areas | Yes (default for S7-1500) | Compile output, not configurable |
| Compiler warnings for hidden address | Treat as errors during migration | Project Settings > Compile |
Troubleshooting Matrix
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Compiler error: "The ANY pointer can only point to a data block area in the local instance" | Literal P# with indexed DB on Static | Use POKE, BLKMOV with constructed ANY, or DPRD_DAT |
| PEEK returns 0 against an optimised block | Block is configured as optimised | Disable Optimized block access on the FB |
| BLKMOV returns 0x80B1 | Source ANY length exceeds destination Static size | Reduce #byteCount or extend the Static array |
| BLKMOV returns 0x80B2 | Destination Static size exceeds source | Verify length field matches Static declaration |
| DPRD_DAT returns 0x8090 | LADDR or HW ID invalid | Re-check device properties > System constants |
| DPRD_DAT returns 0x80A0 | IO controller NACK | Verify partner is online and not faulted |
| PEEK/POKE reads stale data | AR1 not re-loaded between calls | Re-load AR1 with TAR1 #destAddr before each PEEK |
| AREA LENGTH MISMATCH at compile time | Static area shorter than PROFINET transmit area | Resize Static array to match the configured slot |
| Code compiles on S7-300 but not S7-1500 | Compiler grammar differs across CPU families | Wrap the construct in an IF family = 1500 END_IF guard, or rewrite using the workarounds above |
Best Practices
- Choose the right primitive. PEEK/POKE for one-off byte writes, BLKMOV with constructed ANY for bulk transfers, and DPRD_DAT/DPWR_DAT for PROFINET consistency. Mixing them in the same FB obscures intent.
- Keep the Instance DB non-optimised. When an FB will use indexed ANY pointers, un-check Optimized block access on the FB. Document this requirement in the FB header comment so the next engineer does not re-enable it.
- Validate the runtime DB number. The S7-1500 does not raise a protection violation for an in-range but wrong DB number; it silently reads/writes. Add a whitelist check (#dbnr between 1 and 10, for example) before issuing the move.
- Document the ANY-pointer construction. Pointer-string assembly in TEMP variables is a frequent source of cut-and-paste errors. Add a comment block above the assembly explaining each field's purpose and value range.
- Test on PLCSIM first, then on real hardware. PLCSIM returns different error codes (0x80C3, 0x80C4) when the target DB is missing or the wrong size. A clean PLCSIM run is a strong indicator that the real CPU will accept the move.
-
Avoid dual-target code. The same STL source will not always compile identically on S7-300 and S7-1500. Maintain two FB variants if the project requires both, or use the compiler-conditional
{S7_1500}pragma to gate language extensions.
Verification Procedure
- Compile the FB in TIA Portal V19. No "pointer string invalid" or "ANY pointer can only point to a data block area in the local instance" errors should remain.
- Download to a CPU 1515-2 PN (or equivalent S7-1500 model) running firmware V2.9.x. PLCSIM V19 may be used for offline validation.
- Online > Monitor/Modify > trigger the indexed move with
#dbnr = 1,#byteCount = 32,#offset = 0. - Inspect the destination Static buffer (e.g., in a watch table) and compare against a known pattern in the source DB. A byte-by-byte diff should return zero.
- Sweep
#dbnrfrom 1 to 10 and confirm all 10 transfers complete withretVal = 0. - Force a deliberate mismatch (e.g., set
#byteCount = 64against a 32-byte Static buffer) and confirm BLKMOV returns0x80B1rather than overwriting adjacent memory. - Reset the Instance DB retention and re-run the sweep to confirm cold-start behaviour matches warm-start.
Field-Proven Caveats
Three additional findings from the original engineering thread and the Siemens support response are worth documenting before closing:
- DB_ANY conversion does not help. The user attempted to convert the DB number via DB_ANY and pass it as a parameter; the resulting POINTER/ANY still fails the same Static-area compiler check. DB_ANY is a transport type, not a binding for ANY construction.
-
L DINO + OPN is the canonical pre-step. Loading the DB number with
L DINOafterOPN DB [#dbnr]is the standard pattern; the failure is not in the load but in the subsequent ANY literal. The OPN instruction does not need to remain in the FB - it suffices that #dbnr holds a valid value when BLKMOV reads it. - DPRD_DAT/DPRW_DAT and Instance DBs are tightly coupled. The "connected parts" comment from Siemens Support refers to PROFINET IO devices whose transmit-area lengths are configured in the device description. If the Static area is one byte longer or shorter than the configured transmit area, the FB will refuse to compile regardless of how BLKMOV is wired.
FAQ
Why does TIA Portal V19 reject P#DB[#dbnr] DBX0.0 WORD 2 on a Static area when the same construct compiles on a Global DB?
The TIA Portal V19 STL grammar allows indexed DB numbers in a P# literal only when the destination is a Global DB. For a Static area, the editor cannot validate the length field at edit time and refuses the literal. Use BLKMOV with a runtime-constructed ANY or PEEK/POKE for indexed Static access.
Does disabling Optimized block access on the FB fix the compile error?
No. Disabling Optimized block access is a prerequisite for PEEK/POKE to read the correct offset, but it does not change the compiler rule that bans indexed P# literals on Static areas. Both changes are required: disable optimisation, and replace the P# literal with one of the workarounds in this article.
What is the difference between PEEK and POKE variants for S7-1500?
PEEK reads and POKE writes a single byte from/to a memory area. The CPU 1500 instruction set adds PEEK_BOOL/POKE_BOOL for bit access. PEEK/POKE accept a runtime DB number in the form PEEK DB [#dbnr], #offset, which is the only memory instruction that allows an indexed DB number directly without constructing an ANY.
Can BLKMOV copy data between two Static areas of different FBs?
Yes, provided both Instance DBs are non-optimised. Construct the source ANY in TEMP from a parameter or input, and use P##staticBufferSrc / P##staticBufferDst as the ANY literals. The CPU copies the smaller of the two declared lengths; an explicit length check before the call is recommended.
Why does DPRD_DAT fail to compile when the Static buffer does not match the PROFINET transmit area size?
The S7-1500 CPU recognises the configured Transmit areas of its connected PROFINET partners at compile time. TIA Portal therefore enforces a length match between the RECORD operand and the partner's transmit area; a mismatch raises "Area length mismatch" at edit time. Resize the Static array to the exact byte count configured in the device properties.
What retVal does BLKMOV return on a successful move, and how should the FB handle non-zero values?
BLKMOV returns 0 on success, 0x80B1 if the source is shorter than the destination, 0x80B2 if the destination is shorter, 0x80B5 for an invalid area code, 0x80C3/0x80C4 in PLCSIM when the target DB is missing or wrong-sized. The FB should branch on retVal and surface a diagnostic bit; silent ignoring masks data-integrity issues during commissioning.