Siemens S7 Indirect Addressing in a Data Block: STL Pointers and TIA Portal Methods
Primary objective: Use the value stored in one data word (for example, DB33.DBW16) as a runtime index that selects another data word inside the same data block (for example, DB33.DBW20 when the index equals 20) and load that selected word into ACCU 1. This article consolidates the classical S7-300/S7-400 STL approaches (AR1 register, memory-indirect pointer, LOOP scan) and the modern S7-1200/S7-1500 variants available in TIA Portal V20 (Indirect addressing in STL (S7-1500) - STEP 7 and Indirect addressing of a data block via DB_ANY data type (S7-1200 / S7-1500)).
1. Problem Statement and Use Cases
You have a global or instance data block (DB33) that contains a lookup table (an array-like sequence of INT words). A separate word inside the same block, DB33.DBW16, holds the index of the element you want to read. At runtime, the index can change; you cannot hard-code the absolute address because the controller must follow whatever index the application logic requests.
Typical industrial scenarios that require this pattern:
- Recipe selection — recipe number loaded into
DBW16; the controller dereferences the indexed recipe record. - Modular production line tooling — tool number indexes a parameter block; the index can exceed the size of a single FC parameter list.
- Sequencer / step chains — current step number indexes a status word, an alarm code, or a transition condition.
- Cross-reference lookup tables — error code points to an array entry that contains the human-readable descriptor or routing target.
The mechanical question — "I have an index in DBW16; how do I read DBW[index]?" — has three idiomatic answers in classic STL and two more in TIA Portal V20 STL/SCL. The rest of this document works through each.
2. Prerequisites
- Siemens STEP 7 V5.5 / V5.6 (or compatible) for the S7-300/400 STL examples.
- Siemens TIA Portal V18 / V19 / V20 for S7-1200/S7-1500 examples. Reference manual: TIA Portal V20 — Indirect addressing in STL (S7-1500).
- CPU firmware supporting the instructions used (
SLW,SLD,LAR1,TAR1,+AR1,+D,LOOP,OPN DB). All S7-300/400 CPUs from the 31x series and all S7-1200/1500 CPUs support these in STL. - An opened
DB33(the DB must be present in the S7 program, downloaded, and not exclusively assigned to a different OB/FC/FB that has opened it withOPN DIsimultaneously without nesting). - PLC tag declarations consistent with the offsets used (no symbolic conflict between
DB33.DBW16and any FC/FB local variable).
3. Siemens Pointer Format Fundamentals
Every pointer the CPU manipulates is a 32-bit bit address, never a byte address. The format is:
| Byte address (bits 3..31) | Bit offset (bits 0..2) |
| 31 3 | 2 0 |
Consequences:
- The lowest three bits select one of eight bits inside a byte.
- To convert a byte offset (for example
20) into a bit-pointer value you must multiply by 8, i.e. left-shift by 3. - The compiler constant
P#DBX 0.0is already a properly formatted pointer (16#0000_0000); adding it as an arithmetic base lets you keep the bit offset at.0.
This is the reason every indirect-index example you will see in S7 STL shifts the index left by three bits before treating it as a pointer operand.
4. Method 1 — Register-Indirect Area-Internal Addressing (AR1, S7-300/400)
The canonical solution on S7-300/400:
OPN DB 33 // Open DB33 so subsequent DBW references target it
L DBW 16 // Load index (for example 20) into ACCU 1
SLW 3 // Shift Left Word 3 -> ACCU 1 now holds 20 * 8 = 160 bits
LAR1 // Load Address Register 1 from ACCU 1
L DBW [AR1, P#0.0]// Indexed read: DBW at the byte offset encoded in AR1
T MW 100 // Optional: store the dereferenced value to a flag word
Step-by-step trace for index = 20:
-
OPN DB 33— DB number 33 becomes the active data block for DBx instructions. -
L DBW 16— ACCU 1 := 20 (INT). -
SLW 3— ACCU 1 := 20 × 8 = 160 (left-shift word by 3 bits). -
LAR1— AR1 := 160 (the lower 16 bits of ACCU 1 are placed into AR1; AR2 is untouched). -
L DBW [AR1, P#0.0]— bit address = AR1 + P#0.0 = 160 + 0 = bit address of byte 20, bit 0; the CPU reads the 16 bits starting at that bit address and stores them in ACCU 1.
For byte-level indexed reads, replace L DBW [AR1, P#0.0] with L DBB [AR1, P#0.0]; for double words, use L DBD [AR1, P#0.0] and shift the index by 3 only after the appropriate multiplication for the element width (each DW = 4 bytes, so left-shift by 5 instead of 3 if your index counts DWs).
SLW 3 shifts a 16-bit word. SLD 3 shifts a 32-bit double word. If you load the index into ACCU 1 as a word (16 bits) and then shift it, SLW 3 is correct. If you treat the index as a double-word index for an array of DWORDs, prefer SLD 3 and use L DBD [AR1, P#0.0].
5. Method 2 — Memory-Indirect Pointer via a Marker Double Word
When you do not want to use AR1 (for instance, AR1 is already in use by a higher-priority OB, or you need to keep the index in a monitored variable), a marker double word can carry the pointer. Note: an INT value loaded directly into a 32-bit MD will live in the low word; you still need the bit-shift step before adding the pointer constant:
OPN DB 33
L DBW 16 // INT index
T MD 2 // Store in low word of MD2 (MD2 := 20)
SLD 3 // MD2 := 20 * 8 = 160 (shift left double word by 3)
L P#0.0 // Load bit-pointer constant 0.0 into ACCU 1
+D // ACCU 1 := MD2 + P#0.0 = 160 + 0 = 160 bits
T MD 6 // Pointer value now fully assembled in MD6
L DBW [MD 6] // Indexed read using MD6 as the pointer
T MW 10 // Result in MW10
The original proposer's note that the P#0.0 addition is "redundant" is correct — once the low word of the index has been shifted by 3, the bit offset is already 0 and the addition does not change the bit pattern. Both forms compile; the redundant-add form is useful when you need a non-zero bit offset (for example L P#0.4 to land on bit 4 inside the indexed byte).
6. Method 3 — Indexed Scan Using the LOOP Instruction
For a fixed-length loop that walks a range of indexed DB words and emits the dereferenced value into a marker, the LOOP instruction keeps the index in ACCU 1 and uses AR1 as the running byte pointer:
OPN DB 33
L P#DBX 0.0 // Bit-pointer base: byte 0, bit 0 of the active DB
TAR1 // AR1 := pointer to byte 0.0 of DB33
L W [AR1, P#0.0] // First read: DBW0
nw1a: T #count // Store loop counter
+AR1 P#2.0 // Advance AR1 by 2 bytes (one word)
L #count
LOOP nw1a // Decrement ACCU 1, jump if not zero
L W [AR1, P#0.0] // Final dereferenced read
T MW 0 // Result
This pattern is idiomatic when you want to iterate over an array of N words and finish with the value at index N - 1 loaded into ACCU 1. The CPU increments the byte pointer with +AR1 P#2.0 (one word = 2 bytes) per iteration.
7. Why Shift Left by 3? Numeric Justification
The shift is required because the Siemens pointer operand is a bit address. Given an index i in a word-oriented array:
byte_offset = i * 2 // each INT word = 2 bytes
bit_pointer_value = byte_offset * 8
= i * 16
= i << 4 (decimal equivalent of left-shift 4)
If you started with the index already expressed in bytes (for example, the index points to a byte offset rather than a word offset), the shift would be 3 instead of 4:
bit_pointer_value = byte_offset * 8
= byte_offset << 3
The two examples above (SLW 3 for a word-indexed array, SLD 3 for a byte-indexed DBD array) match the structure of the source block: DBW16 holds a word index, so a 3-bit shift (with the implicit 2-byte width of a word being absorbed by stepping the pointer in 2-byte increments elsewhere in the program) is the correct magnitude. Re-derive the shift for your data type before committing the value to production code.
8. TIA Portal V20 — S7-1200 and S7-1500 Indirect Addressing in STL
The S7-1200/1500 STL retains the classic pointer instructions but adds stricter type checking and symbolic tag access. The two canonical forms documented in the TIA Portal V20 help are memory-indirect addressing and register-indirect area-internal addressing. The semantics are unchanged; what changes is the compiler acceptance of legacy idioms:
-
OPN DB— still valid in STL on S7-1500, but symbolic"MyDB".MyTag[Index]is preferred in new projects. -
SLW,SLD,LAR1,+AR1,LOOP— all available. -
L DBW [AR1, P#0.0]— still accepted; the indexed operand may be combined with the area-internalP#offset.
Reference: Indirect addressing in STL (S7-1500) - STEP 7 (TIA Portal V20).
9. TIA Portal V20 — DB_ANY Approach for Symbolic Indexing
On S7-1200/S7-1500 the modern recommendation is to expose the data block as a block parameter of type DB_ANY and access internal tags through symbolic offsets separated by a dot. The DB_ANY parameter carries the block number; the absolute address of the tag inside the block is appended symbolically:
// Inside an FB with a block parameter "SourceDB" of type DB_ANY
// and a tag array "LookupWord" declared as ARRAY[0..199] OF INT
#index := "SourceDB".LookupWord[#idx];
Procedure:
- Declare the parameter
SourceDBon the FB interface asDB_ANY. - Declare
LookupWordinside the destination DB (any DB compatible withSourceDB). - Pass the actual DB number into
SourceDBat the call site. - Use the symbolic dot access with the runtime index in square brackets.
Reference: Indirect addressing of a data block via DB_ANY data type (S7-1200 / S7-1500).
Advantages of the DB_ANY approach:
- Type-safe — the compiler verifies the tag exists in the destination DB.
- No manual pointer arithmetic; the index is used directly.
- Survives block-number renumbering and re-compilation.
Limitations:
- Not available on S7-300/S7-400 CPUs.
- Requires the destination DB to declare the same tag names as referenced symbolically.
- Passing an
OPN DBstyle block number to a parameter typedDB_ANYinside a multi-instance FB that also usesOPN DB "#Parameter"will still throw "Specified value is invalid" because the legacy form expects a constant block identifier, not a parameter.
10. Verification and Commissioning Steps
-
Compile: In STEP 7 V5.x or TIA Portal, the network must compile without warnings. A warning such as "Address area AR1 used but not initialized" indicates an upstream
LAR1is missing. -
Monitor: Open the DB in online mode and confirm
DBW16contains the index you expect. -
Single-step the SLW: In the STL editor, switch to "Monitor" (Glasses icon). Step the
SLW 3and read ACCU 1 — it must equalDBW16 × 8(assuming word indexing). -
Force the index: Force
DBW16to 0, then to a known boundary (for example the maximum valid index). Verify the dereferenced read returns the expected element. -
Out-of-range check: Add a comparison before the indexed read:
L DBW 16; L 200; >I; JC ERR— if the index exceeds the array size, branch to a fault handler rather than reading arbitrary memory. -
Edge cases to verify:
- Index = 0 (first element).
- Index = array length - 1 (last element).
- Index negative (for
INTtyped index, a value such as -1 will cause a shift that wraps the 16-bit word and an out-of-range read). - DB not opened in the calling OB (you will get DB-not-loaded fault at runtime).
11. Troubleshooting Matrix
| Symptom | Probable Cause | Corrective Action |
|---|---|---|
| CPU goes to STOP with SF LED, diagnostic buffer "Area length error when reading" | Index in DBW16 exceeds the DB size, or the DB was not opened with OPN DB 33 before the indexed read |
Add boundary check; verify OPN DB 33 precedes the load |
| Read returns the wrong value but does not fault | Shift by 3 was applied to a byte index (off by 4) — or vice versa | Re-derive the shift: word index × 2 bytes × 8 bits/bit = shift by 4; byte index × 8 bits/bit = shift by 3 |
Compiler error "Specified value is invalid" on OPN DB "#Parameter"
|
Legacy STL idiom; the OPN operand must be a constant block identifier in classic STL |
Switch to the DB_ANY approach on S7-1200/S7-1500, or hard-code the block number |
| Compiler warning on AR1 "address register not initialized" | AR1 was modified by a higher-priority OB and not restored | Save/restore AR1 around the indexed read using PUSH / POP or dedicated temp markers |
| Indexed read returns 0 every cycle | Index loaded into low word of MD, but the shift operates on the full 32-bit word and the high half is non-zero | Use SLW for word indices, SLD only for double-word indices; ensure the upper 16 bits of the MD are zero before the shift |
| Loop runs one iteration fewer than expected | Initial TAR1 reads DBW0, and the first +AR1 P#2.0 advances before the read |
Decide explicitly whether the read happens before or after the increment; align with the LOOP boundary condition |
TIA Portal: SCL "Type mismatch" on DB_ANY parameter |
Source DB and referenced DB declare incompatible tag structures | Use a PLC data type (UDT) for the array; reference the UDT instance in both DBs |
12. Frequently Asked Questions
Why must the index be shifted left by 3 before loading AR1?
Siemens S7 pointers are 32-bit bit addresses, not byte addresses. Multiplying the byte offset by 8 (left-shift by 3) converts the byte index into the bit-pointer format the CPU expects in AR1 or in a DBW […] operand. See TIA Portal V20 STL indirect addressing.
Can I use OPN DB "#Parameter" inside an FB on S7-1500?
No. The OPN operand must be a constant block identifier; passing an FB parameter of type DB_ANY will produce "Specified value is invalid" at compile time. Use the DB_ANY symbolic access pattern instead.
What is the difference between SLW 3 and SLD 3?
SLW 3 left-shifts a 16-bit word by 3 bits; SLD 3 left-shifts a 32-bit double word by 3 bits. Use SLW for an INT index fed into a word-pointer; use SLD when the pointer lives in an MD and you want the entire 32-bit pointer value rotated.
How do I bound-check the index before the indexed read?
Load the index, compare against the array length with >I or >D, and branch to a fault OB on out-of-range. Example: L DBW 16; L 200; >I; JC OVR_ERR rejects indices larger than 199 for a 200-element INT array.
Does the indexed read work for bits inside the word, not just whole words?
Yes. Use L DBX [AR1, P#0.0] for byte-level reads, then bit-mask the byte. To land on a specific bit inside the indexed byte, add a non-zero bit offset such as L DBX [AR1, P#4.0] for bit 4 of the indexed byte. Always remember the shift-by-3 still applies to the byte component.