PLC Packaging Machine Programming: Cam Sequencing, Reject Systems & OEE in Structured Text

High-speed packaging demands precise timing, reliable product tracking, and zero-tolerance reject systems. Build production-grade packaging machine logic with cam sequencing, tracking arrays, and real-time OEE in Structured Text.

How a packaging machine is put together

A typical packaging machine combines continuous motion (film transport, conveyors) with discrete events (seal, cut, fill, label) that have to happen at exact positions in the machine cycle. That is a different world from process control: you measure timing in degrees of machine rotation, not in seconds.

The machine cycle in degrees

Machine Cycle (0° to 360°)
│
├── 0°–30°     : Film advance / index
├── 30°–60°    : Product drop / fill
├── 60°–120°   : Cross-seal (jaws closed)
├── 120°–150°  : Seal cooling
├── 150°–180°  : Jaw open / package release
├── 180°–270°  : Label application
├── 270°–330°  : Inspection / vision check
├── 330°–360°  : Reject / accept gate
└── 360° = 0°  : Cycle repeats

Sequencing with virtual cams

Packaging machines sequence with virtual cam switches instead of timers. A cam switch is an output that turns on and off at set encoder positions:

TYPE CamSwitch :
STRUCT
    OnPosition   : REAL;       // Degrees where output turns ON
    OffPosition  : REAL;       // Degrees where output turns OFF
    Output       : BOOL;       // Current state
    Enabled      : BOOL;       // Master enable
    SpeedComp    : REAL;       // Advance ON position at high speed (degrees)
END_STRUCT;
END_TYPE

FUNCTION_BLOCK FB_CamController
VAR_INPUT
    MachineAngle    : REAL;       // 0.0 to 360.0 from encoder
    MachineSpeed    : REAL;       // RPM for speed compensation
    Enable          : BOOL;
END_VAR
VAR
    Cams            : ARRAY[1..16] OF CamSwitch;
    NumCams         : INT := 8;
END_VAR
VAR
    i               : INT;
    adjustedOn      : REAL;
END_VAR

IF NOT Enable THEN
    FOR i := 1 TO NumCams DO
        Cams[i].Output := FALSE;
    END_FOR;
    RETURN;
END_IF;

FOR i := 1 TO NumCams DO
    IF NOT Cams[i].Enabled THEN
        Cams[i].Output := FALSE;
    ELSE
        // Speed compensation: advance ON point at higher speeds
        // to account for actuator response time
        adjustedOn := Cams[i].OnPosition - (Cams[i].SpeedComp * MachineSpeed / 100.0);
        IF adjustedOn < 0.0 THEN adjustedOn := adjustedOn + 360.0; END_IF;
        
        // Handle wrap-around (e.g., ON=350°, OFF=10°)
        IF adjustedOn < Cams[i].OffPosition THEN
            Cams[i].Output := (MachineAngle >= adjustedOn) AND (MachineAngle < Cams[i].OffPosition);
        ELSE
            // Wrap-around case
            Cams[i].Output := (MachineAngle >= adjustedOn) OR (MachineAngle < Cams[i].OffPosition);
        END_IF;
    END_IF;
END_FOR;

Cam setup for a VFFS machine

// Vertical Form Fill Seal (VFFS) cam configuration
CamCtrl.Cams[1].OnPosition := 0.0;     CamCtrl.Cams[1].OffPosition := 30.0;    // Film pull
CamCtrl.Cams[2].OnPosition := 35.0;    CamCtrl.Cams[2].OffPosition := 55.0;    // Product fill
CamCtrl.Cams[3].OnPosition := 60.0;    CamCtrl.Cams[3].OffPosition := 120.0;   // Cross-seal jaws
CamCtrl.Cams[4].OnPosition := 70.0;    CamCtrl.Cams[4].OffPosition := 115.0;   // Seal heater
CamCtrl.Cams[5].OnPosition := 180.0;   CamCtrl.Cams[5].OffPosition := 260.0;   // Labeler
CamCtrl.Cams[6].OnPosition := 270.0;   CamCtrl.Cams[6].OffPosition := 320.0;   // Vision trigger
CamCtrl.Cams[7].OnPosition := 330.0;   CamCtrl.Cams[7].OffPosition := 355.0;   // Reject gate

