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.

🏭 Packaging Machine Architecture

A typical packaging machine combines continuous motion (film transport, conveyors) with discrete events (seal, cut, fill, label) that must happen at precise positions in the machine cycle. This is fundamentally different from process control — timing is measured in degrees of machine rotation, not seconds.

The Machine Cycle Concept

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

📐 Cam-Based Sequencing

Instead of timers, packaging machines use virtual cam switches — outputs that turn on/off at specific 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;

Typical 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;

📦 Product Tracking Array

On high-speed lines, multiple products are in the machine simultaneously at different stages. A tracking shift register follows each product:

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 must be 100% reliable — a missed reject means a bad product reaches the customer:

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;

📊 Real-Time OEE Calculation

OEE (Overall Equipment Effectiveness) is the gold standard KPI for packaging lines:

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 Level | Rating | Typical For | | 85%+ | World-class | Best-in-class automated lines | | 60–85% | Good | Well-maintained production | | 40–60% | Average | Opportunity for improvement | | < 40% | Poor | Significant losses — investigate root causes |

Summary

| Concept | Implementation | | Cam sequencing | Position-based outputs with speed compensation | | Product tracking | Shift register following products through stations | | Quality inspection | Station-by-station checks updating product records | | Reject system | Confirmed ejection with missed-reject alarm | | OEE calculation | Real-time Availability × Performance × Quality | | Registration | Print mark detection with correction offset |

Packaging machine programming is where precision meets speed. Every millisecond of timing, every degree of cam position, and every product tracked through the machine directly impacts your customer's bottom line.