Chapter 10 / 32 · Part 3 · Time and counting

Off-delay and pulse timers

Keep things running after a signal drops with TOF, fire fixed pulses with TP, and build a flasher from two timers.

Off-delay: keep going after the signal drops

An air heater in a drying tunnel has a 12 kW element bank and a fan blowing across it. When the heater switches off, the elements are still glowing. Stop the fan at the same moment and the heat soaks into the housing, the over-temperature thermostat trips, and in a bad case the insulation on the element terminals cooks. The rule in the manual is simple: the fan keeps running for two minutes after the heater stops. That is an off-delay, and the timer for it is TOF.

TOF has the same four pins as the TON from chapter 9 (IN, PT, Q, ET), but it times the other edge:

  1. While IN is TRUE, Q is TRUE and ET is T#0s. There is no delay on the way in.
  2. When IN goes FALSE, ET starts counting. Q stays TRUE until ET reaches PT, then goes FALSE.
  3. If IN comes back TRUE before PT runs out, ET returns to zero and Q simply stays TRUE. The run-on starts again from the next falling edge.

Here is the heater fan, with 5 s instead of two minutes so you don't have to wait:

PROGRAM Main
VAR
    Heater   : BOOL;   (* the heater contactor command, from elsewhere in the program *)
    FanRunOn : TOF;
    Fan      : BOOL;   (* output: fan contactor *)
END_VAR

