PLC Conveyor Sorting System: Complete Structured Text Tutorial with Sensor Logic & Diverter Control

Program a multi-lane conveyor sorting system from scratch — with photoelectric sensor logic, product tracking arrays, pneumatic diverter timing, and reject bin management in Structured Text.

Conveyor Sorting System Overview

Automated sorting systems are the backbone of logistics, food processing, packaging, and manufacturing. A typical system uses sensors to identify products, then activates diverters (pneumatic pushers, pop-up wheels, or tilt trays) to route items to the correct lane.

System Architecture

                    ┌─── Lane 1 (Small)
                    │
Feed ──► Scanner ──►├─── Lane 2 (Medium)
Belt     Station    │
                    ├─── Lane 3 (Large)
                    │
                    └─── Lane 4 (Reject)

Components We'll Program

ComponentI/O TypeDescription
Main conveyor motorDOVFD-controlled belt
Infeed photoeyeDIDetects product arrival
Height sensorAIMeasures product height
Weight scaleAIIn-motion weighing
Barcode scannerSerial/EthernetProduct identification
Diverter 1-3DOPneumatic pusher solenoids
Diverter confirm 1-3DIPusher extended feedback
Lane full sensors 1-4DIDownstream backup detection
E-StopDIEmergency stop circuit
Stack lightDO × 3Green/Yellow/Red status

Data Structures

Product Tracking

TYPE ProductInfo :
STRUCT
    ID            : INT;
    Barcode       : STRING(20);
    Height        : REAL;          // mm
    Weight        : REAL;          // grams
    Category      : INT;           // 1=Small, 2=Medium, 3=Large, 0=Unknown
    TargetLane    : INT;           // 1-3, 4=reject
    Position      : REAL;          // mm from scanner station
    Sorted        : BOOL;
    Valid         : BOOL;          // Tracking slot in use
END_STRUCT;
END_TYPE

TYPE DiverterStatus : STRUCT SolenoidCmd : BOOL; Extended : BOOL; Retracted : BOOL; CycleCount : DINT; // Maintenance tracking FaultFlag : BOOL; END_STRUCT; END_TYPE

TYPE SystemCounters : STRUCT TotalProducts : DINT; SortedLane1 : DINT; SortedLane2 : DINT; SortedLane3 : DINT; Rejected : DINT; Throughput : REAL; // Products per minute END_STRUCT; END_TYPE

Product Detection & Classification

FUNCTION_BLOCK FB_ProductClassifier
VAR_INPUT
    InfeedSensor    : BOOL;
    HeightRaw       : INT;         // Analog 0-27648
    WeightRaw       : INT;
    BarcodeReady    : BOOL;
    BarcodeData     : STRING(20);
END_VAR
VAR_OUTPUT
    NewProduct      : BOOL;
    ProductData     : ProductInfo;
END_VAR
VAR
    PrevSensor      : BOOL := FALSE;
    RisingEdge      : BOOL;
    Height_mm       : REAL;
    Weight_g        : REAL;
    NextID          : INT := 1;
    SmallMaxHeight  : REAL := 100.0;
    SmallMaxWeight  : REAL := 500.0;
    MediumMaxHeight : REAL := 250.0;
    MediumMaxWeight : REAL := 2000.0;
END_VAR

RisingEdge := InfeedSensor AND NOT PrevSensor; PrevSensor := InfeedSensor; NewProduct := FALSE;

IF RisingEdge THEN Height_mm := (INT_TO_REAL(HeightRaw) / 27648.0) * 500.0; Weight_g := (INT_TO_REAL(WeightRaw) / 27648.0) * 5000.0; ProductData.ID := NextID; NextID := NextID + 1; IF NextID > 32767 THEN NextID := 1; END_IF; ProductData.Height := Height_mm; ProductData.Weight := Weight_g; ProductData.Position := 0.0; ProductData.Sorted := FALSE; ProductData.Valid := TRUE; IF Height_mm <= SmallMaxHeight AND Weight_g <= SmallMaxWeight THEN ProductData.Category := 1; ProductData.TargetLane := 1; ELSIF Height_mm <= MediumMaxHeight AND Weight_g <= MediumMaxWeight THEN ProductData.Category := 2; ProductData.TargetLane := 2; ELSIF Height_mm > MediumMaxHeight OR Weight_g > MediumMaxWeight THEN ProductData.Category := 3; ProductData.TargetLane := 3; ELSE ProductData.Category := 0; ProductData.TargetLane := 4; END_IF; IF BarcodeReady THEN ProductData.Barcode := BarcodeData; END_IF; NewProduct := TRUE; END_IF; END_FUNCTION_BLOCK

