Siemens S7 SCL Indirect I/O Addressing: Reading PIW by Address

David Krause15 min read
HMI ProgrammingSiemensTutorial / 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

Siemens S7 SCL Indirect I/O Addressing: Reading PIW by Address

Reading a process input word (PIW) by passing its address as a function parameter is one of the most common - and most misunderstood - tasks in S7 programming. A user has an RTD module mapped at peripheral word 512 and wants a single, reusable function (for example getRTD) that returns the scaled temperature regardless of which PIW address is wired in. The same pattern is needed for digital input bytes/words, output words, and counters. This reference covers both SCL and STL techniques, the 32-bit area pointer that makes STL indirect addressing work, the SFC105 READ_SCALE call that turns raw counts into engineering units, and the diagnostics implications of indirect I/O in TIA Portal.

Problem Definition: Variable-Address I/O Access

Symbolic addressing in STEP 7 and TIA Portal resolves at compile time. The PLC symbol table ties the literal PIW512 to a hardware address, and the generated code references that address directly. Once compiled, the symbol cannot be moved or parameterised at runtime. To reuse the same code body against different addresses, the program must dereference the address at runtime.

Three runtime-deferred address sources are common in industrial code:

  • Operator-selected slot numbers (recipe-driven measurement channels).
  • Index-based scanning of a module's full address range (loop over PIW words of an 8-channel AI card).
  • Function block (FB) instance data that carries the I/O address as an INT tag.

For S7-300/400 CPUs the compiler accepts the syntax PIW[Idx], IW[Idx], and QW[Idx] directly in SCL. For S7-1200/1500 the equivalent pattern is %IW[Idx] over an AT overlay, or the PEEK/POKE instruction set. STL on every S7 platform supports indirect addressing through an area pointer in AR1/AR2.

Hardware caveat: On S7-1500 CPUs, blocks default to optimised access. Indirect I/O works, but the absolute address must be hand-calculated from the device configuration because symbolic tag resolution is locked to the data block view. Set the block's access mode to standard (non-optimised) when the FB or FC must perform pointer arithmetic over I/O.

Prerequisites

  • Engineering tool: STEP 7 V5.5 SP2+ for S7-300/400, or TIA Portal V16+ for S7-1200/1500.
  • CPU firmware: S7-300 (any), S7-400 (any), S7-1200 firmware V4.2+, S7-1500 firmware V1.8+ recommended.
  • SCL compiler: Installed and enabled (separate licence on legacy STEP 7; bundled with TIA Portal).
  • Analog input module configured in HW Config / Device Configuration with type 4-wire RTD (e.g., 6ES7 331-7PF01-0AB0) or thermocouple. Verify measurement range and resolution in the module properties.
  • Symbolic I/O disabled for the address ranges used indirectly (TIA Portal: PLC → Properties → General → Symbolic access), otherwise the compiler rejects the indexed form.

SCL Method - Direct Indexing of the Process Image

The shortest possible implementation is a single SCL statement inside an FC:

FUNCTION getRTD : INT
VAR_INPUT
    Addr : INT;
END_VAR
BEGIN
    getRTD := WORD_TO_INT(PIW[Addr]);
END_FUNCTION

The PIW square-bracket operator returns the raw 16-bit count at the supplied peripheral address. Because the function returns INT and PIW is declared as WORD, an explicit conversion (WORD_TO_INT) is required. For signed bipolar measurements use INT_TO_REAL after the conversion, then linearise against the configured range.

Digital I/O and Memory Areas

The same operator works for any peripheral or process-image area:

Area SCL syntax Data type Notes
Process image input word IW[Addr] WORD Updates each OB1 scan; no peripheral refresh delay.
Peripheral input word PIW[Addr] WORD Bypasses the process image; reads directly from the module.
Process image output word QW[Addr] WORD Reflects the last value written by the program.
Peripheral output word PQW[Addr] WORD Direct write to module; readable only as the most recent written value.
Bit memory MW[Addr] WORD Use only when scan-time savings outweigh diagnostic clarity.
Bit (digital) I[Addr.Bit] or I[Addr] with byte offset BOOL Bit access requires BOOL declarations in the FC.

