Draw the path from an operator command to a physical terminal. Every boundary should have an owner and a meaning. At the top, Start is a request that can be refused with a reason. In the coordinator, an accepted request becomes remembered work. In the device layer, the request meets ordinary permissions and response supervision. At the boundary, one mapping assigns the physical output. You should be able to follow the return path of evidence just as clearly.
Design for the person investigating a stopped machine
Six months after commissioning, a technician opens your project because a conveyor will not start. They should be able to find the command owner, see the refused permission, trace that permission to its source and understand the recovery action. That is a more useful test of structure than counting folders.
A maintainable project makes important relationships easy to follow. It does not hide simple actions behind endless abstraction, and it does not let every routine write every variable. The right amount of structure depends on the machine, but ownership and direction of data flow remain useful at every size.
PLCopen publishes software construction guidance covering coding, reusable libraries and related practices. Use it alongside the site's conventions and the target platform's rules. The structure below is an original teaching arrangement, not a claim that one folder tree is mandated by a standard. Explore PLCopen's software construction guidelines.
Separate the reasons code changes
Hardware can change without the production sequence changing. A recipe can change without the valve wiring changing. An HMI can gain a new screen without becoming the owner of a motor output. Organise the project so those changes have clear boundaries.
One useful flow is:
Physical inputs and network data
↓ map, decode, validate, qualify
Equipment observations
↓ interpret state and accept requests
Equipment modules ↔ machine or batch coordinator
↓ resolved ordinary commands
Output mapping and device adapters
HMI proposals → validated command interface
Status and evidence → HMI, alarms and records
The arrows represent ownership and responsibility, not necessarily separate PLC tasks. A small machine can implement this flow inside one cyclic task. Splitting it across tasks introduces data-consistency and timing questions that need justification.
One writer makes a value explainable
If five routines assign ConveyorRun, the watch window shows only the final result. The reason depends on execution order, which may be far from the routine you are reading. Instead, give the automatic sequence, manual interface and maintenance logic separate request variables. One arbitration owner selects the eligible request. One equipment owner applies its documented process permissions. One mapping location writes the physical output.
The rule is not “a variable can appear on only one line.” A clearly bounded state machine may assign its own state in several branches. The rule is that one component owns the decision and all its writes are understandable together.
This ST body excerpt shows an explicit mode selection. It assumes mode values are represented by a declared enumeration and commands have already passed their input-boundary validation.
SelectedRunRequest := FALSE;
SelectedSpeed_mm_s := 0.0;
CASE OperatingMode OF
ModeAutomatic:
SelectedRunRequest := AutoRunRequest;
SelectedSpeed_mm_s := AutoSpeed_mm_s;
ModeManual:
SelectedRunRequest := ManualRunRequest;
SelectedSpeed_mm_s := ManualSpeed_mm_s;
END_CASE;
The default assignments define behaviour for other modes and unexpected enum values. A complete machine also detects invalid state rather than merely staying quiet. Explicit defaults prevent old requests from surviving because a branch did not execute this scan.
Names should reveal the kind of fact
RunRequest, RunCommand and RunningFeedback describe three different things. FillTimeoutPreset and FillElapsed separate configuration from observation. Weight_kg carries units; WeightValid carries whether the value can support a decision.
Choose a consistent convention for scope, data types and device identity. Do not force every name to encode information the IDE already shows if it makes the physical meaning unreadable. Long names are not automatically clear: BOOL_Station_One_Internal_Auto_Run_Enable_Flag can still conceal whether it is a request or a permission.
Use constants or enumerations for meaningful states and result codes. An unexplained State = 37 creates a dependency on a programmer's memory. Put the description where future maintainers will find it: type definition, transition table and visible state label.
Configuration deserves a review path
Separate commissioned settings from live state. Validate ranges and relationships at startup and before acceptance. Record a version for the configuration format, especially if retained structures or external recipe files can outlive a software update.
A timeout is not just a number in a declaration. Record what physical allowance it represents and which test established it. A future maintainer should know whether increasing it permits slower normal operation or merely postpones detection of a failure.
Avoid machine-name special cases inside reusable blocks. If one conveyor needs a different acceleration allowance, pass a documented configuration value. If it has genuinely different behaviour, give that behaviour an explicit interface or a different module. IF MachineName = 'Line7' hides a design decision in a string comparison.
Test contracts, then integration
Unit-level tests can exercise a calculation, a state transition or a block with simulated feedback. A motor block test might request a start, withhold feedback and verify the timeout and restart policy. A scaling test should cover endpoints, midpoint, invalid configuration and bad quality.
Integration tests ask whether the assembled components agree. Does the sequence wait for the equipment result it actually receives? Does the HMI display accepted settings rather than unaccepted edits? Does a network outage invalidate permission before the sequence uses stale data?
Keep test cases as short stories with an expected trace. “Given the machine is idle with no part, when Start is requested, it remains idle and reports missing part” is easier to review than a giant screenshot of green bits. Tests should expose consequences, not merely repeat the same expression as the implementation.
Make the release reproducible
Record source revision, controller and device configuration, library versions, hardware identity and the validated build environment. Keep a recoverable previous release and a documented method for confirming which version is running. An exported project on somebody's desktop is not a release process.
Review online changes according to the plant's change procedure. A small edit can alter initialisation, memory layout, timing or active equipment behaviour. “Only one line” describes typing effort, not operational consequence.
The handover should include the behaviour specification, I/O and network mapping, alarm response information, approved settings, tests and known limitations. If a recovery procedure requires a programmer to force a hidden state bit, the implementation or operating procedure is not ready for normal handover.
Try it
An HMI routine writes ValveOpen := ManualButton. Later, an automatic sequence writes ValveOpen := FillStep. A fault routine at the end writes false only while a fault is active. The machine sometimes ignores manual commands and sometimes opens immediately after a fault clears. Refactor the ownership without changing the physical I/O address.
Work through the answer
Replace direct writes with ManualOpenRequest and AutoOpenRequest. A mode owner selects exactly one eligible request and defines what happens when mode changes. A valve module applies the process permissions, fault latch and explicit restart policy. The final output mapper assigns the resulting command to the original I/O address once.
Now test manual, automatic, invalid mode, active fault and cleared fault. A cleared fault should follow the specified restart contract rather than exposing whichever request happened to remain true. The address did not change; the reason for its value became understandable. That is the central benefit of a maintainable project.