Overview: What the S5 DO Instruction Does
The Step 5 DO instruction performs indirect data block opening. The operand following DO supplies the DB number at runtime rather than at compile time. After DO executes, every subsequent data-word (L DW / T DW) or data-doubleword (L DD / T DD) access targets the DB whose number was just resolved.
Classic S5 source pattern from the original program:
L FW 252 // load DB number from flag word 252
DO FW 252 // open DB whose number is in FW252
L DD 0 // load data doubleword 0 from the resolved DB
T DD 2 // transfer to DD 2 of the same DB
DO FW 250 // switch DB to number held in FW250
L DD 0
L DD 6
DO FW 248 // switch DB to number held in FW248
L DD 0
L DD 6
Runtime semantics: if FW252 = 16, DO FW 252 opens DB 16 and the following L DD 0 loads doubleword 0 of DB 16. DO also permits the operand FY (flag byte), FW (flag word), or FD (flag doubleword), or directly the accumulator-1 via DO with no operand in some S5 firmware variants.
Memory Model: S5 Words vs S7 Bytes (Critical Difference)
The single largest source of data corruption during S5-to-S7 conversion is the change in data-block addressing granularity.
| Reference | S5 layout (words) | S7 layout (bytes) |
|---|---|---|
| First 16-bit unit | DW 0 = DL 0 + DR 0 | DBW 0 = DBB 0 + DBB 1 |
| Second 16-bit unit | DW 1 = DL 1 + DR 1 | DBW 2 = DBB 2 + DBB 3 |
| Third 16-bit unit | DW 2 = DL 2 + DR 2 | DBW 4 = DBB 4 + DBB 5 |
The S5 word address DW n therefore maps to S7 byte address DBW (2*n). Doubling the offset is mandatory; otherwise the new code reads and writes overlapping data.
DBW 1 is legal in the editor, but it spans DBB 1 and DBB 2 — the half-word structure no longer matches S5's DW 1 = DL 1 + DR 1 mapping, and adjacent words are silently corrupted on every write. Always regenerate S5 DW indices as DW n → DBW 2*n.Similarly for doublewords:
-
DD 0(S5) =DBD 0(S7) -
DD 1(S5) =DBD 4(S7) -
DD n(S5) =DBD 4*n(S7)
S7 Indirect Addressing Architecture
S7-300/400 STL supports three pointer-based indirect forms. The decision tree is governed by the destination operand area.
| Form | Syntax | Pointer source | Used for |
|---|---|---|---|
| Memory-indirect, area-internal | L DBD [MD10] |
32-bit pointer in MD/DBD/LD | Data block access with variable byte offset |
| Register-indirect, area-internal | L DBD [AR1, P#0.0] |
Address register AR1 or AR2 | DB access with pointer + fixed offset |
| Register-indirect, area-crossing | L PIB [AR1, P#0.0] |
AR1/AR2 with area ID bits | Peripherals, inputs, outputs, bit memory with variable area |
The pointer format for area-internal memory-indirect addressing (e.g., DBD[MD10]) is a 32-bit word:
| Bits | Field | Meaning |
|---|---|---|
| 31 | ID bit | 0 = area-internal (always 0 for DBB/DBW/DBD[...]) |
| 30..19 | Unused | Must be 0 |
| 18..3 | Byte address | Byte offset within the DB (0..65535) |
| 2..0 | Bit address | Bit offset (0..7) |
For area-crossing register-indirect addressing (PIB [AR1, P#0.0]), bits 24..26 hold the area identifier and bit 31 remains 0.
| Bits 26 25 24 | Area |
|---|---|
| 0 0 0 | PQ (peripheral outputs) |
| 0 0 1 | PI (peripheral inputs, direct) |
| 1 0 0 | QB / PIB (process image output / input bytes) |
| 1 0 1 | MB / M (bit memory) |
| 1 1 0 | DB (data block) |
| 1 1 1 | DI (instance DB) |
Converting DO FW to STL Pointer Logic
There is no DO equivalent in S7. The conversion is structural: replace the indirect DB-open with a pointer, and convert every L DD n / T DD n that follows into a memory-indirect or register-indirect load/transfer.
Conversion pattern:
// Step 5
L FW 252 // DB number, e.g., 16
DO FW 252 // open DB 16
L DD 0 // load DBD 0 of opened DB
T DD 2 // transfer to DBD 2
// Step 7 STL equivalent (area-internal)
L #DB_no // INT source, value 16 in this example
ITD // widen INT to DINT (avoid sign issues)
SLD 5 // shift left 5 — converts DBD index to byte-pointer
T #ptr_dbd // temp DWORD pointer
OPN DB [#DB_no] // open the DB explicitly
L DBD [#ptr_dbd] // load DBD 0
T DBD [#ptr_dbd] // transfer to DBD 2 ... or another offset
Key observations:
-
OPN DB [#DB_no]opens the data block whose number is in#DB_no. This replacesDO FW 252. The operand must be an INT or WORD; using a DWORD can trigger an area length error if the upper bits are non-zero on older CPUs. - The
SLD 5shift is required becauseDBD [#ptr_dbd]expects a byte pointer (byte offset in bits 3..18), but the natural index is a DBD index. Multiplying by 32 (= shifting left by 5) produces the correct pointer format with bit address 0. - Always widen with
ITDbefore shifting — shifting an INT left by 5 can promote sign bits into the byte-offset field and corrupt the pointer.
The SLD Shift Constants Explained
The shift constant depends on the operand width of the data being addressed. The rule is: the shift equals the binary log of the bytes-per-unit, plus 3 (the 3 is the bit-address field reserved at the LSB of every S7 pointer).
| Index type | Bytes per unit | Shift | Pointer multiplier |
|---|---|---|---|
| DBB n | 1 | SLD 3 | n × 8 |
| DBW n | 2 | SLD 4 | n × 16 |
| DBD n | 4 | SLD 5 | n × 32 |
Examples:
// Load DBW 5 (= DBB 10..11) using a variable index
L #DBW_index // INT, value 5
ITD
SLD 4 // 5 * 16 = 80 = P#10.0
T #ptr_dbw
L DBW [#ptr_dbw] // loads DBW 10
// Load DBB 7 using a variable index
L #DBB_index // INT, value 7
ITD
SLD 3 // 7 * 8 = 56 = P#7.0
T #ptr_dbb
L DBB [#ptr_dbb] // loads DBB 7
The same rule applies to address registers:
// Load AR1 with pointer to DBW 5
L P#10.0 // hard-coded pointer (= DBW 5 byte address)
LAR1
L DBW [AR1, P#0.0]
k to the pointer before shifting. Example: L 5; + L 2; ITD; SLD 3; LAR1 produces a pointer to bit 2 of byte 5. Or use the constant form L P#5.2; LAR1.STL Conversion Examples: Byte, Word, Dword
Variable DBD access (replaces L DD n / T DD n)
// FC parameter: in_word WORD (DBD index, S5-style)
// in_dbd POINTER (any-pointer to receive pointer)
// out_val DWORD (loaded value)
L #in_word // S5-style DBD index
ITD
SLD 5 // convert to byte-pointer
T #in_dbd // pointer now valid for DBD[#in_dbd]
OPN DB [#in_word] // open DB whose number == in_word (assumed equal)
L DBD [#in_dbd] // load variable DBD
T #out_val
Variable DBW access (replaces L DW n / T DW n)
L #in_word
ITD
SLD 4
T #in_dbd
OPN DB [#db_no]
L DBW [#in_dbd]
T #out_word
Variable DBB access (replaces L DR n / L DL n / T DR n / T DL n)
L #in_byte
ITD
SLD 3
T #in_dbd
L DBB [#in_dbd]
T #out_byte
Read-only pattern using AR1 (register-indirect, area-internal)
L P#0.0
L #in_word
ITD
SLD 5
+D // accumulator now holds base + offset pointer
LAR1 // load AR1 with final pointer
OPN DB [#db_no]
L DBD [AR1, P#0.0]
T #out_val
Area-Crossing Indirect Addressing (PIB, PQB, MB)
The original S5 program also uses L PY 0 — loading a peripheral byte from the P area. S7 still supports the P periphery, but the typical pattern is the process image (PIB, PQB). For area-crossing indirect access, the pointer must carry the area identifier in bits 24..26.
Setting up an AR1 pointer for PIB
// Goal: load PIB[#input_offset]
L #input_offset // BYTE/INT, e.g., 10
ITD
SLD 3 // convert byte offset to pointer
L B#16#81 // area ID for input bytes (100 0001 bin)
OD // OR area ID into bits 24..26
T #temp_ptr
LAR1
L PIB [AR1, P#0.0]
T #out_byte
Area-ID constants:
| Area | Hex | Binary (26..24) |
|---|---|---|
| PIB / PQB | 0x81 | 100 |
| MB | 0x83 | 101 |
| DB | 0x84 | 110 |
| DI | 0x85 | 111 |
Error case from the field report
The S5 program fragment DO FW252 / L PY 0 triggered an Area length error in S7 after this conversion:
T #conv_akku1
L STW
T #conv_stw
L MB 253
SLW 3
LAR1
L PIB [AR1, P#0.0]
Root cause:
-
SLW 3is a 16-bit shift, not a 32-bit shift. The result occupies only the low word of ACCU 1; the high word is zero. AR1 receives a 32-bit value where bits 16..31 are zero. - Bits 24..26 are therefore zero — the area-ID field is missing, so the CPU cannot determine whether AR1 points to PI, PQ, MB, or DB. The CPU falls back to DB and rejects the
PIBoperand as illegal. -
MB 253itself is also problematic: bit-memory byte 253 is in the last valid byte of the default flag area on most S7-300 CPUs (0..255), but on CPUs with only 256 flag bytes the byte index must be multiplied by 8 to become a pointer, whichSLW 3handles only inside the low word.
Corrected code:
T #conv_akku1
L STW
T #conv_stw
L #input_offset // proper INT source
ITD
SLD 3 // 32-bit shift — preserves high word
L B#16#81 // area ID for PIB
OD
LAR1
L PIB [AR1, P#0.0]
T #conv_akku1
P#byte.bit form for fixed offsets and L P##var + L <byte_offset> style for variable offsets. Reserve the OR-with-area-ID pattern for dynamic area selection only.Area Length Error: Root Cause and Fix
An Area length error (SF LED lit, diagnostic buffer entry) occurs when STL indirect addressing reads or writes outside the area bound by OPN, or when the area-ID is inconsistent with the operand syntax.
| Trigger | Cause | Fix |
|---|---|---|
OPN DB [#var] with #var = 0 or > max DB number |
DB 0 cannot be opened; some legacy S5 code referenced DB 0 as the working DB. | Open a real DB (e.g., DB 1..max). S7 forbids DB 0 entirely. |
L DBB [#ptr] where #ptr bit 31 = 1 |
DBB[...] is area-internal; bit 31 must be 0. | Clear bit 31: L 0; ==D; ... not needed; instead ensure the shift source is INT widened with ITD and never loaded with a negative literal. |
L PIB [AR1, P#0.0] with AR1 area ID missing |
SLW instead of SLD, or area ID not OR-ed in. | Use SLD and OR with B#16#81 (or appropriate area constant). |
DBD [#ptr] with offset > DB length × 8 |
Pointer points past end of DB. | Bound-check the index against the DB length before the indirect access. |
Diagnostic buffer entry in STEP 7:
Event ID 0x2522 / 0x2521
Text: "Area length error when reading/writing"
Stack: STL source location of the L/T instruction
The error class on S7-300 is 4 (OB 121 priority-fault) and on S7-400 is 16. Install an OB 121 / OB 122 temporarily to capture the exact instruction and ACCU contents at the fault.
Multi-Level Pointer Chains and Nested DO
S5 programs often use a "pointer-to-pointer" pattern where FW252 contains the DB number and a data word within that DB holds the actual target offset:
// S5
DO FW 252 // open DB indexed by FW252
L DW 0 // load from DW 0 of opened DB — this DW contains the real offset
T FW 250 // store offset into FW250
DO FW 250 // open DB indexed by FW250
The S7 equivalent requires nested pointer arithmetic:
// S7
L #DB_no_outer // from FW252
ITD
T #outer_db
OPN DB [#outer_db]
L DBD 0 // the "pointer-to-pointer" data in DW 0 of outer DB
ITD // the stored offset is an S5-style DW index
SLD 4 // convert DW offset to DBW byte pointer
T #ptr_inner
L DBD [#ptr_inner] // load the real value
If the stored offset is itself a DBD-style index (S5 DD), use SLD 5. If it is a byte index (S5 DR/DL), use SLD 3. Document which one is required at the DB level to avoid silent misuse.
SCL Alternative for New Development
New code in STEP 7 should use SCL (Structured Control Language) instead of indirect STL where possible. SCL provides typed, bounds-checked accessors that eliminate the SLD/AR1 pitfalls.
// SCL equivalent of the conversion
FUNCTION_BLOCK FB_DoEmulator
VAR_INPUT
i_db_no : INT;
i_offset : INT; // S5-style DBD index
END_VAR
VAR_OUTPUT
q_value : DWORD;
END_VAR
VAR_TEMP
t_db_no : INT;
t_ptr : DWORD;
END_VAR
BEGIN
t_db_no := i_db_no;
IF t_db_no > 0 AND t_db_no <= 32767 THEN
t_ptr := SHL(IN := DWORD#i_offset * 32, N := 0); // build pointer
// SCL cannot index DBs symbolically without instance/static blocks,
// so open and read with ATTRIB/DWORD_TO_* primitives:
q_value := WORD_TO_DWORD(ATTRIB(int := t_db_no,
offset := i_offset * 4,
area := 6));
END_IF;
END_FUNCTION_BLOCK
Verification and Commissioning
-
Static check (offline) — Open the converted STL in STEP 7 / TIA Portal and confirm no symbol remains marked red.
DO,DW,DD,DL,DRmust all be replaced. -
Cross-reference audit — Use Reference Data → Cross-references to list every
OPN DBand everyDBD / DBW / DBBaccess. Each S5DD nmust now readDBD 4*nunless indirect. -
Online watch table — Force the flag words that fed
DOand confirm in VAT thatOPN DBdisplays the correct DB number, and thatDBDoffsets land on the expected byte address. - Diagnostic buffer sweep — Run the converted program in OB 100 (restart) and read the diagnostic buffer for Area length error or Addressing error within the first scan. None should appear.
-
Hardware OB fault capture — Load
OB 121/OB 122temporarily with aSTPor a buffer-stamp so a real fault halts the CPU at the exact instruction. -
Equivalence test — If the original S5 is still operational, capture I/O traces and replicate them on the S7. Compare data block contents byte-by-byte at the end of each cycle; any mismatch is a missing 2× multiplier on a
DW/DBWtranslation.
Frequently Asked Questions
Why does my S7 STL give an area length error on the very first execution after DO conversion?
The most common cause is the missing 32-bit shift (SLW used instead of SLD) or an uninitialized pointer. Use ITD then SLD 5 for DBD indices, SLD 4 for DBW, SLD 3 for DBB. Confirm the resulting 32-bit value has bit 31 = 0 and that bits 24..26 hold the correct area ID for cross-area access.
Can I still use DB 0 in S7 the way S5 let me?
No. S7 rejects OPN DB 0 and any indirect form that resolves to DB 0. Allocate a dedicated working DB (e.g., DB 1) and rewrite the S5 code that referenced DB 0 to use that DB explicitly. Confirm with the Siemens STEP 7 - From S5 to S7 manual section on data-block selection.
How do I map an S5 DD index to the correct S7 DBD offset?
Multiply by 4: S5 DD n = S7 DBD 4*n. Example: S5 L DD 3 becomes S7 L DBD 12. The same scaling applies to DW (x2) and DL/DR (x1, but never use an odd byte for a word read).
What is the smallest STL snippet that replaces DO FW n with a pointer-based equivalent?
L #db_no; OPN DB [#db_no]; L #index_word; ITD; SLD 5; T #ptr_dbd; L DBD [#ptr_dbd]. This opens the data block whose number is in #db_no and loads the DBD at the byte offset held in #index_word (S5-style DBD index).
Should I rewrite legacy S5 indirect logic in SCL or keep it in STL?
Keep STL only for one-to-one port verification or where SCL cannot express the pattern (e.g., dynamic area-crossing peripheral access). For new functions, SCL is faster to read, type-checked, and avoids the SLD/AR1 pitfalls entirely. Always comment the SLD width (3, 4, or 5) so the next maintainer knows the operand size.