Fixing TIA Portal Local Variable Errors in Migrated STL FBs

David Krause17 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 Summary

A STEP 7 V5.5 STL function block copied verbatim into a TIA Portal project targeting an ET 200S IM151-8 PN/DP (or any S7-1200/S7-1500 CPU) fails to compile, downloads with warnings, or silently writes zeros at runtime. The reported failure is on the local variable area: statements such as A L 1.0, = L 3.1, L PIW [AR1, P#10.0], and T PQW [AR1, P#8.0] are accepted by the S7-300/400 STL compiler but rejected or semantically changed on the newer targets. The block drives a PROFIdrive slave through the PKW/PZD channel (parameter channel PCV_PCA, PCV_IND, Data) and uses the local temp area as a scratch register while assembling the control word.

The visible symptom in the user's log is one of the following:

  • "The address area 'L' does not exist in this CPU type."
  • "STL is not supported on the selected CPU."
  • The block compiles, but nOutState, fDrvRdy, and Valor_Ref stay at zero, the drive never leaves ramp-up, and FALHA_MON latches because no control-word toggle is seen.
  • Compiler emits a warning that AR1, AR2, and DB registers are being used in a way that is not guaranteed safe for the optimization level selected on the S7-1500.

This article gives the exact root cause for each symptom and a reproducible migration path that preserves the drive interface while moving the code to a target TIA Portal can compile against.

Root Cause Analysis

There are four overlapping root causes, all originating from the same migration mistake: the user assumed that copying STL source from STEP 7 V5.5 into a TIA Portal FB keeps the semantics of the original block. It does not, because:

  1. STL language support is hardware-dependent. S7-1200 firmware does not implement STL at all. S7-1500 implements STL since firmware V1.5 (with restrictions: indirect memory access via AR1/AR2 and area-crossing pointer arithmetic are rejected). The ET 200S IM151-8 PN/DP (6ES7151-8AB01-0AB0) compiles as an S7-1500-class CPU and behaves like an S7-1500 regarding STL restrictions.
  2. The local data area layout (L stack) is implementation-defined. On S7-300/400, the compiler allocates 16-bit local data words and the programmer addresses them by byte/bit offset (L 1.0 = local data byte 1, bit 0; L 3.2 = byte 3, bit 2). On S7-1500 the compiler is free to reorder locals for optimization; only the symbolic name is guaranteed. The bit-slice access on the literal L stack is removed.
  3. The pointer-arithmetic pattern SLW 3 / LAR1 followed by PIW [AR1, P#10.0] builds an area-internal byte pointer from the logical address #IW_INIC. This is a deprecated access pattern on S7-1500 even where STL is permitted; TIA Portal V14 and later issue a warning and the code is no longer portable to SCL.
  4. PROFIdrive PKW/PZD telegrams (the Data_receive and Data_send structs with PCV_PCA, PCV_IND, Data) require a fixed slot order on the slave side and a consistent slot mapping in HWCN. If the FB is rewritten in SCL without preserving the byte layout (PWE on bytes 8-11 of the PKW region), the drive will reply with PKE = 0x7000 (parameter not found) even though the user code looks correct.
Critical: Before changing any code, confirm the target CPU. The only way to keep the original STL body untouched is to redeploy the project to an S7-300/S7-400 CPU in STEP 7 V5.5, or to an S7-1500 CPU with firmware < V1.5 and TIA Portal V13 SP1. Any other target requires rewriting the body.

Local Variable Addressing Model: S7-300/400 vs S7-1200/1500

The original STL relies on the S7-300/400 local data stack layout. The compiler reserved 32 bytes of L stack per priority class and mapped each declared local (temp) variable to a fixed byte offset inside that frame. The user then addressed bits by literal offset:

Statement Meaning on S7-300/400 Meaning on S7-1200/1500
A L 1.0 AND local data bit at byte 1, bit 0 Not supported. Compiler error.
= L 3.1 Assign to local data bit at byte 3, bit 1 Not supported.
L PIW [AR1, P#10.0] Load process input word at pointer (AR1 + 10 bytes) Warning on S7-1500; rejected if block optimized.
T PQW [AR1, P#8.0] Transfer to process output word at pointer (AR1 + 8 bytes) Warning on S7-1500; rejected if block optimized.

On S7-1500 (and therefore on ET 200S IM151-8 PN), the official replacement is to declare an explicit temp variable of type BOOL, INT, WORD, or STRUCT and to address it by symbol. The compiler still places it in a local data area, but the area is opaque to the programmer. See the Siemens SIMATIC S7-1200 Programmable Controller — Data Types reference for the supported scalar and structured types.

Variable Declaration Mapping

The user's variable table is correct in shape; it is the use that breaks. The following mapping is the minimal correct declaration for the SCL/optimized-block equivalent, and is also valid for STL on S7-1500 with V1.5+ if you keep STL:

Original Name Type Section Replacement Notes
IW_INIC INT Input Logical base I/O address of the drive (e.g., 256 for IW 256).
REF_MESTRE INT Input Master setpoint (engineering units).
RR_drv BOOL Input Run request from the operator.
RESET BOOL Input Fault reset edge.
fSim BOOL Input Simulation mode enable.
Hab_Par BOOL Input Parameter channel enable.
fRdWr BOOL Input Read/write direction for PKW.
bRC BYTE Input Return code from previous request.
nPNU INT Input Parameter number (PNU).
bSubIndex BYTE Input Parameter sub-index.
lSetPVA REAL Input Setpoint in physical units.
FALHA_MON BOOL Input External fault monitor.
Drive_OK BOOL Output Aggregate "drive healthy" flag.
Valor_Ref INT Output Normalized reference (0-27648).
lActPVA REAL Output Actual process value in engineering units.
nInState INT Temp Status word mirror.
nOutState INT Temp Control word being assembled.
fDrvRdy BOOL Temp Intermediate "drive ready" flag.
nAdrOff WORD Temp Address offset (byte).
nRetVal INT Temp Return value for error reporting.
Data_receive STRUCT Temp {PCV_PCA: INT, PCV_IND: INT, Data: DINT}
Data_send STRUCT Temp {PCV_PCA: INT, PCV_IND: INT, Data: DINT}
lDiv REAL Temp Intermediate divisor.
Note on PKW layout: The struct {PCV_PCA, PCV_IND, Data} in the original code uses INT for the first two and DINT for the value. That is byte-compatible with PROFIdrive profile V4 only if Data is treated as two consecutive words with no padding. When you retype this in SCL, declare Data as DWORD or ARRAY[0..3] OF BYTE to avoid implicit alignment padding that some TIA Portal versions insert.

Step-by-Step Migration Path

Follow the sequence below. Steps 1–3 confirm the cause. Steps 4–7 are the actual fix. Steps 8–9 verify on the running CPU.

  1. Confirm the target CPU in the TIA Portal device configuration. Open Devices & networks > [your CPU] and read the order number and firmware. If the firmware is on an S7-1200 (6ES7 2xx), STL is unavailable; the FB must be rewritten. If the firmware is on an S7-1500 or an ET 200S IM151-8 PN (6ES7 151-8Axx), STL is available but restricted; rewriting is still recommended for portability.
  2. Open the FB and inspect the access pattern. Use Ctrl+F on the literal L (with trailing space). Every hit that is not a load (L) of a temp variable is a candidate for rewrite. In the original block, hits include A L 1.0, = L 3.0…= L 3.7, = L 2.2 — eight such statements.
  3. Export the FB as external source. Right-click the FB > Generate source from blocks. This produces a .src file with the original STL body. Keep it as a reference for the bit-layout you must preserve.
  4. Replace each local-bit access with a named boolean temp. Map the original L 1.0, L 1.3, L 1.7 into bRun, bSim, bMonOK (or similar) and similarly for the eight control-word bits L 2.2, L 3.0…L 3.7. The mapping is arbitrary as long as the assembled nOutState ends up with the same bit pattern.
  5. Replace the pointer arithmetic with a symbolic PEEK/POKE or a HWCN tag. Two clean replacements are available:
      a. Use PEEK_WORD / POKE_WORD in SCL against a hardware offset. Example: Valor_Ref := PEEK_WORD(area := 16#81, byteOffset := INT_TO_DWORD(#IW_INIC) * 2 + 10) / 2; is the symbolic equivalent of L PIW [AR1, P#10.0].
      b. Declare DriveIn and DriveOut as HW_IO (HW submodules in the device configuration) and reference them by symbolic name. This is the preferred path on S7-1500 and removes the address arithmetic entirely.
  6. Rewrite the bit logic in SCL or LAD. Use the SCL block below as a starting point. It compiles on S7-1200 firmware V4.0+, S7-1500, and ET 200S IM151-8 PN.
  7. Keep the PROFIdrive PKW slot mapping identical to the original. If the original used a standard telegram (e.g., Telegram 1 = PZD-2/2, Telegram 3 = PZD-3/5 PPO type 3 with PKW), do not change the HWCN slot configuration. Only the user code that fills PCV_PCA, PCV_IND, and Data is rewritten.
  8. Compile the project. Open the project tree, right-click the CPU > Compile > All (rebuild). There must be no warnings on the FB. The common remaining warning is "Temporary variable can be merged", which is harmless.
  9. Download and verify on the running CPU. See the Verification section below.

SCL Replacement for the Bit-Logic Block

The following SCL block is functionally identical to the original STL body, but compiles on every TIA Portal target. Drop it into a new FB named, for example, FB_Drive_IF.

FUNCTION_BLOCK "FB_Drive_IF"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT
      IW_INIC     : INT;     // base logical I/O address
      REF_MESTRE  : INT;     // master setpoint (raw)
      RR_drv      : BOOL;    // run request
      RESET       : BOOL;    // fault reset (edge)
      fSim        : BOOL;    // simulation
      Hab_Par     : BOOL;    // PKW channel enable
      fRdWr       : BOOL;    // 0 = read, 1 = write
      bRC         : BYTE;    // last return code
      nPNU        : INT;     // parameter number
      bSubIndex   : BYTE;    // sub-index
      lSetPVA     : REAL;    // setpoint in engineering units
      FALHA_MON   : BOOL;    // external monitor
   END_VAR

   VAR_OUTPUT
      Drive_OK    : BOOL;
      Valor_Ref   : INT;
      lActPVA     : REAL;
   END_VAR

   VAR_TEMP
      nInState  : INT;
      nOutState : INT;
      fDrvRdy   : BOOL;
      nAdrOff   : WORD;
      nRetVal   : INT;
      Data_receive : STRUCT
         PCV_PCA : INT;
         PCV_IND : INT;
         Data    : DINT;
      END_STRUCT;
      Data_send : STRUCT
         PCV_PCA : INT;
         PCV_IND : INT;
         Data    : DINT;
      END_STRUCT;
      lDiv      : REAL;
      iRawIn    : INT;
      iRawOut   : INT;
   END_VAR

BEGIN
   // --- Bit logic: replaces A L 1.0 / AN L 1.3 / AN L 1.7 ---
   fDrvRdy := RR_drv AND NOT fSim AND NOT FALHA_MON;
   Drive_OK := fDrvRdy OR fSim;

   // --- Scaling: replaces PIW [AR1,P#10.0] / ITD / *D / /D ---
   // 27648 = S7 nominal analog range. 16384 = scaling denominator.
   iRawIn := PEEK_WORD(area := 16#81,
                       byteOffset := INT_TO_DWORD(IW_INIC) * 2 + 10);
   Valor_Ref := REAL_TO_INT(INT_TO_REAL(iRawIn) * 27648.0 / 16384.0);

   // --- Control word assembly: replaces the eight = L 3.x lines ---
   nOutState := 0;
   IF RR_drv  THEN nOutState := nOutState OR 16#0001; END_IF;  // bit 0
   IF RESET   THEN nOutState := nOutState OR 16#0080; END_IF;  // bit 7
   // bit 1..6 remain SET (no OFF1/OFF2/OFF3 commanded)
   nOutState := nOutState OR 16#007E;
   // bit 2 reserved (manufacturer-specific ramp enable)

   // --- Write control word: replaces T PQW [AR1,P#8.0] ---
   POKE_WORD(area := 16#82,
             byteOffset := INT_TO_DWORD(IW_INIC) * 2 + 8,
             value := INT_TO_WORD(nOutState));

   // --- PKW/PZD assembly placeholder ---
   Data_send.PCV_PCA := SHL(WORD_TO_INT(INT_TO_WORD(nPNU)), 8)
                        OR (fRdWr ? 16#01 : 16#00)
                        OR WORD_TO_INT(INT_TO_WORD(bSubIndex));
   Data_send.PCV_IND := 0;
   Data_send.Data    := REAL_TO_DINT(lSetPVA);
END_FUNCTION_BLOCK
PEEK/POKE area codes: 16#81 = process inputs (PI), 16#82 = process outputs (PQ). The byte offset IW_INIC * 2 + 10 reproduces the original SLW 3 / LAR1 / PIW [AR1, P#10.0] pattern, where the SLW 3 shifted the word address to byte address. If your drive uses 16-bit process words, use PEEK_WORD; for 32-bit PZDs use PEEK_DWORD.

Indirect Addressing Conversion (AR1 → Symbolic)

The original STL built a byte pointer from #IW_INIC and used AR1 as the base register. The full sequence is:

L  #IW_INIC     // load logical input address (e.g., 256)
SLW 3           // shift left 3 = multiply by 8 = byte address
LAR1            // load AR1 = P#2048.0
L  PIW [AR1, P#10.0]   // read PIW at byte offset 2048 + 10
T  PQW [AR1, P#8.0]    // write PQW at byte offset 2048 + 8

This is the S7-300/400 idiom for indirect addressing with a constant offset into the I/O area. The replacement matrix is:

Original STL SCL on S7-1200/1500 (optimized) SCL using PEEK/POKE
SLW 3 / LAR1 (none needed — use symbolic HW tag) nAdrOff := INT_TO_WORD(#IW_INIC) * 8;
L PIW [AR1, P#10.0] iRawIn := "DB_Drive".InputWord; (where InputWord is mapped in HWCN) iRawIn := PEEK_WORD(area := 16#81, byteOffset := INT_TO_DWORD(#IW_INIC) * 2 + 10);
T PQW [AR1, P#8.0] POKE_WORD(area := 16#82, byteOffset := INT_TO_DWORD(#IW_INIC) * 2 + 8, value := INT_TO_WORD(nOutState));

The symbolic HW-tag path is preferred: in the device configuration, open the drive submodule, expose the PZDs as I/O addresses, and reference them by tag. This removes the arithmetic, the address-bookkeeping temp nAdrOff, and the dependency on the logical address being a multiple of 8.

PROFIdrive PKW/PZD Channel Rebuild

The two structs Data_receive and Data_send are the parameter channel (PKW). The PROFIdrive profile places them at the head of the telegram when the configured PPO type includes PKW (PPO types 1, 3, and 5 on PROFIBUS, or explicit PKW-enabled telegrams on PROFINET). The byte layout is fixed:

Word Index Field Width Content
0 PCV_PCA (PKE) 16 bits Bits 0–10 = PNU, bit 10 = SPM, bit 11 = reserved, bit 12–15 = AK (request/response ID).
1 PCV_IND (IND) 16 bits Bits 0–7 = sub-index, bits 8–15 = page index.
2 Data word high 16 bits Parameter value high word (or unused for byte parameters).
3 Data word low 16 bits Parameter value low word.

Common AK codes (bits 12–15 of PKE):

AK (hex) Meaning (Request) Meaning (Response)
0x0 — No response (parameter access rejected)
0x1 Read parameter value Read parameter value (single word)
0x2 Write parameter value (word) Write parameter value (single word)
0x3 — Read parameter value (double word)
0x4 Write parameter value (double word) Write parameter value (double word)
0x6 Read parameter value (array) Read parameter value (array, words)
0x7 Write parameter value (array) Write parameter value (array)
0x8 — Read parameter value (array, double words)
0xD — Read parameter value (array, blocks)
0xE — Read parameter value (multi-parameter)

The original code's Data_receive is the read-back slot; Data_send is the request slot. The bit packing shown above for Data_send.PCV_PCA follows the standard. Failure to keep this packing intact is the most common cause of the drive replying with PKE = 0x7000 ("parameter not found") even when the PNU is correct.

Verification and Commissioning

After rewriting, perform the following checks in order. Each step has a single observable pass criterion.

  1. Offline syntax check. Right-click the FB > Compile > Software (rebuild). The "Error" column must be 0.
  2. Watch table — control word. Create a watch table that forces RR_drv := TRUE, fSim := FALSE, FALHA_MON := FALSE. Observe nOutState in the FB's instance DB. Expected value: 16#007F (bits 0..6 set, bit 7 from RESET = 0). The drive should leave Ready to switch on state.
  3. Watch table — PKW round-trip. Force nPNU := 3 (drive type, common PNU), fRdWr := FALSE, bSubIndex := 0. Trigger one OB1 cycle and read Data_receive.PCV_PCA. Expected: AK = 0x1 (response = read single word), PNU field = 3, SPM bit clear. If the AK field is 0x7, the request has been echoed back rather than answered — check the slot mapping.
  4. Online — drive status word. Read the PZD that mirrors ZSW1 (status word 1, PROFIdrive). Bit 0 (Ready to switch on) and bit 1 (Ready) must both come on within 200 ms of the first valid control word. If not, the issue is in the telegram mapping, not the FB.
  5. Online — reference scaling. Set REF_MESTRE := 16384 and observe Valor_Ref. Expected: approximately 27648 (the S7 nominal full-scale). If Valor_Ref is half the expected value, the SCL arithmetic has been written with 16384 and 32768 swapped.
  6. Fault reset path. Pulse RESET for one cycle. nOutState bit 7 (control-word bit 7, fault acknowledge on most drives) must toggle. If it does not, the bit mask in the SCL block is wrong (verify 16#0080).

Troubleshooting Matrix

Observed Symptom Likely Cause Diagnostic Step Fix
Compiler error: "Address L does not exist" STL L-bit access used on S7-1200 or S7-1500 Search for L tokens in the FB source Replace with named BOOL temps
Compiler error: "STL not supported" S7-1200 target Check CPU order number Switch to SCL/LAD/FBD
Valor_Ref = 0 PEEK area wrong or offset wrong Inspect byteOffset calculation Use IW_INIC * 2 + 10 for 16-bit PZD
Drive replies with PKE = 0x7000 Wrong AK field in Data_send.PCV_PCA Online view of the PZD Re-pack AK into bits 12–15 of PCV_PCA
nOutState stuck at 0 Bit-by-bit assignment block was not entered (BB condition) Step through with single-cycle breakpoints Confirm RR_drv is TRUE at the assignment
Block downloads but drive does not respond at all Telegram mismatch in HWCN Compare configured PPO type with drive firmware Re-add the drive in HWCN and re-bind the PZD/PKW slots
Implicit warning on S7-1500: "AR1/AR2 not allowed in optimized block" Old STL pointer idiom retained Search for AR1, AR2 in FB source Switch to symbolic I/O access or PEEK/POKE
Compilation succeeds on S7-1500 STL but execution differs from S7-300 Compiler re-ordered locals; old L 1.0 now points to a different bit Add a unique sentinel write at the start of the FB Rewrite the body without L-stack literals

Field-Proven Caveats

  • Don't keep STL "just because it works on the old CPU". Once the ET 200S IM151-8 PN/DP is in scope, you are on an S7-1500-class firmware. The next firmware update will likely tighten the STL restrictions further.
  • Check the block's "Optimized block access" attribute. If the FB was created with optimized access = TRUE (default on TIA Portal V14+), you cannot use absolute L or AR1 access at all. Switch the attribute to FALSE only as a last resort for legacy STL, and document the trade-off.
  • The scale factor pair (27648 / 16384) corresponds to the S7 analog-input nominal range (27648) divided by the half-range of a normalized reference (16384 = 2^14). For unipolar 4–20 mA or 0–10 V signals, this is correct. For bipolar (±10 V) signals, use 27648 with a signed offset; for full 16-bit raw range, use 65535 / 32768 instead.
  • PKW vs PZD separation. Some drives (notably older SIMOREG and SIMOVERT) swap the PZD-1 and PZD-2 direction relative to the modern SINAMICS mapping. Verify with the drive's parameter manual; the FB code is otherwise identical.
  • CPU ET 200S IM151-8 communication errors are independent of this FB. If the FB compiles, downloads, and the FB's outputs toggle, but the drive does not respond, the issue is on the bus side (PROFINET name, slot mismatch, watchdog, or PKW length = 4 words but the drive is configured for 8). Open a separate diagnostic for the network path; the local-variable rewrite will not fix it.

FAQ

Does the S7-1200 support STL?

No. The S7-1200 supports LAD and FBD only. SCL is supported from firmware V4.0 onward. To migrate STL code to an S7-1200 target, rewrite the block in SCL or LAD/FBD. Reference the SIMATIC S7-1200 manual collection on docs.tia.siemens.cloud for supported languages and data types.

Can the ET 200S IM151-8 PN/DP run STL blocks compiled in TIA Portal?

Yes, but the STL behaves like S7-1500 STL: AR1/AR2 pointer arithmetic, area-crossing pointer math, and absolute L-stack bit access are restricted. The recommended migration is to SCL with optimized block access enabled.

How do I convert A L 1.0 / = L 3.2 style bit access to TIA Portal?

Declare a named BOOL temp for each bit-slice you need, replace A L 1.0 with IF bBit_1_0 THEN, and replace = L 3.2 with a direct boolean assignment. For control-word assembly, OR-mask the bits into an INT temp such as nOutState := nOutState OR 16#0004;.

What replaces PIW [AR1, P#10.0] in TIA Portal SCL?

Use PEEK_WORD(area := 16#81, byteOffset := <offset>) for direct access, or expose the PZD as a symbolic HW tag in the device configuration. For 32-bit PZDs, use PEEK_DWORD. Avoid the AR1 pattern on S7-1500 because it is rejected in optimized blocks.

Why does nOutState stay at zero even after I rewrite the bit assignments?

Most often the assignment block is gated by an earlier BB or the implicit ENO is being cleared by a divide-by-zero in the Valor_Ref line. Check the instance DB online with Monitor & force: if lDiv is zero, the SCL block exits early. Add a RETURN guard after the scaling and re-test.

Back to blog