// Speed compensation: seal jaws need 5° advance per 100 RPM
CamCtrl.Cams[3].SpeedComp := 5.0;
CamCtrl.Cams[4].SpeedComp := 5.0;

Tracking products through the machine

On a high-speed line several products are in the machine at once, each at a different stage. A tracking shift register follows each one:

TYPE ProductRecord :
STRUCT
    Present      : BOOL;        // Product exists at this station
    Weight       : REAL;        // Filled weight
    SealOK       : BOOL;        // Seal integrity check passed
    LabelOK      : BOOL;        // Label present and readable
    VisionPass   : BOOL;        // Vision inspection passed
    RejectCode   : INT;         // 0=good, >0=reject reason
    BatchID      : DINT;
END_STRUCT;
END_TYPE

FUNCTION_BLOCK FB_ProductTracker
VAR
    Track       : ARRAY[1..20] OF ProductRecord;   // 20 stations in machine
    NumStations : INT := 12;
END_VAR

// Shift register: advance all products by one station
// Called once per machine cycle (at 0° or cycle-complete signal)
VAR_INPUT
    AdvancePulse : BOOL;
    NewProduct   : BOOL;         // Product entering station 1
    NewWeight    : REAL;
END_VAR
VAR
    i : INT;
END_VAR

IF AdvancePulse THEN
    // Shift everything forward (station N → station N+1)
    FOR i := NumStations TO 2 BY -1 DO
        Track[i] := Track[i-1];
    END_FOR;
    
    // Load new product at station 1
    Track[1].Present := NewProduct;
    Track[1].Weight := NewWeight;
    Track[1].SealOK := FALSE;
    Track[1].LabelOK := FALSE;
    Track[1].VisionPass := FALSE;
    Track[1].RejectCode := 0;
    Track[1].BatchID := CurrentBatchID;
END_IF;

Quality checks at each station

// Station 4: Checkweigher
IF Track[4].Present THEN
    IF Track[4].Weight < MinWeight OR Track[4].Weight > MaxWeight THEN
        Track[4].RejectCode := 1;    // Weight out of tolerance
    END_IF;
END_IF;

// Station 7: Seal inspection
IF Track[7].Present THEN
    Track[7].SealOK := SealInspectionSensor;
    IF NOT Track[7].SealOK THEN
        Track[7].RejectCode := 2;    // Seal failure
    END_IF;
END_IF;

// Station 9: Vision system
IF Track[9].Present THEN
    Track[9].VisionPass := VisionSystem_Result;
    Track[9].LabelOK := VisionSystem_LabelOK;
    IF NOT Track[9].VisionPass THEN
        Track[9].RejectCode := 3;    // Vision reject
    END_IF;
END_IF;

Reject station logic

The reject system has to work 100% of the time, because a missed reject means a bad product reaches the customer. So the logic below does not stop at firing the reject: it waits for a sensor to confirm the product left the line, and raises an alarm if it did not.

FUNCTION_BLOCK FB_RejectStation
VAR_INPUT
    Product       : ProductRecord;
    StationIndex  : INT;          // Which tracking station is the reject at
    RejectConfirm : BOOL;         // Sensor confirming product was ejected
END_VAR
VAR_OUTPUT
    RejectCmd     : BOOL;         // Activate reject mechanism
    RejectActive  : BOOL;
    MissedReject  : BOOL;         // CRITICAL: product wasn't ejected
    RejectCount   : DINT;
    RejectByCode  : ARRAY[1..10] OF DINT;  // Count per reject reason
END_VAR
VAR
    tmrReject     : TON;
    tmrConfirm    : TON;
    WaitingConfirm: BOOL;
END_VAR

RejectCmd := FALSE;
MissedReject := FALSE;

IF Product.Present AND Product.RejectCode > 0 THEN
    // Product needs rejecting
    RejectCmd := TRUE;
    RejectActive := TRUE;
    WaitingConfirm := TRUE;
    RejectCount := RejectCount + 1;
    
    // Track reject reasons
    IF Product.RejectCode >= 1 AND Product.RejectCode <= 10 THEN
        RejectByCode[Product.RejectCode] := RejectByCode[Product.RejectCode] + 1;
    END_IF;
