Reusing S7 SCL Timers Multi-Instance FB Pattern for Belt

David Krause11 min read
S7-1200SiemensTutorial / 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

Problem Statement: Monitoring 20 Elevator Belts with One Timer Block

A common S7 SCL question is: "Can a single timer instance drive 20 independent belt-rotation watchdogs?" The short answer is no. A TIMER or IEC timer instance is a binary state container — it can only hold one running value, one elapsed value, and one output bit at a time. Calling the same timer from twenty parallel logic branches creates scan-race conditions, residual values from the previous branch, and unpredictable STAT outputs.

This article walks through three field-proven patterns for reusing timer logic across many monitored devices:

  1. Multi-instance Function Block (SFB4 / TON) — the canonical Siemens answer.
  2. Counter-based emulation using OB35 100 ms pulses — the original poster's working solution.
  3. IEC_TON / TP inside a re-instantiable FB on S7-1200/1500 in TIA Portal — the modern equivalent.

All three patterns share one principle: the timer state must live in its own background DB (instance DB), one per monitored device, never shared.

Safety note: Elevator and conveyor belt watchdogs are safety-relevant. Use this logic only for status/indication. Hard-wired E-Stops, motor contactors, and certified safety relays (e.g., Sirius 3SK1) must remain the primary protective devices regardless of PLC timer logic.

Why a Single S5TIME / IEC Timer Cannot Be Reused

Each call to S_ODT, S_PULSE, S_PEXT, or S_ODTS writes to a fixed word-addressable timer word (T0..T2047 on S7-300/400, T0..T511 on S7-1200). The timer word contains the BCD-encoded time base, the time value, and the output bit. Two concurrent invocations of the same timer word overwrite each other every scan.

According to the SIMATIC S7-1200 manual on Timer Operation (IEC Timers), each IEC timer block (TP, TON, TOF, TONR) is a system function block (SFB3 / SFB4 / SFB5) that requires a dedicated instance DB holding the running elapsed time ET and the output Q. Reusing one instance DB is not supported and produces undefined behavior on overflow of the internal time accumulator.

Three documented options exist:

  1. Pass the timer as an in-out parameter and instantiate 20 separate instance DBs at the call site.
  2. Create a re-instantiable FB and instantiate it 20 times (multi-instance pattern).
  3. Replace the hardware/software timer with a software counter driven by a fixed clock OB.

Prerequisites

  • STEP 7 V5.x (S7-300/400) or TIA Portal V16+ (S7-1200/1500).
  • SCL compiler enabled (Options → Package Manager → SCL).
  • For pattern 1/2: at least 20 unused timer words in the system data, or 20 unused IEC timer instance DBs.
  • For pattern 3: OB35 (cyclic interrupt) configured to 100 ms in the CPU properties (Hardware → Cyclic Interrupts).
  • Familiarity with FB/DB creation and multi-instance capability of the CPU (any S7-300/400/1200/1500 supports multi-instances).

Pattern 1: Multi-Instance FB with SFB4 (S_ODTS / TON)

The cleanest pattern is to wrap the timer inside a re-instantiable FB. Each call to the FB automatically allocates a new instance DB holding the timer state.

FB definition (S7-300/400 SCL)

FUNCTION_BLOCK FB_BeltWatchdog
VAR_INPUT
  Run         : BOOL;   // motor running feedback
  Pulse       : BOOL;   // one pulse per motor revolution
  Preset_ms   : INT;    // timeout in milliseconds
END_VAR
VAR_OUTPUT
  Alarm       : BOOL;   // TRUE if no pulse within Preset_ms
END_VAR
VAR
  TonInst     : SFB4;   // IEC "TON" – multi-instance capable
  ET_ms       : INT;    // elapsed time in ms (sampled)
  xEdge       : BOOL;   // rising edge memory
END_VAR
BEGIN
  // TON: output Q high when IN has been TRUE for PT duration
  TonInst(IN := Run AND NOT Pulse,
          PT := DINT_TO_TIME(INT_TO_DINT(Preset_ms)),
          Q  := Alarm);
