Resolving STL Pointer P## Errors in S7-1500 TIA Portal FC Blocks

David Krause13 min read
SiemensTIA PortalTroubleshooting
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

Problem Description

A PLC programmer migrating from S7-300/S7-400 to the S7-1500 platform attempts to write a Function (FC) in STL (Statement List) that consolidates 16 individual BOOL input bits (named off2 through reserve6, plus additional flags) into a single WORD output for motor direction control. The intended logic is to copy each input bit into a corresponding bit of a TEMP-declared word bTempBit0 and then assign the assembled word to an OutputWord tag for downstream use.

The programmer attempts the following STL construct inside an FC:

FUNCTION FC_MotorControl : VOID
VAR_TEMP
    bTempBit0  : BOOL;
    bTempBit1  : BOOL;
    ...
    bTempBit15 : BOOL;
    wTempWord  : WORD;
END_VAR

BEGIN
    // Expected approach (fails on S7-1500)
    LAR1  P##bTempBit0;        // <- Compiler error in TIA Portal V14 SP1
    ...
END_FUNCTION

The TIA Portal V14 SP1 compiler returns an error at the line LAR1 P##bTempBit0 (or P##wTempWord). The error message indicates that the pointer cannot be resolved against the local TEMP variable when the FC is parameterized, and no usable pointer/offset is generated for the FC's instance data area.

Error signatures observed:

  • "The address of the variable cannot be determined" / "Invalid pointer" at the P## reference.
  • Unresolved symbolic address for bTempBit0 when the FC is called from an OB.
  • OK flag is not generated; the FC fails to compile cleanly and the call site shows red status.

Root Cause

The root cause is architectural: the S7-1500 CPU family (firmware V1.0 and later, including V2.x used with TIA Portal V14 SP1) does not support the same register-passing mechanism that S7-300/400 STL exposes through LAR1 P##.... In S7-300/400, P##<symbol> returns a pointer to a local TEMP variable by resolving the AR1/AR2 register-relative offset on the local stack frame. This mechanism was tied to the classic 300/400 stack model.

On S7-1500, the optimized block architecture (introduced with TIA Portal) places TEMP variables in a different memory layout and most blocks default to optimized access (formerly: "non-remanent, symbolic only"). Optimized blocks do not expose absolute addresses for symbolic variables, so any STL construct that depends on a fixed stack-relative offset is either rejected at compile time or generates a warning that the value is undefined at runtime.