END_IF;

// Verify reject actually happened
IF WaitingConfirm THEN
    tmrConfirm(IN := TRUE, PT := T#500MS);   // 500ms window to confirm
    
    IF RejectConfirm THEN
        // Product confirmed ejected
        WaitingConfirm := FALSE;
        tmrConfirm(IN := FALSE);
    ELSIF tmrConfirm.Q THEN
        // MISSED REJECT — critical alarm
        MissedReject := TRUE;
        WaitingConfirm := FALSE;
        tmrConfirm(IN := FALSE);
        // Stop the line or activate secondary reject
    END_IF;
END_IF;

Calculating OEE in real time

OEE (Overall Equipment Effectiveness) is the standard KPI for packaging lines. It multiplies three ratios:

OEE = Availability × Performance × Quality

Availability = Run Time / Planned Production Time
Performance  = (Ideal Cycle Time × Total Count) / Run Time
Quality      = Good Count / Total Count

FUNCTION_BLOCK FB_OEECalculator
VAR_INPUT
    MachineRunning    : BOOL;
    PlannedProduction : BOOL;     // Shift is active
    CycleComplete     : BOOL;     // One product completed
    ProductGood       : BOOL;     // Product passed all inspections
    IdealCycleTime    : REAL;     // Seconds per unit at rated speed
END_VAR
VAR_OUTPUT
    OEE_Pct           : REAL;
    Availability_Pct  : REAL;
    Performance_Pct   : REAL;
    Quality_Pct       : REAL;
    GoodCount         : DINT;
    TotalCount        : DINT;
    RejectCount       : DINT;
    DowntimeMinutes   : REAL;
END_VAR
VAR
    PlannedTimeSec    : REAL := 0.0;
    RunTimeSec        : REAL := 0.0;
    scanTimeSec       : REAL := 0.01;
END_VAR

// Accumulate time
IF PlannedProduction THEN
    PlannedTimeSec := PlannedTimeSec + scanTimeSec;
    
    IF MachineRunning THEN
        RunTimeSec := RunTimeSec + scanTimeSec;
    END_IF;
END_IF;

// Count products (rising edge)
IF CycleComplete THEN
    TotalCount := TotalCount + 1;
    IF ProductGood THEN
        GoodCount := GoodCount + 1;
    ELSE
        RejectCount := RejectCount + 1;
    END_IF;
END_IF;

// Calculate OEE components
IF PlannedTimeSec > 0.0 THEN
    // Availability
    Availability_Pct := (RunTimeSec / PlannedTimeSec) * 100.0;
    DowntimeMinutes := (PlannedTimeSec - RunTimeSec) / 60.0;
    
    // Performance
    IF RunTimeSec > 0.0 THEN
        Performance_Pct := ((IdealCycleTime * DINT_TO_REAL(TotalCount)) / RunTimeSec) * 100.0;
        IF Performance_Pct > 100.0 THEN Performance_Pct := 100.0; END_IF;
    END_IF;
    
    // Quality
    IF TotalCount > 0 THEN
        Quality_Pct := (DINT_TO_REAL(GoodCount) / DINT_TO_REAL(TotalCount)) * 100.0;
    END_IF;
    
    // OEE
    OEE_Pct := (Availability_Pct / 100.0)
             * (Performance_Pct / 100.0)
             * (Quality_Pct / 100.0)
             * 100.0;
END_IF;

OEE benchmarks

OEE LevelRatingTypical For
85%+World-classBest-in-class automated lines
60–85%GoodWell-maintained production
40–60%AverageOpportunity for improvement
< 40%PoorSignificant losses, investigate root causes

The techniques at a glance

ConceptImplementation
Cam sequencingPosition-based outputs with speed compensation
Product trackingShift register following products through stations
Quality inspectionStation-by-station checks updating product records
Reject systemConfirmed ejection with missed-reject alarm
OEE calculationReal-time Availability × Performance × Quality
On a packaging machine, every millisecond of timing, every degree of cam position and every product tracked through the machine shows up directly in your customer's costs.