Siemens S7 Indirect Addressing with POINTER Parameters in STL FBs

David Krause21 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: From Hardcoded I/O to a Reusable POINTER-Based FB

Hardcoded process outputs such as T QB 22 or T QB 3 are the fastest path to a working program, but they make function blocks (FBs) non-reusable. The moment a customer wants to call the same FB three times for three independent handshake channels, the hardcoded QB 22 forces three separate code blocks and three independent fault paths. The fix is to pass the I/O address as a POINTER parameter and dereference it inside the FB using STL address-register arithmetic.

This article documents the incremental build of a reusable S7-300/400 FB that takes a single POINTER to an output byte area, writes a command byte and two data bytes, polls three acknowledgment input bytes plus two status bits, and latches a fault if the handshake does not complete within 2 seconds. Three iterations are shown: FB3 (byte-index parameter, simplest case), FB4 (POINTER parameter, single byte), and FB5 (POINTER parameter, multi-byte with sealed fault). Each iteration exposes a specific failure mode of the previous one and the STL pattern that fixes it.

Prerequisites and Target Hardware

  • CPU: S7-300 (CPU 31x series, e.g. CPU 315-2 DP) or S7-400 (CPU 41x series). The technique is firmware-agnostic within the S7-300/400 family.
  • Engineering tool: STEP 7 V5.5 or later, or TIA Portal with the S7-300/400 optional package.
  • Programming language: STL (Statement List / AWL). Ladder and FBD cannot dereference POINTER parameters inside the FB body.
  • Library access: System Function Block SFB4 (IEC Timer On-Delay) from the Standard library, or a user-defined TON multi-instance.
  • Diagnostic block: OB121 (Programming Error OB) loaded as a placeholder BE during development to keep the CPU in RUN while pointer faults are being debugged.
  • Working knowledge of: instance DBs, multi-instance FBs, address registers AR1 and AR2, and the 6-byte POINTER format described in the next section.

The 6-Byte POINTER Format in S7-300/400 STL

A POINTER in S7-300/400 STL is a 6-byte (48-bit) structure that locates a single bit inside a memory area. It is the smallest addressing type that can be passed as an FB parameter and dereferenced at runtime.