Product Position Tracking

Products move along the conveyor at belt speed. We track each product's position and fire diverters when they reach the correct station:

PROGRAM ProductTracker
VAR
    TrackBuffer     : ARRAY[0..31] OF ProductInfo;
    BeltSpeed       : REAL := 500.0;   // mm/s
    ScanInterval    : REAL := 0.01;    // 10ms PLC scan
    
    Diverter1_Pos   : REAL := 2000.0;  // mm from scanner
    Diverter2_Pos   : REAL := 3500.0;
    Diverter3_Pos   : REAL := 5000.0;
    TriggerWindow   : REAL := 50.0;    // mm tolerance
    
    FireDiverter1   : BOOL := FALSE;
    FireDiverter2   : BOOL := FALSE;
    FireDiverter3   : BOOL := FALSE;
    
    i               : INT;
    TargetPos       : REAL;
END_VAR

FOR i := 0 TO 31 DO IF TrackBuffer[i].Valid AND NOT TrackBuffer[i].Sorted THEN TrackBuffer[i].Position := TrackBuffer[i].Position + (BeltSpeed * ScanInterval); CASE TrackBuffer[i].TargetLane OF 1: TargetPos := Diverter1_Pos; 2: TargetPos := Diverter2_Pos; 3: TargetPos := Diverter3_Pos; ELSE TargetPos := 6000.0; END_CASE; IF ABS(TrackBuffer[i].Position - TargetPos) <= TriggerWindow THEN CASE TrackBuffer[i].TargetLane OF 1: FireDiverter1 := TRUE; 2: FireDiverter2 := TRUE; 3: FireDiverter3 := TRUE; END_CASE; TrackBuffer[i].Sorted := TRUE; END_IF; IF TrackBuffer[i].Position > 7000.0 THEN TrackBuffer[i].Valid := FALSE; END_IF; END_IF; END_FOR; END_PROGRAM

Pneumatic Diverter Control with Fault Detection

FUNCTION_BLOCK FB_DiverterControl
VAR_INPUT
    Fire            : BOOL;
    ExtendedFB      : BOOL;
    RetractedFB     : BOOL;
    LaneFullSensor  : BOOL;
    Enable          : BOOL;
END_VAR
VAR_OUTPUT
    SolenoidOut     : BOOL;
    Fault           : BOOL;
    Busy            : BOOL;
    CycleCount      : DINT;
END_VAR
VAR
    State           : INT := 0;
    ExtendTimer     : TON;
    HoldTimer       : TON;
    RetractTimer    : TON;
    ExtendTimeout   : TIME := T#500ms;
    HoldTime        : TIME := T#300ms;
    RetractTimeout  : TIME := T#500ms;
END_VAR

Fault := FALSE;

IF NOT Enable THEN SolenoidOut := FALSE; State := 0; Busy := FALSE; RETURN; END_IF;

CASE State OF 0: // IDLE SolenoidOut := FALSE; Busy := FALSE; IF Fire AND NOT LaneFullSensor THEN State := 1; Busy := TRUE; CycleCount := CycleCount + 1; END_IF; 1: // EXTENDING SolenoidOut := TRUE; ExtendTimer(IN := TRUE, PT := ExtendTimeout); IF ExtendedFB THEN ExtendTimer(IN := FALSE, PT := T#0s); State := 2; ELSIF ExtendTimer.Q THEN Fault := TRUE; SolenoidOut := FALSE; ExtendTimer(IN := FALSE, PT := T#0s); State := 0; END_IF; 2: // HOLD SolenoidOut := TRUE; HoldTimer(IN := TRUE, PT := HoldTime); IF HoldTimer.Q THEN SolenoidOut := FALSE; HoldTimer(IN := FALSE, PT := T#0s); State := 3; END_IF; 3: // RETRACTING SolenoidOut := FALSE; RetractTimer(IN := TRUE, PT := RetractTimeout); IF RetractedFB THEN RetractTimer(IN := FALSE, PT := T#0s); State := 0; ELSIF RetractTimer.Q THEN Fault := TRUE; RetractTimer(IN := FALSE, PT := T#0s); State := 0; END_IF; END_CASE; END_FUNCTION_BLOCK

Throughput Monitoring

FUNCTION_BLOCK FB_ThroughputMonitor
VAR_INPUT
    ProductDetected : BOOL;
    ResetCounters   : BOOL;
END_VAR
VAR_OUTPUT
    ProductsPerMin  : REAL;
    ProductsPerHour : REAL;
    TotalCount      : DINT;
    PeakRate        : REAL;