Multi-Channel Reading in a Loop

Loop over a contiguous block of addresses:

FUNCTION ReadAllChannels : VOID
VAR_INPUT
    StartAddr : INT;
    ChannelCount : INT;
END_VAR
VAR_TEMP
    i : INT;
END_VAR
BEGIN
    FOR i := 0 TO ChannelCount - 1 DO
        // Store into a global array of WORD; adjust offsets to local DB if needed
        g_ChannelRaw[i] := PIW[StartAddr + i];
    END_FOR;
END_FUNCTION
Compiler bounds check: SCL validates the declared type but not the runtime index. A bad value (e.g., StartAddr + i = 999999) reads garbage or causes a peripheral access fault. Always validate the input range with a IF guard before the indexed read.

STL Method - Indirect Addressing with an Area Pointer

STL requires a fully built 32-bit area pointer before any indirect access. The standard four-TEMP pattern used by the Siemens SFC library is:

FUNCTION_BLOCK readRTD_STL
VAR_INPUT
    Addr : INT;       // Peripheral byte address of the analog channel
END_VAR
VAR_TEMP
    PerPointer : DWORD;   // 32-bit pointer in memory area format
    IntVal     : WORD;    // Captured PIW value
    RetStat    : WORD;    // Return status of SFC105
    ReadVal    : REAL;    // Scaled engineering value
