Overview
The LOOP instruction is a classic Statement List (STL) jump helper for the SIMATIC S7-300 CPU family. It is part of the "Basic Instructions" set in TIA Portal V13 Update 4 and remains available in every subsequent release up to and including the current TIA Portal version. The instruction is short, but its semantics — using the accumulator as both operand and decrement target — make it the source of recurring pitfalls: missed counter reloads, infinite loops, and most importantly, watchdog timeouts that force the CPU into STOP.
This reference rebuilds the LOOP instruction from first principles, demonstrates a complete working STL pattern for a CPU 314C-2 PN, calculates the maximum safe iteration count against the OB1 watchdog, and provides the equivalent SCL FOR ... END_FOR implementation that most programmers use today. The article also covers compatibility behavior when migrating projects written in V12 SP1 or earlier into V13 and above.
Prerequisites
- STEP 7 V13 Update 4 or newer installed in TIA Portal (V13 SP1 Update 6 was the last widely-deployed V13 branch; current projects should move to V16/V17/V18). See the Compatibility of PLC programs from versions prior to V13 SP1 page in the official TIA Portal V21 readme for migration caveats.
- An S7-300 station created with at least one of: CPU 312, CPU 314, CPU 315-2 PN/DP, CPU 317-2 PN/DP, CPU 319-3 PN/DP.
- A working STL program block (OB1, FB, or FC). STL must be the active editor language for the block. To switch: right-click the block → Properties → General → "Created in" → STL.
- Online connection (PROFINET, PROFIBUS, or MPI) for verification, or PLCSIM V13 SP1+ for offline testing.
STL LOOP Instruction Mechanics
The LOOP <label> instruction performs three operations in a single cycle:
- Decrement ACCU1-L (the low word of Accumulator 1) by 1.
- Test the new value: if ACCU1-L > 0, jump to the specified label; if ACCU1-L = 0 (or, with wrap-around, becomes negative when interpreted as signed INT), fall through to the next network.
- No implicit save of ACCU1. If you need the decremented value later in the cycle you must move it back to a memory word yourself.
LOOP does not touch ACCU2, the status word flags, or AR1/AR2. It is therefore safe to use inside arithmetic chains where ACCU2 still holds the previous result.
Status word flags touched by LOOP
| Bit | Symbol | Effect |
|---|---|---|
| CC 1 | — | Set if decrement resulted in a borrow / underflow (i.e., counter started at 0 and was decremented to 0xFFFF / -1). |
| CC 0 | — | Set when ACCU1-L == 0 after decrement (i.e., the loop just finished). |
| OV, OS | — | Unchanged by LOOP. |
Because CC 0 is set exactly when the loop terminates, you can use it to detect completion without an extra compare:
L MW 30 // load loop counter
NEXT: T MW 30 // (optional) store decremented value
... user code ...
L MW 30 // reload counter
LOOP NEXT // decrement, jump while > 0
JC END_LOOP // CC0 = 1 means counter just hit zero
Working Memory Counter Behavior
The most frequent programmer error with LOOP is forgetting the reload. The instruction does not preserve ACCU1-L across the loop body — any instruction that writes to ACCU1 (L, L P#, L DBLG, etc.) overwrites it. There are two robust patterns.
Pattern A — reload from MW every iteration (safe)
L 100 // initial iteration count
T MW 30 // MW30 holds the counter
NEXT: L MW 30 // reload counter
LOOP NEXT // decrement, jump while > 0
// Fall-through: counter = 0
This pattern is the one shipped in the TIA Portal help. It is wasteful (a load every cycle) but always correct.
Pattern B — implicit accumulator carry (efficient)
L 100 // initial iteration count
NEXT: ... user code ...
TAK // swap ACCU1 <-> ACCU2 (preserves previous ACCU1 in ACCU2)
LOOP NEXT // decrements ACCU1 (the count), jumps if > 0
TAK // restore original ACCU1 from ACCU2
Pattern B works only if the user code inside the loop body never executes L on its own. It is fragile and not recommended for production code; mention it only when reviewing legacy programs.
Step-by-Step Implementation: 16-Iteration Moving Average
The classic loop use case on S7-300 is summing a fixed number of input words into an accumulator. The example below reads 16 consecutive process words from IW 100 .. IW 130 (using a computed offset) and stores the average in MW 200.
Block header
| Attribute | Value |
|---|---|
| Block | FC 100 "AvgCalc" |
| Language | STL |
| Author | (your name) |
| Family | Math |
| Version | 1.0 |
STL code
// FC100 - 16-sample moving average over IW100..IW130
// Counter in MW30 (loop iterations)
// Index in MW32 (byte offset, 0,2,4,...,30)
// Sum in MD40 (DWORD, accumulates up to 16 * 32767)
// Result in MW200 (word average)
L 0
T MD 40 // clear accumulator
L 0
T MW 32 // clear index
L 16 // number of samples
T MW 30 // MW30 = loop counter
LOOP_A: L MW 32 // load byte offset
SRD 1 // /2 -> word offset (0..15)
T MW 34
L IW 100 // base input word
+I // offset = base + word offset... but +I is 16-bit only
T MW 36 // address of current sample
L MW 36
L MW 34
+D
SLD 3 // convert to byte pointer
LAR1 // AR1 = pointer to current IW
L IW [AR1,P#0.0] // indirect load of sample
ITD // INT -> DINT
L MD 40
+D
T MD 40 // sum += sample
L MW 32
L 2
+I
T MW 32 // index += 2 (next word)
L MW 30
LOOP LOOP_A // decrement MW30, jump if > 0
// Counter just hit zero -- compute average
L MD 40
SRD 4 // /16
T MW 200 // MW200 = average
BE
Why this is 16 iterations, not 16,000
The above loop runs exactly 16 times. With a scan time of ~3 ms per iteration (memory-indirect load + ITD + +D + storage), total loop time ≈ 48 ms — well under the 150 ms OB1 watchdog on CPU 314C-2 PN. If you scaled this to 1,000 samples without changing anything else, the loop would take ~3,000 ms and the CPU would fault with SF LED on, BF possibly lit, diagnostic buffer entry "OB1 watchdog timeout" (event ID 16#4580).
Watchdog Timer Management
The OB1 watchdog on every S7-300 CPU is configurable in the CPU properties. Defaults by CPU:
| CPU | Order number (example) | Default OB1 watchdog | Maximum configurable |
|---|---|---|---|
| CPU 312 | 6ES7 312-1AE14-0AB0 | 150 ms | 6,000 ms |
| CPU 314C-2 PN/DP | 6ES7 314-6EH04-0AB0 | 150 ms | 6,000 ms |
| CPU 315-2 PN/DP | 6ES7 315-2EH14-0AB0 | 150 ms | 6,000 ms |
| CPU 317-2 PN/DP | 6ES7 317-2EK14-0AB0 | 150 ms | 6,000 ms |
| CPU 319-3 PN/DP | 6ES7 318-3EL01-0AB0 | 150 ms | 6,000 ms |
- 16#4580 — OB1 watchdog timeout (most common LOOP overrun symptom).
- 16#45xx — Other OB watchdog timeouts (OB35, OB40, etc.).
- 16#35xx — Communication fault during the loop (caused by stop).
Use this formula to verify that a loop is safe:
t_loop_max = (N_iterations × t_avg_instruction) × safety_factor
where:
N_iterations = integer count passed to LOOP
t_avg_instruction = measured scan time per iteration (use OB1 cycle in online diagnostics)
safety_factor = 0.7 (30 % headroom for asynchronous OB interrupts)
Example: scan time per iteration = 8 µs, safety factor 0.7, watchdog 150 ms:
N_max = (0.150 s × 0.7) / 8 µs
= 0.105 / 0.000008
= 13,125 iterations
Round down to 13,000. Any larger and you risk a STOP event under heavy CIP/PN traffic.
SCL FOR Loop Alternative
For new code on S7-300 (firmware ≥ V2.x for the CPU 31xC series, or any 31x-2 PN/DP), use SCL. It compiles to identical STL, but the source is far easier to maintain and the compiler inserts the counter reload for you. The same moving average in SCL:
// FC101 "AvgCalc_SCL" - 16-sample moving average
// Uses FOR loop instead of LOOP
FUNCTION FC101 : VOID
VAR
sum : DINT; // accumulator
idx : INT; // word offset 0..15
sample : INT;
avg : WORD;
END_VAR
BEGIN
sum := 0;
FOR idx := 0 TO 15 DO
sample := WORD_TO_INT(%IW[100 + idx * 2]); // indirect read
sum := sum + INT_TO_DINT(sample);
END_FOR;
avg := DINT_TO_WORD(sum / 16);
%MW200 := avg;
END_FUNCTION
SCL FOR vs. STL LOOP — when to use which
| Criterion | STL LOOP | SCL FOR |
|---|---|---|
| CPU firmware required | Any S7-300 CPU (original 312/314/315/317). | S7-300 CPU with SCL support (any 31x-2 PN/DP, 319-3, or 31xC). |
| Watchdog safety | Manual — programmer must size the loop. | Compiler inserts safe counter; runtime identical to manual. |
| AT overlay | Not available — manual pointer math. | Supported via AT on any variable, including TEMP. |
| Code clarity | Low — easy to break counter. | High — explicit start, end, step. |
| Migration cost | None — already STL. | Block must be re-authored. |
| Performance (compiled) | ~identical. | ~identical. |
Rule of thumb: use SCL FOR for anything written after 2010; keep STL LOOP only when maintaining legacy code or when the block already exists in STL and budget forbids a rewrite.
Edge Cases and Common Errors
1. Counter initialized to 0
Loading 0 and calling LOOP immediately produces a single jump to the label with ACCU1-L = 0xFFFF (-1 as signed INT). The loop body executes once. CC 0 will not be set on exit because the loop "ran out" on the first decrement. Treat zero as "execute body once".
2. Counter wraps around unsigned
LOOP compares ACCU1-L > 0 using unsigned word arithmetic. Loading 0x0000, looping 65,536 times — each decrement underflows, so the jump condition remains true until 0xFFFF (effectively -1 wrapped). This is rarely useful. Always load a sensible positive integer.
3. Counter in DB vs. MW
Using a data-block word (e.g., DB1.DBW10) as the counter works, but every L DBW10 inside the loop generates two network instructions (open DB + load). For tight loops, prefer MW or a static variable in an FB instance DB.
4. LOOP inside LOOP (nested)
Legal but dangerous. Each nested level adds label complexity and watch the inner counter on every iteration of the outer loop. Maximum nesting depth for STL jumps is implementation-defined but stays safe at 3–4 levels.
5. Calling FC/FB inside the loop
Avoid calling FBs with their own instance DB inside a tight LOOP — the DB-open overhead can multiply the per-iteration cost 3–5×. Inline the logic or call the FC only every Nth iteration using MOD on the counter.
Migration Compatibility: V12 SP1 and Earlier
Projects authored in TIA Portal V12 SP1 (or older) and opened in V13 SP1 Update 4 or newer require an automatic conversion. The Compatibility of PLC programs from versions prior to V13 SP1 documentation lists the LOOP-relevant points:
- No code change is required for STL blocks that already use LOOP. The instruction's binary representation is identical across V12..V18.
- Watchdog defaults did not change on S7-300 between V12 and V13. CPU properties are preserved on conversion.
-
SCL projects that referenced the (deprecated in V12)
LOOPkeyword inside SCL source — this never existed; SCL usesFORonly. Confusion arises from the STL/SCL mix; verify the block language in the project tree. - Block consistency check: after conversion, run "Project → Compile all (rebuild all blocks)" to force a fresh STL generation. V13 SP1+ tightened the SCL-to-STL compiler; loops with non-constant bounds may now warn.
-
Library handling: if your project uses a global library containing FCs with LOOP, recompile the library in the new version before reusing — pointer offsets in
LAR1calls may shift by one byte on some V13 service packs.
Verification and Diagnostics
After loading the program, perform this sequence:
- Online → Monitor / Modify on the block. Step through the loop with single-scan breakpoints (S-Pause) to confirm each iteration decrements MW30.
- Watch MW30 in the variable table. After the loop it must read 0. If it reads anything else, the reload is missing.
- CPU → Online & Diagnostics → Cycle time: record OB1 last cycle. It must be < 80 % of the configured watchdog.
- Diagnostic buffer: clear it before download (Online → "Clear diagnostic buffer"). Run the program for 5 minutes. The only entries should be normal stop/start events. No 16#4580 entries indicates the loop is safe.
- Force a worst case: temporarily double the iteration count and run. If the CPU stays in RUN, halve the count and ship.
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| CPU goes to STOP immediately after first scan. | Loop count too high; OB1 watchdog expired (16#4580). | Reduce N or move loop to OB35 with a longer cycle. |
| Loop body never executes. | Initial counter loaded as 0 or negative. | Load a positive INT and verify with monitor. |
| Loop body executes once, then program continues with wrong data. | ACCU1 overwritten inside the loop body; counter never decremented. | Reload counter with L MW30 immediately before LOOP. |
| Average result is wrong on every cycle. | MW used both for counter and for result; they collide. | Allocate separate words for counter (MW30) and result (MW200). |
| Loop runs but PF (Programming error) LED lit. | Indirect pointer LAR1 not aligned to word boundary. |
Verify offset calculation produces even values; SLD 3 yields byte pointer. |
| SCL FOR compiles but runtime is slower than STL. | Compiler inserted defensive DBW checks. | Profile with SCL debugger; usually negligible for N < 1,000. |
Field-Commissioning Checklist
- ☐ Loop counter variable declared with unique symbolic name (e.g.,
"loop_i"in DB or MW table). - ☐ Initial load value > 0 and < calculated N_max from the watchdog formula.
- ☐ Reload (
L MW30) placed immediately aboveLOOP. - ☐ Watchdog headroom ≥ 30 % at peak load.
- ☐ Diagnostic buffer clean of 16#4580 for 60 minutes continuous run.
- ☐ Loop never executed from OB1 if N > 5,000 — move to OB35 or use cyclic interrupt OB.
- ☐ Backup of pre-conversion STL source retained for 90 days after any TIA Portal upgrade.
FAQ
What does the LOOP instruction actually do in an S7-300 CPU?
LOOP decrements the low word of Accumulator 1 (ACCU1-L) by 1 and jumps to a label while the result is greater than zero. It performs both the decrement and the conditional jump in a single 32-bit instruction, leaving ACCU2 unchanged.
Why does my S7-300 CPU go to STOP with SF on when using LOOP?
The most common cause is OB1 watchdog timeout, logged as diagnostic event 16#4580. Your loop iterations × per-instruction time exceed the configured OB1 watchdog (default 150 ms). Reduce the iteration count, move the loop to OB35, or extend the watchdog up to its 6,000 ms maximum.
Do I have to reload the loop counter before every LOOP call?
Yes, unless you are absolutely sure no instruction between the LOOP and its target label writes to ACCU1. The safe, canonical pattern is L MW30 immediately before LOOP label. This guarantees the counter is current on every iteration.
Is SCL FOR loop compatible with the S7-300 in TIA Portal V13?
Yes. Any S7-300 CPU with SCL support (CPU 31xC, 31x-2 PN/DP, 319-3 PN/DP) compiles SCL FOR ... END_FOR to the same STL output, including the implicit counter reload. It is the recommended replacement for legacy STL LOOP code.
Will my V12 SP1 project with LOOP blocks convert cleanly to V13 SP1 Update 4?
Yes. LOOP has stable binary representation across V12 through V18. Run "Project → Compile all (rebuild all blocks)" after the conversion to regenerate STL, and review the diagnostic buffer for any 16#4580 events during the first hour of run-time, since conversion may tighten compiler-generated watchdog checks around indirect pointers.