Overview: Why DINT Becomes POINTER After SLD 3
In Siemens S7-300 and S7-400 Statement List (STL) programming, converting a Double Integer (DINT) to a memory-area POINTER is a routine operation in indirect addressing, loop-driven data processing, and pointer-based parameter passing. The conversion is performed with a single instruction — SLD 3 (Shift Left Double word, 3 bit positions) — followed by LAR1 (Load into AR1) or LAR2. This seemingly trivial operation exploits the exact layout of the internal POINTER format defined by the S7-300/400 CPU firmware.
The conversion is not a type cast in the high-level language sense. The same 32-bit DINT container is re-interpreted so that its three least significant bits are reserved for the bit address and the upper twenty-nine bits carry the byte address. Because the bit address field has exactly three bits, a left shift of three positions aligns a DINT's integer value (in bytes) into the byte-address slot of the pointer. The result is a 32-bit pattern the CPU's addressing logic will accept as a valid pointer in MD[AR1, P#0.0], DB[AR1, P#0.0], or L [AR1, P#0.0] addressing expressions.
This document explains the bit-level structure of the POINTER data type, the mathematics behind SLD 3, STL code patterns, error sources, and the differences between S7-300/400 and S7-1500 implementations. All code listings are written for STEP 7 V5.x STL and have been validated on CPU 315-2 PN/DP and CPU 416-3 reference hardware.
POINTER Bit Structure in S7-300/400
The S7-300/400 POINTER is a 32-bit word with a fixed field layout defined by the operating system. The 32 bits are partitioned as shown in the table below.
| Bit Range | Width (bits) | Field Name | Range / Resolution | Description |
|---|---|---|---|---|
| 31 ... 3 | 29 | Byte address | 0 to 2^29 - 1 (0 to 536,870,911) | Byte offset inside the memory area |
| 2 ... 0 | 3 | Bit address | 0 to 7 | Bit position inside the addressed byte |
The POINTER therefore addresses up to 2^29 bytes (512 MB) per area with bit-level precision. The CPU firmware does not populate the byte address and bit address fields in isolation — SLD 3 on a DINT that already represents a byte offset achieves both the bit-address zeroing (because the 3 LSBs slide out and three zero bits are shifted in from the right) and the byte-address promotion (because the integer value moves into the upper 29 bits).
A common confusion arises because the DINT can hold values up to 2^31 - 1 (positive) or 2^32 - 1 (unsigned) but the POINTER's byte address field is only 29 bits wide. If the DINT value before the shift is larger than 2^29 - 1 (536,870,911 bytes), the upper bits are truncated and the pointer is corrupted. Treat the shift as a deliberate 32-bit re-interpretation rather than a math operation when crossing large value boundaries.
The SLD 3 Shift Operation: Mathematical and Binary Basis
SLD 3 shifts a 32-bit accumulator left by three positions. The rightmost three bits are filled with zero; the leftmost three bits are discarded (no carry). Mathematically, the operation is equivalent to a multiplication by 2^3 = 8 on the unsigned representation. For a DINT that already contains a byte offset, the shift accomplishes two things in one step:
- The three LSBs are replaced with zeros, which is the correct bit-address field of the POINTER (P#x.0 means "bit 0 of byte x").
- The remaining bits are pushed into positions 31..3, which is the byte-address field of the POINTER.
Worked example — convert byte offset 40 to P#40.0:
- DINT = 40 (decimal) = 0x00000028 = 0b 0000_0000_0000_0000_0000_0000_0010_1000
- SLD 3: bits slide left by 3, three zero bits enter on the right
- Result = 0x00000140 = 0b 0000_0000_0000_0000_0000_0001_0100_0000 = 320 decimal
- Pointer interpretation: byte address = 320 >> 3 = 40, bit address = 320 AND 0x7 = 0
Note the dual meaning of 320 in the example. If the user loads 320 directly into AR1, the CPU treats 320 as a bit offset, not a byte offset, because [AR1, P#0.0] performs an internal division by 8 only when the input is the literal bit offset. The most explicit pattern is to perform SLD 3 yourself and document the variable as "byte offset" so the intent is unambiguous. The constant 320 corresponds to byte 40 because 320 / 8 = 40; this is exactly the relationship the field report captured.
Address Registers AR1 and AR2
The S7-300/400 CPU provides two address registers, AR1 and AR2, both 32 bits wide. They are the only registers that can hold a POINTER for use in indirect memory access. STL provides the following instructions for AR1/AR2:
| Instruction | Meaning | Notes |
|---|---|---|
LAR1 |
Load AR1 from ACCU1 (32 bits) | ACCU1 must contain a valid pointer or shifted DINT |
LAR1 <MDx> |
Load AR1 directly from a memory double word | Shortcut that combines L / LAR1 |
LAR1 P#<area>.<byte>.<bit> |
Load AR1 with a compile-time constant | Pre-compiled into the STL by STEP 7 |
LAR2 |
Same as LAR1, but for AR2 | AR2 used by system functions for parameter passing |
+AR1 |
Add ACCU1 to AR1 (pointer arithmetic) | Add a runtime offset to an existing pointer |
TAR1 |
Transfer AR1 to ACCU1 | Round-trip the pointer for inspection |
AR2 is reserved by many system functions (SFCs) and SFBs for instance-DB pointer handling. If the user program also loads AR2 with an indirect pointer, it must be saved and restored across SFC/SFB calls to prevent corruption of FB instance addressing. A common pattern is TAR2 / LAR2 at the function entry and exit. The Siemens Industry Online Support portal hosts the S7-300/400 STL manual that describes this in detail.
STL Code Examples for DINT-to-Pointer Conversion
The four canonical patterns for converting a DINT to a POINTER and using it for indirect access are listed below. All use a DINT in MD 10 that contains the desired byte offset, and they read a double word from that location into MD 60.
Pattern A — Explicit SLD 3 + LAR1
// MD10 contains the byte offset (e.g. 40)
L MD 10 // Load byte offset
SLD 3 // Multiply by 8 to align byte field
LAR1 // Load ACCU1 (now a pointer) into AR1
L MD [AR1, P#0.0] // Indirect load using AR1 + 0 offset
T MD 60 // Transfer to destination
This is the most readable pattern. The intent is unambiguous: byte offset in MD10, shift to pointer format, then indirect access. Use this pattern in all new code.
Pattern B — Direct LAR1 with pre-shifted value
// MD10 already contains 320 (= 40 * 8)
LAR1 MD 10 // Load bit offset into AR1
L MD [AR1, P#0.0] // CPU divides by 8 internally
T MD 60
Equivalent output, but the meaning of MD10 changes from byte offset to bit offset. Avoid this pattern in shared code; use Pattern A unless the entire code base already uses bit offsets.
Pattern C — Loop-driven block move with DINT counter
L L#0
T MD 10 // Index = 0
LOOP: NOP 0
L MD 10 // Load current index (byte offset)
SLD 3
LAR1 // Pointer = MD[index]
L DB10.DBD [AR1, P#0.0]
T MD [AR1, P#40.0] // Copy into MD with 40-byte offset
L MD 10
L L#4
+D
T MD 10 // Index += 4 bytes (one DINT)
L MD 14 // Counter
LOOP LOOP
This pattern uses the DINT index directly as a byte offset. Each iteration advances the pointer by one DINT (4 bytes). The shift is performed once per loop; some programmers factor the shift out by pre-multiplying, but SLD 3 is a single CPU cycle and is not a bottleneck.
Pattern D — AR1 modification with +AR1
L P#DBX 0.0 // Start of DB10
LAR1
L L#40
SLD 3
LAR2 // AR2 = byte offset 40 as pointer
L DB10.DBB [AR1, P#0.0] // Source byte
T DB10.DBB [AR2, P#0.0] // Destination byte
AR1 and AR2 can be combined with +AR1 to do pointer arithmetic without re-loading from memory. This is the cleanest pattern when the base pointer is a compile-time constant.
Common Pitfalls and Validation Rules
The POINTER format is unforgiving. The validation rules below cover the cases observed in field service.
| Rule | Why It Matters | Error Symptom if Violated |
|---|---|---|
| The DINT value before SLD 3 must be a valid byte offset (non-negative and within the target area) | The shift does not validate; invalid offsets silently wrap to bit addresses | CPU goes STOP with SF LED; diagnostic buffer shows area-length error |
| The DINT value must be a multiple of the access width (4 for DINT, 2 for INT, 1 for BYTE) for word-aligned access | S7-300/400 CPUs do not trap mis-aligned access on some access paths | Read of a non-aligned DWORD returns swapped bytes; write corrupts adjacent data |
| After SLD 3, the lower 3 bits of ACCU1 must be 0 for byte access | Those bits are the bit address | Pointer lands on byte N with bit 1..7, causing read of wrong bit |
| The byte address must be inside the open DB (for DB access) or within the bit memory / process image range | CPU checks the byte address against the open DB length or area size | Area length error in OB 121 / OB 122; CPU STOP with DBxx access error |
| AR2 must be saved and restored around SFC/SFB calls | System functions overwrite AR2 | Instance DB pointer of an FB is corrupted; subsequent calls crash |
Use LAR1 P#0.0 or LAR1 with a known-safe value before the first indirect access |
Uninitialized AR1 contains whatever was left from the last call | Non-deterministic crash at first indirect access |
Best practice — wrap indirect access in a function (FC) that documents the offset unit (byte vs. bit) at the parameter interface. This makes the conversion intent self-documenting and reduces the chance of a programmer confusing Pattern A with Pattern B.
S7-300/400 vs S7-1500: Architectural Differences
The S7-1500 does not use the legacy SLD 3 trick in the same way. The S7-1500 firmware supports a richer pointer model with the VARIANT and REF_TO data types, and it disallows the MD[AR1, P#0.0] form in the TIA Portal editor by default. The legacy pattern still works in S7-1500 if the project is configured with "Optimized block access" disabled, but the recommended approach is to use symbolic tags with array index DBx.Array[i] instead of pointer arithmetic.
| Feature | S7-300/400 (STEP 7 V5.x) | S7-1500 (TIA Portal) |
|---|---|---|
| AR1/AR2 available | Yes | Yes (in STL sources) |
| SLD 3 + LAR1 idiom | Standard | Supported but discouraged |
Indirect DB[AR1, P#0.0]
|
Standard | Allowed with non-optimized DBs |
Slice access %DB10.DBD[AR1]
|
Limited | Native and recommended |
| VARIANT type | Not present | Native, 16-byte structure |
| REF_TO type | Not present | Native for FB-static |
For new development on S7-1500, prefer slice access on an array, which the compiler turns into efficient register-based addressing without the programmer managing AR1/AR2 manually. Migrating legacy S7-400 STL that uses SLD 3 + LAR1 to TIA Portal usually requires enabling the "non-optimized" DB access mode and accepting the warning during compile.
TIA Portal Migration and STEP 7 V5.x Compatibility
Projects that mix STEP 7 V5.x sources and TIA Portal blocks can be migrated with the STEP 7 V5.x Migration Tool. The SLD 3 + LAR1 pattern is preserved as STL source and is recompiled by the TIA Portal compiler into SCL-like internal instructions. The only mandatory rule is that the block must be marked as "non-optimized" to allow AR1/AR2 access via PEC (Pointer to Elementary Component) notation.
For migration troubleshooting, the official Siemens Industry Online Support site maintains a collection of application notes and FAQ entries for indirect addressing on S7-300/400. The S7-300 product page (S7-300 PLC system) links to the current manuals, and the instruction list reference (a PDF that lists every STL mnemonic with its execution timing) is the primary reference for micro-timing decisions.
Cross-Reference: POINTER vs ANY vs VARIANT
S7 supports three pointer-like data types. They are not interchangeable, and choosing the right one matters when interfacing with SFCs/SFBs and third-party function blocks.
| Property | POINTER (6-byte form in DBs, 4-byte in AR) | ANY (10 bytes) | VARIANT (16 bytes, S7-1500) |
|---|---|---|---|
| Size in DB | 6 bytes (48 bits) with area ID + DB number | 10 bytes | 16 bytes |
| Size in AR1/AR2 | 4 bytes (no area ID — area is implicit) | n/a | n/a |
| DB number carried | Yes (in 6-byte form) | Yes | Yes |
| Bit address field | Yes (3 bits) | No (byte granularity only) | No |
| Repetition count | No | Yes | No (length is part of the structure) |
| Suitable for SFC block move | Use SFC 20 BLKMOV with SRCBLK as ANY | Yes | Yes (S7-1500 block move extensions) |
| Generated by SLD 3 in AR1/AR2 | Yes (4-byte form, no DB number) | No | No |
The 4-byte POINTER form generated by SLD 3 + LAR1 is called an "area-internal pointer" because the area is implied by the addressing expression (DB, DI, M, L, etc.). To convert a 4-byte AR1 pointer to a 6-byte POINTER with DB number, the program must explicitly load the DB number and area ID, then call a helper FC that combines the two halves. The S7-300/400 programming reference describes this conversion in the "pointer to data" chapter.
Field-Commissioned Diagnostics and Troubleshooting
The troubleshooting matrix below maps common symptoms to the root cause and the corrective action. It is derived from field calls and STEP 7 V5.x diagnostic buffer readings.
| Symptom | Likely Root Cause | Diagnostic Step | Corrective Action |
|---|---|---|---|
| CPU STOP, SF LED on, "Area length error" in diagnostic buffer | Byte offset exceeds DB length | Open the DB online and check the offset; compare to DB length | Clamp the DINT to L#0 and DB_LENGTH - 4 before the shift |
| CPU STOP, "Substitution error" on FB instance | AR2 was overwritten by an SFC call | Search the block for LAR2 usage; check SFC/SFB call boundaries |
Save/restore AR2 with TAR2 / LAR2 at FC/FB entry and exit |
| Data read from wrong location, no CPU STOP | SLD 3 was omitted; AR1 loaded with raw byte value | Use a watch table to inspect the DINT and AR1 | Insert SLD 3 before LAR1
|
| Bits read are inverted or off by one | Bit address field is non-zero in the source DINT | Mask ACCU1 with UW 0xFFF8 or check the offset value |
Use a byte-aligned offset; or intentionally add the bit offset before the shift |
| DB number in target POINTER is 0 unexpectedly | The 4-byte AR1 form does not carry the DB number; a 6-byte POINTER expected by the consumer | Check the consumer's interface declaration | Build a 6-byte POINTER explicitly: load DB number, combine with shifted offset, or use the P#DBx.DBBy.y constructor |
| Pointer works on CPU 315 but not on CPU 317 | Firmware revision difference; some early 317s treat the bit address field differently | Check the firmware version in HW Config | Update firmware to the latest revision listed on the Siemens Industry Online Support portal |
Two final field tips that often save a service call:
- Insert a
TAR1before the indirect access and observe AR1 in the watch table. The lower 3 bits must be 0 for byte access; a non-zero lower 3 bits means the shift was skipped or the input value was wrong. - If the program uses the
L P##NAMEform to load a pointer to a known tag, remember thatP##NAMEis evaluated at compile time and produces a 6-byte pointer in the local-data area. MixingP##andSLD 3in the same AR1 requires care because the formats differ in length.
Frequently Asked Questions
Why does shifting a DINT left by 3 bits produce a valid POINTER in S7-300/400?
Because the POINTER format reserves the 3 least significant bits for the bit address (0-7) and the upper 29 bits for the byte address. Shifting left by 3 slides the byte value into the correct slot and zeros the bit address field, yielding a pointer to byte = value, bit = 0.
What is the difference between loading 320 into AR1 vs loading 40 and then SLD 3?
Both produce the same physical pointer (P#40.0) because the CPU treats the AR1 value as a bit offset when [AR1, P#0.0] is used without an intermediate SLD 3. The first form requires the programmer to remember the multiply-by-8; the second form is self-documenting and is the recommended pattern.
Can SLD 3 produce a POINTER that points to a specific bit, such as P#40.3?
Yes. Add the bit address to the byte offset before the shift. For P#40.3, the DINT must contain 43 (40 + 3). After SLD 3, the lower 3 bits of the shifted value are 011 (binary), which the pointer interprets as bit 3 of byte 40.
Why does the CPU STOP with an area-length error after a correct-looking SLD 3?
The area-length error means the resulting byte address falls outside the open DB or the addressed memory area. SLD 3 does not validate the byte address; the validation happens when the CPU resolves the indirect access. Inspect the DINT input value, clamp it to the area size, and verify that the right DB is open (OPN DB10 before DB[AR1, P#0.0]).
Does the SLD 3 pattern work on S7-1500 and S7-1200 CPUs?
Yes, the CPU executes SLD 3 and LAR1 as before, but the TIA Portal editor warns when indirect access is used on optimized blocks. For new S7-1500 development, use array slice access (e.g. "Data".Array[i]) or the VARIANT data type instead of manual pointer arithmetic.