Resolving IEC Timer Re-trigger Failures Inside Siemens FBs

David Krause14 min read
SiemensTIA PortalTroubleshooting
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 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 IN starts a fixed-duration pulse on Q; IN can be reset to FALSE any time after the edge without affecting the running pulse.
  • TON (On-Delay Timer): rising edge of IN starts the timing run; output Q goes high only after ET = PT. IN must remain TRUE for the entire run.
  • TOF (Off-Delay Timer): rising edge of IN sets Q = TRUE immediately; falling edge of IN starts the off-delay; Q drops after ET = PT.
  • TONR (Retentive On-Delay): rising edge of IN accumulates ET; Q goes high at ET ≥ PT; ET is held when IN = FALSE; only a RESET input 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:

  1. The IN expression is evaluated every cycle. It is the only place the boolean is computed.
  2. The rising edge is generated by the operator-controlled signal bStart or by the previous-state flag falling from TRUE to FALSE.
  3. 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.

  1. Watch-table test: open the FB instance DB in the "Monitor / Modify" view, force bStart := TRUE for one cycle, observe tWait.IN, tWait.ET, and tWait.Q advancing. Set bStart := FALSE, confirm tWait.ET stops and tWait.Q drops (for TON).
  2. Trace recording: use the TIA Portal "Trace" tool to record bStart, tWait.IN, tWait.ET, and tWait.Q at the OB1 cycle rate. Verify the rising edge of IN precedes the start of ET increment by exactly one OB1 cycle.
  3. 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 Q to drop before re-arming. Confirm the FB transitions correctly each iteration.
  4. 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 JMP is flagged.
  5. 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

Warning: do not use a coil --() 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.
Warning: do not place the IEC timer call inside a 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.
Caution: the 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.

Back to blog