END_VAR
VAR
    WindowCounts    : ARRAY[0..59] OF INT;
    WindowIndex     : INT := 0;
    SecondTimer     : TON;
    WindowTotal     : INT;
    CurrentSecCount : INT := 0;
    PrevDetected    : BOOL := FALSE;
    i               : INT;
END_VAR

IF ProductDetected AND NOT PrevDetected THEN CurrentSecCount := CurrentSecCount + 1; TotalCount := TotalCount + 1; END_IF; PrevDetected := ProductDetected;

SecondTimer(IN := TRUE, PT := T#1s); IF SecondTimer.Q THEN WindowCounts[WindowIndex] := CurrentSecCount; CurrentSecCount := 0; WindowIndex := WindowIndex + 1; IF WindowIndex > 59 THEN WindowIndex := 0; END_IF; SecondTimer(IN := FALSE, PT := T#0s); WindowTotal := 0; FOR i := 0 TO 59 DO WindowTotal := WindowTotal + WindowCounts[i]; END_FOR; ProductsPerMin := INT_TO_REAL(WindowTotal); ProductsPerHour := ProductsPerMin * 60.0; IF ProductsPerMin > PeakRate THEN PeakRate := ProductsPerMin; END_IF; END_IF;

IF ResetCounters THEN TotalCount := 0; PeakRate := 0.0; FOR i := 0 TO 59 DO WindowCounts[i] := 0; END_FOR; END_IF; END_FUNCTION_BLOCK

Complete Sorting System Main Program

PROGRAM ConveyorSortingSystem
VAR
    Classifier      : FB_ProductClassifier;
    Diverter        : ARRAY[1..3] OF FB_DiverterControl;
    Throughput      : FB_ThroughputMonitor;
    
    ConveyorMotor   : BOOL := FALSE;
    InfeedPhotoeye  : BOOL;
    HeightSensor    : INT;
    WeightSensor    : INT;
    EStop           : BOOL;
    
    DiverterFB_Ext  : ARRAY[1..3] OF BOOL;
    DiverterFB_Ret  : ARRAY[1..3] OF BOOL;
    LaneFull        : ARRAY[1..4] OF BOOL;
    
    Light_Green     : BOOL;
    Light_Yellow    : BOOL;
    Light_Red       : BOOL;
    
    SystemRunning   : BOOL := FALSE;
    AnyDiverterFault: BOOL := FALSE;
    AnyLaneFull     : BOOL := FALSE;
    i               : INT;
END_VAR

IF EStop THEN SystemRunning := FALSE; ConveyorMotor := FALSE; Light_Red := TRUE; Light_Green := FALSE; Light_Yellow := FALSE; RETURN; END_IF;

ConveyorMotor := SystemRunning;

Classifier( InfeedSensor := InfeedPhotoeye, HeightRaw := HeightSensor, WeightRaw := WeightSensor );

Throughput(ProductDetected := Classifier.NewProduct);

FOR i := 1 TO 3 DO Diverteri; END_FOR;

AnyDiverterFault := Diverter[1].Fault OR Diverter[2].Fault OR Diverter[3].Fault; AnyLaneFull := LaneFull[1] OR LaneFull[2] OR LaneFull[3] OR LaneFull[4];

Light_Green := SystemRunning AND NOT AnyDiverterFault AND NOT AnyLaneFull; Light_Yellow := SystemRunning AND (AnyLaneFull OR AnyDiverterFault); Light_Red := NOT SystemRunning OR AnyDiverterFault; END_PROGRAM

Performance Optimization Tips

1. Encoder-Based Tracking

Use a conveyor encoder instead of estimating position from belt speed. Encoders account for belt slip and VFD speed changes:

Product.Position := Product.Position + 
    (INT_TO_REAL(EncoderDelta) * mmPerPulse);

2. Diverter Pre-Trigger Compensation

Pneumatic cylinders take 50-150ms to extend. Fire the diverter early to compensate:

TriggerPos := DiverterPos - (BeltSpeed * 0.100);  // 100ms advance

3. Gap Control

Ensure minimum spacing between products so diverters can retract:

MinGap_mm := 0.800 * BeltSpeed;  // 800ms cycle × speed
IF GapToNextProduct < MinGap_mm THEN
    SlowInfeedConveyor := TRUE;
END_IF;

Summary

A conveyor sorting system brings together nearly every PLC programming skill: sensor edge detection for product arrival, analog scaling for height/weight measurement, array-based tracking for following products along the belt, state machines for diverter control with fault detection, and throughput monitoring for production KPIs. The modular approach — separate FBs for classification, tracking, diverter control, and monitoring — makes the system maintainable and testable from simple 2-lane systems to complex multi-tier sorting operations.