Siemens S7 Dynamic PIW Addressing in STL: Memory Indirect

David Krause13 min read
S7-300SiemensTutorial / 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 of Dynamic Addressing in S7-300/S7-400 STEP 7

Hard-coded absolute addresses (PIW 256, DB1.DBW0, PQW 288) make STEP 7 programs rigid: every new I/O slot requires a code edit, a recompile, and a download. Dynamic addressing computes the target byte or word at runtime using a pointer or an arithmetic operation, allowing a single Function Block (FB) or Function (FC) to read or write a variable set of peripheral words, data block registers, or instance DB fields without rewriting the source.

On S7-300 and S7-400 CPUs, dynamic addressing is implemented in STL (Statement List) through three primary mechanisms:

  1. Memory-indirect addressing — the address offset is stored in a flag (M), local (L), or data word (DBW/DMD), and the operand is qualified with that word in square brackets (e.g., PIW[MD20]).
  2. Register-indirect addressing — the address and area pointer are loaded into address registers AR1 or AR2, and the operand is qualified by the register (PIW[AR1,P#0.0] or DID[AR1,P#0.0]).
  3. Area-crossing pointers — a full 32-bit area-internal pointer (DW#16#84000000 for the PIW area, DW#16#84010000 for the PQW area) is built in the accumulator and loaded into AR1 before the indirect access.

Dynamic PIW/PQW addressing is essential when an FB must mirror peripheral values into a process image database, when a Profibus DP slave exchanges more than 16 words, or when the slot index of an analog card is parameterised at runtime.

Pointer Formats and Data Types in STL

STEP 7 uses two pointer formats depending on the addressing mode:

Pointer Type Length Bit Layout Typical Use
Pointer (POINTER, ANY) 48 bits (6 bytes) Bits 31–24 = byte address × 8 (DB), bits 23–16 = byte in DB, bits 15–3 = byte offset, bits 2–0 = bit offset FB formal parameters of type POINTER, multi-instance calls
Area-internal pointer 32 bits (DWORD) Bits 31–24 unused, bits 23–16 = area ID, bits 15–3 = byte offset, bits 2–0 = bit offset AR1/AR2 register indirect, area-crossing pointer constants

The area identifier byte in an area-internal pointer has the following encodings:

Area ID Hex Area Typical Constant
DW#16#81000000 Inputs (I / PE) AR1 loaded with P#I0.0
DW#16#82000000 Outputs (Q / PA) AR1 loaded with P#Q0.0
DW#16#83000000 Merker (M) AR1 loaded with P#M0.0
DW#16#84000000 Process image inputs (PIW / PE in peripheral area) Used for PIW dynamic indexing
DW#16#84010000 Process image outputs / peripheral outputs (PQW) Used for PQW dynamic indexing
DW#16#85000000 Data block (DB) AR1 loaded with P#DBX0.0
DW#16#87000000 Instance data block (DID) Used inside FBs for STAT area access

Every pointer must be byte-aligned. When you only need to increment by whole words, build the pointer with a bit offset of zero (P#x.0) so that +D with P#2.0 steps cleanly across word boundaries. Reference: Siemens TIA Portal — Address areas (S7-300, S7-400).

Memory Indirect Addressing for PIW and PQW Areas

Memory-indirect addressing is the simplest way to compute a PIW or PQW address at runtime. The base address is loaded into a flag or local double-word and the operand is qualified with that word in square brackets. STEP 7 will resolve the operand address by adding the value of the bracketed pointer to the area identifier.

The following STL snippet reads four consecutive process-image words starting at PIW 216 and writes them to MW30–MW36:

L P#216.0          // base offset, bit offset 0 (word aligned)
T MD20             // pointer to PIW area, used as DWORD
L P#30.0           // destination offset in MW area
T MD24

NewW: L PIW[MD20]   // dynamic PIW read using MD20
      T MW[MD24]    // dynamic MW write using MD24
      L MD20
      L P#2.0       // step +2 bytes for next word
      +D
      T MD20
      L MD24
      L P#2.0
      +D
      T MD24
      // iteration limit check (not shown)
      JC  NewW

Notes on this technique:

  • Width restriction. PIW[MD20] always reads a word (2 bytes) and PQW[MD20] always writes a word. To read bytes use PIB[MD20]; to read double-words use PID[MD20]. The operand width determines the number of bytes the pointer steps per access.
  • Pointer width. The bracketed pointer may be a DWORD (MD / LD / DBD) when you need to step across more than one byte at a time, or a WORD (MW / LW) when the stride is 1 byte.
  • Byte indexing from a variable. If the byte index is computed at runtime, multiply by 8 to produce a pointer-compatible bit offset: L #ByteIndex; L 8; *I; L MD20; +D; T MD20.
  • Watch-table visibility. Open the pointer double-word in the watch table formatted as a pointer (binary display) to verify the bit-offset bits 2:0 are zero before a PIW/PQW access; a non-zero bit offset will force an addressing error (OB121) on S7-300/400.

Register Indirect Addressing with AR1 and AR2

Address registers AR1 and AR2 are 32-bit registers inside the S7-300/400 CPU that hold a full area-internal pointer. Loading AR1 with P#I0.0 gives bit-by-bit access to the inputs; loading it with a constructed DWORD gives access to PIW, PQW, or DI areas.

The same four-word copy expressed with registers avoids any flag-word or local double-word juggling:

L P#216.0          // PIW base, bit offset 0
LAR1               // AR1 holds the area-internal pointer
L P#30.0           // MW destination base
LAR2

NewW: L PIW[AR1, P#0.0]   // read PIW at AR1 + 0.0
      T MW[AR2, P#0.0]    // write MW at AR2 + 0.0
      +AR1, P#2.0         // step pointer +2 bytes
      +AR2, P#2.0
      LOOP NewW           // ACCU1 must hold the loop counter

AR2 is the standard register used by STEP 7 for parameter passing and multi-instance FBs. The Siemens address-area documentation recommends saving and restoring AR2 around any code that uses it for a different purpose. Wrap AR2-dependent logic inside an FB that explicitly saves AR2 to a local DWORD at entry and restores it before exit so multi-instance calls remain safe.

Field practice: On S7-314, S7-315, S7-317, and S7-319 CPUs the LOOP instruction decrements ACCU1-L and jumps if the result is non-zero. Load the loop counter into ACCU1-L immediately before each LOOP. Any arithmetic in between will destroy it.

Two-Step DB Access: OPN DB with a Pointer Variable

The S7-300/400 STL cannot load a full 6-byte POINTER (DB number + byte offset) in one instruction because the accumulators are only 4 bytes wide. The standard pattern is to keep the DB number in one variable and the byte offset in another, then combine them via OPN DB and indirect access.

L     #nS7DB            // DB number (WORD)
T     #wTemp            // copy because OPN DB needs an address
OPN   DB[#wTemp]        // open the destination DB by number
L     #pS7              // byte/bit offset, area-crossing DWORD
LAR1                     // AR1 holds PIW or DBW pointer
L     DBD[AR1, P#0.0]   // read double-word at AR1 offset
T     DID[#pRMC]        // store into instance DB at _pRMC

This pattern is mandatory when copying between an instance DB (the FB STAT area) and a work DB whose number is calculated at runtime — for example, when an FB moves Profibus DP data into a per-axis DB selected by the user program.

A common bug is to write T DID[AR1, P#0.0] before issuing OPN DB [#nS7DB]. The DID area is the currently-open instance DB; if no instance DB is open, the CPU raises OB121 with event ID 16#2522 (area length error) or 16#2523 (area not loaded). Always OPN DB first, then access DID.

Bulk Copy Loops: Reals Between Instance DBs and Work DBs

The pattern below is taken from an FB that shuttles real-valued axis data between an S7 instance DB and a Profibus DP data block. It runs in OB1 cyclic time and is bounded by #nCount words. The pointer is built from P##_RMC75RD, the compiler-generated address of the FB STAT variable, plus the byte offset 4.0 (two words) because the STAT variable itself is preceded by a control word.

L     P##_RMC75RD       // any-pointer to start of STAT area
L     P#4.0
+D
T     #_pRMC            // _pRMC is a STAT DWORD pointer
L     #nS7DB
T     #_wTemp
OPN   DB[#_wTemp]
L     #pS7
LAR1
L     #nCount

L31:  T     #_nI
      L     DID [#_pRMC]      // load instance-DB double-word
      T     DBD [AR1, P#0.0]  // store into work DB at pS7
      L     #_pRMC
      L     P#4.0
      +D
      T     #_pRMC
      +AR1  P#4.0
      L     #_nI
      LOOP  L31

The reverse direction (work DB → instance DB) is symmetric: swap the L DID and T DBD operands. Keep the increment stride equal to the operand width: 4.0 for DBD, 2.0 for DBW, 1.0 for DBB.

Cross-Area Pointers (PIW ↔ DBW) in a Single Block

When the FB must take the source from PIW and the destination from DBW in the same scan, build a pointer with the PIW area ID DW#16#84000000 and add the desired byte offset. Likewise, PQW uses DW#16#84010000. After LAR1, access PIW[AR1, P#0.0] or PQW[AR1, P#0.0].

L     DW#16#84000000    // PIW area ID
L     #pS7              // desired byte/bit offset (e.g., P#216.0)
+D                       // ACCU1-L now holds full PIW pointer
LAR1
L     PIW[AR1, P#0.0]   // read PIW at runtime offset

To build the PQW pointer change the area ID constant to DW#16#84010000. According to the Siemens functional description, the address of an I/O area must include the I identifier for inputs and the Q identifier for outputs, and the area ID byte is the runtime mirror of those identifiers (Siemens TIA Portal address-area reference).

SCL Equivalent in TIA Portal

In TIA Portal SCL the indirect PIW access is a one-line statement once the address is in an INT or DINT variable. The SCL compiler emits the same area-internal pointer code that STL uses, so the runtime behaviour is identical.

VAR
  iAddr    : INT;       // PIW byte offset (0, 2, 4, ...)
  iValue   : INT;
END_VAR

iAddr := 216;           // would normally come from a parameter
iValue := WORD_TO_INT(PIW[iAddr]);   // equivalent to L PIW[MD20]

Note that PIW[iAddr] in SCL is an INT-returning function-call syntax; an older style MyWord := PIW[#Addr] used in classic STEP 7 SCL is still accepted. The SiePortal post Indirect addressing SCL — PIW[#MyAddr] confirms this SCL syntax on a SIMATIC S7-315 CPU. For variable assignment to a typed tag such as PEW (Peripheral Word), TIA Portal V16+ also accepts PEW{Offset}":P" array-of-slice syntax.

S7-200 Special Case: Bit-Number-Based Width Selection

The S7-200 PPM driver used by OPC servers such as KEPware interprets the bit-number portion of a pointer to decide whether the access is a byte, word, or double-word:

Bit Number Range Default Width Example Address
0–7 Byte (B) VB100 / V100.5
8–15 Word (W) VW100 (treated as a word)
16–31 Double-word (D) VD100

This behaviour comes from the S7-200 PPM addressing definition published by PTC: S7-200 PPM Addressing. It does not apply to native S7-300/400 STL — on those CPUs the pointer bit-offset is always preserved by the CPU, and a PIW access always reads exactly one word regardless of the bit offset in the pointer. If the bit offset is non-zero the CPU raises OB121 (area-length error / width mismatch).

Verification, Diagnostic Buffer, and Watch-Table Procedures

  1. Compile and download the FB in STEP 7 / TIA Portal. In classic STEP 7, choose Edit > Check Block Consistency; in TIA Portal use Compile > Software (rebuild all).
  2. Open the instance DB in online mode and confirm the start of _RMC75RD contains the expected axis data. Step the FB once in single-scan (CRTL+F9 in classic STEP 7; Monitor & Force Table > Single Step in TIA Portal) so the loop runs once.
  3. Inspect the pointer double-words (MD20, MD24, LD_wTemp) in binary view. Bits 2:0 must be 000 for word-aligned PIW/PQW/DBW accesses. Bits 31:24 should match the area ID.
  4. Check the diagnostic buffer on the target CPU (online > CPU > Diagnostic Buffer). The following event IDs indicate common pointer faults:
Event ID Meaning Likely Cause
16#2522 Area length error during read Pointer offset exceeds area size, or wrong DB opened
16#2523 Area length error during write Same as above, on the write side
16#2524 Area error during read (bit) Bit offset bits 2:0 not zero for PIW/PQW
16#2525 Area error during write (bit) Same as above, on the write side
16#2530 DB not loaded OPN DB issued with an unloaded DB number
16#2534 FC/FB parameter error POINTER formal parameter with bad area ID
16#2942 I/O access error PIW points to an uninstalled slot or sub-module
  1. Force the peripheral area (online > Monitor/Modify > Peripheral Outputs) to verify that dynamic PQW writes reach the slot. Set PQW[MD24] with MD24 = P#288.0 and confirm the actuator reacts.
OPC UA / Ignition access: External SCADA tools such as Inductive Automation Ignition connect to S7-300/400 PLCs over TCP using the S7 protocol (Ignition Siemens driver manual). When Ignition reports BAD quality for a tag, check that the area ID is supported and that the TSAP / rack / slot parameters match the CPU; do not assume the PLC program is at fault.

Troubleshooting Matrix

Symptom Probable Root Cause Corrective Action
CPU goes STOP after first scan Bit-offset bits 2:0 in pointer not zero on PIW/PQW access Set pointer base as P#x.0; rebuild pointer with +D and stride P#2.0
PIW reads return the value of MW20 Missing area ID; pointer loaded as plain DWORD instead of PIW-crossing pointer Pre-load DW#16#84000000, then +D with offset before LAR1
DBW read returns zero even though DB has values OPN DB issued with wrong DB number, or pointer is in DI area Verify #nS7DB; remember DID is the instance DB, DBD is the work DB
Real values appear swapped high/low Endian assumption mismatched between Profibus DP slave and S7 Confirm byte order; swap words in the loop using TAW or CAD as required
Loop runs forever LOOP counter loaded into ACCU1-L but destroyed by arithmetic above Reload #nCount into ACCU1-L immediately before each LOOP
SCL compile error "pointer expression not allowed" TIA Portal SCL version older than V13; PIW needs absolute constant Use slice syntax PEW{Offset}":P" or move address to an INT variable
OB121 priority class error after re-download New FB instance DB created with shorter STAT layout; old DI pointers now outside area Re-initialise instance DB (online > Reset to Initial Values) and re-issue P##_RMC75RD

Frequently Asked Questions

What is the difference between PIW and PQW dynamic addressing?

PIW reads from the process image of inputs (area ID DW#16#84000000) and PQW writes to the process image of outputs (area ID DW#16#84010000). Use the correct area-ID constant before loading AR1, otherwise the CPU will read from the wrong area and either return a wrong value or raise OB121 16#2522.

Can I use a full 6-byte POINTER for indirect DB access on S7-300?

No. The S7-300 accumulator is only 4 bytes wide, so a full POINTER (DB number + 32-bit offset) cannot be loaded in a single instruction. Split it: keep the DB number in a WORD, the offset in a DWORD, open the DB with OPN DB[#wTemp], and access DBD[MDxx]. This is the only pattern STEP 7 supports.

How do I increment a pointer by a runtime byte count?

Convert the byte count to a bit offset by multiplying by 8: L #ByteCount; L 8; *I; L MDxx; +D; T MDxx. Always use +D (32-bit add) so the pointer stays aligned as a DWORD. A non-zero bit offset will force an OB121 error on PIW/PQW/DBW access.

Why does the S7-200 PPM driver pick the data width from the bit number?

The S7-200 PPM driver uses the bit-offset field of the pointer to infer the data width: 0–7 means byte, 8–15 means word, 16–31 means double-word. This is a driver-level convention, not an S7-300/400 CPU behaviour. S7-300/400 STL ignores the bit number for width selection; it always reads exactly one word on PIW.

What should I check first when dynamic PIW reads return zero?

Verify three things in order: (1) the PIW pointer has bits 2:0 = 000 (word aligned), (2) the area ID DW#16#84000000 was OR-ed or added before LAR1 so the operand is in the PIW area, (3) the slot/sub-module referenced by the offset is physically installed and not faulted. Use the diagnostic buffer event ID 16#2942 to confirm an I/O access error when the slot is missing.

Back to blog