Converting S5 DO Instruction to S7: Indirect Addressing Migration

David Krause13 min read
HMI ProgrammingSiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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.

Reference: The original S5 instruction set is documented in the Siemens STEP 5 Programming Manual. The migration procedure used here is taken from the Siemens conversion manual STEP 7 - From S5 to S7.

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.

Never use odd DBW numbers in S7. 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:

  1. OPN DB [#DB_no] opens the data block whose number is in #DB_no. This replaces DO 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.
  2. The SLD 5 shift is required because DBD [#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.
  3. Always widen with ITD before 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]
Bit addressing variant: if you need to start at bit k within a byte, add 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:

  1. SLW 3 is 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.
  2. 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 PIB operand as illegal.
  3. MB 253 itself 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, which SLW 3 handles 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
Alternative (recommended for clarity): Use the pointer constant 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
Practical recommendation: Use SCL for new code paths; use STL pointer arithmetic only where SCL cannot express the pattern (e.g., area-crossing peripheral access in tight loops, or when porting legacy code verbatim). Always comment the SLD value with the operand width (DBB/DBW/DBD) to prevent the next maintainer from miscounting.

Verification and Commissioning

  1. Static check (offline) — Open the converted STL in STEP 7 / TIA Portal and confirm no symbol remains marked red. DO, DW, DD, DL, DR must all be replaced.
  2. Cross-reference audit — Use Reference Data → Cross-references to list every OPN DB and every DBD / DBW / DBB access. Each S5 DD n must now read DBD 4*n unless indirect.
  3. Online watch table — Force the flag words that fed DO and confirm in VAT that OPN DB displays the correct DB number, and that DBD offsets land on the expected byte address.
  4. 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.
  5. Hardware OB fault capture — Load OB 121 / OB 122 temporarily with a STP or a buffer-stamp so a real fault halts the CPU at the exact instruction.
  6. 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/DBW translation.
Safety reminder: This conversion is structural. Always re-validate any safety-related DB (e.g., F-runtime groups) with the manufacturer-approved migration procedure rather than the indirect STL pattern.

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.

Back to blog