END_FUNCTION_BLOCK

OB1 call site

DATA_BLOCK DB_Belt_01 FB_BeltWatchdog;   BEGIN END_DATA_BLOCK;
DATA_BLOCK DB_Belt_02 FB_BeltWatchdog;   BEGIN END_DATA_BLOCK;
... (repeat 20 times) ...

// OB1 – cyclic
FB_BeltWatchdog.DB_Belt_01(Run := "Mot01_Run",  Pulse := "Mot01_RevPulse", Preset_ms := 8000);
FB_BeltWatchdog.DB_Belt_02(Run := "Mot02_Run",  Pulse := "Mot02_RevPulse", Preset_ms := 8000);
...

Each DB_Belt_NN is a true instance DB and contains its own SFB4 state. The timer word is allocated automatically by the SFB4 instance — no manual T-number management required. This is the method Siemens officially recommends in the SiePortal "Timer in SCL" thread and in the S7-1200/1500 system manual.

Multi-instance vs. separate instance DBs: S7-300/400/1200/1500 all support calling a single FB as a multi-instance inside another FB, so you can build FB_ElevatorBank containing 20 static FB_BeltWatchdog variables — only one IDB for the whole bank. This scales better than 20 separate DBs and is preferred for plants with hundreds of monitored devices.

Pattern 2: OB35 Counter Emulation (Original Working Solution)

When the CPU has no spare SFB4 instances, or you want a fully software-defined timer independent of T-numbers, replace the timer with a down-counter clocked by OB35. The original poster's FB_CHECKTURN_ELEVATOR uses exactly this technique. Cleaned up and ported to modern SCL:

OB35 configuration (100 ms)

Open Hardware → CPU Properties → Cyclic Interrupts. Set OB35 execution time to 100 ms. The OB35 priority (default 12) pre-empts OB1, guaranteeing the 100 ms tick is consistent regardless of OB1 scan time.

OB35 – clock flag generator

// OB35 (priority 12, 100 ms)
"Clock_100ms" := NOT "Clock_100ms";   // 50% duty, 100 ms period

FB_CHECKTURN_ELEVATOR (SCL, S7-300/400-compatible)

FUNCTION_BLOCK FB_CHECKTURN_ELEVATOR
VAR_INPUT
  PV        : WORD;    // preset in 100 ms units (e.g., 80 = 8 s)
  CV_I      : WORD;    // retained counter value (in)
  PULS      : BOOL;    // motor revolution pulse
  RUN       : BOOL;    // motor running feedback
  BITM_I    : BOOL;    // edge memory (retained)
END_VAR
VAR_OUTPUT
  STAT      : BOOL;    // alarm
  CV_O      : WORD;    // retained counter value (out)
  BITM_O    : BOOL;    // edge memory (out)
END_VAR
VAR
  CV        : INT;
  xEdge     : BOOL;
END_VAR
BEGIN
  // Load retained value or reset on stop / pulse
  IF NOT RUN OR PULS THEN
    CV := WORD_TO_INT(PV);
  ELSE
    CV := WORD_TO_INT(CV_I);
  END_IF;

  // 100 ms rising-edge detection
  xEdge  := "Clock_100ms" AND NOT BITM_I;
  BITM_O := "Clock_100ms";

  IF xEdge THEN
    IF RUN THEN CV := CV - 1; END_IF;
    IF CV < 0 THEN CV := 0; END_IF;
  END_IF;

  STAT := (CV = 0);
  CV_O := INT_TO_WORD(CV);
END_FUNCTION_BLOCK

Why the retained values matter

Because OB35 is a separate priority class, the FB's read-modify-write of CV must use the value persisted in the instance DB at the end of the last call. Inputs CV_I / BITM_I and outputs CV_O / BITM_O are passed by the caller so the values survive OB35 restarts. The example shown by the original poster implements exactly this retention through the instance DB.