Byte Offset Field Width Example (P#Q3.0) Notes
0-1 DB number WORD W#16#0000 0 for non-DB areas (I, Q, M, L)
2 Area code BYTE B#16#82 0x82 = Q area (see area code table below)
3-5 Bit address 3 bytes (24 bits) 0x000018 = 24 Byte number × 8 + bit number; bit number occupies the low 3 bits

For P#Q3.0 the 6-byte value is W#16#0000 B#16#82 B#16#00 B#16#00 B#16#18. Loaded as a DWORD (the lower 4 bytes) with L D [... ], the value is DW#16#82000018. The address portion stores the bit position, not the byte number. To convert byte 3 to its 24-bit equivalent, the source uses SLD 3 on the byte value 3, producing 24 (0x18). This is why the diagnostic comment in the source reads 18 = 3 SLD 3.

POINTER Layout (6 bytes, 48 bits) — Example: P#Q3.0 DB Number W#16#0000 2 bytes Area B#16#82 1 byte Bit# 0 3 bits Byte Address (29 bits) DW#16#000003 3 bytes Loaded as DWORD: DW#16#82000018 → T QB [AR1,P#0.0] writes to byte 3 of the Q area

Core STL Instructions for Pointer Manipulation

Instruction Operands Purpose
L P##<param> FB/FC parameter Load the 6-byte address of a parameter (pointer to the pointer)
LAR1 ACCU1 Load address register 1 from ACCU1 (user-controlled)
LAR2 ACCU1 Load address register 2 (instance data register — do not modify)
TAR1 Transfer AR1 to ACCU1 for inspection or arithmetic
TAR2 Transfer AR2 (current instance base) to ACCU1
AD <const> ACCU1 Add 32-bit constant to ACCU1
+D ACCU1 + ACCU2 32-bit addition; result in ACCU1
SLD <n> ACCU1 Shift left double word by n bits (used to scale byte offset to bit address)
OPN DB ACCU1 or constant Open a data block as the current DB for subsequent L D / T D accesses
L DINO Load the number of the currently open instance DB into ACCU1
L D [AR1, P#x.y] AR1 + offset Load DWORD from address AR1 + byte/bit offset (memory-indirect)
T QB [AR1, P#x.y] AR1 + offset Transfer to process output via AR1 + offset
L IB [AR1, P#x.y] AR1 + offset Load process input via AR1 + offset
SAVE Save RLO in BR memory; required before BEC or end of FB
BEC Conditional block end (BR-dependent)
Critical: AR2 is the system-managed instance data register for FBs. Never overwrite AR2 inside an FB; the runtime uses it to resolve all subsequent L #param and T #param accesses. Use AR1 for any user-controlled indirect addressing. Modifying AR2 will silently corrupt instance data accesses later in the same FB call.

Building FB3: Single-Byte Handshake with SLD Pointer

FB3 is the first iteration. The input is a BYTE parameter representing the offset into the Q area, not a full POINTER. This avoids the need for pointer dereferencing and is the simplest possible indirect-access pattern. It also exposes the most common pitfall: a non-zero bit address in a byte-oriented access.

FUNCTION_BLOCK FB 3
TITLE = VERSION : 0.1
VAR_INPUT
  Pnt_QB3 : BYTE;   // byte offset into Q area (e.g. 3 for QB3)
  CmdByte : BYTE;   // command byte to write
END_VAR
VAR_OUTPUT
  Ackd    : BOOL;   // handshake acknowledged (IB = QB)
  FaultBit: BOOL;   // 2 s timeout reached without ack
END_VAR
VAR
  Faulted : SFB 4;  // IEC on-delay timer instance
END_VAR
VAR_TEMP
  temp    : BOOL;
  NotAckd : BOOL;
  DWPntr  : DWORD;
END_VAR
BEGIN
NETWORK TITLE = Handshake with indirect byte offset
  L  #Pnt_QB3;       // load byte offset (e.g. 3)
  T  #DWPntr;        // store as DWORD for indirect use
  SLD 3;             // shift left 3 → 24 = bit address 3.0
                     // (mandatory: byte access requires bit 0 = 0)
  L  #CmdByte;       // load command byte
  T  QB [#DWPntr];   // write to QB at the offset

  L  IB [#DWPntr];   // load echo input byte
  L  QB [#DWPntr];   // load what we just wrote
  ==I ;              // compare
  =  #Ackd;          // set ack if echoed correctly

  AN #Ackd;          // if NOT acked
  =  #NotAckd;       // feed timer input
  IN CALL #Faulted (  // start SFB4 (TON)
       IN := #NotAckd,
       PT := T#2S,    // 2-second handshake window
       Q  := #FaultBit // fault latches if no ack in 2 s
       ET := );
  AN #temp;
  SAVE ;              // save RLO into BR for block boundary
END_FUNCTION_BLOCK

The critical line SLD 3 converts the byte offset 3 into its 24-bit equivalent (24 = 0x18). Without this shift, the indirect access to QB treats the value as a bit address and tries to access a bit that does not exist for byte-aligned access, producing the OB121 alignment error described in the troubleshooting section below.

The ==I instruction (integer compare) checks whether the loaded input byte matches the output byte just written; if they are equal, the Ackd output is set. AN #Ackd inverts the ack signal to drive the IN input of SFB4 (IEC on-delay timer). If the handshake is not acknowledged within 2 seconds, the timer's Q output sets the FaultBit. The unused ET parameter is left open.

Building FB4: The Pointer-to-Pointer Dereference Pattern

FB4 upgrades the interface to a true POINTER parameter. With a POINTER you can pass any memory area (I, Q, M, DB) by reference, but the FB must first dereference the pointer — the parameter value is itself a POINTER stored inside the instance DB, and you must look it up through the instance data area to read the actual target address.

FUNCTION_BLOCK FB 4
TITLE = VERSION : 0.1
VAR_INPUT
  pCmdAddress : POINTER ;   // pass any P#Qx.y or P#Ix.y or P#Mx.y
  CmdByte     : BYTE ;
END_VAR
VAR_OUTPUT
  Ackd        : BOOL ;
  FaultBit    : BOOL ;
END_VAR
VAR
  Faulted     : "TON" ;    // multi-instance TON (also valid)
END_VAR
VAR_TEMP
  temp        : BOOL ;
  NotAckd     : BOOL ;
  DWPntr      : DWORD ;
  wDBNo       : WORD ;
END_VAR
BEGIN
NETWORK TITLE = Dereference POINTER, then handshake
  L  P##pCmdAddress;  // pointer to the parameter slot in the instance DB
  AD  DW#16#FFFFF;    // mask to isolate the address portion
  TAR2 ;              // current FB instance base (AR2 value)
  +D ;                // absolute byte offset of pCmdAddress inside DI
  LAR1 ;              // AR1 ← absolute offset
  L  DINO;            // instance DB number
  T  #wDBNo;          // store
  OPN DB [#wDBNo];    // open instance DB as current DB for L D[AR1,…]
  L  D [AR1,P#2.0];   // read area pointer portion of the passed POINTER
  LAR1 ;              // AR1 ← actual I/O address (e.g. DW#16#82000018)
  T  #DWPntr;         // copy for QB[ ] access

  L  #CmdByte;
  T  QB [#DWPntr];    // write command byte at the pointed-to Q address
  L  IB [#DWPntr];    // read ack input at the same offset
  L  QB [#DWPntr];
  ==I ;
  =  #Ackd;

  AN #Ackd;
  =  #NotAckd;
  IN CALL #Faulted (
       IN := #NotAckd,
       PT := T#2S,
       Q  := #FaultBit
       ET := );
  AN #temp;
  SAVE ;
END_FUNCTION_BLOCK

The dereference sequence is the heart of FB4. Engineers who have not worked with the S7 pointer format before frequently get stuck at L D [AR1, P#2.0] because the AR1 at that point does not contain the I/O address yet — it contains the byte offset of the parameter slot inside the instance DB.

  1. L P##pCmdAddress — load the 6-byte address of the parameter slot. The 6-byte format is [DB num | area | addr | addr | addr].
  2. AD DW#16#FFFFF — mask the area code so the resulting DWORD can be combined with the instance base to produce a byte offset. The exact mask width depends on the CPU family; the 20-bit mask keeps the bit-address portion and clears the area code byte for S7-300/400.
  3. TAR2 — load the current FB instance base address into ACCU1. ACCU1 (with the masked pointer-to-pointer) shifts to ACCU2.
  4. +D — add ACCU1 + ACCU2 to produce the absolute byte offset of pCmdAddress within the instance DB.
  5. LAR1 — store the result in AR1.
  6. L DINO / T #wDBNo / OPN DB [#wDBNo] — get the current instance DB number and open it as the current DB so that the next L D [AR1, ...] reads from instance data.
  7. L D [AR1, P#2.0] — read 4 bytes starting at AR1+2. This is the area pointer portion of the stored POINTER (i.e., the actual I/O address to use).
  8. LAR1 — load the area pointer into AR1 for subsequent indirect access. After this line, T QB [AR1, P#0.0] writes to the output pointed to by the passed POINTER.
Why the mask: The source includes AD DW#16#FFFFF with the comment "mask off area". The intent is to isolate the bit-address portion (24 bits) of the pointer-to-pointer from any bits that would misalign the subsequent addition with TAR2. Engineers porting this code should verify the mask width against the current Siemens Programming and Operating Manual for the target CPU; S7-300/400 STL is sensitive to non-zero bits in the low 3 positions of byte-aligned DWORD operations.

Building FB5: Multi-Byte Handshake with TON Fault Detection

FB5 is the production-ready version. It scales the single-byte pattern to three consecutive output bytes (Command, X data, Y data) and two individual output bits (X-negative flag, Y-negative flag), with matching input acknowledgments and a sealed fault bit. The caller passes a single POINTER to the start of the I/O area; FB5 walks the offsets itself.

FUNCTION_BLOCK FB 5
TITLE = VERSION : 0.1
VAR_INPUT
  pCmdAddress : POINTER ;
  CmdByte     : BYTE ;
  XData       : BYTE ;
  YData       : BYTE ;
  XNeg        : BOOL ;
  YNeg        : BOOL ;
END_VAR
VAR_OUTPUT
  Ackd        : BOOL ;
END_VAR
VAR_IN_OUT
  FaultBit    : BOOL ;     // sealed by caller's SR; reset by caller's fault reset
END_VAR
VAR
  Faulted     : SFB 4 ;    // IEC on-delay timer, 2 s window
END_VAR
VAR_TEMP
  NotAckd     : BOOL ;
  wDBNo       : WORD ;
  AckByt0     : BOOL ;
  AckByt1     : BOOL ;
  AckByt2     : BOOL ;
  AckBits     : BOOL ;
END_VAR
BEGIN
NETWORK TITLE = Dereference and write 3 output bytes + 2 bits
  L  P##pCmdAddress;    // pointer to the parameter slot
  AD  DW#16#FFFFF;      // mask off area code
  TAR2 ;                // instance base
  +D ;                  // absolute offset of pCmdAddress in DI
  LAR1 ;                // AR1 ← offset of pCmdAddress
  L  DINO;              // instance DB number
  T  #wDBNo;
  OPN DB [#wDBNo];      // open DI as current DB
  L  D [AR1,P#2.0];     // read area pointer portion of passed POINTER
  LAR1 ;                // AR1 ← I/O address (e.g. DW#16#82000018)

  L  #CmdByte;          // command byte
  T  QB [AR1,P#0.0];    // QB at offset 0
  L  #XData;            // X data register
  T  QB [AR1,P#1.0];    // QB at offset 1
  L  #YData;            // Y data register
  T  QB [AR1,P#2.0];    // QB at offset 2

  A  #XNeg;             // X-negative flag
  =  Q  [AR1,P#3.1];    // bit 1 of byte 3
  A  #YNeg;             // Y-negative flag
  =  Q  [AR1,P#3.2];    // bit 2 of byte 3

NETWORK TITLE = Read back and compare (3 bytes + 2 bits)
  L  IB [AR1,P#0.0];    // echo byte 0
  L  QB [AR1,P#0.0];
  ==I ;
  =  #AckByt0;          // byte 0 echoed
  L  IB [AR1,P#1.0];
  L  QB [AR1,P#1.0];
  ==I ;
  =  #AckByt1;          // byte 1 echoed
  L  IB [AR1,P#2.0];
  L  QB [AR1,P#2.0];
  ==I ;
  =  #AckByt2;          // byte 2 echoed

  A(  A  #XNeg;
      A  I  [AR1,P#3.1]  // asserted and ack asserted
  O ;
      AN #XNeg;
      AN I  [AR1,P#3.1]  // deasserted and ack deasserted
  ) ;
  A(  A  #YNeg;
      A  I  [AR1,P#3.2]
  O ;
      AN #YNeg;
      AN I  [AR1,P#3.2]
  ) ;
  =  #AckBits;          // both bits echoed correctly

  A  #AckByt0;
  A  #AckByt1;
  A  #AckByt2;
  A  #AckBits;
  =  #Ackd;             // OUT: all four conditions met

NETWORK TITLE = 2 s timeout → latched fault
  AN #Ackd;             // if not acked
  =  #NotAckd;          // timer input TRUE
  IN CALL #Faulted (    // SFB4 TON
       IN := #NotAckd,
       PT := T#2S,      // 2-second handshake window
       Q  := #FaultBit  // FaultBit is VAR_IN_OUT → sealed by caller's SR
       ET := );
  O  #Ackd;             // whether ack on or off, exit cleanly
  ON #Ackd;
  SAVE ;                // preserve RLO into BR for FB boundary
END_FUNCTION_BLOCK

Key design decisions in FB5:

  • Single POINTER, multiple bytes. The caller passes the start address; FB5 accesses [AR1,P#0.0], [AR1,P#1.0], and [AR1,P#2.0] for the three bytes. This is far more readable than passing three separate pointers and lets the call site read as a single line per channel.
  • Bit flags at offset 3. The X-negative and Y-negative flags are written to Q [AR1,P#3.1] and Q [AR1,P#3.2]. The read-back comparison uses XOR-style equivalence (A #XNeg / A I[AR1,P#3.1] OR AN #XNeg / AN I[AR1,P#3.1]) to handle both the asserted and de-asserted cases correctly.
  • SR-style fault latching via VAR_IN_OUT. The fault bit is declared VAR_IN_OUT FaultBit : BOOL so the caller can wire it to a seal-in contact (an SR flip-flop) and reset it with a fault reset signal. VAR_IN_OUT passes the address, so the FB writes directly to the caller's memory without a separate output coil.
  • SFB4 (IEC TON). The on-delay timer is the standard S7-300/400 system function block for on-delay timing. PT of T#2S gives a 2-second handshake timeout. The Q output drives the FaultBit IN_OUT; the ET (elapsed time) parameter is left unwired.
  • SAVE before end. The SAVE instruction copies RLO to BR memory so the FB exits cleanly without leaving an undefined BR state for the calling block.

The acknowledgment check uses a four-condition AND: byte 0 echoed, byte 1 echoed, byte 2 echoed, and the X/Y negative flag bits echoed with the same polarity they were written. If any condition fails, the timer keeps timing out and the fault bit latches after 2 seconds. Once latched, only the caller's fault reset can clear it.

Common Errors and Their Root Causes

Symptom Diagnostic Root Cause Resolution
CPU goes to STOP, OB121 triggered "Alignment error on reading" / "Alignment error on writing" Bit address bits 0-2 non-zero for byte/word/dword indirect access Use SLD 3 on the byte offset, or zero the low 3 bits of AR1 before T QB [AR1,…]
FB will not compile in STL editor "Statement not permitted for DWORD/DINT indir. instruction address" Trying to load QB with a 32-bit pointer constant in a context that requires 16-bit Convert with SLD 3 on the byte value, or use the proper POINTER format with LAR1
Output writes to wrong address No diagnostic; visual miswiring online Modified AR2 instead of using AR1 Always use AR1 for user-controlled pointer; AR2 is system-managed by the FB instance machinery
Pointer reads garbage (zero or random) No diagnostic; values are wrong Forgot to OPN DB before L D [AR1,P#2.0] Add L DINO / T wDBNo / OPN DB [#wDBNo] sequence before dereference
FaultBit set immediately on first scan TON Q output high on first scan Timer not reset between calls; IN latched from previous invocation Ensure the calling OB initializes the timer; or use a reset coil on the IN parameter
Output writes to a completely different area Online monitor shows wrong Q address Caller passed a POINTER to a DB area (0x80) instead of Q (0x82); FB writes to nonexistent DB range Verify the area code byte of the passed POINTER before T QB [AR1,…]; reject DB pointers explicitly
BR is undefined at end of FB Calling block behaves unpredictably Forgot SAVE at the end of the FB Add SAVE (or = #Ackd; SAVE; pattern) at every FB exit path
OB121 specifics: Programming errors that violate the addressing rules cause the CPU to call OB121. With OB121 unloaded (the default for a fresh project), the CPU goes to STOP. Always load OB121 as a "do-nothing" block (just BE) during development so the CPU stays in RUN and you can diagnose online with the VAT or program status. Replace OB121 with a proper error handler (logged error, fault latch, OB call to OB122 for I/O access errors) before commissioning.

Area Code Reference Table for POINTER Constants

Area Code (hex) Area Example Pointer DWORD Value Loaded as L D Notes
0x80 DB (data block) P#DB5.DBX10.0 DW#16#80000050 Used with L DBNO / OPN DB for DB-relative access
0x81 I (process input) P#I0.0 DW#16#81000000 Process image input
0x82 Q (process output) P#Q3.0 DW#16#82000018 Process image output
0x83 M (bit memory) P#M10.0 DW#16#83000050 Bit memory / flags
0x84 L (local data, current FB) P#L0.0 DW#16#84000000 Local stack of current block
0x85 VL (previous local data) P#VL0.0 DW#16#85000000 Local stack of calling block
0x86 V (instance data of calling FB) P#V0.0 DW#16#86000000 Instance DB of the calling FB

To verify a pointer in your project, create a VAT and watch the DWORD value while online. For example, passing P#Q3.0 to FB4/FB5 should produce DW#16#82000018 in the pCmdAddress parameter slot of the instance DB. If you see DW#16#00000000, the parameter was not wired at the call site; if you see a different area code, the wrong symbol is being passed.

Verification and Commissioning Checklist

  1. Declare OB121 as a placeholder block (BE) before commissioning to keep the CPU in RUN during initial testing. Remove or upgrade before site acceptance.
  2. Create a VAT with the instance DB and observe pCmdAddress as a DWORD. Confirm the area code matches the intended area (0x81 for I, 0x82 for Q, 0x83 for M).
  3. Single-step the FB in STL view with BR breakpoints at SAVE. Verify AR1 contains the correct byte offset before each T QB [AR1,…] and L IB [AR1,…].
  4. Cross-reference all T QB / L IB accesses in the FB to confirm no two FBs share the same Q area without an interlock. Overlapping handshakes will produce intermittent fault latches.
  5. Test the fault path: disconnect the physical input wiring for the acknowledgment byte (or force the IB to a different value via VAT). Confirm the fault bit latches after 2 seconds exactly.
  6. Reset the fault and confirm the handshake resumes without CPU STOP. The VAR_IN_OUT + SR pattern must release cleanly.
  7. Repeat steps 2-6 for each call site of the FB. The customer's pattern calls it three times (Command, X, Y); all three must pass independently.
  8. Test boundary conditions: byte offset 0, maximum byte offset in the CPU's process image, bit 7 of a byte, and bit 0 of the next byte (to confirm SLD 3 is not double-applied).

LAD vs STL: When Indirect Addressing Forces STL

Ladder does not support POINTER parameters on FBs in the same way STL does. The "ANY Pointer" LAD workaround (right-click the LAD network, set the address type to "ANY pointer", wire it with a MOVE box) is constrained to parameter passing at the call site, not to dereferencing inside the FB body. For dereferencing and indirect I/O access, STL is mandatory in S7-300/400.

For customers who require 100% ladder in the calling code, an alternative is the FB3 pattern: use a BYTE index parameter and call the FB three times — one per handshake channel. The trade-off is three call sites and three independent fault bits instead of one POINTER call, but the calling logic remains pure ladder and the FB body can be STL.

Another alternative is SCL (Structured Control Language). SCL on S7-300/400 supports POINTER types indirectly through the POINTER keyword and the ^ dereference operator, which compiles to STL. If the customer accepts SCL blocks, the readability of the FB body improves dramatically and the pointer arithmetic is hidden behind a POINTER TO BYTE declaration.

S7-1200/1500 Migration Notes

For projects migrating to S7-1200 or S7-1500 with TIA Portal V20, the indirect addressing model changes substantially. The S7-1500 uses 64-bit pointers and a new VARIANT type replaces many POINTER use cases. For detailed semantics, refer to the official TIA Portal V20 documentation on indirect addressing in STL for S7-1500.

Key differences for S7-1500:

  • The SLD 3 / pointer-byte conversion is no longer required because the addressing model uses byte granularity natively and the bit number is encoded separately.
  • AR1 and AR2 still exist, but the underlying pointer format is 64-bit; the FB instance machinery is different and uses optimized block access by default.
  • PEEK and POKE instructions in SCL provide a more readable alternative to STL pointer arithmetic and handle byte/word/dword selection implicitly.
  • VARIANT pointers can be passed across FB boundaries without the dereference ceremony required for S7-300/400 POINTERs; TypeOf and IS_NULL operators let the FB validate the passed area at runtime.
  • Optimized blocks (the default in TIA Portal) prohibit direct pointer access to instance data; use the standard (non-optimized) block setting or move data through an in/out parameter for the same effect.

FAQ

Why does my S7-300 FB crash with OB121 "Alignment error on reading"?

The bit-address portion of your pointer (bits 0-2 of the effective byte address) is non-zero. For byte, word, or dword indirect access, bits 0-2 of the effective address must be zero. Use SLD 3 to convert a byte offset into the 24-bit bit-address form, or mask the low 3 bits to zero before loading AR1. Loading 3 directly into AR1 and using T QB [AR1,P#0.0] will trigger OB121 on S7-300/400.

Can I use a POINTER parameter in a LAD/FBD function block on S7-300?

Not for dereferencing inside the FB body. LAD supports POINTER only as a parameter type for inter-block passing; the actual indirect I/O access must be coded in STL or SCL. Use the FB3 byte-index pattern if 100% LAD is mandatory at the call site, or convert the FB body to SCL with a POINTER TO BYTE declaration for cleaner syntax.

What is the difference between POINTER and ANY in S7-300/400?

POINTER is 6 bytes (DB number, area, 24-bit address) and points to a single bit. ANY is 10 bytes and adds repetition count and data type information, allowing you to point at structured data such as arrays. For a single I/O byte or contiguous byte range, POINTER is sufficient and faster to dereference; use ANY only when the call site needs to pass a variable-length block.

Why must I use AR1 and not AR2 for indirect addressing inside an FB?

AR2 is the instance data register managed by the S7-300/400 runtime to resolve all L #param and T #param accesses in the FB. Modifying AR2 inside the FB corrupts the instance data context and causes all subsequent parameter accesses to read or write the wrong memory. AR1 is the safe user-controlled register for indirect access patterns and is the standard target for LAR1 from a dereferenced POINTER.

How do I monitor the actual DWORD value of a POINTER parameter online?

Open the instance DB in STEP 7 data view and watch the parameter slot, or create a VAT referencing the instance DB and add the parameter as a DWORD column. The DWORD value will show the area-coded pointer (e.g., DW#16#82000018 for P#Q3.0). If the value is DW#16#00000000, the parameter was not wired at the call site; if the area code byte is wrong (e.g., 0x83 when 0x82 was expected), the wrong symbol is being passed.

Why does my fault bit latch immediately on the first PLC scan after download?

The TON instance retains its state across warm restarts but is reset on cold restart. If the first scan after download executes the FB with NotAckd = TRUE (because the IB does not yet match the QB), the timer starts timing on cycle 1 and reaches 2 s before the handshake can complete. Add a first-scan flag (e.g., OB100 / OB101 setting an M-bit) to mask the timer input until cycle 2, or initialize the QB to match the IB before the first FB call.

Back to blog