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:
-
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]). -
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]orDID[AR1,P#0.0]). - 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) andPQW[MD20]always writes a word. To read bytes usePIB[MD20]; to read double-words usePID[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.
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
- 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).
-
Open the instance DB in online mode and confirm the start of
_RMC75RDcontains 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. -
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. - 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 |
-
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.
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.