Function Blocks
PLC Counter Programming: CTU, CTD & CTUD Examples in Structured Text
Learn how to use CTU, CTD, and CTUD counters in Structured Text with practical industrial examples — from part counting to batch control.
The three IEC 61131-3 counters
Counters are one of the basic building blocks of a PLC program. The IEC 61131-3 standard defines three counter function blocks, and you will use all of them:
| Counter | Full Name | Purpose |
|---|---|---|
| CTU | Count Up | Increments on each rising edge |
| CTD | Count Down | Decrements on each rising edge |
| CTUD | Count Up/Down | Combines both directions |
CTU: count up
The CTU counter increments its CV (Current Value) each time the CU input sees a rising edge. When CV reaches the preset PV, the output Q goes TRUE.
PROGRAM PartCounter
VAR
PartSensor : BOOL := FALSE;
ResetButton : BOOL := FALSE;
Counter : CTU;
PartsCount : INT := 0;
BatchComplete : BOOL := FALSE;
END_VAR
Counter(CU := PartSensor, RESET := ResetButton, PV := 50);
PartsCount := Counter.CV;
BatchComplete := Counter.Q;
// When 50 parts counted, signal batch complete
IF BatchComplete THEN
// Trigger conveyor stop, alert operator
END_IF;
CTU inputs and outputs
CU: the count input. It increments on a rising edge only.RESET: sets CV back to 0.PV: the preset value (target count).Q: TRUE when CV >= PV.CV: the current count value.CTD: count down
The CTD counter decrements from a loaded value. Use it to track how many items remain, or to run a countdown sequence.
PROGRAM MaterialTracker
VAR
ItemRemoved : BOOL := FALSE;
LoadParts : BOOL := FALSE;
DownCounter : CTD;
Remaining : INT := 0;
BinEmpty : BOOL := FALSE;
END_VAR
DownCounter(CD := ItemRemoved, LOAD := LoadParts, PV := 100);
Remaining := DownCounter.CV;
BinEmpty := DownCounter.Q;
IF BinEmpty THEN
// Request material refill
END_IF;
CTD inputs and outputs
CD: the count down input. It decrements on a rising edge.LOAD: loads PV into CV (resets the count to full).Q: TRUE when CV <= 0.Typical jobs for a CTD are inventory tracking, magazine feeders and remaining-cycle counters.
CTUD: count up and down
The CTUD counts in both directions. You need it wherever items move both ways or enter and leave a zone, such as a reversible conveyor or a buffer zone.
PROGRAM BufferZoneTracker
VAR
ItemEnters : BOOL := FALSE;
ItemExits : BOOL := FALSE;
ResetCount : BOOL := FALSE;
LoadCount : BOOL := FALSE;
Buffer : CTUD;
ItemsInZone : INT := 0;
ZoneFull : BOOL := FALSE;
ZoneEmpty : BOOL := FALSE;
END_VAR
Buffer(
CU := ItemEnters,
CD := ItemExits,
RESET := ResetCount,
LOAD := LoadCount,
PV := 20
);
ItemsInZone := Buffer.CV;
ZoneFull := Buffer.QU; // TRUE when CV >= PV
ZoneEmpty := Buffer.QD; // TRUE when CV <= 0
IF ZoneFull THEN
// Stop infeed conveyor
ELSIF ZoneEmpty THEN
// Signal upstream to send more
END_IF;
Example: batch counter with reject tracking
This example uses two CTU counters, one for good parts and one for rejects, and works out the reject rate from them:
PROGRAM BatchProduction
VAR
GoodPartSensor : BOOL := FALSE;
RejectSensor : BOOL := FALSE;
NewBatchCmd : BOOL := FALSE;
GoodCounter : CTU;
RejectCounter : CTU;
GoodParts : INT := 0;
RejectParts : INT := 0;
TotalParts : INT := 0;
RejectRate : REAL := 0.0;
BatchDone : BOOL := FALSE;
END_VAR
GoodCounter(CU := GoodPartSensor, RESET := NewBatchCmd, PV := 1000);
RejectCounter(CU := RejectSensor, RESET := NewBatchCmd, PV := 100);
GoodParts := GoodCounter.CV;
RejectParts := RejectCounter.CV;
TotalParts := GoodParts + RejectParts;
BatchDone := GoodCounter.Q;
IF TotalParts > 0 THEN
RejectRate := INT_TO_REAL(RejectParts) / INT_TO_REAL(TotalParts) * 100.0;
END_IF;
// Alarm if reject rate exceeds 5%
IF RejectRate > 5.0 THEN
// Trigger quality alert
END_IF;
Common counter mistakes
Try it in the editor
Build a part counting system in the online PLC simulator. Start with a single CTU, then add reject tracking with a second counter.