Setting CV_O = CV_I on the same call does not work in a single-pass FB: the new value would be written before other instances read the old one, causing skew between belts. Always commit via the instance DB at the end of OB35, and read it at the start.

Pattern 3: Modern IEC Timer in TIA Portal (S7-1200/1500)

On S7-1200 firmware V4.0+ and S7-1500, the IEC timer blocks are first-class citizens of the SCL language. The cleanest reuse pattern uses the multi-instance FB approach with a system IEC timer:

FUNCTION_BLOCK FB_BeltWatchdog_1200
VAR_INPUT
  Run         : BOOL;
  Pulse       : BOOL;
  PresetTime  : TIME;   // e.g., T#8s
END_VAR
VAR_OUTPUT
  Alarm       : BOOL;
END_VAR
VAR
  Ton_Inst    : IEC_TIMER;   // TON, TP, TOF, or TONR
  Ton_DB      : IEC_TON_DB;  // background DB type, allocated by SCL
END_VAR
BEGIN
  Ton_Inst(IN := Run AND NOT Pulse,
           PT := PresetTime,
           Q  := Alarm);
END_FUNCTION_BLOCK

Compile and call the FB twenty times — TIA Portal automatically generates twenty distinct instance DBs of type FB_BeltWatchdog_1200, each containing its own IEC_TON_DB. No manual T-number bookkeeping, no OB35 needed, no CPU priority tuning.

Parameter Reference: Timer-Type Selection

Timer type Block Behavior Belt-watchdog fit
TP – pulse SFB3 / IEC_TP Q high for fixed PT, retriggerable Poor (does not latch)
TON – on-delay SFB4 / IEC_TON Q high after IN true for PT Best for this use case
TOF – off-delay SFB5 / IEC_TOF Q stays high PT after IN false Good alternative
TONR – retentive on-delay SFB6 / IEC_TONR Accumulates; reset via R input OK; needs explicit reset
S_ODT / S5TIME Classic BCD-encoded, max 9 990 s Legacy only
OB35 down-counter User code Counts 100 ms ticks, no SFB CPU without spare timers

Verification Procedure

  1. Offline compile. In STEP 7 / TIA Portal, compile the SCL source. Expect 0 errors. Warnings about "timer not used" should be ignored if the timer is hidden inside the FB.
  2. Cross-reference. Run PLC → Monitor/Modify → Cross References on the FB name. Confirm 20 unique instance DBs, each referencing exactly one timer word or one IEC_TON_DB.
  3. Online force PULS low. With the motor running, force PULS = FALSE on one belt. Verify that STAT transitions TRUE within PV × 100 ms (OB35 pattern) or PresetTime (IEC timer pattern).
  4. Online force PULS high. Verify that STAT clears on the next pulse and the counter reloads PV.
  5. Load distribution check. In OB35 mode, set PV to a known value (e.g., 50 = 5 s) and observe CV_O in the instance DB decrementing once every 100 ms with "Clock_100ms" toggling.
  6. OB35 overrun check. In TIA Portal: Online → Diagnostics → Cycle Time. The OB35 measured time must be < configured time (100 ms) or the system raises OB80 (time error) and skips ticks. With 20 belts, OB35 runtime is typically < 1 ms, well within budget.
  7. Restart test. Power-cycle the CPU (or STOP→RUN). Verify retained values CV_O / BITM_O persist when using the OB35 pattern with the instance DB marked Non-Retain = No (default for IDBs).

Common Pitfalls and Diagnostics

Symptom Likely cause Fix
Alarm never asserts on any belt PULS and RUN are inverted at I/O wiring Cross-check with the motor's encoder; trace Run AND NOT Pulse in monitor
All 20 belts alarm simultaneously One shared timer word re-used (no multi-instance) Convert to multi-instance FB or 20 unique instance DBs
OB80 "time error" CPU diagnostic OB35 runtime > 100 ms, or OB35 disabled Reduce OB35 logic; verify OB35 is loaded in CPU
CV counter drifts across belts OB35 priority pre-empted by OB1 in user-modified priority schema Do not lower OB35 priority below 12
Timer overflow at 9 990 s (S5TIME legacy) S5TIME 16-bit BCD limit Switch to IEC_TON with TIME datatype on S7-1200/1500
TON Q flickers on a noisy pulse Mechanical contact bounce on PULS input Add 5 ms hardware filter or use IEC_TON with PT ≥ 50 ms
Multi-instance FB will not compile Inside the FB, the IEC timer is declared as VAR_TEMP Move to VAR / VAR_STATIC so the IDB retains it

