1. Overview
On a Siemens SIMATIC S7-300 or S7-400 controller running classic STEP 7 (V5.x), indirect addressing is the mechanism that lets a single instruction operate on a memory location that is determined at runtime rather than hard-coded at compile time. The field-proven use case is straightforward: a memory word (e.g. MW300) holds the number an HMI operator enters, and the CPU uses that value as the index of a peripheral input word (PIW) to be read into a target memory word (e.g. MW302). When MW300 = 256, the CPU reads PIW256; when MW300 = 512, the CPU reads PIW512. No code change, no re-compile, no re-download — the HMI is the only data source.
This article documents the canonical STL implementation using LAR1 and the area-crossing pointer format, the matching SCL and LAD/FBD expressions, peripheral fault handling through OB122, the HMI tag wiring, and the diagnostic checks needed to commission a multi-thousand-channel analog plant where the operator only ever inspects a small subset of the I/O at any one time. The technique is documented in the STEP 7 programming reference manual from Siemens, in the "Addressing" chapter of the S7-300/400 system manuals, and in the Siemens Industry Online Support entry on indirect addressing.
2. The S7-300/400 Addressing Model
STEP 7 distinguishes three address spaces, and the distinction matters when you build pointers:
-
Process Image Input/Output (PII/PIQ) —
IW/QW. Updated once per OB1 cycle. Cannot be written back to inputs (IB/IW) safely because the next process-image update overwrites your value. -
Peripheral I/O (PIP/POP) —
PIW/PQW. Direct read/write to the module's hardware register, bypassing the process image. Used whenever the data must be sampled at a deterministic point in the cycle, or when the slot exists outside the configured PII/PIQ range. -
Bit memory / data blocks —
MW/DBW. CPU scratch memory, retained or non-retained as configured.
Every PIW address points to a 16-bit word at the module's peripheral address. The address is byte-aligned and lives in the peripheral area, not in the process image. The valid peripheral address range for a S7-300 CPU is hardware-dependent: a CPU 315-2 PN/DP, for example, exposes 0–2047 bytes of peripheral I/O per rack row, but the practical range for analog modules is bounded by the number of configured channels (slot-by-slot addresses assigned by HW Config). Refer to the S7-300 Automation System System Manual (entry ID 15318415 on Siemens Support) for the per-CPU limits.
3. Pointer Formats in STEP 7
STEP 7 supports four pointer structures, each written in a different notation. The correct format must be matched to the addressing mode you intend to use.
| Pointer type | Width | Format | Where it is used |
|---|---|---|---|
| Area-internal (intra-area) | 16-bit | Unsigned integer offset within the currently open area (DB, DI, M, L, P, Q) |
L IW [MD10] — pointer must be an even byte offset |
| Area-crossing (cross-area) | 32-bit | Bit 31–24 = area code, bits 23–0 = byte.bit offset |
L PIW [AR1, P#0.0] — pointer sits in address register 1 or 2 |
| DB pointer (32-bit) | 32-bit | Area = 0x8E (DB) or 0x86 (DI), bits 23–0 = DBW offset | Pre-loaded for use with full/partial DB access |
| POINTER / ANY (48/80 bit) | 48 / 80 | Byte 0 = type, byte 1 = length, byte 2–3 = DB#, byte 4–7 = byte offset | FB formal parameters, SFC20 BLKMOV, SFCs 58/59 |
Area codes used in 32-bit cross-area pointer (Siemens standard, see STEP 7 Programming Manual, entry 45522307):
| Area code (hex) | Area | Address assignment |
|---|---|---|
| 0x80 | PIW / PQB (peripheral I/O) | Inputs from P, outputs to P |
| 0x81 | Output (PAB/PQB) | Direct peripheral output |
| 0x82 | Inputs (PIB/PIW) | Direct peripheral input |
| 0x83 | Bit memory (M) | MW / MB / MX |
| 0x84 | Data block (DB) | DBW / DBB / DBX |
| 0x85 | Instance DB (DI) | DIW / DIB / DIX |
| 0x86 | Local data (L) | LW / LB / LX — only inside the current block |
| 0x87 | Previous local data (VL) | Caller stack frame |
4. Memory Word as a Pointer: How It Works
The single-instruction form L PIW [MW300] works only when MW300 is interpreted as an area-internal offset into the peripheral input area. STEP 7 implicitly assumes you are pointing inside the I/P area and treats the value as a byte offset, so the pointer must be even and within the configured PII range. For full flexibility — including the ability to read from memory or data blocks later in the same block — the cross-area pointer form with LAR1 is the canonical solution and is the one used in the Siemens sample code referenced in Siemens support post 357628.
The pattern is:
- Read the operator-entered value (
MW300) and convert it into a 32-bit cross-area pointer with the peripheral-input area code (0x82) in the high byte. - Load the pointer into
AR1withLAR1. - Use the indexed
PIW [AR1, P#0.0]syntax in the read instruction. - Transfer the result to
MW302.
This pattern scales to any number of channels, any other area, and any block type. The same AR1 can be re-used for both reads and writes inside the same OB by simply changing the area code before LAR1.
5. STL Implementation: OB1 Block Body
The minimal working block, written in AWL/STL, that performs the field-described operation is shown below. Place the code in OB1 or in a cyclic FB called from OB1. The conversion step is required because a bare integer such as 256 would, when loaded into AR1, be interpreted by the CPU as a pointer pointing into the bit-memory area at byte 256, not into the peripheral area at PIW 256.
// --- Build 32-bit cross-area pointer from MW300 ----------------
// We need: bits 31..24 = 0x82 (peripheral inputs), bits 23..0 = byte offset = MW300
// MW300 holds the desired PIW number, e.g. 256, 400, 500
L 1000_0000_0000_0000_0000_0000_1000_0010 // 0x82000000 area=0x82, offset=0
L MW300 // load operator-entered index
+D // OR the offset into the low 24 bits
T MD304 // store the full 32-bit pointer in MD304
// --- Load into address register 1 ------------------------------
LAR1 MD304 // AR1 now points to PIW<MW300>
// --- Read the peripheral input word ---------------------------
L PIW [AR1, P#0.0] // read PIW at the indexed location
T MW302 // deliver to HMI tag
// --- Optional: clear AR1 to a known state ---------------------
LAR1 P#0.0 // reset
The constant 0x82000000 is the area code 0x82 (peripheral input, 16-bit access) shifted to bits 31–24. The line L MW300 / +D packs the operator's index into the low 24 bits. A bitwise OR would be cleaner, but +D is equivalent here because bits 24–31 of MW300 are zero in normal HMI use. If you need bit-exact OR semantics, use:
L 16#82000000
L MW300
OW
T MD304
LAR1 MD304
6. SCL and LAD/FBD Equivalents
The same logic in SCL (Structured Control Language) is considerably shorter because SCL lets you compute a POINTER at compile-time and use it inside the block scope. The SCL source for a function block FB_RdPIW with an input i_Index : INT and an output o_Value : INT is:
FUNCTION_BLOCK FB_RdPIW
VAR_INPUT
i_Index : INT; // operator-entered PIW number
END_VAR
VAR_OUTPUT
o_Value : INT; // sampled value
END_VAR
VAR_TEMP
t_AR1 : POINTER;
END_VAR
BEGIN
t_AR1 := P#0.0; // cross-area pointer initial value
// For SCL on S7-300, use SFC-free indexed read:
o_Value := WORD_TO_INT(PIW[i_Index]);
END_FUNCTION_BLOCK
Note: PIW[i_Index] in SCL is area-internal and is only valid for the peripheral input area. For S7-400 with SCL you can build an ANY pointer and pass it to SFC20 BLKMOV for an 8-channel burst read. The SCL compiler on STEP 7 V5.5 SP4 and later does the area-crossing pointer construction automatically when the index expression is a variable.
In LAD (Ladder) or FBD (Function Block Diagram), the canonical building block is the Move box with an indexed input. You cannot write PIW[MW300] directly in LAD on classic STEP 7; you must either drop into STL for that one network or use a pre-assembled library block. Field-proven alternative: a self-built FB that implements the STL above and is called from LAD as PIW_reader.MW302 := PIW_reader.Read(PIW_reader.MW300);.
7. PIW Address Range and Module Slot Map
The PIW value entered by the operator is not arbitrary; it must be one of the physical addresses assigned by STEP 7 HW Config to the analog input module. For a SIMATIC S7-300 ET 200M station:
| Slot | Typical module | PIW range (byte addresses) | Channel count |
|---|---|---|---|
| 4 | SM 331 AI8x12bit (6ES7331-1KF02-0AB0) | PIW 256..271 | 8 channels / 2 bytes each |
| 5 | SM 331 AI8x12bit | PIW 272..287 | 8 channels |
| 6 | SM 332 AO4x12bit | PQW 288..295 | 4 channels |
| 7 | SM 321 DI16xDC24V | PIW 304..305 | 16 bits |
The exact addresses are visible in HW Config → Module → Properties → Addresses. The default PII start for slot 4 is 0, but you can move it anywhere in the 0–2047 range. The operator HMI should be programmed to display the address from the same HW Config, not from a hard-coded list — otherwise a slot reconfiguration will silently break the read.
PIW 257 is an unaligned access; the CPU will either fault or read the wrong word depending on the module. Always force the operator entry to even values through an HMI limit or a server-side ladder rung that masks bit 0.8. Fault Handling: OB122 and the SF LED
If the operator enters a PIW number that is not assigned to any configured module, the CPU raises a peripheral I/O access error (event ID 0x2942 in the diagnostic buffer; startup OB for it is OB122 in classic STEP 7). On most S7-300/400 CPUs the SF (System Fault) LED turns red, the diagnostic buffer records the offending byte address, and — critically — if OB122 is not loaded the CPU transitions to STOP.
Always load OB122 in the project. Inside the OB, write the error info to a global data block so the HMI can display "Bad address" to the operator rather than letting the CPU go to STOP. The temporary local variables of OB122 (declared automatically by STEP 7) include:
| OB122 local | Meaning | Use |
|---|---|---|
| OB122_EV_CLASS | Event class (16#39 = incoming error) | Filter |
| OB122_SW_FLT | Software fault code (16#42 = I/O access error) | Distinguish peripheral error from DB error |
| OB122_BLK_TYPE | Block type that caused the fault | Skip if not your block |
| OB122_BLK_NUM | Block number | Skip if not your block |
| OB122_PRM_ADDR | Faulting peripheral address (byte) | Display on HMI |
| OB122_MEM_AREA | Access type: 16#80 = read P, 16#81 = write P, 16#82 = read P 16-bit, 16#83 = write P 16-bit | Filter |
The minimal fault-handling body in OB122 is:
// OB122 body: record the fault without stopping the CPU
L #OB122_PRM_ADDR // faulting byte address
T DB_Faults.fault_addr
L 1
T DB_Faults.fault_latch // set latch (HMI clears it on "ack")
For the operator's HMI tag, also enforce range validity on the HMI side (WinCC flex / TIA Comfort / ProTool/WinCC). An input field with a configured lower and upper limit stops the bad value from being sent to the controller in the first place and removes the dependence on OB122 for normal operation.
9. HMI Integration
The HMI side of the application is a two-tag system:
-
Index tag (write): a
Word-typed tag pointing toMW300. Configure as anInputfield with a low limit equal to the lowest configured PIW byte (e.g. 0) and a high limit equal to the highest (e.g. 1022, even). Set acquisition mode to "cyclic continuous". -
Value tag (read): a
Word-typed tag pointing toMW302. Configure as a numericOutputfield with display format "DEC 16-bit signed" or "DEC 16-bit unsigned" depending on the analog module's scaling (12-/13-/14-bit left-aligned, sign in bit 15).
Use the Scale / Unscaling field property if you want the HMI to display the engineering unit directly. The PLC's MW302 continues to carry the raw integer, and a separate scaling tag can be used if you also want a derived EU value for the HMI. For SIMATIC WinCC (V7 or Professional) the tag connection is set up in the Variable Manager; for WinCC flexible / TIA Portal it is in the HMI tag table. Both tag tables must reference the same connection — S7ONLINE over MPI/Profibus/Profinet — and the update rate for the index tag should be at least 500 ms to avoid hammering the CPU with bad entries during number entry.
MW300 write inside the same OB1 cycle in which the HMI displays the result. Either delay the read by one cycle (cheap: use a MB toggle) or, better, gate the read with a MW300 <> MW300_old change detector so the indexed access happens only when the operator finishes typing. This reduces OB1 scan time on large plants and keeps the SF LED off in the steady state.10. Validation and Commissioning Steps
-
Static check: in the online watch table, force
MW300 = 0. ConfirmMW302equalsPIW0(truncated fromIB0/IB1). Repeat withMW300 = 256and confirmMW302equalsPIW256from the first configured SM 331. -
Range boundary: force
MW300 = 1022(highest even PIW for a 1 KB peripheral window). Verify the module is present and the read returns within one OB1 cycle. -
Fault injection: force
MW300 = 1023(odd — should be rejected by the alignment mask). ForceMW300 = 1500(outside the configured PII range). Confirm the SF LED is on, the diagnostic buffer entry has event ID 16#2942, and the CPU remains in RUN becauseOB122is loaded. - End-to-end: type a known-good PIW number into the HMI input field. Confirm the displayed value tracks the analog input on the field signal generator within one HMI update cycle.
11. Performance and Scan-Time Considerations
The LAR1 + indexed PIW + T sequence executes in roughly 4–7 microseconds on a CPU 315-2 PN/DP and 1–2 microseconds on a CPU 417. There is no measurable OB1 scan-time penalty for doing one indexed read per cycle. What does cost scan time is reading many PIWs in a tight loop:
| Approach | Channels read per cycle | Time on CPU 315-2 PN/DP | Use case |
|---|---|---|---|
| Indexed PIW + T (single channel) | 1 | ~5 µs | Operator-driven spot reading |
| Loop with P#2.0 increment (manual) | N (loop count) | ~5 µs + 2 µs per channel | Sequential scan, N < 32 |
| SFC20 BLKMOV with ANY pointer | N (burst) | ~30 µs + 1 µs per channel | Bulk read, N up to 512 |
| SFC59 RD_REC to module diagnostic | Variable | ~2 ms per call | Read additional module status |
For "thousands of analog inputs" with the operator inspecting one at a time, the indexed single-channel read is the only sensible option. A bulk scan that touches every PIW on every cycle would be wasteful and would also generate 1 to N fault entries in the diagnostic buffer if any single channel has been unplugged. The selection of the indexed single-channel read is not just a code-style choice; it is the throughput and safety choice.
12. Method Comparison
| Method | Syntax complexity | Scales to other areas | Peripheral-fault safe | Best for |
|---|---|---|---|---|
Inline STL with LAR1 + PIW [AR1, P#0.0]
|
Medium | Yes — change area code | Yes — with OB122
|
One-channel, area-crossing reads |
Area-internal PIW[MW300]
|
Low | No — P area only | Yes — with OB122
|
PIW-only spot reading |
SCL PIW[i_Index]
|
Lowest | Compiler-dependent | Yes — with OB122
|
Quick-and-dirty SCADA exports |
| SFC20 BLKMOV with ANY pointer | High | Yes — all areas | Yes — but copies everything or faults | Bulk acquisition for logging |
| SFC58 RD_REC / SFC59 WR_REC | High | Module-specific | Returns error code in RET_VAL | Reading module diagnostics records |
BLKMOV approach is faster for many channels per cycle, but it does not gracefully handle a single bad address; one unmapped byte aborts the whole copy. For diagnostic-readout-of-everything scenarios prefer the STL index approach executed in a loop with a per-channel OB122 fault skip.13. Field-Edge Cases and Caveats
-
Word/big-endianness: Siemens uses big-endian bit numbering on the 32-bit cross-area pointer. The constant
0x82lives in bits 31–24, not 7–0.SLD/SRDis your friend when building the pointer by hand. -
Time stamping: the read in
PIW [AR1, P#0.0]is a direct peripheral read, so it does not use the time stamp of the process image. For SIL-2/3 analog applications that require time-tagged values, copy the result into a time-stamped DB word immediately. - Two CPUs in H-system / redundancy: the pointer is constructed from the index, not from the slot number, so the same code runs unchanged on an H-CPU pair. The PIW value entered by the operator, however, must be the logical address visible to both lead and reserve — the S7-400H system manual lists the supported address range (entry ID 1186523).
-
Migration to TIA Portal / S7-1500: on a S7-1500 with the optimized-block access model, the area-internal pointer form is the only legal one, and
LAR1is not exposed in the same way. Use SCL with the"DB".Tag[i]indexed syntax for arrays, or move the operator index into aTEMPWORDand use aPEEKlibrary function for raw peripheral reads. See the S7-1500 system manual (entry ID 109751826) for the migration checklist.
14. Reference: STL Building Blocks Recap
// 1. Build cross-area pointer
L 16#82000000
L MW300
OW
T MD304
// 2. Load pointer into AR1
LAR1 MD304
// 3. Indexed read
L PIW [AR1, P#0.0]
T MW302
For a write counterpart (operator-entered MW300 value to a PQW), replace the area code 0x82 with 0x81 and the read with LAR1 / L MWxxx / T PQW [AR1, P#0.0]. For a memory read, use 0x83 and reference MW [AR1, P#0.0].
15. FAQ
Why does the SF LED turn red when I type a bad PIW number into MW300?
The cross-area pointer from MW300 points to a peripheral address that is not assigned to any configured module, so the CPU raises a peripheral I/O access error. The SF LED is set by event ID 16#2942 in the diagnostic buffer. Load OB122 and the CPU stays in RUN; without OB122 the CPU transitions to STOP.
Can I use the same trick to read from a data block (DBW) or from bit memory (MW)?
Yes. Change the area code at the top of the pointer from 0x82 (peripheral input) to 0x84 (DB) or 0x83 (bit memory) and replace the PIW [AR1, P#0.0] instruction with DBW [AR1, P#0.0] or MW [AR1, P#0.0]. The same LAR1 / T / L pattern is reused.
What is the valid PIW address range on an S7-300 CPU?
It depends on the CPU. The standard S7-300 (CPU 31x) peripheral I/O range is 0–2047 bytes (0–2047 PIW for 16-bit access). The exact byte range, including any reserved areas for the integrated I/O, is listed in the S7-300 System Manual on Siemens Support (entry ID 15318415). On S7-400 the range is 0–65535 bytes.
Is there a way to read many PIWs in one cycle for SCADA logging?
Use SFC20 BLKMOV with an ANY pointer built in a DB. The source is PIW[lowByteAddr] and the destination is a DBW range. Be aware that one bad address in the source range aborts the copy unless the destination is set up to mask faults; for fault-tolerant bulk reads use the indexed PIW [AR1, P#0.0] pattern inside a loop with OB122 fault suppression.
Will the indirect PIW read work on a S7-1200 or S7-1500 with TIA Portal?
On S7-1200 the same area-internal indexed read is supported in SCL with PEEK/POKE. On S7-1500 the S7-300/400 style 32-bit cross-area pointer with LAR1 is not exposed; use SCL with array indexing for DBs and the optimized-block PEEK function for raw peripheral reads. See the S7-1500 system manual (entry ID 109751826) for the migration table.