Siemens documents this restriction directly. Per the Siemens Industry Online Support FAQ "Why is it not possible to mix register passing and explicit parameter transfer with the S7-1500 in STEP 7 (TIA Portal)?", explicit assignment of the AR1/AR2 register combined with parameter transfer (such as P##) is not permitted on S7-1500 CPUs. The CPU does not support the STL register transfer syntax used in legacy S7-300/400 code.

Additional contributing factors:

  • STL support on S7-1500 is limited. Per the S7-1500 system manual, STL is supported only for a subset of operations; operations that rely on CPU register internals are not part of the supported set.
  • Optimized block access is the default for new FCs created in TIA Portal V14 SP1; symbolic access hides absolute addresses, which is what P## needs.
  • Mixing explicit and register-passing parameter access is rejected by the compiler per the FAQ cited above.

Affected Versions and Platforms

Component Affected Version Notes
STEP 7 (TIA Portal) V14 SP1 and later up to V17 Compiler error on P## inside S7-1500 FCs
S7-1500 CPU firmware V1.5, V1.8, V2.0, V2.1, V2.5, V2.6, V2.9 Register-passing not supported in optimized blocks
ET 200SP CPU Firmware V2.0+ Same restriction as S7-1500
S7-1200 CPU Firmware V4.0+ STL limited; P## is rejected
S7-300/400 CPU All Not affected - P## works as documented
Compatibility caveat: Disabling the optimized block access ("Standard" / non-optimized) on the S7-1500 does not restore full S7-300/400 STL register semantics. Even in non-optimized FCs, TIA Portal V14 SP1 issues warnings on P## operations because the compiler's handling of TEMP addresses has changed.

Solution 1: AT Overlay (Recommended)

The recommended pattern on S7-1500 is the AT overlay view, which lets you interpret one memory area as multiple data types. This avoids any pointer arithmetic and uses purely symbolic access that the TIA Portal compiler fully understands.

Step-by-step implementation

  1. Declare a TEMP variable of the atomic type you want to assemble (here, a WORD).
  2. Declare an AT view that overlays the word with an ARRAY OF BOOL or a structured bit record.
  3. Assign the input bits to the desired AT-view elements symbolically.
  4. Pass the base word variable to the motor logic.
FUNCTION "FC_MotorControl" : VOID
{ S7_Optimized_Access := 'TRUE' }
VAR_TEMP
    wMotorCtrl   : WORD;        // The assembled control word
    aBits        : AT  // 16-element bit view of the word
        ARRAY[0..15] OF BOOL
        := [16(FALSE)];
    // Wait, AT on TEMP needs a struct or array; alternative below
END_VAR

BEGIN
    // Direct symbolic assignment of the bit positions in a word
    "FC_MotorControl".wMotorCtrl.%X0  := #off2;
    "FC_MotorControl".wMotorCtrl.%X1  := #runEnable;
    "FC_MotorControl".wMotorCtrl.%X2  := #directionRight;
    "FC_MotorControl".wMotorCtrl.%X3  := #directionLeft;
    "FC_MotorControl".wMotorCtrl.%X4  := #fastStop;
    "FC_MotorControl".wMotorCtrl.%X5  := #reserve1;
    "FC_MotorControl".wMotorCtrl.%X6  := #reserve2;
    "FC_MotorControl".wMotorCtrl.%X7  := #reserve3;
    "FC_MotorControl".wMotorCtrl.%X8  := #reserve4;
    "FC_MotorControl".wMotorCtrl.%X9  := #reserve5;
    "FC_MotorControl".wMotorCtrl.%X10 := #reserve6;
    // ... continue for X11..X15

    // Write the assembled word to the output tag
    "MotorCtrlWord" := #wMotorCtrl;
END_FUNCTION

Key points:

  • The %X0 ... %X15 syntax is the sliced access feature for elementary data types. It is documented in the S7-1500 system manual and in the TIA Portal help under "Accessing individual bits in a tag of an elementary data type".
  • The compiler generates the correct bit-mask instructions (L 0x0001; T ... equivalents) without exposing any register.
  • No STL pointer is required, and the block remains fully optimized.

Using a STRUCT + AT overlay on a TEMP word

If the programmer prefers a structured declaration (e.g., to POKE the resulting word into a DB or pass it to a drive telegram), use the following pattern in the FC's VAR_TEMP region:

VAR_TEMP
    wTemp       : WORD;          // atomic word (real storage)
    stBits AT wTemp : STRUCT     // AT overlay
        bBit0   : BOOL;
        bBit1   : BOOL;
        bBit2   : BOOL;
        bBit3   : BOOL;
        bBit4   : BOOL;
        bBit5   : BOOL;
        bBit6   : BOOL;
        bBit7   : BOOL;
        bBit8   : BOOL;
        bBit9   : BOOL;
        bBit10  : BOOL;
        bBit11  : BOOL;
        bBit12  : BOOL;
        bBit13  : BOOL;
        bBit14  : BOOL;
        bBit15  : BOOL;
    END_STRUCT;
END_VAR

BEGIN
    #stBits.bBit0  := #off2;
    #stBits.bBit1  := #runEnable;
    #stBits.bBit2  := #directionRight;
    #stBits.bBit3  := #directionLeft;
    #stBits.bBit4  := #fastStop;
    #stBits.bBit5  := #reserve1;
    #stBits.bBit6  := #reserve2;
    #stBits.bBit7  := #reserve3;
    #stBits.bBit8  := #reserve4;
    #stBits.bBit9  := #reserve5;
    #stBits.bBit10 := #reserve6;
    // bBit11..bBit15 set as needed

    "MotorCtrlWord" := #wTemp;
END_FUNCTION
Important rule for AT overlays: The overlay variable must be the same size as the base variable. A STRUCT of 16 BOOL members = 2 bytes, matching WORD. The base must be a non-optimized-compatible atomic type (WORD, DWORD, BYTE, INT, etc.). The S7-1500 system manual "Programming and Operating Manual" describes this technique under the section "Overview of the data types" → "AT overlay".

Solution 2: Sliced Access Without AT

For simple cases, the %X<n> bit-slice on a WORD is the cleanest approach. The syntax is supported in LAD, FBD, and SCL (and in STL where the corresponding bit-mask instructions are emitted). Sliced access was added in TIA Portal V13 for S7-1500 and is the recommended path for production code.

Reference: SIMATIC S7-1500 Programming Guidelines documents the use of sliced access for control word assembly.

Solution 3: Direct MOVE in SCL (Alternative)

If neither STL nor symbolic slicing is preferred, an SCL FC that does the same job can be implemented with bit-shift logic:

FUNCTION "FC_MotorControlSCL" : VOID
VAR_INPUT
    iBit0  : BOOL;
    iBit1  : BOOL;
    iBit2  : BOOL;
    iBit3  : BOOL;
    iBit4  : BOOL;
    iBit5  : BOOL;
    iBit6  : BOOL;
    iBit7  : BOOL;
    iBit8  : BOOL;
    iBit9  : BOOL;
    iBit10 : BOOL;
    iBit11 : BOOL;
    iBit12 : BOOL;
    iBit13 : BOOL;
    iBit14 : BOOL;
    iBit15 : BOOL;
END_VAR
VAR_OUTPUT
    oMotorCtrlWord : WORD;
END_VAR

BEGIN
    oMotorCtrlWord.%X0  := iBit0;
    oMotorCtrlWord.%X1  := iBit1;
    oMotorCtrlWord.%X2  := iBit2;
    oMotorCtrlWord.%X3  := iBit3;
    oMotorCtrlWord.%X4  := iBit4;
    oMotorCtrlWord.%X5  := iBit5;
    oMotorCtrlWord.%X6  := iBit6;
    oMotorCtrlWord.%X7  := iBit7;
    oMotorCtrlWord.%X8  := iBit8;
    oMotorCtrlWord.%X9  := iBit9;
    oMotorCtrlWord.%X10 := iBit10;
    oMotorCtrlWord.%X11 := iBit11;
    oMotorCtrlWord.%X12 := iBit12;
    oMotorCtrlWord.%X13 := iBit13;
    oMotorCtrlWord.%X14 := iBit14;
    oMotorCtrlWord.%X15 := iBit15;
END_FUNCTION

The compiler emits equivalent SET / CLR / UW / OW / L / T instructions and the resulting STL is symbolic-only - no P## needed.

Solution 4: Array of BOOL in a Static / Temp + Word Overlay

For maintainability, define an ARRAY[0..15] OF BOOL as a single input, then use the AT-overlay pattern. The motor bit list (e.g., off2, runEnable, directionRight...) becomes a single ARRAY tag in the OB's call interface, and the FC does a single MOVE in symbolic form.

VAR_INPUT
    aMotorBits : ARRAY[0..15] OF BOOL;
END_VAR
VAR_OUTPUT
    oMotorCtrlWord : WORD;
END_VAR
VAR_TEMP
    wWord AT oMotorCtrlWord : STRUCT
        bBit0  : BOOL; bBit1  : BOOL; bBit2  : BOOL; bBit3  : BOOL;
        bBit4  : BOOL; bBit5  : BOOL; bBit6  : BOOL; bBit7  : BOOL;
        bBit8  : BOOL; bBit9  : BOOL; bBit10 : BOOL; bBit11 : BOOL;
        bBit12 : BOOL; bBit13 : BOOL; bBit14 : BOOL; bBit15 : BOOL;
    END_STRUCT;
END_VAR

BEGIN
    #wWord.bBit0  := #aMotorBits[0];
    #wWord.bBit1  := #aMotorBits[1];
    ...
    #wWord.bBit15 := #aMotorBits[15];
END_FUNCTION

Verification

After applying one of the solutions above, verify the FC compiles and behaves correctly in the S7-1500:

  1. Compile check: In the project tree, the FC should display a green check mark. If a yellow triangle appears, double-click to inspect the warning. The original P## error should be gone.
  2. Download to PLCSIM or to the S7-1500 CPU (firmware V1.8 minimum for the S7-1500 used with TIA Portal V14 SP1; the simulator requires the matching PLCSIM V14 SP1 install).
  3. Watch table test: Open a watch table and force each of the 16 input BOOLs to 1 in turn, observing that the resulting MotorCtrlWord shows the correct hex value (bit 0 → 0x0001, bit 1 → 0x0002, bit 15 → 0x8000).
  4. Bit-slice cross-check: In the watch table, read MotorCtrlWord.%X0 through MotorCtrlWord.%X15 and verify each reflects the forced input.
  5. OB1 call integrity: Confirm the FC call in OB1 (or the cyclic OB) shows a green status. If the FC is invoked from an FB, ensure the FB's instance DB is regenerated (right-click → Compile → Software (rebuild all blocks)).
  6. Online → Monitor / Modify: Step through the FC in the online monitor and confirm the symbolic view shows the bits set as expected, with no "Invalid value" indicators on the TEMP variable.

Troubleshooting Matrix

Symptom Likely Cause Fix
"P## cannot be resolved" / compile error in FC STL register-passing not supported on S7-1500 Replace with AT overlay or sliced access
Block compiles but watch table shows ?????? Block is optimized and address unknown at runtime Ensure access is fully symbolic; do not use ANY_POINTER
Bits set but word shows wrong value Endian mismatch in AT overlay Verify AT-view mapping: %X0 is LSB on S7-1500 (little-endian WORD)
FC generates "Block has no parameters" error at call Mixing register-passing with parameter transfer Remove all LAR1/LAR2 statements; use the input parameters as the only data source
Warning: "TEMP variable may not be initialized" AT overlay element used before assignment Set initial value in the base variable or assign all 16 bits explicitly
Code works in S7-300/400 but not S7-1500 Different block access model (optimized) Port to symbolic / AT pattern as shown above

Why STL Pointer Coding Should Be Avoided on S7-1500

The legacy S7-300/400 STL pattern of LAR1 P##symbol; L IB[AR1,P#0.0] is brittle, CPU-model specific, and incompatible with optimized block access. On S7-1500, attempting to retain that style leads to several categories of bug:

  • Non-portable code - the same source will not build across CPU families.
  • Optimized block warnings - the compiler inserts placeholder values that behave like pointers but are not stable across compile cycles.
  • Lack of symbolic diagnostic - online monitor cannot display the variable at the pointer offset.
  • Loss of safety/Security Integrated integration - the STL pointer pattern is not supported in safety blocks (F-CPU).

Siemens explicitly recommends using symbolic, slice-based, and AT-overlay techniques for S7-1500. See the SIMATIC S7-1500 manual at "SIMATIC S7-1500 Automation System System Manual" for the authoritative data-type and access rules.

Migration Checklist from S7-300/400 STL to S7-1500

  1. Search the project for P## in the cross-reference (right-click → Cross-references). Every hit must be replaced.
  2. Search for LAR1, LAR2, TAR1, TAR2, CAR, AR1, AR2 in the code; these instructions are unsupported on S7-1500 STL.
  3. Convert pointer-based DB accesses (DBW[AR1,P#0.0]) into WORD_TO_WORD typed symbolic references or use slice access on the DB variable.
  4. For indirect addressing of array elements, use "Tag"[index] SCL syntax, or the PEEK/POKE helpers in SCL for byte-level access (see FAQ 48709306 on SCL PEEK/POKE).
  5. Re-test the FC in PLCSIM V14 SP1 (or later) before downloading to the physical CPU.
  6. Document the migration in the change log; reference the Siemens FAQ 109750304 in the comment field for traceability.

Edge Cases and Field-Proven Caveats

1. Endianness of %Xn. On S7-1500, sliced bit access on a WORD uses little-endian semantics: %X0 is the LSB, %X15 is the MSB. A value of 16#8000 shows bit 15 set. If a legacy block treated the word as big-endian, swap the assignment order.

2. AT overlay on a TEMP word inside a multi-instance FB. Multi-instance FBs nest TEMP regions. The AT overlay remains valid as long as the base variable and the overlay share the same name scope. If the compiler rejects the AT, rename the base variable (e.g., wTemp → wBits) to avoid clashes with a multi-instance field of the same name.

3. AT overlay on a TEMP WORD with structured access by a connected drive telegram. If the assembled word feeds a SINAMICS drive (PROFIdrive PZD), the bit ordering is defined by the drive's PZD1 word structure. Verify against the drive's parameter manual; Siemens provides the reference in S7-1500 system manual appendix.

4. Calling the FC from an F-run OB (F-CPU). Sliced access and AT overlays are supported in the F-program. STL pointer code is not - attempting to use it in a fail-safe FC produces a compile error in the F-editor.

5. Compiler firmware mismatch. If the project is opened in TIA Portal V14 SP1 but the CPU is configured at firmware V2.5 or V2.9, the SCL compiler may emit a downgrade message on sliced access. The fix is either to use the matching TIA Portal version or to upgrade the CPU firmware. Reference: S7-1500 system manual firmware compatibility table.

6. Pre-initialized TEMP variables. TEMP variables are not re-initialized on each FC call in optimized blocks. If a bit is not assigned in one code path, it carries its value from the previous call. Always assign all 16 bits, or initialize the base word at function entry: #wMotorCtrl := 0; in SCL.

Safety note: Do not use ANY or POINTER data types in FC inputs of an S7-1500 safety-related (F-) program. The F-CPU rejects pointer parameters entirely. For non-safety motor control, sliced access and AT overlay remain valid.

Summary of Best Practice

  • Use the S7-1500's native %Xn bit-slice on a WORD for control-word assembly.
  • Use the AT overlay for structured bit views when a symbolic name is preferred for each bit.
  • Use SCL MOVE or bit-slice when the consuming block expects an elementary WORD tag.
  • Avoid all LAR1 P##, LAR2 P##, and register-relative indirect addressing on S7-1500.
  • Reference Siemens FAQ 109750304 in code comments to document the migration rationale.

FAQ

Why does TIA Portal V14 SP1 reject P## inside an S7-1500 FC?

Because the S7-1500 CPU family does not support the STL register-passing mechanism that S7-300/400 STL exposes through P##<symbol>. TIA Portal V14 SP1 enforces this restriction at compile time per Siemens FAQ 109750304. Use sliced access (%X0..%X15) or an AT overlay instead.

Can I keep using STL on S7-1500 with P## by disabling optimized block access?

No. Disabling optimized access does not restore the S7-300/400 register model. The compiler still warns or errors on P## references because TEMP variable offsets are not resolved at compile time. The supported approach is purely symbolic with sliced access or AT overlay.

What is the correct way to combine 16 BOOLs into a single WORD on S7-1500?

Use the sliced access syntax wWord.%X0 through wWord.%X15 on a TEMP or local WORD, assign each BOOL input to a bit, then pass the resulting WORD to the consumer. This compiles in TIA Portal V13 SP1 and later and is valid in LAD, FBD, SCL, and STL.

Does an AT overlay work in a TEMP region of an S7-1500 FC?

Yes. Declare a WORD (or other atomic type) as the base and a STRUCT of 16 BOOL members (or an ARRAY[0..15] OF BOOL) as the AT overlay. The combined size must match (16 BOOL = 2 bytes = WORD). This is fully supported in optimized blocks.

Are PEEK and POKE allowed in S7-1500 SCL for the same task?

Yes, PEEK_WORD and POKE_WORD from SCL can assemble or disassemble words at byte-level offsets. However, for control-word assembly the recommended method is sliced access because it remains symbolic and survives firmware upgrades. See Siemens FAQ 48709306 for the PEEK/POKE reference.

Back to blog