Migration and re-architecture of a discrete bottle-filling control system from OpenPLC to CODESYS V3.5, implemented in IEC 61131-3 Ladder Diagram with a deterministic finite-state machine
This project re-implements a discrete bottle-filling station inside the CODESYS V3.5 development environment using IEC 61131-3 Ladder Diagram (LD). The objective was to migrate a legacy OpenPLC application onto a vendor-grade IDE while improving program determinism, scan-cycle behavior, and maintainability for an industrial deployment context.
Rather than performing a one-to-one port of the original rungs, the control logic was re-architected around an explicit deterministic finite-state machine (FSM) with mutually exclusive states — Idle, Filling, Transfer, and Complete — and well-defined transition conditions. This approach mirrors industry-standard practice for sequential process control and produces predictable, auditable behavior across every PLC scan cycle.
Bottle-filling lines impose strict sequencing requirements: each bottle must index into position, dwell for a fixed fill duration governed by valve open-time, and then index out before the next unit is admitted. A naive implementation using direct sensor-to-output coupling is prone to several well-known failure modes — output chattering when the sensor signal is noisy, double-triggering when a bottle dwells across multiple scans, race conditions between the fill timer and the conveyor restart, and ambiguous machine state during fault recovery. The control system therefore needs a single source of truth for machine state, debounced and latched transitions, and unambiguous operator feedback at every phase of the cycle.
State retention is implemented using Set (S) and Reset (R) coils rather than self-holding seal-in branches, giving each state a distinct latch instruction and eliminating the ambiguity of overlapping seal contacts. Transitions are guarded by the conjunction of the current state coil and its trigger condition, which guarantees mutual exclusivity — at any scan, exactly one state coil is energized. This pattern aligns with how PLCopen and most industrial integrators structure sequential function logic, and it makes the program directly translatable to SFC if the project ever needs to scale to a more complex recipe.
VAR
(* Field I/O *)
Bottle_Sensor : BOOL; (* DI - bottle presence at fill nozzle *)
Fill_Valve : BOOL; (* DO - solenoid valve, active during fill *)
Conveyor_Motor : BOOL; (* DO - conveyor drive command *)
Fill_Complete : BOOL; (* Status flag for HMI / operator feedback *)
(* FSM state coils - mutually exclusive *)
State_Idle : BOOL := TRUE;
State_Filling : BOOL;
State_Transfer : BOOL;
State_Complete : BOOL;
(* IEC 61131-3 timer FB - 5 second fill dwell *)
Fill_Timer : TON;
END_VAR
Rung 1 - Entry transition (Idle -> Filling): | State_Idle Bottle_Sensor (S) State_Filling | |----| |----------| |--------------------------------------( )---------| Rung 2 - Reset Idle latch on the same transition: | State_Idle Bottle_Sensor (R) State_Idle | |----| |----------| |--------------------------------------( )---------| Rung 3 - Output coupling (valve follows state, not sensor): | State_Filling Fill_Valve | |------| |---------------------------------------------------( )-------| Rung 4 - Dwell timer driven by state coil: | State_Filling TON | |------| |--------------------------------IN Q------- | | T#5s--PT ET------- | Rung 5 - Exit transition (Filling -> Transfer on TON.Q): | State_Filling Fill_Timer.Q (S) State_Transfer | |------| |----------| |---------------------------------( )------------|
Validation was performed against CODESYS Control Win runtime using the IDE's online mode, which provides live power-flow visualization, real-time variable inspection, and the ability to force I/O independently of the underlying program logic. Each transition was exercised in isolation and the outputs verified against the FSM specification.
The most substantial challenge was reconciling behavioral differences between the OpenPLC runtime and the CODESYS scan engine. Online monitoring semantics, the behavior of forced variables, and the way Set/Reset coils interact with task scheduling all differ between the two environments, which required rebuilding my mental model of how the program executes per scan cycle and adapting my debugging workflow accordingly.
A second challenge was eliminating instability that originated from coupling outputs directly to sensor inputs in the original implementation. That pattern is fragile under noisy field signals and produces non-deterministic behavior at state boundaries. Re-architecting the program around an explicit FSM with latched states and guarded transitions removed this entire class of failure mode and produced a control program with predictable, repeatable cycle behavior suitable for an industrial environment.