S7-300 Indirect Addressing in TIA Portal V16: Summing 30 DBs
Adding 30 data blocks (DB1 through DB30), each containing three double-word (DINT) values at byte offsets 100, 200, and 300, results in 90 individual arithmetic operations. The naive approach of inserting an ADD block per operand creates an unmaintainable program, and the suggested STL alternative raises legitimate questions about the S7-300 memory model, address register use, and TIA Portal V16 SCL syntax. This reference consolidates the working SCL pattern using PEEK_DWORD, the STL register-indirect equivalent, the boundary conditions for TIA Portal V16 on S7-300 firmware V3.x, and the diagnostic steps to verify the sum on PLCSIM or a real CPU.
Problem Definition
The task is to compute the scalar sum:
sum = Σ (i=1..30) [ DBD_i_100 + DBD_i_200 + DBD_i_300 ]
Each DBD is a 32-bit DINT, so the operand total is 30 × 3 = 90 reads and 89 additions. With an ADD block per operand, the network becomes 90 blocks long and any DB-number change requires manual rewiring. The right solution exploits the fact that the DB number and the byte offset are the only varying inputs; both are integers that can drive a single loop.
Prerequisites
- TIA Portal V16 (Build 16.0 or later) installed with the S7-300 add-on selected in the TIA setup.
- S7-300 CPU with firmware V3.2 or later (CPU 31x PN/DP). SCL for S7-300/400 requires the optional SCL package to be present in TIA Portal; it is no longer a separate license in V16, but the SCL compiler must be enabled in the project properties (right-click the S7-300 station → Properties → Protection → "Permit access with PUT/GET" is not relevant; check the SCL editor is installed under "Options → Support Packages").
- S7 program containing the 30 DBs. Each DB must declare the values at byte offsets 100, 200, and 300. Using non-optimized access is mandatory on S7-300 because PEEK_DWORD uses absolute byte offsets.
- S7-PLCSIM V16 or an online connection to the target CPU for verification.
Indirect Addressing on the S7-300
S7-300 supports three flavors of indirect addressing, all available in TIA Portal V16:
- Memory-indirect addressing – the operand address is a word or double-word in M, L, or DB that the CPU adds to the address register at runtime.
- Register-indirect (area-internal) – AR1 or AR2 holds the offset inside the currently opened area (DB, DI, M, …).
- Register-indirect (area-crossing) – the pointer loaded into AR1/AR2 includes the area identifier (16#85 for DB) and the DB number, allowing OPN DB and field reads to use one pointer.
STL instructions that consume these pointers include L DBD [AR1,P#0.0], OPN DB [AR2,P#0.0], L P#area, and LAR1 P#pointer. SCL exposes a much smaller surface but is sufficient for this problem: the standard compiler functions PEEK_BOOL, PEEK_BYTE, PEEK_WORD, and PEEK_DWORD read from a (area, dbNumber, byteOffset) triple, and POKE_BYTE / POKE_WORD / POKE_DWORD / POKE_BOOL write the same way.
Why STL Is Not Required
The S7-300 SCL compiler emits STL, so the underlying instruction mix is identical. The maintenance benefit of SCL is that the index loop compiles to register-indirect machine code, removing the need to maintain a jump chain. A correctly written SCL program is also easier to step through in the TIA Portal debugger, because the variable watch reflects the loop iterator and the intermediate sum without manual register monitoring.
Method 1 – SCL with PEEK_DWORD
The cleanest implementation uses a single FOR loop. PEEK_DWORD returns a 32-bit value; the result is cast to DINT for arithmetic, then summed into the accumulator. The complete function block is:
// FB_Sum30DBs – sum of 90 DINTs across 30 DBs
FUNCTION_BLOCK FB_Sum30DBs
VAR
iDB : INT; // 1..30
iSum : DINT; // accumulator
dVal : DINT; // scratch
END_VAR
BEGIN
iSum := 0;
FOR iDB := 1 TO 30 BY 1 DO
dVal := DINT_TO_DINT( PEEK_DWORD(
area := BYTE#16#85, // DB area
dbNumber := WORD#iDB, // 1..30
byteOffset := DINT#100 )); // byte offset
iSum := iSum + dVal;
dVal := PEEK_DWORD(
area := BYTE#16#85,
dbNumber := WORD#iDB,
byteOffset := DINT#200 );
iSum := iSum + dVal;
dVal := PEEK_DWORD(
area := BYTE#16#85,
dbNumber := WORD#iDB,
byteOffset := DINT#300 );
iSum := iSum + dVal;
END_FOR;
END_FUNCTION_BLOCK
Area Constant Reference
| Constant (BYTE) | Area | Source |
|---|---|---|
| 16#81 | P / PE | Process inputs (periphery) |
| 16#82 | PA / PQ | Process outputs (periphery) |
| 16#83 | I / IE | Inputs (PII) |
| 16#84 | M | Bit memory |
| 16#85 | DB / DI | Data block (current or addressed by dbNumber) |
When area := 16#85, the dbNumber parameter selects the DB. The SCL compiler emits an internal OPN DB and a register-indirect load, then restores the previous OPN state on return. The call is therefore re-entrant; nested calls in different priority classes are safe as long as the CPU is S7-300 V3.x and the DB number is in the legal range 1..32 767.
Method 2 – SCL with POINTER and FOR Loop
Where the offset itself is dynamic (for example, when the byte offsets are configured in an HMI table or read from a recipe), a POINTER-based variant removes the hardcoded 100/200/300 values:
VAR CONSTANT
OFFSETS : ARRAY[1..3] OF DINT := [100, 200, 300];
END_VAR
VAR
iDB : INT;
iOff : INT;
pData : POINTER;
dVal : DINT;
iSum : DINT;
END_VAR
BEGIN
iSum := 0;
FOR iDB := 1 TO 30 BY 1 DO
FOR iOff := 1 TO 3 BY 1 DO
pData := P#DB[INT_TO_WORD(iDB)].DBX[OFFSETS[iOff]].0;
dVal := DWORD_TO_DINT( PEEK_DWORD(
area := BYTE#16#85,
dbNumber := WORD#iDB,
byteOffset := OFFSETS[iOff] ));
iSum := iSum + dVal;
END_FOR;
END_FOR;
END
^ operator is restricted on this platform. PEEK_* / POKE_* with the explicit (area, dbNumber, offset) signature is the safe path. Do not rely on pData^ to compile on every S7-300 firmware revision.
Method 3 – STL Register-Indirect
STL is still legal in TIA Portal V16 for S7-300/400. The cleanest mapping for the same task uses one AR register and a memory word holding the DB number:
// FC_Sum30DBs_STL – S7-300 STL implementation
// Output: SUM (DINT) in local word 0
// Temp: iDB (INT) in local word 2
L 0
T #iSum
L 1
T #iDB
NEXT: NOP 0
OPN DB [#iDB] // Open DB by number from MW
L DBD 100
L DBD 200
+D
L DBD 300
+D
L #iSum
+D
T #iSum
L #iDB
+ 1
T #iDB
L 30
>=I
JC END
JU NEXT
END: NOP 0
To use an area-crossing pointer instead of a memory word – the historical Siemens-recommended pattern – encode the area and DB number into the pointer and let OPN DB [AR1,P#0.0] do the rest:
LAR1 P##DB_TPL // load template pointer
L 1
T #iDB
NEXT: NOP 0
OPN DB [AR1,P#0.0]
L DBD 100
L DBD 200
+D
L DBD 300
+D
L #iSum
+D
T #iSum
+AR1 P#1.0 // pointer offset = 1 byte per DB number
L #iDB
+ 1
T #iDB
L 30
>=I
JC END
JU NEXT
END: NOP 0
DB_TPL: P#DB1.DBX 0.0 // template; AR1 indexes over the DB byte
The area-crossing pointer approach is what Siemens documented in the original S7-300/400 STL handbooks; it eliminates the OPN DB cycle by encoding the area and DB number into the pointer. The maintenance cost is the template pointer and the loop index discipline.
Selection Guide
| Criterion | SCL + PEEK_DWORD | STL register-indirect |
|---|---|---|
| Code length | ≈25 lines | ≈30 lines |
| Step-through debug | High (variable watch) | Low (STL monitor only) |
| Compiler output | PEEK_DWORD expands to L DBD [AR1,P#0.0] | Direct emit, one less call |
| Re-entrancy | Yes (compiler manages OPN state) | No (OPN DB is global) |
| Readability for non-STL users | High | Low |
| CPU cycle cost on CPU 315-2 PN/DP | ≈12 µs per call | ≈9 µs per iteration |
For 30 iterations, the cycle-time difference is in the order of 100 µs – negligible against a typical OB1 scan of 5–20 ms. Pick SCL unless the CPU is at the watchdog limit and the surplus is needed elsewhere.
Data Block Requirements
For PEEK_DWORD to read a DINT at a precise byte offset, the DB must use non-optimized access. The path is:
- Right-click the DB in the project tree → Properties → Attributes.
- Confirm that "Optimized block access" is unchecked. (S7-300/400 only ever supported the non-optimized model; the checkbox exists for compatibility checking when the DB is shared with an S7-1500 station.)
- Compile and download.
If the DBs were created from a UDT or imported from an S7-1500 export, they may have the optimized flag set; TIA Portal V16 will refuse to compile PEEK calls against optimized DBs on S7-300 and the SCL editor will flag the call site. Convert each DB to non-optimized in bulk by selecting them all in the project tree, opening Properties, and clearing the checkbox – the structure, initial values, and download slots are preserved.
Overflow and Numeric Range
DINT range is −2 147 483 648 .. +2 147 483 647. Ninety values near the extremes push the sum to roughly ±1.9 × 10¹¹, which fits. If the per-DB values come from a 16-bit ADC (range −32 768 .. +32 767) the sum is bounded at 30 × 3 × 32 768 = 2 949 120, well within DINT. If the values come from a 32-bit counter or process accumulator, the bound is tight and the LREAL variant below is the safer choice:
VAR
rSum : LREAL; // 64-bit float accumulator
END_VAR
BEGIN
rSum := 0.0;
FOR iDB := 1 TO 30 BY 1 DO
rSum := rSum + DINT_TO_LREAL(
PEEK_DWORD(BYTE#16#85, WORD#iDB, DINT#100) ) +
DINT_TO_LREAL(
PEEK_DWORD(BYTE#16#85, WORD#iDB, DINT#200) ) +
DINT_TO_LREAL(
PEEK_DWORD(BYTE#16#85, WORD#iDB, DINT#300) );
END_FOR;
END
LREAL is supported on every S7-300 CPU V3.x but with no hardware FPU – the runtime cost is roughly 6–10 µs per floating-point operation, still acceptable for a 30-iteration loop. If the integer path is preserved, use DWORD_TO_DINT only when the source can be negative; the bit pattern is preserved by PEEK_DWORD and the cast is a no-op at the bit level.
Edge Cases and Pitfalls
DB Number Outside the Project
PEEK_DWORD does not validate that the DB exists in the S7-300 online program. On a real CPU, reading a non-existent DB raises OB121 (programming error) and the CPU goes to STOP if OB121 is not loaded. Defensive coding wraps the call with a try-catch equivalent – S7-300/400 SCL does not have try/catch, so the workaround is to verify the DB number against a constant array of valid DBs before the call, or to load a stub OB121 that increments a non-retentive error counter and returns.
OPN DB Side Effects
PEEK_DWORD opens and closes the DB internally. The OPN state seen by the caller is preserved, but if the caller is running in OB1 and an interrupt OB (OB35, OB82) executes the same FC in parallel, the two will interleave OPN states. Mitigations: keep PEEK in OB1 only, or duplicate the FC for the high-priority OB and add a global "in use" semaphore (set/reset on a BOOL in M).
Watchdog on Slow CPUs
CPU 312 (the smallest S7-300) executes a single PEEK_DWORD call in approximately 35 µs. A 30 × 3 = 90 call sequence plus the loop overhead is ≈ 3.2 ms. The default OB1 watchdog on CPU 312 is 150 ms, so a single call is safe. If the FC is invoked from a 10 ms cyclic OB (OB35), the watchdog must be re-checked; on CPU 312 the budget is tight and the project will require either a faster CPU or a lower call frequency. The cycle-time table for the relevant CPUs:
| CPU | Bit op (µs) | Word op (µs) | DINT fixed-point (µs) | Default OB1 watchdog |
|---|---|---|---|---|
| CPU 312 | 0.2 | 0.4 | 5 | 150 ms |
| CPU 314 | 0.1 | 0.2 | 2 | 150 ms |
| CPU 315-2 PN/DP | 0.05 | 0.1 | 0.8 | 150 ms |
| CPU 317-2 PN/DP | 0.025 | 0.05 | 0.4 | 150 ms |
| CPU 319-3 PN/DP | 0.01 | 0.02 | 0.02 | 150 ms |
Byte-Order on PEEK_DWORD
PEEK_DWORD reads in the S7-300 byte order. The result is a DWORD that the CPU treats as a 32-bit integer. Cast to DINT for signed arithmetic; the bit pattern is preserved.
Compound Operators
The S7-300/400 SCL compiler does not support +=, -=, *=, or /=. Use the long form iSum := iSum + dVal;. The long form compiles on every S7-300 CPU firmware V3.x and avoids the type-coercion rules that newer SCL versions apply to the compound form.
Verification Procedure
- Open PLCSIM V16 and start the project with the FB_Sum30DBs instance called from OB1.
- Open a watch table, force DB1.DBD100 = 1 000 000, DB15.DBD200 = 2 147 483 647 (max DINT), DB30.DBD300 = −2 147 483 648 (min DINT), and the remaining 87 DBDs = 0.
- Single-scan OB1 (PLCSIM: Execute → "Single scan") so the loop runs exactly once.
- Read iSum from the instance DB. Expected: 1 000 000 + 2 147 483 647 − 2 147 483 648 = 999 999.
- Repeat the test with all 90 DBDs = 1 000 000. Expected: 90 000 000.
- On a live CPU, use "Monitor / Modify" with the watch table and tick "Update" every 200 ms to see the running sum.
Online diagnostics that confirm the PEEK path is healthy:
- SF (System Fault) LED off – no OB121 / OB122 loaded.
- Diagnostic buffer entry "No faults" after the test.
- The instance DB shows iSum equal to the expected scalar.
HMI Display of the Result
Once iSum is updated, an HMI tag is required to surface it on a WinCC (TIA) panel. Configure the tag in the HMI tags table with the following parameters:
| Parameter | Value |
|---|---|
| Name | Tag_iSum |
| Connection | S7-300/400 (default for the configured PN interface) |
| PLC tag | FB_Sum30DBs_DB.iSum (instance DB of FB_Sum30DBs) |
| Data type | DInt (32-bit signed) |
| Acquisition mode | Cyclic, 500 ms |
| Display | Decimal, signed, 0 fractional digits |
For trending, add a tag log with a 1 s acquisition cycle and a 24 h retention; the PEEK loop runs on every OB1 scan, so the 1 s log rate avoids redundant data without missing peak values.
Alternative: ARRAY of UDT Instances
If the 30 DBs were created as a side effect of an early design choice, the architectural fix is to consolidate them into a single DB with an array of a UDT:
TYPE UDT_Meas
STRUCT
ValueAt100 : DINT; // offset 0 inside the struct
ValueAt200 : DINT; // offset 4
ValueAt300 : DINT; // offset 8
// ...
END_STRUCT
END_TYPE
DATA_BLOCK DB_Sum
STRUCT
Channels : ARRAY[1..30] OF UDT_Meas;
Total : DINT;
END_STRUCT
END_DATA_BLOCK
With this layout, the sum is a 30-iteration loop over DB_Sum.Channels[i].ValueAt100 + .ValueAt200 + .ValueAt300, no PEEK needed, no absolute offsets, and the absolute-addressing flag is no longer required. The PEEK pattern is the right answer when the 30 DBs already exist and cannot be consolidated, which is the situation that motivated the original question.
Migration Note: S7-1500 Path
S7-1500 SCL accepts the same PEEK function with a tighter type system, but the recommended approach on that platform is the symbolic slice access. The 30-DB problem reduces to a single FOR loop over an ARRAY of PLC data types, and PEEK is reserved for the rare case of accessing a DB whose structure is not known to the compiler (for example, a hand-off DB populated by an external tool). If the project is on the upgrade path, plan the migration of the 30 DBs to a single array-DB in the same window as the controller change. The TIA Portal V16 project migrator preserves the 30 DBs and converts the PEEK calls verbatim, but the symbolic access path is the long-term target.
Why does PEEK_DWORD need a non-optimized DB on S7-300?
PEEK_DWORD addresses memory by absolute byte offset, and S7-300 only supports the absolute-addressing (non-optimized) DB model. Optimized access with symbolic-only addressing was introduced with S7-1500 and is not available on the S7-300 CPU family; the PEEK call cannot be compiled against an optimized block even if the editor does not flag it.
Can I use PEEK on DB numbers above 255?
Yes. PEEK takes a WORD for the dbNumber parameter, so the legal range is 1 to 65 535. The S7-300 CPU family supports DB numbers 1..32 767 in standard mode. Confirm the bound in the CPU's technical data sheet (the upper bound varies by work memory size, but 32 767 is the practical ceiling for V3.x firmware).
Does the SCL FOR loop generate STL under the hood?
Yes. The S7-300 SCL compiler emits STL; the FOR loop becomes a counter in the local data area, an increment / compare / conditional branch, and a PEEK_DWORD call per array index. The compiled STL is functionally equivalent to a hand-written register-indirect loop and runs in the same number of CPU cycles, plus the call/return overhead of the PEEK function block.
What happens if a DB does not exist in the CPU?
PEEK_DWORD does not validate DB presence. The CPU raises OB121 (programming error) at the first instruction that addresses the missing DB. If OB121 is not loaded, the CPU goes to STOP. Load a stub OB121 in the project tree to keep the CPU in RUN while the data is filled in, and have the OB increment a non-retentive error counter in the instance DB so the diagnostic buffer reflects the misconfiguration.
Is the "+=" operator available in S7-300 SCL?
No. The compound assignment operators (+=, -=, *=, /=) are S7-1500 SCL additions and are not in the S7-300/400 SCL compiler. Use the long form: iSum := iSum + dVal;. The long form compiles on every S7-300 CPU firmware V3.x and avoids the implicit-type-conversion rules that newer SCL versions apply to the compound form.