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:

CounterFull NamePurpose
CTUCount UpIncrements on each rising edge
CTDCount DownDecrements on each rising edge
CTUDCount Up/DownCombines 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

  • Forgetting that counters count rising edges. If your input stays TRUE, it counts only once.
  • Leaving out the reset. Always give the counter a reset path, especially a CTU in continuous production.
  • Overflow. INT counters max out at 32767, so use DINT or LINT for large counts.
  • Using a counter as a timer. A counter counts events, not time; use TON/TOF for time-based logic.
  • 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.