Migration: S5TIME to IEC_TIMER

Legacy S7-300/400 code frequently uses S_ODT(T_NO := T12, ...) with S5TIME values. When porting to S7-1200/1500:

  • Replace S5TIME with TIME (1.0 ns resolution; T#8s literal).
  • Replace T_NO parameter with a multi-instance variable of type IEC_TIMER.
  • Convert BI (BCD out) to ET (elapsed TIME) — BI is removed in IEC blocks.
  • Confirm CPU firmware ≥ V4.0 (S7-1200) or V1.8 (S7-1500) for full IEC_TON support.

Field Notes and Recommendations

For plants of 20 to 50 monitored belts, the multi-instance FB pattern (Pattern 1 or 3) is the lowest-maintenance solution. It uses one timer SFB per FB instance, all allocated automatically, and the FB is fully re-usable across projects. Reserve Pattern 2 (OB35 counter) for very small CPUs where SFB4 instance count is constrained, or where the time base must be exact and independent of SFB4 scheduling jitter.

Avoid mixing patterns: do not call Pattern 1 and Pattern 2 against the same alarm tag, or the alarm semantics (latched vs. pulse) will differ between belts.

Document the time base — "PresetTime = T#8s means 8.0 s ± 1 OB35 tick" — in the FB header comment. Field engineers are frequently tripped up by OB35 pattern quantisation when an SFB TON would deliver 1 ms precision.

Reminder: This article documents control logic only. Elevator and conveyor protection must also include hardware-based overspeed, slack-rope, and stop-permission monitoring per EN 81-20 / ISO 13849-1 PL d (or the local equivalent standard). The PLC timer pattern described here is a status indicator, not a safety function.

FAQ

Can a single S7 timer word (T12) really drive 20 independent alarms?

No. A timer word stores one running value, one output bit, and one BCD time base. Concurrent invocations overwrite each other. Use a multi-instance FB so each monitored device has its own instance DB, or use a counter clocked by OB35 at 100 ms.

What is the difference between SFB4 and IEC_TON in TIA Portal?

SFB4 is the legacy multi-instance capable on-delay timer for S7-300/400. IEC_TON is the equivalent on S7-1200/1500, declared as a variable of type IEC_TIMER inside a re-instantiable FB. Both produce the same Q-output semantics: Q goes high when IN has been true continuously for PT.

How do I set OB35 to 100 ms on a S7-300 CPU 315-2 PN/DP?

Open HW Config → CPU 315-2 PN/DP → Properties → Cyclic Interrupts. Set OB35 execution time to 100 ms, priority 12 (default). Save and download to the CPU. The OB35 will be called every 100 ms regardless of OB1 scan time.

What S5TIME value gives 8 seconds on a legacy S7-300?

S5TIME is BCD-encoded: time base 1 = 0.01 s, base 2 = 0.1 s, base 3 = 1 s, base 4 = 10 s. For 8 s use base 3 with value 8, i.e., W#16#3008. Note the maximum representable time is 9 990 s (base 4, value 999); for longer times migrate to IEC_TON with TIME datatype on S7-1200/1500.

Will the counter values survive a CPU STOP→RUN if the instance DB is non-retain?

Yes. S7 instance DBs (multi-instance or single) are non-volatile by default and retain their last written values across STOP→RUN transitions and power cycles, because the load memory is mirrored to work memory at startup. You only lose values if you mark the IDB as "Non-Retain" or call SFC 82 / SFC 83 to re-initialise the DB.

Back to blog