PLC Code Organization Best Practices
How you organise PLC code matters more than which language you use. A well-organised project takes weeks to commission and minutes to debug; a poorly-organised one becomes a brownfield horror story years later. This guide covers the patterns that distinguish maintainable PLC code from spaghetti — routine structure, naming, function block reuse, code review, and version control.
Routine structure: one routine per equipment
The single most important organisational principle: one routine per equipment item.
- Pump P-101 has its own routine that handles its start/stop, alarms, runtime totaliser, and faults.
- Conveyor C-201 has its own routine.
- Mixer M-301 has its own routine.
- The main routine calls these in order, passing only mode/recipe state.
Why: changes to pump P-101 don't touch the conveyor code. Bugs can be isolated. Code review focuses on the changed routine. New engineers can understand one routine at a time.
Reuse via AOIs / Function Blocks
If you have 20 pumps with identical control behaviour, you don't copy/paste 20 pump routines. You write one reusable Function Block (Add-On Instruction in Allen-Bradley terminology) and call it 20 times with different parameters.
(* Studio 5000 ST — single AOI handles every pump in the plant *)
PumpControl_AOI(
Tag := P101,
Run_Cmd := Recipe.P101_Run,
Aux_FB := P101_AUX_FB,
OL_Trip := P101_OL,
Status => P101_Stat
);
PumpControl_AOI(
Tag := P102,
Run_Cmd := Recipe.P102_Run,
Aux_FB := P102_AUX_FB,
OL_Trip := P102_OL,
Status => P102_Stat
);Benefits: bug fixes propagate to every pump automatically; the AOI/FB is the documented behaviour; new pumps need one line of code instead of a routine.
Build a library: standard FBs for pump, motor with VFD, valve, motor with soft starter, conveyor, two-position cylinder, three-position selector, etc. Reuse across projects.
Descriptive naming conventions
Tag names are documentation. Adopt one convention and enforce it.
Names should convey intent and direction
- ✅
E_Stop_OK— TRUE means E-stop NOT pressed (system OK to run) - ❌
E_Stop— does TRUE mean pressed or not pressed? Ambiguous. - ✅
Conveyor1_RunCmd— clearly an output command, not a status - ❌
M01.0— meaningless without a separate decoder spreadsheet - ✅
Tank1_Level_Pct— what it is, where, in what units - ❌
DB1.DBW10— same data, zero documentation
Standard suffixes
_Cmd— output command (Pump1_Cmd)_FB— feedback (Pump1_FB)_Status— status word_OK— boolean, TRUE = healthy_Fault— boolean, TRUE = fault active_Pct,_DegC,_Bar,_RPM— engineering units_SP— setpoint_PV— process variable
Comments at three levels
- Routine header — what this routine controls, what it depends on, who modified it last and why.
- Rung comment — why this rung exists, especially for interlocks. Reference the HAZOP item or FDS section that mandated it.
- Tag description — what the tag means in plant terms, including units. Visible in monitor view, HMI binding, and historian.
(* Routine: PumpControl_P101
Equipment: Tank-1 Outlet Pump
Last modified: 2026-04-15 by JM (MOC #2026-019)
Reason for change: added run-on cooling per HAZOP item 4.7
Calls: PumpControl_AOI v2.3 *)
(* Rung 8 — anti-deadhead interlock
Per HAZOP item 4.2: pump must not run if outlet valve closed
for more than 30 sec (overheat risk).
FDS section: 7.3.2 *)
──┤ P101.Run ├──┤ MV-101.Open ├──┤/T_Deadhead.Q ├──( P101_Permissive )──Version control for PLC code
- Export to text — Studio 5000 L5X export, TIA Portal SCL/AWL export, CODESYS export. These are diff-friendly text files.
- Git repository — one repo per project. Branch per change. Pull requests reviewed by another engineer.
- Commit messages — link to MOC ticket, describe the change, list affected equipment.
- Tag releases — every commissioned version gets a Git tag with the date and version number visible on the HMI.
- Backup the binary — even with text export, keep the original .ACD / .AP19 / .project file in version control or in a parallel artefact store.
Code review checklist
Before committing or downloading a code change, work through this:
- Does the change have a corresponding MOC ticket?
- Does it touch only the equipment / routine the ticket scoped?
- Are tag names descriptive and consistent with project convention?
- Are rung comments updated where logic changed?
- Is the routine header updated with the change date and reason?
- Are no magic numbers introduced? (everything in named tags)
- Are all field signals quality-checked?
- Are interlocks tested in simulation before deployment?
- Is the cause-and-effect matrix updated if interlock logic changed?
- Is a peer engineer signed off?