Problem Description
An IEC standard timer (TP, TON, TOF, TONR) placed inside a Function Block (FB) fires correctly the first time the block is entered after a re-initialization, but on every subsequent scan the timer ignores new trigger pulses. The Q output latches high, ET stops incrementing, and calling the FB with IN := TRUE from a different branch of the program has no effect. The symptom appears most often when the timer instance is declared inside a CASE structure that runs as a finite state machine.
This is not a firmware defect and not a CPU scan-time problem. It is a deterministic consequence of how the IEC 61131-3 timer specification defines its IN input, combined with the way TIA Portal treats FB instance data blocks. The same root cause is observed on SIMATIC S7-1200 CPU firmware 4.2 through 4.7, on SIMATIC S7-1500 CPU firmware 2.9 through 3.1, and on the ET 200SP CPU 1510SP/1512SP. TIA Portal versions V15.1, V16, V17, V18, and V19 all exhibit the symptom because the timer block itself is unchanged across these versions.
Root Cause: Edge Detection on the IN Input
All four IEC timers evaluate the IN boolean with internal rising-edge detection. The timer does not start when IN = TRUE; it starts when IN transitions from FALSE to TRUE. The relevant statements from the PLCopen IEC 61131-3 specification (3rd edition, section 6.4.2) are reproduced here in the form the Siemens library implements them:
-
TP (Pulse Timer): rising edge of
INstarts a fixed-duration pulse onQ;INcan be reset toFALSEany time after the edge without affecting the running pulse. -
TON (On-Delay Timer): rising edge of
INstarts the timing run; outputQgoes high only afterET = PT.INmust remainTRUEfor the entire run. -
TOF (Off-Delay Timer): rising edge of
INsetsQ = TRUEimmediately; falling edge ofINstarts the off-delay;Qdrops afterET = PT. -
TONR (Retentive On-Delay): rising edge of
INaccumulatesET;Qgoes high atET ≥ PT;ETis held whenIN = FALSE; only aRESETinput clears the accumulator.
When the timer instance is declared as a VAR_TEMP or as a non-retentive local VAR, the IN and the previous IN flag are reset on every block exit, so the next call always sees a rising edge and the timer fires once. When the same timer is declared as a STAT (static) inside the FB instance DB, the IN history persists between cycles, and a second call with the same IN = TRUE value is treated as a level, not an edge. The result is a timer that appears to be "stuck" at the value it had after the first edge.
Why the CASE Statement Makes It Worse
A typical state-machine pattern looks like the following SCL excerpt. The programmer stores the timer as STAT, which is correct, but assigns IN from inside the active state branch:
FUNCTION_BLOCK FB_StateMachine
VAR
eState : INT; // current state
END_VAR
VAR STATIC
tWait : TON; // static IEC timer
bStart : BOOL; // trigger flag
tPreset : TIME := T#5s; // 5 s on-delay
END_VAR
BEGIN
CASE eState OF
10: // running
IF bStart THEN
tWait(IN := TRUE, PT := tPreset); // PROBLEM: IN = TRUE on every scan
IF tWait.Q THEN eState := 20; END_IF;
END_IF;
20: // next state
tWait(IN := FALSE); // only this branch clears IN
END_CASE;
END_FUNCTION_BLOCK
Inside state 10 the timer sees IN = TRUE on the rising edge of bStart, starts running, and reaches Q = TRUE. The program transitions to state 20. The next cycle the program is no longer in state 10, so tWait is no longer called with IN = TRUE. The timer holds its last ET and last Q because the static block has no further call. If the program returns to state 10 a second time and again writes tWait(IN := TRUE, ...), the timer evaluates a level that has been TRUE continuously in its internal flip-flop and therefore does not see a new rising edge.
The same anti-pattern is reproduced in ladder logic when a --|P|-- contact is wired to a TP/TON coil whose enable input is a coil under the same rung, because the coil is energized on every scan after the first edge.
IEC 61131-3 Timer Reference
| Timer block | Trigger condition | Q behaviour | Reset method | Typical use in FB |
|---|---|---|---|---|
| TP (IEC_PULSE) | Rising edge on IN | Pulse of fixed duration PT | Auto-resets after PT | Single-shot output pulse, e.g. valve kick |
| TON (IEC_ON) | Rising edge on IN, level held TRUE | TRUE after ET = PT | IN := FALSE clears ET and Q | Debounce, startup delay |
| TOF (IEC_OFF) | Falling edge on IN | TRUE on IN=TRUE, drops after PT | IN := TRUE clears ET | Cooling delay, fan run-on |
| TONR (IEC_RETENTIVE) | Rising edge on IN, accumulated | TRUE at ET ≥ PT | RESET := TRUE clears ET and Q | Total run-time counter, dwell accumulator |
Source: STEP 7 V19 Programming and Operating Manual, section 6.4 and the S7-1500 System Manual, chapter 6.
Solution 1: Move the IN Trigger Outside the CASE Structure
The deterministic fix is to compute the boolean that feeds IN outside the CASE block, so the timer is called unconditionally on every cycle. The CASE block only decides which transition to take based on the timer's outputs. Rewrite the FB as shown below.
FUNCTION_BLOCK FB_StateMachine
VAR_INPUT
bStart : BOOL; // start request, may be a momentary pushbutton
bReset : BOOL; // explicit reset
END_VAR
VAR_OUTPUT
bDone : BOOL;
END_VAR
VAR
eState : INT; // current state
END_VAR
VAR STATIC
tWait : TON; // IEC on-delay, static -> retains state
tPreset : TIME := T#5s;
END_VAR
BEGIN
// -------- timer is called every cycle, regardless of state --------
tWait(IN := bStart AND (eState = 10), PT := tPreset);
bDone := tWait.Q;
// -------- state transitions read tWait.Q and clear bStart ---------
CASE eState OF
0: IF bStart THEN eState := 10; END_IF;
10: IF bDone THEN
eState := 20;
bStart := FALSE; // force IN back to FALSE for next arming
END_IF;
20: ; // holding state
END_CASE;
IF bReset THEN
eState := 0;
bStart := FALSE;
END_IF;
END_FUNCTION_BLOCK
Three rules guarantee repeatable behaviour:
- The
INexpression is evaluated every cycle. It is the only place the boolean is computed. - The rising edge is generated by the operator-controlled signal
bStartor by the previous-state flag falling fromTRUEtoFALSE. - The transition logic explicitly drops the trigger before re-arming the timer.
Solution 2: Use TP for Single-Shot Events
If the timer is used to produce a one-shot pulse (for example a 200 ms output to a valve), the TP block is preferable. TP ignores IN after the rising edge and self-resets after PT, so it does not need an explicit reset coil. This is the recommended type inside FBs that drive pneumatic actuators, where re-triggering the same pulse mid-run has no useful meaning.
VAR STATIC
tpKick : TP; // pulse timer
tKickPT : TIME := T#200ms;
END_VAR
// The same rising-edge principle applies: bArm must be FALSE at least one cycle
tpKick(IN := bArm, PT := tKickPT);
bValveCmd := tpKick.Q;
IF tpKick.Q THEN bArm := FALSE; END_IF;
Solution 3: Explicit RESET for TONR
The TONR accumulator is held even when IN goes low, so the only way to clear it is the RESET input. A common bug is to write RESET := bStart, which causes a self-resetting timer on the rising edge of bStart. RESET must be a separate signal that is true only when the application explicitly wants to clear the accumulated time:
VAR STATIC
tRun : TONR;
tPreset : TIME := T#30s;
END_VAR
tRun(IN := bRunCommand, PT := tPreset, RESET := bResetCommand);
bRuntimeExceeded := tRun.Q;
IF bResetCommand THEN bRunCommand := FALSE; END_IF;
The wiring of RESET is described in the STEP 7 V19 programming manual, section 6.4.4.
Alternative Pattern: Edge-Detection Inside the FB
If the host program does not give a clean momentary TRUE signal, generate the edge inside the FB. The instance DB guarantees the flag is retained between scans:
VAR STATIC
bRunPrev : BOOL; // previous cycle value of bRun
bRunEdge : BOOL; // rising-edge pulse, one cycle TRUE
END_VAR
bRunEdge := bRun AND NOT bRunPrev;
bRunPrev := bRun; // written every cycle, unconditionally
tWait(IN := bRunEdge, PT := tPreset);
This pattern is robust against bRun being a maintained input, a HMI tag, or a value written from a higher-level state machine. It is the same edge-detect primitive that the IEC timer itself uses internally, and it satisfies the rising-edge requirement of the TP, TON, and TONR blocks.
Ladder Logic Equivalent
For engineers who prefer ladder, the equivalent circuit on an S7-1500 CPU 1515-2 PN is shown below. Network 1 generates the rising edge; network 2 calls the timer; network 3 handles transitions. Place the timer in a separate network and never inside a JMP/LBL skip region.
// Network 1: edge
bStart bStart_prev
----| |--------|/|---( bStart_edge )--- // rising edge pulse
// Network 2: timer call (no JMP/LBL around it)
bStart_edge tPreset
----| |----------[TP]-----[TON, PT := tPreset]---( tWait.Q )
// Network 3: state transition
tWait.Q
----| |----------( SET eState := 20 )---
----| |----------( RST bStart_edge )---
The TIA Portal LAD editor treats the TP/TON/TOF/TONR blocks as function blocks with an automatically generated instance DB; do not call them inside a CASE/IF that can be skipped by JMP, otherwise the edge detection misses cycles. See S7-1200 Easy Book, section 4.3 for a worked example.
Static Variables and the Instance DB
Timer instances declared as VAR (without STAT) are re-initialised on every call to the FB, so the IEC timer's internal edge bit is reset and the timer always sees a rising edge on the next call. This is a frequent "fix" that masks the underlying problem: the timer now fires on every scan and never reaches Q = TRUE because ET is wiped before it can accumulate. Always declare IEC timers as VAR STATIC inside the FB:
| Declaration | Edge bit retained? | ET retained? | Q retained? | Resulting behaviour |
|---|---|---|---|---|
| VAR (non-retentive temporary) | No | No | No | Timer fires every scan, never reaches Q |
| VAR STATIC | Yes | Yes | Yes | Correct edge-detected behaviour |
| VAR STATIC RETAIN | Yes (warm restart retains) | Yes | Yes | Use for TONR-style accumulators across power-cycle |
| Global DB instance | Yes | Yes | Yes | Equivalent to VAR STATIC, but multi-instance DBs not supported on S7-1200 prior to firmware 4.4 |
The instance DB is generated automatically by TIA Portal under "Program blocks > System blocks > FB instance DB". On the S7-1500, multi-instance capability (more than one IEC timer inside a single parent FB) is fully supported from CPU firmware 2.0 onwards.
System Clock Bits as Periodic Re-Trigger Sources
For cyclic tasks, the most reliable input to a TON is one of the CPU's system clock bits, e.g. Clock_1Hz from the "System and Clock Memory" configuration of the CPU. Because the bit toggles at 1 Hz, the rising edge occurs once per second, which is suitable for periodic health-check timers:
VAR STATIC
tHealth : TON; // 2 s on-delay
END_VAR
tHealth(IN := Clock_1Hz, PT := T#2s); // Q goes high 2 s after each 1 Hz rising edge
bWatchdogOK := tHealth.Q;
Enable the clock byte in Device Configuration > Properties > System and Clock Memory. The default frequency of Clock_1Hz is documented in the S7-1500 System Manual, chapter 4.5.
Commissioning and Verification
After applying the fix, validate the FB in the following order before deploying to a real machine.
-
Watch-table test: open the FB instance DB in the "Monitor / Modify" view, force
bStart := TRUEfor one cycle, observetWait.IN,tWait.ET, andtWait.Qadvancing. SetbStart := FALSE, confirmtWait.ETstops andtWait.Qdrops (forTON). -
Trace recording: use the TIA Portal "Trace" tool to record
bStart,tWait.IN,tWait.ET, andtWait.Qat the OB1 cycle rate. Verify the rising edge ofINprecedes the start ofETincrement by exactly one OB1 cycle. -
Step sequence test: from the PLC program, write a small state sequence that triggers the timer five times in a row, each time waiting for
Qto drop before re-arming. Confirm the FB transitions correctly each iteration. -
Online consistency check: in TIA Portal V18 and later, use "Program path > Consistency check" to confirm the timer call is reachable from the OB1 cycle. Skipped code under
JMPis flagged. - Retain test (for TONR): run the accumulator for 10 s, perform a STOP/RUN on the CPU with retain enabled, and confirm the accumulated value is preserved.
Troubleshooting Matrix
| Observed symptom | Likely cause | Diagnostic step | Corrective action |
|---|---|---|---|
| Timer fires once, then Q is stuck high | IN never reset, edge not seen | Monitor tWait.IN in watch table | Move IN calculation outside CASE, drop trigger after Q |
| Timer never reaches Q, ET stays 0 | Timer declared as VAR (temp) | Open FB interface | Change to VAR STATIC |
| TONR does not accumulate | RESET coil assigned to same trigger as IN | Trace RESET input | Wire RESET to a separate explicit reset tag |
| TP pulse length wrong on cold start | TP instance cleared by PLC restart | Check retain attribute on instance DB | Enable retain for the TP or treat cold start as new cycle |
| Timer Q flickers in OB1 | Two FBs call the same instance | Cross-reference usage | Use multi-instance FBs or per-instance DBs |
| Compile warning: "Instance DB not updated" | FB interface changed but DB not recompiled | Right-click DB > Compile | Recompile all instance DBs after interface change |
Common Pitfalls to Avoid
--() in ladder to drive IN directly. The coil is energised every scan after the first time it goes high, so the IEC timer never sees a fresh edge on subsequent cycles. Always derive IN from a rising-edge contact or from an SCL := assignment that is forced back to FALSE in the same cycle that Q is consumed.CASE arm that is bypassed by a transition. If the timer is only called from one state, a transition out of that state and back in will produce either a stuck timer (if it was running) or a non-deterministic start (if the static edge bit was set). Always call the timer unconditionally at the top of the FB body.PT (preset time) input of the IEC timer is read at every cycle. Changing PT while the timer is running is allowed for TP and TONR (it takes effect on the next cycle), but for TON it only affects the next run, not the current one. Do not assume a mid-run change of PT will shorten the active pulse.Multi-Instance Considerations on S7-1200
The S7-1200 prior to firmware 4.4 did not support multi-instance DBs for IEC timers declared as VAR STATIC inside a user FB. On those firmware versions, the IEC timer had to be called as a "Single Instance" with a separate DB. From firmware 4.4 onwards, the multi-instance capability is enabled and the pattern shown in this article works as written. Refer to the S7-1200 System Manual, edition 04/2023, section 6.4.2 for the firmware-dependent behaviour table.
FAQ
Why does my Siemens IEC TON timer fire only once inside an FB?
Because the IEC 61131-3 IN input is edge-detected, and the FB's static instance retains the previous IN value. When the same TRUE level is written to IN on a second cycle, the timer interprets it as a continued level, not a new start. Compute the IN expression outside the CASE structure and force it back to FALSE after the timer completes.
Should I declare the IEC timer as VAR, VAR STATIC, or in a global DB?
Use VAR STATIC inside the FB so the timer instance DB is local to the parent FB and survives between calls. VAR (temporary) destroys the edge bit and the elapsed-time accumulator on every call, which makes the timer fire on every scan. A global DB works but breaks the encapsulation of the FB and complicates multi-instance use.
What is the difference between TP, TON, TOF, and TONR in TIA Portal?
TP produces a pulse of fixed duration PT on a rising edge of IN. TON delays a rising edge by PT and requires IN to stay high. TOF holds the output high and delays the falling edge by PT. TONR accumulates elapsed time on rising edges of IN and only resets on a dedicated RESET input. The full specification is in the STEP 7 V19 Programming and Operating Manual, section 6.4.
How do I reset a TONR timer from inside a state machine?
Wire a separate RESET boolean to the timer's RESET input. Trigger the RESET from the state that should clear the accumulated time, and assert it for at least one full OB1 cycle. Do not tie RESET to the same tag as IN, or the timer will self-reset on every rising edge of IN and never accumulate.
Can I use the same IEC timer instance for two different FB calls?
No. A single IEC timer instance can only be called from one FB or one network. If you need the same timer behaviour in two places, instantiate the FB twice (TIA Portal creates two instance DBs automatically) or, on the S7-1500 from firmware 2.0 onwards, use multi-instance FBs so each call has its own internal instance.