S7 Cycle Time Exceed: Resolving LOOP STL Watchdog Timeout in S7-300/400 CPUs
A Cycle Time Exceed entry in the S7 diagnostic buffer is the PLC's watchdog fault mechanism detecting that OB 1 ran longer than the configured maximum cycle monitoring time. On S7-300 and S7-400 CPUs, the fault is logged with event ID 0x2522 (cycle time exceeded) or 0x3581 (watchdog timer overflow), and the CPU goes to STOP unless the time-error OB (OB 80) is loaded. In STL programs the root cause is frequently a misuse of the LOOP instruction combined with indirect register addressing, where the loop counter is not reloaded before the next LOOP is reached, the loop count is far larger than the time budget allows, or a slow system function such as SFC 1 (RD_SYS_T) is called inside the loop body.
This reference covers the mechanics of the cycle watchdog on S7-300/S7-400, the specific failure modes of the LOOP instruction in STL, and a field-proven remediation pattern including OB 80 installation, watchdog retuning, and loop segmentation.
1. Problem Definition: What "Cycle Time Exceed" Actually Means
Every S7-300/400 CPU runs an internal hardware/software timer called the cycle monitoring time (German: Zyklusüberwachungszeit). It is the maximum time allowed for one complete execution of OB 1, including all FCs/FBs called from it. The default value depends on the CPU but is typically 150 ms or 6000 ms. The timer starts at the beginning of each OB 1 pass and is reset on completion.
When the OB 1 pass does not complete within the configured time, the operating system:
- Logs the event in the diagnostic buffer with ID
0x2522(CPU 31x/41x) or0x3581. - Attempts to call
OB 80(Time-Error OB). If OB 80 exists, the program continues in OB 80 and may attempt a controlled recovery. If OB 80 is not loaded, the CPU goes to STOP with the SF (System Fault) LED on and the diagnostic buffer entry "Cycle time exceeded". - Increments internal fault counters accessible via
SZL 0x0131andSZL 0x0222.
The maximum cycle time before the CPU forces STOP, even if OB 80 is present, is twice the configured cycle monitoring time. This is a hard upper limit in firmware and cannot be increased by the user.
Authoritative reference: TIA Portal: Cycle time and maximum cycle time (S7-1500) (the S7-1500 documentation is the most current public reference for the same watchdog model used on S7-300/400).
2. The Reference Code and Why It Stops the CPU
The source STL block is reproduced and annotated below. The fault is not in any single line; it is a combination of three issues acting together.
L 100 // Load constant 100 into ACCU1
T #Cnt // #Cnt := 100 (loop iterations)
L2: NOP 0
L P#0.0 // Pointer start
LAR1 // AR1 := P#0.0
OPN DB400 // Open DB400 in DB register
OPNDI DB401 // Open DI (instance) DB401 in DI register
L DBW[AR1,P#0.0] // Load word from DB400.W[AR1]
T DIW[AR1,P#0.0] // Transfer to DI401.W[AR1]
A M 100.0
JCN L1 // If M100.0 == 0, skip timestamp block
CALL RD_SYS_T // SFC 1 — read system clock (DATE_AND_TIME)
date_type := Date_And_Time
RET_VAL := MW250
OUT := #Time // LD4..LD12 of #Time receive TOD
L MW200
SLD 3
LAR2 // AR2 := MW200 shifted left 3 (pointer)
L %LD4
OPNDI DB404
T DID[ AR2 , P#0.0 ]
L %LD8
T DID[ AR2 , P#4.0 ]
L1: +AR1 P#2.0 // AR1 := AR1 + P#2.0 (next word)
L #Cnt // <-- ACCU1 := #Cnt (read counter)
LOOP L2 // Decrement ACCU1, jump to L2 if not zero
// *** FAULT SITE ***
2.1 Issue 1 — Counter Reload After the Jump Target
The LOOP instruction performs two operations atomically: (a) it decrements ACCU1-L (low word of ACCU1) by 1, and (b) it jumps to the label if the result is not zero. It does not touch ACCU1-H, and it does not reload the counter from #Cnt. On the second pass through the loop, the L #Cnt instruction immediately before the LOOP re-reads #Cnt into ACCU1, so the counter is properly initialized for the decrement — that part of the code is actually correct.
The real defect is hidden one line above the first iteration. The line L 100 / T #Cnt is a one-time initialization placed before the loop body. After the first successful completion of the loop, the value 100 lives only in the local variable #Cnt. This is fine — unless #Cnt is a TEMP variable. In S7-300/400 STL, TEMP variables are undefined at the start of OB 1 and at the start of any FC/FB invocation. On a warm restart or after a STOP → RUN transition, the initialization L 100 / T #Cnt will run again and the program will work once. But on the first pass after the watchdog STOP and subsequent RUN, if the initialization is in an FC that is called multiple times in the same scan or in a path that the compiler can hoist, #Cnt can hold residual data from a previous scan and yield an enormous iteration count, blowing the watchdog on the very first OB 1 pass.
Field symptom: the CPU goes to STOP with "Cycle time exceeded" only on the first scan after a STOP/RUN transition, or only on the first scan after power-up, but runs fine in continuous mode once manually restarted.
2.2 Issue 2 — RD_SYS_T (SFC 1) Inside a 100-Iteration Loop
SFC 1 RD_SYS_T reads the CPU clock and returns a DATE_AND_TIME (8 bytes). On a typical S7-314/315/317 the call itself is fast (≈ 30–80 µs), but it is not free, and on older S7-300 CPUs with firmware 1.x it can take longer due to a documented firmware issue around indirect access to the TOD area on the same scan. Combined with the indirect DID[AR2,P#0.0] writes, the per-iteration cost can approach 150–250 µs, which means 100 iterations consume 15–25 ms — acceptable in isolation, but unacceptable if the loop is invoked multiple times per scan or if the watchdog was tightened (e.g. to 50 ms).
2.3 Issue 3 — Old Firmware (1.x) and the LOOP Instruction
Very old S7-300 CPUs (e.g. 6ES7 314-1AE10-0AB0 with FW V1.x, 6ES7 312-1AD10-0AB0) shipped with a firmware bug where the LOOP instruction, when the target label was more than ±127 statements away or when the loop body contained certain combinations of OPN/OPNDI and indirect register operations, could mis-decrement the counter. The fix was delivered in FW V2.x and later service packs. Updating the firmware and, where impossible, replacing the LOOP with an equivalent DEC+JZ construct resolves the issue.
3. Diagnostic Buffer Entries and Their Meaning
| Event ID | Meaning | CPU behavior | Remediation owner |
|---|---|---|---|
| 0x2522 | Cycle time exceeded (OB 1 overrun) | Call OB 80, else STOP | Program structure / loop logic |
| 0x2523 | OB request error (e.g. OB 80 missing) | STOP | Load OB 80 / OB 121 / OB 122 |
| 0x3581 | Watchdog time overflow (firmware internal) | STOP, no OB call | Reduce loop work; retune watchdog |
| 0x3570 | Firmware / hardware defect suspected | STOP | Update firmware; replace CPU |
| 0x4301 | STOP due to mode selector | Informational | No action |
To read the diagnostic buffer in STEP 7: PLC → Diagnostics/Settings → Diagnostic Buffer. In TIA Portal: Online & Diagnostics → Diagnostics → Diagnostic buffer. Capture the timestamp of the last 3 entries preceding the STOP; the pattern (e.g. "cycle time exceeded at 14:22:01.250") tells you whether the fault is a single runaway loop or a recurring near-miss.
4. Root-Cause Decision Matrix
| Symptom | Most likely cause | Confirm by | Fix |
|---|---|---|---|
| STOP on first scan after power-up only | Uninitialized TEMP counter | Watch #Cnt with VAT online | Move initialization to OB 100 or use STAT |
| STOP only when M100.0 = 1 | SFC 1 inside loop too slow | Remove CALL RD_SYS_T; re-test | Capture TOD once per scan, not per iter |
| STOP on S7-300 FW 1.x, fine on FW 2.x | LOOP firmware bug | Cross-test with newer CPU | Replace LOOP with DEC/JZ or upgrade FW |
| STOP on PLC with very large #Cnt | Loop iterations exceed time budget | Measure cycle time in OB 1 | Segment loop across scans |
| STOP on every scan, regardless of #Cnt | Infinite loop (counter never reaches 0) | Online monitor ACCU1-L before LOOP | Trace counter initial value |
5. Step-by-Step Remediation
5.1 Install OB 80 (Time-Error OB) — Always
Even if you intend to eliminate the cause, loading OB 80 turns a hard STOP into a recoverable runtime error. The CPU calls OB 80, gives the program a chance to set outputs to safe states, log the event, and continue.
- In STEP 7 / TIA Portal, add a new Organization Block.
- Select OB 80 — Time error.
- Inside OB 80, write the diagnostic information to a flag word (e.g.
MW 500) and set a non-recoverable bit so the user sees the event in HMI. Do not attempt to "fix and continue" the loop inside OB 80 — that is the application programmer's job, not the fault handler's. - Download to the CPU in STOP mode. OB 80 must be present at startup.
5.2 Fix the LOOP Counter Pattern
Replace the original initialization with one of the two patterns below. The first uses a STAT variable in a surrounding FB (recommended for cyclic use). The second uses a counter in MW and an explicit DEC + JZ to avoid the FW 1.x LOOP bug.
Pattern A — Surrounding FB with STAT counter (preferred):
FUNCTION_BLOCK FB 100
VAR
Cnt : INT; // STAT — preserved across OB 1 calls
FirstScan : BOOL; // STAT — edge detection
END_VAR
BEGIN
IF NOT #FirstScan THEN
#Cnt := 100;
#FirstScan := TRUE;
END_IF;
L #Cnt
T MW 200 // shadow to ACCU1
L2: NOP 0
L P#0.0
LAR1
// ... (loop body identical to original) ...
L1: +AR1 P#2.0
L #Cnt
LOOP L2
END_FUNCTION_BLOCK
Pattern B — DEC + JZ replacement (firmware 1.x safe):
L 100
T MW 200 // counter in MW 200
L2: NOP 0
// ... (loop body, NO CALL RD_SYS_T inside) ...
L1: +AR1 P#2.0
L MW 200
DEC // ACCU1-L := ACCU1-L - 1
T MW 200 // store back
JPZ L2 // Jump if result > 0
The JPZ jump is available on every S7-300/400 CPU and is immune to the LOOP firmware bug because the comparison and decrement are explicit.
5.3 Move RD_SYS_T Out of the Loop
Read the system clock once at the top of OB 1, store the result in a DATE_AND_TIME in a global DB (e.g. DB 500.DBD 0), and reference that local copy inside the loop:
// In OB 1, before the FB call:
CALL RD_SYS_T
date_type := DB500.TOD_Time // DATE_AND_TIME variable
RET_VAL := MW 250
// Then call FB 100, which uses DB500.TOD_Time internally.
This reduces the per-iteration cost to a few microseconds (a L + T from the DB) and removes the only slow SFC call from the critical loop path.
5.4 Segment the Loop Across Scans
If 100 iterations of the body genuinely take 80 ms and the watchdog is 100 ms, the loop fits in one scan but is close to the limit. If you need 500 iterations, segment the loop into 5 passes of 100 each, gated by a one-hot mask in MW that advances one bit per scan:
// At end of OB 1:
L MW 300 // pass counter / mask
INC 1
T MW 300
L W#16#0020 // bit 5
AW // mask: bit 5 set on 6th pass
JZ L_next
L 0
T MW 300 // reset mask
// trigger the 6th and final batch in next scan
A more maintainable approach is to use a cyclic interrupt OB (e.g. OB 35 with a 100 ms period) for batch work that is not time-critical. The cycle time of OB 1 then stays bounded and predictable.
5.5 Reconfigure the Cycle Monitoring Time
Only as a secondary measure, after the loop is fixed, adjust the cycle monitoring time so the new worst-case OB 1 scan still leaves a 25–50 % margin.
- In STEP 7: HW Config → CPU Properties → Cycle / Clock Memory. Set Cycle monitoring time to a value in ms. Maximum cycle time (the hard upper limit) is automatically 2× the monitoring time.
- In TIA Portal: Device Configuration → CPU Properties → Cycle. The minimum allowed is typically 1 ms; the maximum is 6 000 000 ms depending on CPU.
- Recommended: set the monitoring time to 2× the measured worst-case scan time, never less than 1.5×, and never above the process tolerance. A 100 ms watchdog for a packaging line that requires a 50 ms response is too tight; a 6 s watchdog for a high-speed press is too loose.
6. Verification Procedure
- Download the corrected FB and the new OB 80 to the CPU in STOP mode.
- Switch the CPU to RUN and observe the RUN LED. If the SF LED flashes, open the diagnostic buffer and read the most recent OB 80 call entry — it will show the OB number and the priority in
OB80_FLT_ID. - In a VAT (Variable Table) or watch table, monitor
MW 200(the counter) andOB1_PREV_CYCLE (the previous OB 1 cycle time, in ms, readable from SZL 0x0033 in some CPUs or directly from theOB1_PRIO/time stamps). - Force M 100.0 = 1 to drive the worst-case path (timestamp inside loop). Confirm the cycle time stays below 80 % of the configured monitoring time.
- Power-cycle the CPU. Confirm the first scan does not fault. This validates Pattern A's STAT-based initialization.
- Run for at least 1 hour with M 100.0 = 1 and again with M 100.0 = 0. No diagnostic buffer entry for cycle time exceeded should appear.
7. Edge Cases and Field Cautions
- OB 80 cannot fix an infinite loop. OB 80 runs at the same priority as OB 1 and is itself subject to the watchdog. If the loop is truly infinite, the second time the watchdog fires the CPU will STOP regardless of OB 80.
-
LOOP is signed. The counter is interpreted as a 16-bit signed integer. If the initial value is > 32 767, the counter wraps and the loop terminates early, producing incorrect data without raising any fault. Always keep
#Cnt≤ 32 767 or use a 32-bit counter pattern withLOOPdisabled. -
OPNDI in a loop re-opens the instance DB every iteration. On a CPU 314 with FW 2.x this is ≈ 8 µs; on a CPU 312 with FW 1.x it can be 20 µs. Move the
OPNDI DB401outside the loop if the DB does not change inside. -
Calling SFCs that internally use interrupts (SFC 6, SFC 13, SFC 51) inside a tight loop is a common cause of unexplained cycle overruns. Use
WRIT_ERRor remove them from the loop. -
Cross-reference impact. Changing
#Cntfrom TEMP to STAT may have unintended side effects in other FBs that use the same FB instance; review the cross-reference list before downloading.
8. SFC 1 (RD_SYS_T) Timing Reference
| CPU | Firmware | Approx. SFC 1 execution time | Notes |
|---|---|---|---|
| CPU 312 IFM (6ES7 312-5AC00) | V1.x | ~120 µs | LOOP bug; replace with DEC/JZ |
| CPU 314 (6ES7 314-1AE10) | V1.x | ~90 µs | LOOP bug; replace with DEC/JZ |
| CPU 314 (6ES7 314-1AF10) | V2.x | ~45 µs | LOOP OK |
| CPU 315-2 DP (6ES7 315-2AF00) | V2.x | ~40 µs | LOOP OK |
| CPU 317-2 DP (6ES7 317-2AJ00) | V2.x | ~30 µs | LOOP OK |
Numbers are representative of typical installations; actual values depend on the use of DATE_AND_TIME format conversions, the number of TOD bytes copied, and the surrounding program. Always measure on the target CPU with the diagnostic buffer and a VAT.
9. Commissioning Checklist
- [ ] OB 80 generated, downloaded, and verified to be called on a forced overrun.
- [ ] Loop counter promoted to STAT in a wrapping FB (or MW 200 with DEC/JZ).
- [ ] RD_SYS_T called once per scan, outside the loop, into a global DB.
- [ ] OB 1 worst-case cycle time measured and below 80 % of the monitoring time.
- [ ] Cycle monitoring time retuned to 1.5–2× the measured worst case.
- [ ] Power-cycle and warm-restart tests passed without STOP.
- [ ] Diagnostic buffer clean of cycle time exceed events for ≥ 1 hour under both M 100.0 states.
- [ ] Cross-references reviewed; no other FB instance unintentionally affected.
10. Frequently Asked Questions
What diagnostic buffer event ID indicates a cycle time exceed on S7-300/400?
Event ID 0x2522 indicates OB 1 exceeded the configured cycle monitoring time. Event 0x3581 indicates a watchdog overflow internal to firmware. If OB 80 is not loaded, the CPU goes to STOP. If OB 80 is loaded, the CPU calls OB 80 and the program can continue.
Can I just raise the cycle monitoring time to stop the fault?
Yes, but it is a workaround, not a fix. The configured monitoring time can be raised in HW Config (STEP 7) or Device Configuration (TIA Portal), up to 6 000 000 ms depending on CPU. The hard upper limit is twice the monitoring time and cannot be exceeded. Raising the watchdog masks the real defect and will resurface as a process or safety problem.
Why does the LOOP instruction work in some scans but not others?
On S7-300 CPUs with firmware V1.x, the LOOP instruction had a documented bug that mis-decremented the counter under specific code patterns (indirect register access, OPN/OPNDI combinations, large branch distances). On later firmware, the defect is fixed. If you cannot update firmware, replace LOOP with an explicit DEC + JPZ/JZ construct.
Is it safe to call SFC 1 (RD_SYS_T) inside a 100-iteration loop?
On most S7-300 CPUs with FW 2.x, SFC 1 takes 30–80 µs per call, so 100 calls add 3–8 ms — usually acceptable. On FW 1.x the call can take 90–120 µs, and combined with indirect TOD writes the per-iteration cost can approach 250 µs, so 100 iterations could consume 15–25 ms. Move SFC 1 out of the loop and call it once per scan into a global DB to eliminate the variability.
What is the maximum value the LOOP counter can hold?
LOOP treats ACCU1-L as a 16-bit signed integer, so the practical range is 0 to 32 767 iterations. Negative or zero counters cause the loop body to be skipped (0) or to run once with a wrap (-1 wraps to 0 on decrement, exits immediately). For larger iteration counts, use a 32-bit counter and a manual DEC/JZ pattern.
Does loading OB 80 prevent the CPU from ever going to STOP on cycle overrun?
No. OB 80 is called on the first watchdog event, but the cycle time hard limit is twice the configured monitoring time. If the overrun exceeds that, the CPU enters STOP regardless of OB 80. Treat OB 80 as a controlled-recovery mechanism, not a watchdog-disable switch.