Overview: Why Indirect Addressing in STEP 7 STL
Indirect addressing lets one STL instruction sequence operate on a memory location whose address is computed at runtime rather than fixed at compile time. In Siemens STEP 7 V5.x for the S7-300 and S7-400 families, this technique is essential for parameterised recipes, indexed loops, batch handlers, and any logic that must walk through arrays whose bounds are not known at edit time. The classic form of the construct is the memory-indirect expression A M [DBD 140] or its register-indirect sibling A M [AR1, P#0.0].
The trade-off is that the operand to the right of the square brackets is no longer a constant symbol but a pointer, and the rules for how that pointer is interpreted depend on the address identifier, the data type, and whether you are crossing area boundaries (DB, M, I, Q, L) or staying inside one area. Misinterpreting the pointer format is the single most common reason an indirect read returns the wrong bit or triggers an area-length error during runtime. A general introduction to addressing modes is available on the Wikipedia addressing-mode reference; the rest of this document drills into the Siemens-specific format that STEP 7 STL expects.
This reference walks through both pointer families, shows the bit/byte arithmetic that connects a value such as 1234 to the actual operand M154.2, and demonstrates the SLD 3 conversion used when you need to move a byte address into address register AR1 or AR2. Programming and operating details for STEP 7 are documented in the official Siemens Industry Online Support portal.
Pointer Anatomy: Byte, Bit, and Area Codes
Before dissecting a real instruction it helps to lay out the bits of a STEP 7 pointer. A 32-bit pointer is divided into two fields:
- Byte number (24 bits, bits 3-31): unsigned offset inside the selected area.
- Bit number (3 bits, bits 0-2): value 0-7, the bit selector inside that byte.
When the high byte of the 32-bit value is non-zero, the pointer also carries an area code in bits 24-31. The codes STEP 7 recognises are:
| Area code (hex) | Area | Used in instruction form |
|---|---|---|
| 0x00 | Default (area-internal) |
M[..], DBX[..], I[..], Q[..], L[..]
|
| 0x81 | Inputs (I / PI) | Area-crossing pointer on input area |
| 0x82 | Outputs (Q / PQ) | Area-crossing pointer on output area |
| 0x83 | Locals (L) | Area-crossing pointer on temp area |
| 0x84 | Data block (DB) | Area-crossing pointer opened by OPN DB |
| 0x85 | Instance DB (DI) | Multi-instance area-crossing pointer |
The decimal value the CPU actually stores in a DBD for a bit operand is therefore 8 * byte + bit. This is the relationship every STL programmer eventually commits to muscle memory.
Memory-Indirect Addressing: M [DBD] Explained
The mnemonic M [DBD 140] parses as: read the 32-bit value stored in data-block double-word DBD140 (inside the currently opened DB), treat that value as a bit address inside the memory area, and logically AND the bit at that location with the RLO. A minimal example lifted from the original question:
OPN DB 103 // Open DB 103; subsequent DBW/DBD/DBX use it
A M [DBD 140] // AND M bit whose address is stored in DB103.DBD140
= M 206.0 // Latch the result to M206.0
A M [DBD 384] // Same idea, second pointer slot in DB103
= M 206.2
OPN DB 104 // Switch active DB
A M [DBD 20] // Read pointer from DB104.DBD20 and test the M bit
= M 206.1
Three points are easy to miss on a first reading:
- The expression inside the brackets is always evaluated as a bit address when the operand outside the brackets is bit-typed (
M,I,Q,DBX). This is whyDBD140 = 1234resolves toM154.2and not toM1234.0as the byte-oriented reader may expect. - The data-block operand (
DBD) names a byte offset inside the currently open DB. The CPU does not interpretDBD140as a pointer to an absolute DB number; it is a 32-bit slot in the data block opened by the most recentOPN DB(or a multi-instance DB opened byCDB). Any subsequentOPNretargets every followingDBD[..]reference. - The result
= M 206.0writes to a fixed bit. The indirect address influences only the source of the read, not the destination.
Decoding the DBD Bit Address Math
Siemens packs byte and bit information into a single 32-bit value in the form P#byte.bit. For a bit operand the lowest three bits encode bit (0-7) and the remaining bits encode the unsigned byte offset within the chosen area. The decode is therefore:
byte_offset = pointer_value DIV 8
bit_index = pointer_value MOD 8
operand = M(byte_offset).(bit_index)
Worked examples:
| Value in DBD | Byte (DIV 8) | Bit (MOD 8) | Resolves to |
|---|---|---|---|
| 0 | 0 | 0 | M 0.0 |
| 7 | 0 | 7 | M 0.7 |
| 8 | 1 | 0 | M 1.0 |
| 15 | 1 | 7 | M 1.7 |
| 87 | 10 | 7 | M 10.7 |
| 1000 | 125 | 0 | M 125.0 |
| 1007 | 125 | 7 | M 125.7 |
| 1234 | 154 | 2 | M 154.2 |
| 65535 | 8191 | 7 | M 8191.7 |
The maximum legal value of a 32-bit bit address is 8 * (area_size_in_bytes) - 1. For the M area on an S7-300/400 the byte count is configured in the CPU hardware properties; if the active DB stores a pointer that is larger than the M area can hold, the CPU raises the standard area-length error and calls OB121 if it is loaded. The same arithmetic applies to DBX [DBD..], I [DBD..], and Q [DBD..] — the only thing that changes is which area the byte offset is applied to.
Register-Indirect Addressing: AR1 and AR2
When the source of the address is not in a data block (it may be calculated in an FB's static area, in a multi-instance, or in a local temp variable), STL provides two 32-bit address registers: AR1 and AR2. They are loaded with the LAR1 / LAR2 instructions and consumed with the [AR1, P#0.0] or [AR2, P#0.0] syntax. An optional constant P#x.y adds a fixed offset to the register content. A canonical sequence:
L #Address // INT or DINT byte offset, e.g. 10
SLD 3 // Multiply by 8 -> bit pointer 80
LAR1 // AR1 = P#10.0 (area-internal, M area by default)
OPN DB 1
L DBW [AR1, P#0.0] // Reads DB1.DBW10
T #Result // Move to local WORD
The shape of the value inside AR1 determines what the read will see:
| AR1 contents (hex) | Area | Type | Sample operand |
|---|---|---|---|
00 00 00 50 (P#10.0) |
M (default area-internal) | Bit / Byte / Word / DWord | M[AR1, P#0.0] = M 10.0 |
00 00 01 18 |
DBX inside area-internal | Bit | DBX[AR1, P#0.0] = DBX 14.0 |
84 00 00 50 |
DB (area-crossing) | Bit / Byte / Word / DWord | DBW[AR1, P#0.0] with DB number in high word |
81 00 00 50 |
Input (I) | Bit / Byte / Word / DWord | IB[AR1, P#0.0], IW[AR1, P#0.0] |
82 00 00 50 |
Output (Q) | Bit / Byte / Word / DWord | QB[AR1, P#0.0] |
The high byte in the table is the area code that the S7 uses to route the read. The default area-internal pointer 00 00 00 50 inherits the area from the instruction that uses it, so M[AR1, P#0.0] is M-area and DBW[AR1, P#0.0] is data-block (the DB opened by the most recent OPN DB or the multi-instance DB currently bound by the call stack).
TAR1 / TAR2 to copy AR1/AR2 into the local stack, then re-load with LAR1 / LAR2 on return. This is a common source of corrupted-pointer crashes that only show up on certain scan orderings.Building a Pointer with SLD 3
Most engineers keep their pointers in bytes (array index) or in element numbers (record number). STL, however, expects bits. SLD 3 ("shift left double-word by 3 bits") multiplies the source by 8 and slides the result into the pointer shape. The reason is straightforward: the low three bits are the bit selector, so to clear them and reserve them for the .0 you must shift the integer left by exactly three bit positions. Worked example:
L #IndexByte // INT, e.g. 10 = "the 10th byte"
SLD 3 // 10 << 3 = 80 = P#10.0
LAR1 // AR1 = 0x50
L MB [AR1, P#0.0] // Read MByte 10
T #WorkByte
Alternative ways to land a byte index inside AR1:
| Method | STL snippet | Effect |
|---|---|---|
| Shift + LAR | L #n; SLD 3; LAR1 | Cleanest; explicit "byte to bit pointer" intent |
| Direct pointer literal | LAR1 P#10.0 | Constant; ideal for fixed table walks |
| Pre-shifted DINT | L L#80; LAR1 | Use only when the source is already a bit pointer |
| From DBW (no shift) | L DBW10; SLD 3; LAR1 | Loads a stored byte index and prepares it |
| From DBD pointer | L DBD140; LAR1 | Use when DB already holds a bit pointer (no shift needed) |
If you skip the SLD 3 on a byte-oriented index, the low three bits of the value are not zeroed. A stored index of 10 would land in M10.x where x is whatever was sitting in the low bits. This is the classic "off by a bit or three" bug that surfaces only at certain indices and is one of the harder things to spot during commissioning.
Area-Crossing vs Area-Internal Pointers
STEP 7 distinguishes two pointer formats that interact with the area code in different ways:
-
Area-internal pointer (32 bits) — only the byte/bit field is meaningful; the high byte is zero. The instruction determines the area. Used with
M[..],DBX[..],I[..],Q[..],L[..], etc. -
Area-crossing pointer (48 bits / 6 bytes) — used in the older POINTER data type and in FB parameter passing. Format:
DB-number * 0x100000000 + byte.bit. The high 16 bits hold the DB number; the low 32 bits hold the byte.bit pointer.
The 48-bit pointer is what you see in the symbolik of an FB's IN/OUT/IN_OUT block parameters. To use it for area-crossing reads you need to pre-load the DB number into the high word and not call OPN DB — the register combination is the only thing the CPU trusts. Conversely, the 32-bit pointer you build with SLD 3 is area-internal and the open DB (or the implicit area in the instruction) wins.
Convert a 48-bit POINTER into a 32-bit area-internal pointer in three instructions:
L P##SrcPointer // Load the 48-bit POINTER (e.g. an FB IN)
LAR1 // AR1 = low 32 bits = byte.bit (area-internal)
L P##SrcPointer
T #DBNumber // High 16 bits; remember to call OPN DI / OPN DB
Step-by-Step: Implementing an Indirect Read
Prerequisites
- STEP 7 V5.5 or compatible engineering tool installed
- S7-300/S7-400 CPU with at least firmware V2.x
- A source DB (e.g., DB 103) with DBD140, DBD384 already initialised to valid bit-address values
- OB1, an FB, or an FC to host the STL network
- OB121 and OB122 loaded so the CPU stays in RUN during transient bad-pointer conditions during commissioning
Procedure
- Open the source DB (here DB 103) with
OPN DB 103. - Load the indirect source bit with
A M [DBD 140]. The CPU reads DB103.DBD140, decodes the byte/bit, and tests the corresponding M bit against the current RLO. - Assign the RLO to the latching bit
= M 206.0. - Repeat steps 2-3 for any further slots (e.g.,
A M [DBD 384]; = M 206.2). - If a different DB supplies the next pointer, change the open DB with
OPN DB 104before continuing. - Save the block, download to the CPU, and place the CPU in RUN.
Verification
- Open the DB online (Watch Table or Monitor/Modify) and confirm the pointer values are within range. Use a DB Monitor with the "All" display format to see both decimal and pointer (P#x.y) representations.
- Force the M bit at the decoded address and confirm the result bit toggles.
- Force an out-of-range value (e.g., 99999) and confirm that OB121 is installed and called — that proves the area-length check is active.
- Capture the CPU diagnostic buffer; expect no "Area length error" entries during normal operation.
Step-by-Step: Converting a Byte Address to a Bit Pointer
Use this routine when an operator sets a recipe number (1, 2, 3, …) and your code must look up a parameter record in an FB's STAT area by record number.
- Read the recipe number into a local INT (
#Index). - Multiply by the record size (e.g., 20 bytes) to get the byte offset:
L #Index; L 20; *I; T #ByteOffset. - Convert the byte offset to a bit pointer:
L #ByteOffset; SLD 3; LAR1. - Read the record:
L DBB [AR1, P#0.0]; T #WorkByte(or useDBW[..]/DBD[..]for word/double-word fields). - If the record number is taken from an HMI tag, clamp it before step 2 to avoid OB121.
- Post-increment
AR1with+AR1 P#1.0inside a loop body to walk the record byte-by-byte without reloading the shift constant each iteration.
Full example FB body (FB "RecipeLookup", STAT recipe array of 20 bytes, runtime #Index 1..50):
// Clamp index
L #Index
L 1
<I // If #Index < 1, jump to error
JC ERR
L #Index
L 50
>I // If #Index > 50, jump to error
JC ERR
// Build pointer
L #Index
L 20
-I // Offset = (Index - 1) * 20
DEC // -1 because STL arrays are 0-based
SLD 3 // Byte offset -> bit pointer
LAR1 // AR1 = base address of record
// Read first field of the record (REAL temperature setpoint)
L DBD [AR1, P#0.0] // Record[0]: REAL Temperature_SP
T #TemperatureSet
// Read second field (WORD machine code)
L DBW [AR1, P#4.0] // Record[4]: WORD MachineCode
T #MachineCode
// Walk to next byte for a third field (BYTE options)
L DBB [AR1, P#6.0] // Record[6]: BYTE Options
T #Options
SLD 3 provided the size of an element is 1 byte. For an element that is 4 bytes, use L 4; *D; SLD 3 to walk the array correctly. For a multi-field STRUCT of mixed types, advance AR1 with +AR1 P#fieldsize.0 instead of multiplying.Working with the ANY Pointer Type
When an SFC such as SFC20 BLKMOV, SFC21 FILL, or SFC22 CREAT_DB accepts an ANY pointer, you must populate a 10-byte block that the CPU interprets as source or destination descriptor. The fields are:
| Byte offset | Field | Content | Example |
|---|---|---|---|
| 0-1 | Syntax ID | 10h = BYTE, 11h = WORD, 12h = DWORD, 13h = REAL, 15h = BLOCK_DB, 16h = COUNTER, 19h = TIMER |
W#16#10 for BYTE |
| 2-3 | Data type length (bytes) | 1, 2, 4, 8 | W#16#1 |
| 4-5 | Number of elements | Replication count |
W#16#20 for 32 bytes |
| 6-7 | DB number (or 0) | 0 for non-DB areas |
W#16#0 for M area |
| 8-11 | Area + byte.bit pointer | 32-bit area-internal pointer | P#M 100.0 BYTE 32 |
Build an ANY for a copy from MB100 (32 bytes) into DB200.DBB50 (32 bytes) using only STL:
// Source ANY (M area, byte 100, 32 bytes)
L W#16#10 // Syntax ID = BYTE
T #SrcAny.syntaxID
L 1 // Length of one element
T #SrcAny.length
L 32 // Replication count
T #SrcAny.count
L 0 // No DB for M area
T #SrcAny.dbNumber
L P#M 100.0 BYTE 32 // Area-internal pointer + repetition
T #SrcAny.pointer
// Destination ANY (DB200, byte 50, 32 bytes)
L W#16#10
T #DstAny.syntaxID
L 1
T #DstAny.length
L 32
T #DstAny.count
L 200
T #DstAny.dbNumber
L P#DBX 50.0 BYTE 32
T #DstAny.pointer
// Call SFC20
CALL SFC 20
SRCBLK := #SrcAny
DSTBLK := #DstAny
RET_VAL := #RetVal
Indirect Addressing in FBs vs FCs
The block type affects which pointer sources are practical.
-
FCs have no STAT area, so any pointer must come from a global DB or a literal
P#... The local stack (TEMP) is re-initialised every call, so saving AR1 across a call inside an FC is dangerous; use a global marker or DB slot instead. - FBs own a STAT area, which is the natural home for indirect-pointer bases, index accumulators, and look-up tables. Multi-instance FBs share a single instance DB per parent FB, so the pointer is the same data word regardless of which instance is called.
- OB1 cannot keep state in TEMP across cycles. Any pointer that must survive from one OB1 cycle to the next lives in a global DB or the M area.
Pointer Survival Across Block Calls
AR1 is implicitly used by the system when an FB receives a parameter. If your STL code loaded AR1 with LAR1 P#M 100.0 before a CALL, you will see the value of AR1 changed on the return line. Save and restore in TEMP:
TAR1 // Save current AR1 to accumulator
TAR1 AR2 // Copy AR1 -> AR2
LAR1 P#M 100.0 // Set up pointer for this FB
CALL FB 200 // System uses AR1 internally; AR1 may be clobbered
LAR2 // Reload original pointer from AR2
// ... continue
For FBs the same idiom is implemented by saving the pointer into a STAT slot at the entry of the FB and reloading AR1 at the exit. Modern STEP 7 best practice is to keep all pointer work in STAT and avoid depending on AR1 surviving.
STL vs SCL: Choosing the Right Tool
Indirect addressing in STL is fast (single CPU instruction per read) and fully visible, but error-prone. The same logic in SCL on a S7-300/400 or S7-1500 reads like:
FOR i := 1 TO 50 BY 1 DO
IF DB103.MarkerWord[i] THEN
M206.0 := TRUE;
END_IF;
END_FOR;
The trade-off matrix:
| Criterion | STL with M[DBD] | SCL with array index |
|---|---|---|
| Readability | Low — pointer math required | High — symbolic array access |
| Performance (S7-300/400) | One microsecond-class instruction | Indexed call through compiler-generated loop |
| Compile-time type check | None — symbol enters the brackets as a raw value | Yes — array bounds enforced |
| Migration to S7-1500 | STL is deprecated; manual port required | SCL is the recommended path |
| Commissioning visibility | Excellent — STL is inspectable online | Compiles to STL; harder to step through |
For new code on S7-1500 write SCL. For legacy S7-300/400 maintenance that already uses M[DBD] semantics, keep STL and rely on the patterns above.
Migration to S7-1500 / TIA Portal
S7-1500 CPUs in TIA Portal support SCL directly with full symbolic array access. STL is still accepted for legacy imports but is not the recommended language. The pointer concept disappears in the higher abstraction: an ARRAY[1..50] OF BOOL is indexed by an INT variable and the compiler enforces bounds. If the program must remain in STL on S7-1500, the same SLD 3 / LAR1 / [AR1, P#0.0] sequence still works for area-internal reads, but the older OPN-DB and DBD-indirect patterns are emulated as a compatibility layer. Validate by stepping through Watch Tables after the migration.
Pointer Format Reference Table
| Field | Width (bits) | Range | Notes |
|---|---|---|---|
| Byte number | 24 (bits 3-31) | 0 - 16 777 215 | Address within the selected area |
| Bit number | 3 (bits 0-2) | 0 - 7 | Bit offset inside the byte |
| Area code | 8 (high byte of DWORD) | 00, 81, 82, 83, 84, 85 | 0=M, 81=I, 82=Q, 83=L, 84=DB, 85=DI |
| DB number (48-bit pointer only) | 16 | 0 - 65 535 | High word of 48-bit POINTER / ANY |
For an area-crossing pointer the high word is the 16-bit DB number and the low 32 bits follow the layout above. The area code for DB reads is 84 when the 48-bit pointer came from an FB parameter; for a multi-instance the code is 85. The high byte is only consulted by the CPU when the instruction is a area-crossing read; for an area-internal DBW[AR1,..] the high byte is ignored entirely.
Common STL Errors and Diagnostics
| Symptom | Likely cause | Diagnostic | Fix |
|---|---|---|---|
| CPU goes to STOP with SF lit, OB121 called | Pointer outside the area | Read STL stack info; last executed statement | Clamp pointer or expand the DB |
| Always reads bit 0.0 regardless of pointer | Forgot SLD 3 and the value was < 8 |
Watch table: DBD value vs. AR1 content |
Insert SLD 3
|
| Reads from wrong area | Area-crossing pointer loaded with area-internal layout | Use VAT to display the DWORD as hex | Adjust the high byte to the right area code |
| Corrupted pointer across FB call | AR1 was reused by the called FB | Step through with breakpoints | Save AR1 with TAR1 and reload with LAR1
|
| DB number ignored | Used 32-bit area-internal pointer for an FB IN parameter | Compare VAT view with parameter value | Build full 48-bit pointer or strip the DB number and rely on OPN
|
| SFC20 RET_VAL negative | ANY descriptor corrupted or wrong length | Inspect ANY in VAT in hex view | Rebuild ANY with the correct syntax ID, length, count, DB number, pointer |
| Read returns stale data | DBW/DWD read instead of DBX for a bit test | Online monitor the ACCU2 result | Use DBX[..] for bits, DBW[..] for words |
Verification Checklist
- Pointer value in the source DB equals a valid
P#byte.bitwhen interpreted as bits. -
AR1/AR2content shown online matches the intended byte/bit of the area. - All pointer values that arrive from the HMI / recipe are clamped to the legal range in OB100 or a startup FB.
- OB121 (programming error), OB122 (I/O access error) are loaded in the CPU so the system stays in RUN when a fault is forced for testing.
- The first execution cycle logs the decoded pointer to a VAT for an audit trail.
- Multi-instance FBs share their pointer bases in the parent's instance DB; verify that no instance overwrites another instance's pointer.
- For SFC20/SFC21 calls, the ANY descriptors are populated in the same cycle they are consumed; do not let interrupts write to a partially built ANY.
FAQ
What does M [DBD 140] actually mean in STEP 7 STL?
It is a memory-indirect bit operand. The CPU reads the 32-bit value stored in DB103.DBD140 (the active DB is DB 103), treats it as a bit address inside the M area, and uses the byte/bit split to locate the source bit. For example, the value 1234 resolves to M 154.2 because 1234 = 8 * 154 + 2.
Why do I need SLD 3 before LAR1?
STL address registers expect the full bit address (bits 0-2 are the bit selector). A byte index such as 10 must be shifted left by three bit positions so the value becomes 80 (hex 0x50), which is the bit-pointer form P#10.0. Skipping the shift leaves garbage in the bit-selector field and the read lands on a neighbouring bit.
How are AR1 and AR2 different?
AR1 and AR2 are functionally identical 32-bit address registers; the difference is purely conventional. AR1 is implicitly used by the system for parameter passing, so user code usually saves and restores it with TAR1 / LAR1 around calls. AR2 is generally free for application use, which makes it the preferred scratch register for pointer-heavy code.
What is the difference between an area-internal and an area-crossing pointer?
An area-internal pointer is a single 32-bit value that holds the byte/bit only; the area (M, DB, I, Q) is taken from the instruction. An area-crossing pointer is 48 bits: a 16-bit DB number in the high word plus the 32-bit byte/bit in the low word. You need an area-crossing pointer when the address must carry the DB number along with it, such as an ANY or POINTER parameter to an FB.
How do I prevent OB121 from stopping the CPU on a bad pointer?
Load and configure an OB121 in the S7 project. The CPU will then call OB121 on a programming error (bad pointer, illegal operand combination) and continue with the next cycle. Combine the OB with a clamp routine at the source of every operator-supplied index so the bad value is corrected before it reaches the indirect read.