END_VAR
BEGIN
    L     #Addr;        // Load byte address (e.g., 512)
    SLD   3;            // Shift left 3 bits to make room for bit offset
    T     #PerPointer;  // Store as DWORD

    L     PIW[#PerPointer];  // Indirect read - area ID defaults to PI
    T     #IntVal;           // Hold for SFC105

    CALL  SFC105
        IN     := #IntVal
        HI_LIM := 27648.0     // or module-specific upper limit
        LO_LIM := 0.0
        BIPOLAR:= FALSE
        RET_VAL:= #ReadVal
        OUT    := #ReadVal;
END_FUNCTION_BLOCK

The SLD 3 operation creates space in the lowest three bits for a bit offset that is added later. PIW[#PerPointer] interprets the pointer's area-ID byte (which defaults to peripheral input when omitted) and the shifted byte address, and reads the raw count.

Bit-Addressed Indirect Access with AR2

To read a single digital bit through an address pointer, load the pointer into AR2 and use any of the register-indirect operand forms:

L     #ByteAddr;     // Byte number
SLD   3;             // Shift left 3 bits
L     #BitAddr;      // 0-7
+D;                  // Merge bit offset into pointer
LAR2;                // AR2 holds the full 32-bit pointer

A     I [AR2, P#0.0];   // Indirect AND of the addressed input bit
=     #CoilOut;         // Use as needed

The address-register indirect syntax is mandatory in STL because STL does not allow I[byte.bit] with a runtime-supplied address. SCL, by contrast, handles the pointer construction internally and exposes only the I[Addr.Bit] notation.

Decoding the 32-bit Area Pointer

The 32-bit pointer used by LAR1/LAR2 is a memory-area pointer in bit-and-byte format. Its bit layout is fixed by the Siemens STL specification:

Area ID Bits 24-31 8 bits 0x80 - 0xFF Byte Address Bits 3-23 21 bits 0 - 2,097,151 Bit Address Bits 0-2 3 bits 0 - 7 31 24 23 3 0 32-bit Memory Area Pointer (Pointer Format)

Area Identifier Table

Hex Binary (8 bits) Area Notes
0x80 1000 0000 Input (PII / I) Process image input, also peripheral input (PI).
0x81 1000 0001 Output (PIQ / Q) Process image output; PIQ reads as most-recent written value.
0x82 1000 0010 Bit memory (M) Standard M area.
0x83 1000 0011 Data block (DB) Use DBNR in DB register separately.
0x84 1000 0100 Instance DB (DI) Same format as DB but DI register used.
0x85 1000 0101 Local data (L) Temporary stack.
0x87 1000 0111 Counter (C) Counter current value area.
0x88 1000 1000 Timer (T) Timer current value area.

Worked Example: P#M12.3

Bit-level construction for P#M12.3 (byte 12, bit 3 of the M area):

  1. Byte 12 = 0x0C = 0000 1100. Shifted left 3 bits: 0110 0000 = 0x60.
  2. Add bit 3: 0x60 + 0x03 = 0x63 = 0110 0011.
  3. Area ID for M = 0x82 = 1000 0010.
  4. Concatenate: 1000 0010 0000 0000 0000 0000 0110 0011 = 0x8200_0063.
Common documentation error: Some Siemens training materials list the bit pattern of P#M12.3 with an area byte of 0x83. The correct M-area identifier is 0x82; 0x83 is the data-block area ID. Verify any copied pattern against the standard before pasting it into your STL.

The STL line that builds the same pointer directly is:

L     P#12.3;        // Load literal pointer M12.3
LAR2;                // Place in AR2 for indirect use

Reading PQW and Output Modules

The original problem framed an asymmetry: PIW and IW index cleanly, but PQW appears unreadable. The asymmetry is not a hardware limit - it is an SCL compiler rule. On S7-300/400 SCL, PQW[Addr] is a read/write peripheral reference and works as a right-hand expression:

g_LastSent := PQW[Addr];   // Capture most-recently written peripheral value

What the operator cannot do is read the actual electrical output of an analog output module - the peripheral read returns the value the CPU wrote, not a feedback measurement. Modules without hardware read-back (most 6ES7 332 / 6ES7 135 output cards) cannot return their terminal voltage.

On S7-1200/1500, peripheral output is accessed via the POKE instruction for writes and an equivalent PEEK read with output area qualifier 16#81:

g_LastSent := PEEK(area := 16#81, byteOffset := Addr);   // S7-1200/1500 SCL

When PQW Reads Return Zero

Cause Symptom Fix
Module address gap Readback shows zero even though the program writes a non-zero value. Confirm HW Config: PQW base address must be the same byte the program writes.
Process image update disabled Process image is empty; PIQ differs from PQW. Check the module property "Update of process image". Enable if diagnosis requires PIQ.
Optimised block access SCL rejects PQW[Addr] with error "Memory area not permitted". Set block access mode to standard.
Wrong area ID STL reads garbage from M area. Build area ID 16#81 into the high byte of the pointer, not 16#80.

SFC105 READ_SCALE Integration

SFC105 READ_SCALE converts a raw peripheral integer into a scaled REAL engineering value. It is the canonical Siemens way to linearise analog inputs without writing ladder scaling math. Parameters:

Parameter Declaration Type Description
IN INPUT INT Raw peripheral value (counts).
HI_LIM INPUT REAL Engineering value at HI_LIM counts.
LO_LIM INPUT REAL Engineering value at LO_LIM counts.
BIPOLAR INPUT BOOL TRUE for -27648...27648; FALSE for 0...27648.
RET_VAL OUTPUT REAL Returns the scaled engineering value.
OUT OUTPUT REAL Same value as RET_VAL; one of them may be omitted.

Scaling Formula

SFC105 internally applies:

OUT = ( (IN - 0) / (27648 - 0) ) * (HI_LIM - LO_LIM) + LO_LIM

For bipolar input the denominator is 27648 - (-27648) = 55296. Over-range values are clamped to HI_LIM / LO_LIM. A return status of W#16#0000 indicates no fault; non-zero values indicate wiring or module faults documented in the S7-300/400 System and Standard Functions reference.

Using the Scaled Value in SCL

FUNCTION_BLOCK readRTDSCL
VAR_INPUT
    Addr : INT;
    HI_LIM : REAL := 100.0;
    LO_LIM : REAL := 0.0;
    BIPOLAR : BOOL := FALSE;
END_VAR
VAR
    Scaled : REAL;
END_VAR
BEGIN
    Scaled := SCALE_X_REAL(
        VALUE := INT_TO_REAL(WORD_TO_INT(PIW[Addr])),
        MIN   := 0.0,
        MAX   := 27648.0,
        LO_LIM := LO_LIM,
        HI_LIM := HI_LIM);
END_FUNCTION_BLOCK

For S7-1500 the equivalent IEC function SCALE_X avoids SFC105 entirely and runs natively on every firmware version. For S7-300/400 the SCALE function from the IEC library produces identical results and is preferred for portability.

Cross-Reference Behaviour in TIA Portal

Indirect I/O access deliberately hides the target address from the compiler. The consequence is that the cross-reference (x-ref) view in TIA Portal, in the Used Tags pane, and in the Program Information viewer show the indirect reference as unused. Diagnostic tools cannot navigate from the module tag back to the consumer.

Mitigation strategies:

  1. Maintain a separate IO map workbook. For every indirect accessor (FB, FC), list the addresses it can read/write. Treat it as the authoritative source of field wiring.
  2. Use an enum or constant block for slot numbers. Even with indirect access, naming the slot (RtD_Slot_1, RtD_Slot_2) keeps the symbol table meaningful.
  3. Generate symbol comments from HW Config. The Generate PLC symbols from device configuration option in TIA Portal produces comments for each PIW; those comments show up in the function's source view even when the address is reached indirectly.
  4. Document at the FB interface. Add a multi-line comment above the Addr input declaring which module and slot range the parameter accepts.
Audit tip: When the indirect code is part of a safety function, the TÜV assessor will require proof that every peripheral address reachable by the pointer is safe. Plan for that documentation during design, not at certification.

Verification and Commissioning

  1. Watch table sanity check. Create a VAT with the raw PIW address and a separate row calling the getRTD function with the same address. Both rows must show identical values.
  2. Loop test. For a multichannel module, force a known input (precision decade box or thermocouple calibrator) on each channel and verify the function returns the expected engineering value within module accuracy.
  3. Online monitor of TEMP variables. In STL add the four TEMPs (PerPointer, IntVal, RetStat, ReadVal) to a watch table. PerPointer must equal Addr << 3; IntVal must equal the raw PIW; ReadVal must match the calibrator.
  4. Pointer-range audit. Trigger the FC with maximum and minimum legal Addr. Confirm no diagnostic buffer entry ("Peripheral addressing error") is written by the CPU.
  5. Stop / Run cycle. After a CPU restart, verify the function reads the same value as a cold-start. Some modules hold the last value; others reset. Test the actual behaviour, do not assume.
  6. Compile-mode check. Toggle the SCL block's compiler mode between standard and optimised. The indirect access must compile in both.

Troubleshooting Matrix

Symptom CPU diagnostic Probable root cause Remediation
getRTD always returns 0 No entry Addr not shifted left 3 in STL; area ID missing Insert SLD 3; ensure LAR2 loaded before A I [AR2,P#0.0]
Compile error "Unknown operand" Block does not compile S7-1500 optimised block; PIW not allowed Switch block to standard access; use PEEK
Returns garbage value No entry Addr contains an uninitialised INT Initialise Addr in the FB instance data
CPU goes STOP SF: OB not loaded; time-of-day OB error Illegal peripheral address; PII size exceeded Validate Addr range; install OB122 (peripheral access error)
SFC105 returns negative count No entry BIPOLAR mismatch with module config Match BIPOLAR to module type (RTD = unipolar 0-27648; bipolar TC = TRUE)
Readback differs from input No entry Module has diagnostic interrupt pending Evaluate OB82; check module channel diagnostics in TIA Portal online
PQW read shows zero No entry Process image update disabled for the slot Re-enable PI update; or read PQW directly instead of QW
Cross-reference empty N/A Indirect access - expected behaviour Document the I/O map externally; add comments at the function block

Portability Notes for S7-1200/1500

Code written for S7-300/400 requires minor rework on the S7-1500 platform. The two key adjustments are:

  1. Replace PIW[Addr] with an AT overlay. Declare a global variable of type ARRAY[0..n] OF WORD with an AT view onto the peripheral input range. Index into the array.
  2. Replace SFC105 with SCALE_X or NORM_X. Both IEC 61131-3 functions are part of the standard TIA Portal library and produce identical results without requiring the legacy SFC block.

The S7-1500 instruction PEEK (word) and POKE (word) extend the same indirect pattern to any memory area:

// S7-1500 SCL: read PIW at runtime
g_RawChannel := PEEK(area := 16#81, byteOffset := Addr);

For 16#81 the read is treated as a peripheral input even though the area ID nominally maps to PIQ; the firmware resolves the difference between PEEK of input versus output by the surrounding context. Use POKE with the same area ID to write to the peripheral output.

Field-Proven Caveats

  • OB122 priority. On S7-300 a peripheral read error invokes OB122. If OB122 is not loaded, the CPU transitions to STOP on the first invalid Addr. Always load OB122 when indirect I/O is in use.
  • SFC105 on S7-1500. SFC105 remains available in S7-1500 firmware V1.x but is deprecated in V2.x. Plan migration to SCALE_X on any new project.
  • Bit address in pointer. When constructing a pointer for digital bit access, forgetting the trailing SLD 3 / bit-add merges the bit number into the byte address, producing a wild read. Always include SLD 3 even when the bit offset is zero.
  • Optimised block on S7-1500. Even with the access-mode set to standard, some symbolic tags inside optimised FBs cannot be reached by absolute address. Place any indirect-I/O helper in a non-optimised FC.

Standards and References

Refer to the official Siemens documentation when verifying pointer formats and SFC parameters:

Frequently Asked Questions

Can I read PQW from an S7-300/400 CPU the same way I read PIW?

Yes. The SCL operator PQW[Addr] reads the most-recently written peripheral value on S7-300/400 CPUs. The result is the value the CPU placed on the module, not a feedback measurement from the field wiring. Modules without read-back (e.g., 6ES7 332-5HB01-0AB0) cannot return the actual terminal voltage.

Why does indirect I/O disappear from cross-references in TIA Portal?

Because the address is supplied at runtime, the compiler cannot resolve a static reference. TIA Portal lists the symbol as unused. Document the I/O map externally (workbook or symbol comments) and treat the cross-reference view as incomplete for any indirect accessor.

How do I convert a raw PIW count to engineering units in STL?

Call SFC105 READ_SCALE with the raw WORD as IN, the engineering HI_LIM and LO_LIM as REAL, and a BIPOLAR flag matching the module type. For 0-27648 RTD use BIPOLAR := FALSE; for -27648 to +27648 thermocouple use BIPOLAR := TRUE. The function returns the scaled value in OUT and RET_VAL.

What is the area-ID byte of the 32-bit memory-area pointer?

The high byte (bits 24-31) holds the memory area identifier. Common values are 0x80 for inputs, 0x81 for outputs, 0x82 for M memory, and 0x83 for DB. The byte address occupies bits 3-23 and the bit offset occupies bits 0-2.

My S7-1500 FC rejects PIW[Addr] at compile time - what is the replacement?

Use PEEK(area := 16#81, byteOffset := Addr) from the TIA Portal extended instructions. Alternatively declare an AT overlay over %IW0 as an ARRAY[..] OF WORD and index into the array. Both compile under optimised block access.

Back to blog