FanRunOn(IN := Heater, PT := T#5s);
Fan := FanRunOn.Q;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: Heater.

Switch Heater on: the fan starts on the same scan. Switch it off: the fan carries on, ET climbs, and 5 s later the fan stops. Switch the heater back on half-way through the run-on and watch ET drop to zero while the fan never hesitates.

If you have built this in ladder with a TON and an inverted contact, notice the identity: a TOF's Q is exactly NOT of a TON's Q when the TON is fed NOT IN. "The fan may stop once the heater has been off for 5 s" is an on-delay on heater off. Both are correct. The TOF says what you mean in one line, and that is the better reason to use it.

Here is a 300 ms TOF on this page's 100 ms scan, set beside the TON table from the last chapter so you can compare the two:

ScanINETQNote
1FALSET#0sFALSEidle since power-up: no run-on
2TRUET#0sTRUEQ follows IN at once
3TRUET#0sTRUEET stays at zero while IN is TRUE
4FALSET#0sTRUEfalling edge: run-on starts
5FALSET#100msTRUE
6FALSET#200msTRUE
7FALSET#300msFALSEET reached PT: Q drops
8FALSET#300msFALSEET stays at PT until IN rises again

The last row is the TOF's version of a detail you met with the TON: after the delay, ET rests at PT, and an HMI that shows "run-on remaining" as PT - ET reads zero, which is right. The first row is the one that surprises people. A TOF that has never seen IN TRUE has nothing to run on from, so at power-up its Q is FALSE.

Limits of an off-delay

An off-delay lives in the CPU's memory, and three consequences of that are usually learned the hard way.

It does not survive a power cut. On start-up every TOF begins with Q FALSE, as the first row of the table shows. If the supply drops while the heater is on, the fan stops with it and does not run on when power returns, because the program has no idea the elements were hot. Where the run-on protects equipment, back it up with something physical: a thermostat on the housing that runs the fan above 60 °C, wired so it works whatever the PLC thinks.

It must not delay a stop that should be immediate. The fan's motor-protection breaker and the emergency stop must kill it now, not five seconds from now. Gate the output, not the timer input: Fan := FanRunOn.Q AND NOT FanOverload;. The run-on keeps timing in the background, but the contactor drops. The emergency stop itself does not depend on this line at all: it removes power through the safety relay or safety PLC, and the program only notices that it happened.

It holds the output on through every short dip. That is usually the point, and occasionally a trap. A TOF on a "carton present" signal holds the belt running while cartons arrive closer together than PT; it also hides a photo-eye that chatters. Know which one you are building.

Off-delays and fail-safe signals

There is a fourth limit, and it is a design decision more than a trap. Many permissive signals are wired fail-safe: a pressure switch that is closed when the pressure is good, a level switch that is closed when the level is above it, so a broken wire reads as the bad state. Put a TOF on one of those to ride out short dips and you also delay the reaction to a broken wire, a dead sensor or a real loss of pressure, by the full preset.

Sometimes that is fine. A 2 s TOF on a lubrication-pressure OK signal on a gearbox costs nothing, because the gearbox can run that long without oil pressure. A 10 s TOF on the air pressure permissive of a clamp that holds a part while a robot works on it is a different matter. Whenever you bridge a fail-safe signal with an off-delay, write down how long the machine can safely run without the thing the signal proves, and check that the preset is shorter. And if the answer involves people, the protection belongs in the safety system, designed to ISO 13849-1 or IEC 62061, not in a TOF.

A field story: the bin probe that was dying

A silo feeding a bagging machine had a vibrating-fork level probe near the top, and the program stopped the pneumatic fill line when it reported "full". The probe signal flickered as product slid past the forks, so someone had put a 10 s TOF on it: once the probe saw product, the fill stayed stopped for at least 10 s after it last did. It worked for years.

Then the silo overfilled and blocked the filter on the vent. When the line was opened up, the probe's electronics were failing: for weeks it had been reporting "full" for only fractions of a second at a time, and sometimes not at all. The TOF had stretched every one of those fragments into 10 s of "full", so the fill line still stopped often enough that nobody saw a difference, until the probe stopped reporting altogether.

Nothing in the program was wrong, line by line. The weakness was that a TOF makes a sick sensor look healthy. The fix kept the TOF for control, and added a chatter counter beside it: count the rising edges of the raw probe signal per minute (chapter 11 shows how), and raise a maintenance alarm when the count goes above what the product could cause. A second, independent high-high level switch with its own trip went in at the same time, which is what the silo should have had from the start.

Pulse timer: exactly this long

The third timer answers a different question again: when this happens, do something for exactly this long. A grease button on a press gives one 1 s shot of lubricant per press, however long the fitter leans on it. A reject station fires a 150 ms air blast to knock a bad bottle off the conveyor. A horn gives a 3 s burst when a batch completes. That is the pulse timer, TP.

  1. On a rising edge of IN, Q goes TRUE and ET starts counting.
  2. Q stays TRUE until ET reaches PT, whatever IN does in the meantime. Releasing the button does not cut the pulse short; holding it does not stretch it.
  3. After the pulse, ET stays at PT while IN is still TRUE, and returns to zero once IN is FALSE. Only then can a new rising edge start a new pulse.
PROGRAM Main
VAR
    GreaseBtn  : BOOL;   (* input: push button *)
    Shot       : TP;
    GreasePump : BOOL;   (* output: lubrication pump *)
END_VAR

Shot(IN := GreaseBtn, PT := T#1s);
GreasePump := Shot.Q;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: GreaseBtn.

Tap GreaseBtn and release it at once: the pump still runs for a full second. Press and hold it: the pump stops after a second while the button is still held, and nothing more happens until you let go and press again.

The IEC 61131-3 standard is explicit about one more case: a rising edge that arrives while a pulse is running is ignored. The pulse finishes at its original time; it is not restarted or extended. That is what makes TP safe for metering, since a fitter jabbing the button five times in a second still gets one shot. Try it in the live example: press GreaseBtn again while the pump runs and the pulse still ends one second after the first press, as it does on CODESYS, TwinCAT or an S7-1500.

A pulse is only as precise as the scan. A 150 ms TP on a 10 ms task gives 150 to 160 ms; on a 100 ms task it gives 200 ms. For a reject blast on a line doing 30,000 bottles an hour, where the gap between bottles is 120 ms, that difference decides whether you knock off one bottle or two. Short pulses belong in a fast task or on an output card with pulse-train or cam functions.

A pulse without a TP

Studio 5000 has no pulse timer in its instruction set. Ladder programmers make one from a one-shot (ONS) that starts a timer, and in ST you do the same thing with an edge, a latch and a TON. It is worth building once even on a platform that has TP, because it shows what the block does inside and gives you a pulse whose behaviour you can see and change:

PROGRAM Main
VAR
    GreaseBtn  : BOOL;   (* input: push button *)
    BtnEdge    : R_TRIG;
    ShotTimer  : TON;
    Shot       : BOOL;   (* the pulse, held in a latch *)
    GreasePump : BOOL;   (* output: lubrication pump *)
END_VAR

BtnEdge(CLK := GreaseBtn);
IF BtnEdge.Q AND NOT Shot THEN
    Shot := TRUE;                     (* start a pulse; ignore presses during one *)
END_IF;

ShotTimer(IN := Shot, PT := T#1s);
IF ShotTimer.Q THEN
    Shot := FALSE;                    (* the pulse has lasted PT: end it *)
END_IF;

GreasePump := Shot;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: GreaseBtn.

Tap the button and the pump runs for ten scans. Tap it again during the pulse and nothing changes, because of the AND NOT Shot, so this version ignores a re-press the same way the standard TP does. Delete AND NOT Shot and you have a retriggerable pulse that restarts on every press, which is occasionally what a specification asks for ("keep the beacon on for 3 s after the last fault"). Even then, a TOF fed by the button's edge does the same job in one line.

The Logix version of the same idea in ST uses TONR on an FBD_TIMER tag with .TimerEnable driven by the latch. The order of the lines is the same, and so is the reason for it: the latch is set on the edge, the timer times the latch, and the timer's done bit clears the latch.

When the nozzle is downstream of the eye

The reject challenge assumes the blow-off nozzle sits right at the checker. On most lines it does not. The vision system needs room, so the nozzle is some distance downstream, and the bottle takes time to travel there. The obvious answer is a TON for the travel time in front of the TP: store the reject request, wait the travel time, fire the pulse. It works perfectly on the bench, with one bad bottle at a time.

Time-based tracking has a second weakness that the prediction does not show. The travel time is only constant if the belt speed is. A line that slows down for a short stop or a speed change puts every bottle in transit late at the nozzle, so the blast hits the gap or the next bottle. Use a timer for the travel only when the conveyor runs at one fixed speed and there can never be more than one part between the eye and the actuator. Otherwise track by position.

A flasher from two timers

Every alarm beacon, every "machine running" lamp on a stack light and every HMI warning icon flashes. Some controllers give you a ready-made clock: a Siemens CPU has a configurable clock memory byte, and the CODESYS Util library has a BLINK block. You should still be able to build one, because Studio 5000 has neither and because you will want flash rates of your own.

The Siemens clock memory is switched on in the CPU properties under System and clock memory, where you also choose which memory byte it uses. Each bit then toggles at a fixed rate, with a 50 % duty cycle:

Bit76543210
Frequency (Hz)0.50.62511.2522.5510
Period (s)2.01.61.00.80.50.40.20.1

So bit 5 gives a 1 Hz flash. The bits run asynchronously to the program cycle, which means a bit can change between two reads in the same scan. For a lamp that is harmless. For anything that counts or steps on the clock bit, copy it once at the top of the cycle and use the copy, or build the clock from a timer.

The classic flasher is two TONs that hand over to each other:

PROGRAM Main
VAR
    Enable  : BOOL;   (* input: flashing wanted *)
    OffTime : TON;    (* times the dark half *)
    OnTime  : TON;    (* times the lit half *)
    Beacon  : BOOL;   (* output: beacon lamp *)
END_VAR

OffTime(IN := Enable AND NOT OnTime.Q, PT := T#500ms);
OnTime(IN := OffTime.Q, PT := T#500ms);

Beacon := OffTime.Q;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: Enable.

Follow one cycle. With Enable on, OffTime runs while the lamp is dark. When it finishes, OffTime.Q lights the beacon and starts OnTime. When OnTime finishes, its Q takes OffTime's input away, OffTime resets and the lamp goes dark. On the next scan OnTime loses its input too, resets, and gives OffTime its input back. Round it goes. Each timer's Q is fed back into the other's IN, and that feedback is what keeps the pair oscillating.

Step through it and count: each half lasts its preset plus about one scan, because each reset costs a scan. On this page's 100 ms scan that is visible; on a 10 ms task a "500 ms" half is 510 ms and nobody will ever notice. Different presets give different duty cycles. 200 ms on and 800 ms off reads as "warning", 500/500 as "fault", and many plants define exactly that in their HMI standard.

The self-resetting single timer

You can get a regular tick out of a single TON by feeding its own output back into its input:

PROGRAM Main
VAR
    Pulse  : TON;
    Tick   : BOOL;   (* TRUE for exactly one scan per period *)
    Beacon : BOOL;
    Ticks  : DINT;
END_VAR

Pulse(IN := NOT Pulse.Q, PT := T#500ms);
Tick := Pulse.Q;

IF Tick THEN
    Beacon := NOT Beacon;   (* toggle on every tick: 50 % duty flasher *)
    Ticks  := Ticks + 1;
END_IF;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it.

When the timer finishes, Q is TRUE for one scan. On the next call IN is NOT TRUE, so the timer resets; on the call after that IN is TRUE again and it starts over. The result is a one-scan Tick, which is what you want to step a counter, sample an analog value or toggle a lamp.

Notice the cost: two scans per period, one to reset and one to restart. The period is PT plus two scans, not PT. For a flasher that does not matter. For anything that counts time it does. Use this tick to build a seconds counter on a 10 ms task and every "second" lasts 1.02 s; after an hour the counter reads 3,529 instead of 3,600, more than a minute slow. On a 100 ms task it is ten minutes an hour. Running-hours meters, totalisers and shift timers should accumulate real elapsed time or read the controller's clock (chapter 16), never count self-reset ticks.

One flasher for the whole machine

Build the flasher once, near the top of the program, and let every lamp and every HMI indication use the same bit. Give each alarm its own pair of timers and the beacons start in step and drift apart, because each pair started on a different scan. A panel where three lamps flash out of phase looks broken to an operator, and it is hard to read: you cannot tell at a glance whether two indications belong together.

The usual HMI convention is that flashing means "needs attention" and steady means "known about". An alarm lamp flashes while the alarm is unacknowledged and goes steady once someone has acknowledged it but the cause is still there:

AlarmLamp := AlarmActive AND (Acknowledged OR Flash1Hz);

One shared Flash1Hz bit and one line per lamp. Chapter 22 builds the acknowledged and active states that feed it.

The three timers on other platforms

The IEC names travel well. CODESYS, TwinCAT 3 and TIA Portal all call them TON, TOF and TP, with the same pins, and on an S7-1200 or S7-1500 the multi-instance types are TON_TIME, TOF_TIME and TP_TIME. The differences are in the details around them.

Rockwell is the exception, as it was for the TON. In ladder, the TOF instruction works on a TIMER tag and its bits read differently from the TON's: DN is TRUE while the rung is true and stays TRUE through the delay, TT is TRUE while the delay is running, and ACC counts up from zero after the rung goes false. So DN plays the part of the IEC Q. In ST and FBD the off-delay is TOFR on an FBD_TIMER tag, called the same way as TONR. There is no pulse timer instruction; you build one as shown earlier.

Siemens S7-300 and S7-400 programs that came across from STEP 7 often still use the S5 timers, and two of them sound like the IEC ones: S_OFFDT (off-delay) and S_PULSE (pulse). They are a separate family with their own inputs, a coarser S5TIME preset and their own rules about what the input does during the time. When you migrate one of those programs to an S7-1500, look up the exact behaviour of each S5 timer in the instruction help before you swap it for the IEC block that has a similar name. Do not assume the two are equivalent.

On every platform the rules from chapter 9 carry over unchanged: one instance per purpose, called every scan, condition in IN. A TOF called only while its input is TRUE is the worst version of the trap, because it never sees the falling edge at all and never runs on.

Power-up and the first scan

On the first scan after a power cut or a cold restart, the three timers do what their rules say, which is not always what the machine needs:

TimerIN FALSE on the first scanIN TRUE on the first scan
TONQ FALSE, idlestarts timing from this scan; Q comes on after PT
TOFQ FALSE: no run-on, whatever happened before the power cutQ TRUE at once
TPQ FALSE, idlefires a pulse, because its memory of IN starts FALSE

The TP row is the one that bites. A pulse timer on a signal that is already TRUE when the controller starts sees a rising edge on the first scan, so it fires. A lubrication pulse on "machine ready" gives an extra shot at every power-up. A horn pulse on "batch complete" sounds when the controller starts if the last batch finished before the power cut and the flag was retained. Usually harmless, sometimes not: an extra shot of a two-part adhesive or an extra dose of a chemical is a quality problem. Where it matters, gate the trigger with a first-scan flag or a "system ready" condition that only becomes TRUE after the program has had a few scans to settle.

None of this applies to timer instances you make retentive, which most platforms allow and which is almost never a good idea for TON, TOF or TP. A retained ET resumes timing a process that may have changed completely while the power was off.

Choosing between TON, TOF and TP

All three timers take the same pins. The difference is which question they answer, and almost every timing requirement in a specification is one of these sentences:

The spec saysTimerExample
"…only if it has lasted X"TONLow level for 3 s trips the pump
"…after X, start…"TONConveyor starts 2 s after the horn
"…keep … for X after it stops"TOFFan runs 2 min after the heater
"…for exactly X when…"TP1 s grease shot per press
"…for X after the last…"TOF on the eventBelt sleeps 4 s after the last carton
"…every X…"TON with feedback1 Hz beacon

The same input trace through all three, with PT = 300 ms (three scans):

Scan1234567891011121314151617
IN01100001111110000
TON.Q00000000001110000
TOF.Q01111101111111110
TP.Q01110001110000000

Read it row by row. The TON ignores the two-scan blip completely and only switches after the input has been on for three scans. The TOF follows every rise at once and adds three scans to every fall. The TP turns each rising edge into exactly three scans and ignores how long IN stays on.

One row of the decision table needs a closer look. A TOF fed by a short event becomes a retriggerable one-shot: every new event restarts the run-on, so Q stays on until the events stop. Roller conveyors with 24 V motor-driven rollers use exactly this to sleep a zone when no cartons arrive:

PROGRAM Main
VAR
    CartonEye : BOOL;   (* input: photo-eye at the zone entry *)
    Sleep     : TOF;
    Belt      : BOOL;   (* output: zone motor *)
END_VAR

Sleep(IN := CartonEye, PT := T#4s);   (* every carton restarts the run-on *)
Belt := Sleep.Q;

END_PROGRAM
Live example — open this page with JavaScript to run and edit it. Inputs you can drive: CartonEye.

Toggle CartonEye every couple of seconds and the belt never stops; leave it alone and the belt sleeps 4 s after the last carton cleared the eye. A TP cannot do this, because it would ignore the cartons that arrive during its pulse. When a requirement says "after the last one", reach for a TOF; when it says "exactly once per trigger", reach for a TP.

Work the sleep time out from the zone. The run-on has to be long enough for the last carton to clear the zone after it has passed the entry eye: zone length divided by belt speed, plus a margin. A 1.2 m zone at 0.4 m/s needs 3 s, so 4 s leaves a second of margin. Set it to 2 s and the last carton of every run stops half-way across the zone, where the next zone's eye cannot see it.

Next: counting things

Timers measure how long. The next question on every machine is how many: how many cartons in this layer, how many strokes since the last die change, how many bottles left in the batch. Chapter 11 covers the IEC counters CTU, CTD and CTUD, and the rule that decides whether any of them gives the right answer: count edges, not scans.

Practise this chapter