Packaging
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_TYPEFUNCTION_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_TYPEFUNCTION_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_VARRejectCmd := 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 × QualityAvailability = 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.