Overview
Siemens S7 STL (Statement List) supports two distinct indirect addressing forms, and the syntax of the operand prefix is what determines whether the CPU can resolve the address at runtime. The constructs L MB [MD 4] and L B [MD 4] look superficially similar, but the first compiles and executes correctly while the second is rejected by the S7 compiler as syntactically invalid. This reference documents the underlying reason: the operand token preceding the bracketed pointer must carry both the data width (B, W, D) and the memory area designation (M, I, Q, DB, L, etc.). Stripping the area letter breaks the grammar the STL parser expects, and it is not equivalent to letting the pointer itself carry the area code.
This document consolidates the rules for memory-indirect addressing, area-crossing (register-indirect) addressing, the POINTER parameter type, the memory area hexadecimal codes 0x81 through 0x87, and the bit-address arithmetic that turns a byte pointer into a bit pointer. It applies to S7-300, S7-400, and (with minor syntax adjustments) S7-1200/S7-1500 STL programming. The canonical Siemens Knowledge Base article on the topic is Entry ID 24519683 – Indirect addressing with pointers.
Problem Definition: Why L B[MD4] Fails
The STL operation token L B[MD 4] cannot be assembled into a valid MC7 machine instruction. The instruction L (Load) requires a fully-qualified operand: width, area, and byte number. Width is given as B (byte), W (word), or D (double word). Area is one of the legal prefixes M, I, Q, DB, DI, L, or P (peripherals). The bracketed pointer is a substitution for the byte number, not for the area letter.
The legally equivalent forms are:
-
L MB [MD 4]— load the byte in flag (M) area at the byte address stored in MD 4. -
L IB [MD 4]— load the byte in input (I) area at the byte address stored in MD 4. -
L QB [MD 4]— load the byte in output (Q) area at the byte address stored in MD 4. -
L DBB [MD 4]— load the byte in the opened DB at the byte offset stored in MD 4. -
L LB [MD 4]— load a byte from the L stack of the current block.
The bare token B[MD 4] provides only the data width and is missing the area designation. The compiler flags this as a syntax error, and even if it were accepted at compile time, the CPU firmware has no area code with which to perform the load.
L B[MD 4] line.
Two Forms of Indirect Addressing in STL
STL supports two fundamental indirect addressing modes. Choosing the correct one is what makes area-crossing access possible.
Memory-Indirect Addressing (Within-Area)
The pointer is held in a memory double-word (MD, LD, or DBD) and the operand's area letter is fixed at compile time. This is the form that allows L MB [MD 4] to work. The CPU substitutes the value of MD 4 for the byte number of an M-area access; the area remains M for the life of the program.
Register-Indirect (Area-Internal) and Area-Crossing Register-Indirect
The pointer is held in one of the two address registers, AR1 or AR2, and is loaded with the LAR1 / LAR2 instruction. Register-indirect addressing has two sub-forms:
-
Area-internal —
L B [AR1, P#0.0]. The address register contains a byte/bit offset; the area is given by the operand's area letter (e.g.,MB [AR1, P#0.0]). -
Area-crossing —
L B [AR1, P#0.0]without the area letter on the operand. The full pointer, including area code, is taken from AR1 (or AR2).
The area-crossing form is the only STL construct that can read from an area determined at runtime. The construct L B [MD 4] would be a memory-indirect, area-crossing form. The S7 instruction set does not provide this mode: memory-indirect is always area-internal, and area-crossing always requires AR1 or AR2.
| Mode | Pointer location | Area source | Example (compiles?) |
|---|---|---|---|
| Direct | — | Literal in instruction |
L MB 20 — yes |
| Memory-indirect, area-internal | MD / LD / DBD | Operand's area letter |
L MB [MD 4] — yes |
| Memory-indirect, area-crossing | MD / LD / DBD | — (not supported) |
L B [MD 4] — no |
| Register-indirect, area-internal | AR1 or AR2 | Operand's area letter |
L MB [AR1, P#0.0] — yes |
| Register-indirect, area-crossing | AR1 or AR2 | Pointer (AR1 or AR2) |
L B [AR1, P#0.0] — yes |
POINTER Parameter Type and Memory Area Hex Codes
The POINTER data type in S7 is a 6-byte structure. The first byte carries the memory area as a hexadecimal code, and bytes 1–5 carry a 32-bit byte/bit address in the same format produced by the P# pointer constant.
| Hex code | Memory area | Description |
|---|---|---|
| B#16#81 | I | Process-image input area |
| B#16#82 | Q | Process-image output area |
| B#16#83 | M | Bit-memory (flag) area |
| B#16#84 | DB | Data block |
| B#16#85 | DI | Instance data block |
| B#16#86 | L | Local data (L stack) of the current block |
| B#16#87 | V | Previous local data (from calling block) |
The pointer is typically assembled with the P# literal, which combines an area prefix with a byte.bit offset. For example, P#M 0.0 is loaded into ACCU1 as the double-word W#16#8300_0000 (low word = address, high byte = area code 0x83). Loading this into MD 4 places the full area pointer into the flag area, but it is not consumable by L B[MD 4] because memory-indirect addressing ignores the area byte of the pointer and uses the operand's letter instead.
Working Solution: Use AR1/AR2 for Area-Crossing
To perform an area-crossing load where the area is decided at runtime, load the pointer into an address register first, then reference the area register in the operand.
// --- Build a 30-bit byte/bit pointer and the area code in one constant ---
L P#M 23.0 // ACCU1 = 0x83_0000_0170 (area M, byte 23, bit 0)
LAR1 // AR1 now holds the full pointer including area code
L DW#16#12345678 // arbitrary pattern for demonstration
T MD 20 // MB 20 = 0x12, MB 21 = 0x34, MB 22 = 0x56, MB 23 = 0x78
// --- Area-crossing load: read byte 23 of M area via AR1 ---
L B [AR1, P#0.0] // loads MB 23 = 0x78
T MB 50 // MB 50 = 0x78
If the area code is generated dynamically (for example, by selection logic in the program), assemble the pointer in a double word, then copy it into AR1 with LAR1:
// Suppose MW 100 holds the chosen area code (0x81..0x87) and MD 104 holds
// the byte.bit address as a plain 32-bit value (bit 0..7 of low byte = bit 0..7).
L MW 100 // area code in low byte
T MB 200 // put area code in MB 200
L MD 104 // byte.bit address
T MD 202 // place in MD 202 (DO NOT overlap MB 200 / MD 202!)
// To keep things clean, build the pointer in one double-word and move it:
L MW 100 // area code in low byte, high byte = 0
SLW 8 // shift left 8 bits so area code occupies bits 8..15
OD
T MD 200 // MD 200 = area code | byte.bit address
LAR1 // AR1 = full POINTER
L B [AR1, P#0.0] // area-crossing byte load
LAR1 expects the full 6-byte POINTER in the low double-word of ACCU1. If you copy a POINTER from a DB into MD, do not overwrite the bytes of MD before LAR1 executes. A common pattern is to place the area code in a flag byte that is not part of the same MD that holds the address, then OR them together in a separate MD and load that into AR1.
Memory-Indirect Within One Area: Confirmed Working Pattern
When the area is fixed and only the byte number is variable, the memory-indirect form is the cleanest implementation. The pointer in MD 4 is just the byte number, not a full POINTER.
// --- Touch M byte 0 via memory-indirect addressing ---
L 0
T MD 4 // MD 4 = 0 (byte address in M area)
L MB [MD 4] // loads MB 0
T MB 10
This is functionally identical to L MB 0; T MB 10 at runtime, and is the form Siemens documents in Entry 24519683 as the canonical memory-indirect pattern.
Bit Address Arithmetic
For bit operations through a pointer, the low byte of the pointer holds the address as (byte_number × 8) + bit_number. This applies whether the pointer is held in MD and referenced as = [MD 4] / S [MD 4] / R [MD 4], or in AR1/AR2 and referenced as = [AR1, P#0.0] / S [AR1, P#0.0].
| Target bit | Pointer value (decimal) | Formula |
|---|---|---|
| M 0.0 | 0 | (0 × 8) + 0 |
| M 0.7 | 7 | (0 × 8) + 7 |
| M 1.0 | 8 | (1 × 8) + 0 |
| M 1.7 | 15 | (1 × 8) + 7 |
| M 400.4 | 3204 | (400 × 8) + 4 |
Worked example — set bit M 400.4 via memory-indirect set instruction:
L 400
L 8
*D // ACCU1 = 3200 (byte portion)
L 4
+D // ACCU1 = 3204 (byte + bit portion)
T MD 4
S M [MD 4] // sets M 400.4
Address limits depend on the CPU and area. For the M area on S7-300/400, byte addresses above the configured bit-memory size (e.g., 2048 bytes on a CPU 315-2 DP) will trigger Area length error (SF, OB 121). Always validate the byte address against the CPU's area length before issuing the access.
Verification Procedure
Use the following steps in STEP 7 / TIA Portal to confirm a correct implementation of either indirect addressing form.
- Compile the block. Open the STL source in the editor and trigger Project → Compile (or the equivalent toolbar button). If the syntax is invalid, the line will be flagged in the compiler log with a syntax error pointing at the offending statement.
- Download to the PLC. Use Online → Download with the CPU in STOP, or perform a delta download if the CPU is in RUN with the proper configuration.
- Monitor the pointer. Open the variable table / watch table and add MD 4 (or whichever MD holds the pointer). Force it to a known value, e.g., 0, 100, or 3204 for a bit pointer.
- Monitor the target. Add the target byte or word (e.g., MB 0 if MD 4 = 0) to the watch table and confirm that the value updates after each scan.
- Sweep the range. Use a sequence of pointer values that span the full range of the area (e.g., 0, 100, 1000, max−1) and confirm every access returns the expected value with no OB 121 area-length error in the diagnostic buffer.
- Inspect the diagnostic buffer. If a fault occurs, open Online → Diagnostics → Diagnostic Buffer and look for events with OB 121 / OB 122. These indicate either an area-length error (byte out of range) or a temporal error (the address becomes invalid between the load and the use).
Troubleshooting Matrix
| Symptom | Likely cause | Remedy |
|---|---|---|
Compiler: "Unknown operand or wrong syntax" on L B[MD 4]
|
Bare width token without area letter | Replace with L MB [MD 4], L IB [MD 4], etc., or move the pointer into AR1 and use L B [AR1, P#0.0]. |
| CPU in STOP, SF LED on, OB 121 "Area length error" | Pointer value is outside the configured size of the addressed area | Clamp the pointer to the area's max byte index before the access. |
| CPU in STOP, OB 122 "Temporal error" | Addressed peripheral/module is missing or faulty | Check the slot / module status; verify the pointer is a valid byte offset in the I or Q area. |
| Wrong data is read / written | Pointer is in the wrong MD, or the MD is overwritten by a subsequent statement before the indirect access executes | Reserve a dedicated MD for the pointer and audit all writes to it. Consider using AR1/AR2, which the compiler treats as protected during the indirect access. |
| Bit access sets a different bit than expected | Bit address missing: pointer holds byte number only, not (byte × 8 + bit) | Multiply the target byte by 8 and add the bit number, then store the sum in the pointer MD. |
| Compiler accepts but execution reads from the wrong area | AR1 was loaded with a POINTER whose area byte was zero or wrong | Ensure the area code (0x81–0x87) occupies bits 8..15 of the double word before LAR1. |
Alternative: ANY Pointer
For block calls (FC/FB parameter passing) where the called block must accept a variable type and area, the ANY (10-byte) data type is used. The first two bytes hold a repetition count and the data type code, the third and fourth bytes hold the area code (same 0x81–0x87 scheme), and the remaining six bytes hold the byte address and DB number. STL instructions such as L B [AR1, P#0.0] with a properly populated AR1/AR2 containing the full POINTER will work in the same way. For runtime-constructed ANY pointers in SCL or STL, use the BLD / BLKMOV / UFILL family of instructions and consult the corresponding Siemens KB article on parameter type ANY.
Best Practices for Field Use
- Use memory-indirect (
MB [MD x]) when the area is fixed. It is faster to compile, easier to read, and the CPU executes it with a single MC7 instruction in most firmware revisions. - Use area-crossing register-indirect (
B [AR1, P#0.0]) when the area is selected at runtime. The S7-300/400 CPU uses AR1 by default for theLfamily of pointer-typed parameter reads, so do not clobber AR1 inside an FC/FB that uses these implicit pointer loads. - Reserve AR2 for the OUT parameter side of FB calls, and AR1 for the IN/IN_OUT side. STEP 7 enforces this for FB parameter passing.
- Always validate the byte address against the area length before the indirect access. A simple clamp block at the top of the FC is enough to prevent OB 121 stops.
- Comment every POINTER construction in the program.
P#M 23.0is self-documenting; a hand-assembled MD with shifted area code is not. - Prefer symbolic tag names for the pointer DWords (e.g.,
"DB_Recipe".PointerByte) so the cross-reference in STEP 7 / TIA Portal shows every write to the pointer.
Notes on S7-1200 / S7-1500 Compatibility
STL is available in the S7-1200 and S7-1500 families (since STEP 7 V15 / TIA Portal V15) and uses the same MC7-derived instruction set as S7-300/400 STL. The indirect addressing rules documented above apply. However, the preferred programming style on S7-1500 is SCL, where pointer arithmetic is performed with the POKE / PEEK instructions or with the VARIANT / DB_ANY constructs, and the area code table is the same (0x81–0x87). The area-crossing forms P# and the register-indirect addressing remain valid STL syntax on S7-1500 as of firmware V2.5 and later.
Why does L B[MD 4] fail but L MB[MD 4] work in S7 STL?
The instruction opcode L requires both the data width (B, W, or D) and a memory area letter (M, I, Q, DB, L) in the operand. The bracketed pointer only supplies the byte number; the area letter is fixed at compile time. L B[MD 4] has no area letter and the STL parser rejects it. L MB[MD 4] is parsed as an M-area byte load at the byte number stored in MD 4.
How do I perform an area-crossing load where the area is selected at runtime?
Build a 6-byte POINTER (area code in the upper byte of the low word, byte.bit address in the lower bytes) and load it into AR1 with LAR1. Then use L B [AR1, P#0.0] for the area-crossing byte load. The S7 instruction set does not provide a memory-indirect area-crossing form; AR1/AR2 is the only way.
What are the hexadecimal area codes in a Siemens POINTER?
Byte 0 of the POINTER holds the area code: 0x81 = I, 0x82 = Q, 0x83 = M, 0x84 = DB, 0x85 = DI, 0x86 = L (current block local data), 0x87 = V (previous block local data). The byte.bit address occupies the remaining bits per the P# pointer format.
How do I set bit M 400.4 through a pointer?
Multiply the target byte (400) by 8, add the bit number (4), giving 3204, and store the result in a flag double-word. Then issue S M [MD x]. For register-indirect, load P#M 400.4 into AR1 with LAR1 and issue S [AR1, P#0.0].
What is the maximum byte address for memory-indirect access in the M area?
It depends on the CPU's configured bit-memory size. An S7-300 CPU 315-2 DP supports 2048 bytes of M area by default (byte 0 through 2047). Exceeding the configured size triggers OB 121 "Area length error" and stops the CPU if OB 121 is not loaded. Always clamp the pointer against the configured area size before the indirect access.