Overview
Siemens SFB4 (the IEC Timer On-Delay, "TON") exposes a PT (Preset Time) input in TIME format. A frequent field requirement is to clamp the operator-entered preset to a safe minimum so that a stray T#0ms, T#1ms or T#10ms cannot create an effectively instant on-delay or starve a downstream safety interlock. A typical floor used on machine-tool and packaging machinery is 50 ms — long enough to debounce mechanical contacts and short enough to remain "responsive" in human terms.
This reference shows three working implementations of a 50 ms lower-limit clamp on SFB4 in STEP 7 STL:
- A reusable FC that takes two
TIMEparameters and returns the larger of the two. - A drop-in wrapper FB that encapsulates SFB4 and pre-clamps the caller's PT.
- An inline STL sequence placed at the start of the timer's parent block — the technique the original author settled on because ladder's
LT_Dinstruction requires INT/DINT, notTIME.
The underlying SFB4 specification is documented in the SIMATIC S7-300/400 System Software – System and Standard Functions reference manual; SFB4 ships in the Standard Library under System Function Blocks. STL comparison semantics follow the STEP 7 STL Programming Manual.
Technical Background: SFB4, the TON Instance, and TIME Encoding
SFB4 versus FB4
SFB4 is a system function block that lives in the operating system of the S7-300/S7-400 CPU. The block instance DB that holds its parameters (in/out, static) is generated by the user with CALL SFB4, DBn or implicitly when an instance DB is assigned to a function block that contains the SFB4 call. SFB4 has no instance in the user program code as a standalone FB; its interface is:
| Interface | Name | Type | Description |
|---|---|---|---|
| Input | IN | BOOL | Start input |
| Input | PT | TIME | Preset time – the value ET is compared against |
| Output | Q | BOOL | TRUE once ET ≥ PT |
| Output | ET | TIME | Elapsed time, counted from the last rising edge of IN |
On the S7-1200/S7-1500 platforms the same logic ships as FB IEC_Timer_0_DB (or the IEC TON multi-instance). The block interface is identical, but the encapsulation is an FB with instance DB, not an SFB. All three techniques below transfer verbatim with the rename SFB4 → FB and adjusting the multi-instance declaration.
TIME as a 32-bit signed DINT in Milliseconds
The TIME data type is a 32-bit signed integer with units of milliseconds. Range, resolution and bit layout match DINT exactly:
| Property | Value |
|---|---|
| Storage width | 32 bits (DINT) |
| Unit | milliseconds (ms) |
| Range | –2 147 483 648 ms … +2 147 483 647 ms |
| Zero | T#0ms / L#0 |
| Hex of T#50ms | 16#0000_0032 (50 decimal) |
| Hex of T#1s | 16#0000_03E8 (1000 decimal) |
Because TIME is physically a DINT, any DINT comparison instruction — including <D — can be applied to a TIME operand without conversion. This is the foundation of all three techniques below.
Why a 50 ms Floor Matters
SFB4's elapsed-time accumulator is updated by the priority class 1 OB (OB1). The smallest observable jump in ET is one OB1 cycle. If PT is below the cycle time, the timer can reach its preset mid-cycle but the next comparison only fires after the cycle completes. With modern OB1 cycle times of 1–10 ms on a typical CPU 315-2 PN/DP, a 50 ms floor guarantees at least 5–50 comparison passes before Q asserts, eliminating spurious "instant" outputs caused by:
- Operator panel entry of
T#0msorT#10ms. - Default values from an uninitialised instance DB (PT = T#0ms).
- Mis-scaled HMI entries where seconds were loaded into a millisecond field (or vice-versa).
- HMI write races during start-up where PT is read by SFB4 before the operator station has finished its first variable update.
Why Ladder's LT_D Is Unsuitable
The obvious ladder solution — compare the integer PT against 50 with < D, then MUX in the larger value — fails on a typed variable because LT_D works on INT or DINT, not on TIME. STEP 7 does not implicitly coerce TIME to DINT at the contact level, so the compiler rejects the rung or accepts it with a type warning. Two workarounds exist at the ladder level (move PT to a DINT temporary first, then compare and MUX), but both require extra MOVE/MUX networks that obscure the intent. Most engineers fall back to STL precisely because the comparison is the natural instruction there.
STL Comparison Instructions Available for TIME
| Mnemonic | Operands | Operates on | Result flags |
|---|---|---|---|
| <I / >I / ==I / <>I | 16-bit | INT | BR / CC1 / CC0 |
| <D / >D / ==D / <>D | 32-bit | DINT (and TIME – same encoding) | BR / CC1 / CC0 |
| <R / >R / ==R / <>R | 32-bit | REAL | BR / CC1 / CC0 |
The mnemonic < is a single character; in source-code listings it appears as <I, <D, and so on. Both forms are accepted by the STL editor.
Method 1 – Reusable Low-Limit FC (FC 111)
The cleanest library-style solution is a parameterless FC that takes two TIME inputs and returns the larger. The function below is the exact FC 111 from the field report:
FUNCTION FC 111 : TIME
TITLE =
VERSION : 0.1
VAR_INPUT
tIN : TIME ; // candidate value
tLLIM : TIME ; // lower limit
END_VAR
BEGIN
NETWORK
TITLE =
L #tIN // load candidate
L #tLLIM; // load lower limit
<D // ACCU2 < ACCU1 ?
JC L001; // if tIN < tLLIM, jump over TAK
TAK; // swap – keep tIN as result
L001: T #RET_VAL; // store to return value
SET ;
SAVE ;
END_FUNCTION
How the FC works
-
L #tINpushes the candidate into ACCU1, moving the previous ACCU1 to ACCU2. -
L #tLLIMpushes the limit into ACCU1; the candidate is now in ACCU2. -
<Dcomputes ACCU2 < ACCU1 — i.e. tIN < tLLIM — and sets the BR/CC1/CC0 flags. -
JC L001jumps to the store if the inequality is true (candidate is too small); the TAK is skipped, leaving tLLIM in ACCU1. -
TAKswaps ACCU1 ↔ ACCU2 so the larger value (tIN) ends up in ACCU1 for storing. -
T #RET_VALwrites ACCU1 to the function's return value.
Calling the FC
In the timer FB, the PT passed to SFB4 is replaced by the clamped result:
CALL FC 111 (
tIN := #On_Delay.PT,
tLLIM := T#50MS)
T #On_Delay.PT
Reuse on multiple timers is now trivial: any number of CALL FC 111 lines in any block clamp their own PT to 50 ms with no further code.
Method 2 – Wrapper FB Around SFB4 (FB 904)
FUNCTION_BLOCK FB 904
TITLE =
VERSION : 0.1
VAR_INPUT
in : BOOL ;
PT : TIME ;
END_VAR
VAR_OUTPUT
Q : BOOL ;
ET : TIME ;
END_VAR
VAR
sfb4a : "TON"; // SFB4 instance (multi-instance compatible)
END_VAR
BEGIN
NETWORK
TITLE =
L 50;
L #PT;
<D;
JC ok;
TAK;
ok: T #sfb4a.PT;
CALL #sfb4a (
IN := #in,
Q := #Q,
ET := #ET);
END_FUNCTION_BLOCK
How the wrapper works
- The first
L 50loads the literal DINT 50 (ms). The literal could equally be written asL L#50for clarity. -
L #PTpushes the caller's preset into ACCU1. -
<Dsets flags if 50 < PT — in other words, "PT is large enough". - If PT < 50 the jump is not taken; the TAK swaps the 50 into ACCU1 (overwriting the lower PT in ACCU1).
- If PT ≥ 50 the jump goes to
ok; no TAK runs; PT is still in ACCU1. -
T #sfb4a.PTstores the clamped value into the internal SFB4 instance's PT. - The
CALL #sfb4athen runs the standard on-delay logic; outputs are returned through the wrapper's outputs.
One subtle point: L 50 writes a 32-bit constant 50, which the S7-300/400 assembler treats as a DINT whenever ACCU1 is loaded in 32-bit mode. The subsequent <D sees ACCU2 = 50 and ACCU1 = #PT in 32-bit signed form — identical to L L#50.
Method 3 – Inline STL Clamping in the Timer's Own Block
The simplest patch when only one or two timers exist is to clamp at the top of the parent block, before the SFB4 call. The author's final STL snippet is:
L #On_Delay.PT
L L#50
<I
JCN j10a
T #On_Delay.PT
j10a: NOP 0
L #Off_Delay.PT
L L#50
<I
JCN j10b
T #Off_Delay.PT
j10b: NOP 0
Semantics walk-through
-
L #On_Delay.PTpushes the operator value into ACCU1 (was in #On_Delay.PT as TIME). -
L L#50pushes 50 ms into ACCU1; PT is now in ACCU2. -
<Icompares 16-bit ACCU2 < ACCU1 (PT < 50?). -
JCN j10a— "Jump if CC1 = 0 OR CC0 = 1" — i.e. if the comparison result is not "less than". If PT ≥ 50, jump past theTand keep the original value. -
T #On_Delay.PTstores ACCU1 (50) back into the instance DB, overwriting whatever was there. - The
NOP 0is a required jump target; STEP 7 does not allow a label to be the last statement in a network in STL.
The second triplet repeats the same pattern for an off-delay (SFB5 "TOF") on the same instance DB or a sibling one.
Why <I works against L#50 on a TIME operand
TIME is a 32-bit signed integer, but the S7-300 CPU's <I instruction evaluates only the low 16 bits of each accumulator. For all PT values in the range –32768 ms … +32767 ms the low word equals the low word of the equivalent DINT, so the comparison yields the same boolean result as <D. Above 32 768 ms the high word becomes non-zero and <I silently diverges. Because the purpose of this clamp is to enforce a 50 ms minimum, the high word is essentially zero for all plausible operator inputs, and <I is therefore safe in this specific application. For any code path where PT can legitimately exceed ~32 s, switch to <D unconditionally.
Verification
Static check – bit pattern
Open the instance DB in STEP 7 and inspect the PT offset. After one execution of the clamp, the DWord should read 16#0000_0032 (decimal 50) for any entered PT below 50 ms. Use Monitor/Modify with the display format set to DEC or HEX for the DWord at the PT offset.
Online check – VAT / watch table
- Open a VAT (Variable Table) and enter the instance DB offsets for PT, ET and Q.
- Force PT to
T#0ms, then trigger a single OB1 pass. - Confirm PT now reads
T#50msand Q does not assert on the same scan. - Force PT to
T#5sand confirm PT is unchanged (no clamp on the upper side). - Force PT to
T#-1msand confirm PT clamps toT#50msas well — negative inputs are also "too low".
Cross-reference
From the LAD/FBD/STL editor, right-click the instance DB symbol and choose Go to → Cross-references. Confirm that every read site is preceded by either the inline STL clamp, the FC 111 call, or the wrapper FB 904. Anything outside that set is a leak and should be reworked or its PT value re-routed.
Edge Cases, Cycle-Time Effects, and Platform Notes
SFB4 time accuracy vs. OB1 cycle time
SFB4's elapsed-time accumulator is updated by OB1's priority class. With a 10 ms cycle, ET increments in 10 ms steps; the actual Q-edge can therefore be delayed by up to PT + 1 cycle. A 50 ms floor only smooths out the minimum; for high-precision timing, drive SFB4 from a time-of-day OB (e.g. OB10) or migrate to the S7-1500 platform's Ton (FB1864 / "IEC_Timer_0_DB") where the IEC timer is updated by a 1 ms hardware interrupt. The S7-300/400 IEC timer specifics are covered in the System and Standard Functions manual, Chapter 22.
Negative or inverted PT inputs
Both <D and <I are signed comparisons. A PT of T#-100ms (–100, i.e. less than 0) is correctly identified as below the 50 ms floor and clamped upward. This is the desired behaviour for HMI fields that accept typed TIME inputs — the operator cannot enter a negative on-delay and quietly disable the interlock.
HMI scaling faults
Many WinCC flexible / TIA Portal panels send PT as a DINT scaled in milliseconds. A driver that writes 5 when the operator meant 5 seconds (5 000 ms) is below the 50 ms floor and will be clamped — the timer will still fire, but after 50 ms rather than 5 s. The clamp therefore protects safety but cannot replace proper unit-conversion. Add an HMI-side input range check (min 0.05 s) as a parallel defence.
S7-1200 / S7-1500 equivalents
The wrapper-FB approach is portable to the S7-1200/S7-1500 platforms with two adjustments:
- Replace
SFB4with the IEC TON multi-instance. The system delivers the timer asIEC_Timer_0_DB(default instance) or as a multi-instance of the platform's IEC timer FB. - Replace literal
L 50withL 50orL L#50— both compile to the same constant. Note that on S7-1500 the STL flavour is replaced by SCL (Structured Control Language); an SCL equivalent is#sfb4a.PT := MAX(IN1 := #PT, IN2 := T#50ms);which is functionally identical and type-safe.
Watch out for instance-DB initial values
An instance DB generated for an FB that contains SFB4 sets PT to T#0ms on first download. Until the operator station writes a real value, the timer's first call will see PT = 0 and the clamp will rewrite it to 50 ms. If the timer must run with a known minimum during start-up, place the clamp before the first CALL SFB4 — exactly what FB 904 and the inline STL methods do. The standalone FC 111 must be called explicitly in this order; an uninitialised PT that is never clamped will silently produce a 0 ms on-delay on the first cycle.
SFB3 / SFB4 / SFB5 quick reference
| SFB | IEC name | Function | PT lower-limit clamp code |
|---|---|---|---|
| SFB3 | TP | Pulse timer | Identical (PT is TIME) |
| SFB4 | TON | On-delay | Identical |
| SFB5 | TOF | Off-delay | Identical |
Troubleshooting matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Q asserts instantly despite 50 ms PT | Clamp executes after SFB4 call | Move clamp network before CALL SFB4 in the same FB scan. |
| PT shows 0 ms in VAT but DB is fresh | FC 111 not invoked before first SFB4 call | Insert CALL FC 111 once per scan before each timer's CALL SFB4, or migrate to FB 904 wrapper. |
| Compare flag always true above ~32 s PT | Used <I on a PT in the high-word range |
Switch to <D. |
| Compiler error: type mismatch in ladder |
LT_D against a TIME variable |
Convert: MOVE PT -> tempDINT, then LT_D, or convert ladder network to STL. |
| Timer never reaches Q | PT clamped but FC 111 was called with swapped arguments (limit < candidate accidentally) | Re-check FC 111 call: tIN := #On_Delay.PT, tLLIM := T#50MS. |
| HMI shows clamped value, not operator value | Clamp writes to the same DB offset the HMI reads | Clamp a shadow variable, write the clamped value into PT only on the call; or have the HMI write to a separate "desired PT" field. |
Comparative trade-offs
| Technique | Reusability | Type safety | Caller visibility | Code volume per timer |
|---|---|---|---|---|
| Inline STL clamp | Low – copy/paste | High (no extra types) | Visible in parent FB | ~6 STL lines |
| FC 111 (MAX on TIME) | High – one FC for all timers | High (TIME in / TIME out) | Visible at each CALL site | 3 lines per CALL |
| FB 904 wrapper | High – drop-in replacement for SFB4 | High (mirrors SFB4 signature) | Hidden inside wrapper | 0 at call site |
For one-off clamps, inline STL is fastest. For a library of timers, the wrapper FB 904 is the cleanest contract. For shared utilities, FC 111 wins.
FAQ
Can SFB4 accept PT values above 2 147 483 647 ms?
No. PT is a signed 32-bit TIME (DINT) with a hard ceiling of T#2 147 483 647 ms ≈ 24.86 days. STEP 7 flags out-of-range literals at compile time; runtime values written via HMI must be range-checked before being passed to SFB4.
Why does <I work even though PT is 32-bit?
TIME is encoded as a DINT in milliseconds. <I compares only the low 16 bits, but for any PT below ~32 768 ms the high word is zero so the boolean result matches a 32-bit signed comparison. For PT above 32 768 ms, switch to <D to stay correct.
What happens to PT if the instance DB is freshly downloaded?
The initial value is T#0ms. If your code path runs the timer before the HMI writes a real value, the clamp will set PT to T#50ms on the first scan. The wrapper FB 904 and inline clamp handle this automatically; a separate FC 111 call must be placed before the SFB4 invocation to avoid a one-cycle T#0ms on-delay.
Does the clamp affect timer accuracy?
No. SFB4's accuracy is governed by the OB1 cycle time, not by PT. The clamp only raises the floor of PT; once PT is above the floor, the timer's resolution is still ±1 OB1 cycle. For sub-millisecond timing, drive SFB4 from a higher-priority cyclic OB or migrate to an S7-1500 IEC timer.
Can the same 50 ms minimum be applied to TOF (SFB5) and TP (SFB3)?
Yes. SFB3 (TP – pulse), SFB5 (TOF – off-delay) and the legacy timer FBs all use the same TIME-encoded PT input. The clamp code is identical; only the SFB number changes. Wrap each in its own FB (e.g. FB 904 for TON, FB 905 for TOF) or call FC 111 immediately before each CALL SFBn.