The PLC Programmer's Handbook: Best Practices for Writing Code That Lasts
Share
Introduction: The 3 AM Phone Call
It is 3 AM on a Sunday. Your phone rings. It is the plant manager. A production line is down, and the maintenance team cannot figure out why. They have rebooted the <u>PLC</u>. They have cycled power. They have checked every input and output. Nothing.
You remote into the system. You open the program. What you find is a nightmare: 500 rungs of ladder logic with no comments, no structure, and variables named X1, X2, and Temp. The original programmer left the company two years ago. Nobody understands how it works.
This scenario plays out in factories around the world every single day. And it is almost entirely preventable.
Writing PLC code that is reliable, maintainable, and understandable is not about being clever. It is about discipline. It is about following proven practices that have been developed over decades of industrial automation experience. This guide distills those practices into a practical handbook for automation engineers at every level.
Part 1: The Foundation — Planning Before Coding
1.1 Document the Sequence of Operations First
Before writing a single line of ladder logic or structured text, document the complete Sequence of Operations (SOO) in plain English. The SOO is the single most important deliverable in any controls project. It forces you to think through every scenario, every edge case, and every failure mode before you commit to code.
What a good SOO includes:
-
Normal operation sequence (step-by-step)
-
Startup sequence
-
Shutdown sequence
-
Emergency stop behavior
-
Fault handling and recovery
-
Manual override procedures
-
Alarm conditions and responses
Why this matters: Without an SOO, you are programming by guessing. With an SOO, you have a roadmap that everyone on the team — mechanical, electrical, and controls — can review and agree on before development begins.
1.2 Define Your Naming Conventions Early
Consistent naming is one of the cheapest and most effective ways to improve code maintainability. Decide on conventions before you start typing.
Recommended naming practices:
-
Use meaningful names:
ConveyorSpeednotX1,PumpRunningnotM100 -
Include data type prefixes:
bStartButton(BOOL),iTemperature(INT),rPressure(REAL) -
Use camelCase or snake_case consistently throughout the project
-
For Siemens systems, follow TIA Portal naming recommendations
-
For Rockwell systems, use descriptive tag names with consistent prefixes
Example:
// Poor naming M100.0 // What is this? DB1.DBW2 // No idea // Good naming bConveyorRunning // BOOL — conveyor status iOvenTemperature // INT — temperature reading rTankLevel // REAL — level in meters
1.3 Design the Architecture Before Implementation
Every PLC program should have a clear architecture. Think of it as the blueprint for your code.
Recommended architecture layers:
-
I/O Layer: Maps physical inputs and outputs to logical names
-
Device Layer: Controls individual devices (motors, valves, sensors)
-
Process Layer: Manages sequences and interlocking
-
Supervisory Layer: Handles HMI communication, alarming, and data logging
This layered approach isolates changes. If you replace a sensor, you only update the I/O layer. If you add a new HMI screen, you only update the supervisory layer.
Part 2: Language Selection — Choosing the Right Tool for the Job
2.1 The Multi-Language Reality
IEC 61131-3 defines five programming languages: Ladder Diagram (LD), Function Block Diagram (FBD), Structured Text (ST), Sequential Function Chart (SFC), and (until recently) Instruction List (IL). The 2025 edition of the standard removed IL, reflecting its obsolescence.
The best practice is not to choose one language — it is to use the right language for the right task.
2.2 When to Use Ladder Diagram (LD)
Ladder logic remains the dominant language in North America for a reason: it is visual, intuitive, and easy for maintenance technicians to troubleshoot.
Use LD for:
-
Boolean logic and discrete control
-
Interlocking and safety logic
-
Any logic that maintenance technicians will need to monitor during machine operation
-
Simple machine sequences
Avoid LD for:
-
Complex mathematical calculations
-
Array and data structure manipulation
-
String parsing and communication handling
2.3 When to Use Structured Text (ST)
Structured Text is a high-level language similar to Pascal or C. It handles complex data and algorithms far more efficiently than ladder logic.
Use ST for:
-
Complex calculations and algorithms
-
Array and data structure manipulation
-
String parsing and configuration file handling
-
Communication protocol parsing
-
Recipe management and data logging
Example — Recipe management in ST:
FOR i := 1 TO 10 BY 1 DO IF RecipeArray[i].Active THEN TargetTemp := RecipeArray[i].Temperature; TargetSpeed := RecipeArray[i].Speed; EXIT; END_IF; END_FOR;
2.4 When to Use Function Block Diagram (FBD)
FBD is excellent for visualizing data flow, especially in process control applications.
Use FBD for:
-
PID control loops
-
Analog signal processing
-
Applications with clear data flow paths
2.5 When to Use Sequential Function Chart (SFC)
SFC is ideal for state machines and batch processes.
Use SFC for:
-
Batch processing (mixing, filling, cooking)
-
Machine sequences with clear states
-
Applications with parallel operations
2.6 The Hybrid Approach
The most effective PLC programs use multiple languages in a layered architecture:
-
SFC: Overall sequence structure
-
LD: Discrete I/O logic and safety circuits
-
ST: Complex data handling and algorithms
-
FBD: Analog control and signal processing
This approach leverages the strengths of each language while mitigating their weaknesses.
Part 3: Modular Programming — The Foundation of Maintainable Code
3.1 Break It Down
Monolithic programs — a single block with hundreds of networks doing everything — are a maintenance nightmare. Functionality should be separated into purpose-specific function blocks (FBs) or functions (FCs) with clear interfaces.
The modular principle: Each module should do one thing and do it well.
Example structure:
Project Root ├── Device Modules │ ├── Conveyor_Control (FB) │ ├── Pump_Control (FB) │ └── Valve_Control (FB) ├── Process Modules │ ├── Filling_Sequence (FB) │ ├── Mixing_Sequence (FB) │ └── Packaging_Sequence (FB) ├── Utility Modules │ ├── Alarm_Manager (FB) │ ├── Data_Logger (FB) │ └── Communication_Handler (FB) └── I/O Mapping ├── Input_Mapping (FC) └── Output_Mapping (FC)
3.2 Encapsulate Complexity
Each function block should hide its internal complexity behind a clear interface. The outside world should only see inputs, outputs, and maybe a few status variables.
Benefits of encapsulation:
-
Easier testing (test each module independently)
-
Easier debugging (isolate problems to specific modules)
-
Easier reuse (copy modules between projects)
-
Easier maintenance (update one module without affecting others)
3.3 Reuse Code Wherever Possible
If you find yourself writing the same logic more than twice, create a function block.
Common reusable function blocks:
-
Motor control (start/stop, fault handling, run time monitoring)
-
Valve control (open/close, position feedback, fault detection)
-
PID control (with auto-tuning and bumpless transfer)
-
Alarm management (latching, acknowledgment, logging)
-
Data logging (timestamped value storage)
Most Siemens and Rockwell systems support user-defined function blocks that can be saved in libraries and reused across projects.
Part 4: Code Quality — Writing Code That Works and Stays Working
4.1 Clarity Over Brevity
In PLC programming, clarity is more important than cleverness. A clever one-liner that nobody understands is worse than five lines of straightforward code.
Poor practice:
// Clever but unreadable IF (a AND b) OR (c AND NOT d) OR (e AND f) OR (g AND h) THEN...
Better practice:
// Clear and maintainable bCondition1 := a AND b; bCondition2 := c AND NOT d; bCondition3 := e AND f; bCondition4 := g AND h; IF bCondition1 OR bCondition2 OR bCondition3 OR bCondition4 THEN...
4.2 Comment Your Code — But Comment the Why
Comments should explain why something is done, not what is done. The code itself should explain the what.
Poor comments:
// Add 1 to Counter Counter := Counter + 1;
Good comments:
// Increment part counter for throughput tracking // Reset daily at shift change (06:00 and 18:00) Counter := Counter + 1;
4.3 Use Trap Bits for Debugging
Trap bits are test variables that you set to different values in your PLC program to see if a section of code is being executed.
How to use trap bits:
-
Create a BOOL variable called
Trap_1,Trap_2, etc. -
Set the trap bit to TRUE in a section of code you want to monitor
-
If the trap bit becomes TRUE during operation, you know that section executed
-
Reset trap bits after debugging
This technique is invaluable for tracking down elusive logic problems.
4.4 Implement Fault Capture Routines
A well-designed PLC program should capture faults and make them visible.
Key components of fault handling:
-
Fault detection: Identify when something goes wrong (e.g., motor does not start within timeout)
-
Fault logging: Record the fault with a timestamp and diagnostic information
-
Fault recovery: Attempt automatic recovery where safe
-
Fault display: Present clear fault messages on the HMI
Example — Motor fault handling:
// Motor start request IF bStartRequest AND NOT bMotorRunning THEN // Start the motor bMotorOutput := TRUE; // Start watchdog timer tMotorStartup(IN := TRUE, PT := T#5s); // If timer expires without motor running, fault IF tMotorStartup.Q AND NOT bMotorRunning THEN bMotorFault := TRUE; sFaultMessage := 'Motor failed to start'; END_IF; END_IF;
4.5 Structure Your Scan Cycle
PLC scan cycles are deterministic. Structure your code to take advantage of this:
Recommended scan structure:
-
Read inputs (I/O mapping)
-
Execute safety logic (highest priority)
-
Execute control logic (device control, sequences)
-
Execute communication (HMI, drives, networks)
-
Execute diagnostics (fault detection, logging)
-
Write outputs (I/O mapping)
Place non-critical logic (data logging, reporting) in lower-priority organization blocks (OBs) to avoid slowing down the main control loop.
Part 5: Error Handling — Planning for Failure
5.1 Assume Everything Will Fail
Good PLC programming assumes that every sensor, every actuator, and every communication link will eventually fail. The question is not if something will fail, but when — and what happens when it does.
Design for failure:
-
Timeouts on all communication
-
Watchdog timers on all critical functions
-
Default values for analog inputs when signals are lost
-
Safe positions for actuators when control is lost
-
Clear fault messages for every failure mode
5.2 Use State Machines for Sequences
State machines are the most robust way to implement sequences. They are easy to understand, easy to debug, and easy to modify.
A simple state machine structure:
CASE iState OF 0: // Idle IF bStartRequest THEN iState := 10; END_IF; 10: // Extend cylinder bCylinderExtend := TRUE; IF bCylinderExtended THEN iState := 20; END_IF; 20: // Wait tWait(IN := TRUE, PT := T#2s); IF tWait.Q THEN iState := 30; END_IF; 30: // Retract cylinder bCylinderExtend := FALSE; IF bCylinderRetracted THEN iState := 0; END_IF; ELSE: // Fault state bFault := TRUE; sFaultMessage := 'Invalid state'; END_CASE;
5.3 Never Clear Faults Indiscriminately
When handling faults, only clear faults that are known and safe to recover from. Clearing all faults indiscriminately can hide critical system issues.
Best practice:
-
Require operator acknowledgment before clearing faults
-
Log all faults with timestamps
-
Implement different recovery strategies for different fault types
Part 6: Documentation — The Gift to Your Future Self
6.1 Document Everything
The person who will maintain your code six months from now is probably not the person who wrote it — and even if they are, they will not remember every detail.
What to document:
-
I/O map: Every input and output with physical location and function
-
Variable list: Every significant variable with description and data type
-
Sequence of Operations: The original SOO document
-
Change log: Every change made to the program, with date, author, and reason
-
Network/block comments: Explanations of complex logic
6.2 Use Version Control
Text-based languages like Structured Text work seamlessly with version control systems like Git. Even for ladder logic, many platforms now support version control through text exports.
Benefits of version control:
-
Track who changed what and when
-
Revert to known-good versions
-
Branch for development without affecting production
-
Compare versions to understand changes
6.3 Keep Regular Backups
Backups are not just for disaster recovery — they are also for comparing and reverting logic when needed.
Backup best practices:
-
Backup before every download
-
Backup after every significant change
-
Store backups off the production network
-
Label backups with date and version number
Part 7: Testing — Prove It Works Before Deployment
7.1 Test Offline First
Use simulation tools to test your code before downloading to the physical PLC. Most major PLC platforms offer simulation:
-
Siemens: S7-PLCSIM (integrated with TIA Portal)
-
Rockwell: Emulate 5000 (Studio 5000)
-
Delta: ISPSoft simulation mode
-
Mitsubishi: GX Works3 simulation
7.2 Test Incrementally
Do not test everything at once. Test modules individually, then test integration, then test the complete system.
Testing order:
-
Test each function block independently
-
Test I/O mapping
-
Test device control
-
Test sequences
-
Test HMI communication
-
Test alarms and fault handling
-
Test with hardware (I/O simulation)
-
Test with real loads (empty machine)
-
Test with production (supervised)
7.3 The Empty-Load-Production Rule
Follow the empty-load-production rule: test with no load, then with a simulated load, then with real production. This minimizes the risk of equipment damage, program errors, and logic misoperation.
Part 8: Common Mistakes and How to Avoid Them
| Mistake | Consequence | Prevention |
|---|---|---|
| No documentation | Incomprehensible code; costly maintenance | Document as you code; update with changes |
| Monolithic programs | Hard to debug; hard to modify | Break into small, focused modules |
| Poor variable naming | Confusion; errors | Use meaningful names; follow conventions |
| No fault handling | Unexpected shutdowns; no diagnosis | Implement fault detection and logging |
| No comments | Unmaintainable code | Comment the why, not the what |
| Jumps and gotos | Unpredictable execution; hard to follow | Use structured programming; avoid jumps |
| Mixed logic in one block | Confusion; errors | Separate safety, control, and communication |
| No version control | Lost changes; no traceability | Use Git or platform-specific versioning |
Conclusion: Write Code That Lasts
PLC programming is not about being clever. It is about being disciplined. It is about writing code that works today, can be understood tomorrow, and can be modified next year.
The practices in this handbook are not optional — they are essential. They are the difference between a program that runs for years with minimal maintenance and a program that fails unpredictably and consumes endless engineering hours.
The core principles:
-
Plan before you code — document the sequence of operations first
-
Choose the right language — use LD for logic, ST for algorithms
-
Modularize everything — break code into small, focused modules
-
Write clear code — clarity over cleverness
-
Plan for failure — implement comprehensive fault handling
-
Document everything — the gift to your future self
-
Test thoroughly — offline first, then incremental, then production
At PLC ERA, we supply the tools you need to implement these practices — PLCs from Siemens, Rockwell, Delta, Mitsubishi, Omron, and ABB; programming cables and accessories; and test equipment from Fluke. Visit plcera.com to explore our complete catalog.
References and Further Reading
-
PLCopen. Software Construction Guidelines and Coding Guidelines
-
PLCopen. Guidelines for Object Oriented Programming
-
Industrial Monitor Direct. Writing Great PLC Code: Industrial Best Practices Guide
-
Industrial Monitor Direct. PLC Programming Best Practices: Field Guide for Beginners
-
Industrial Monitor Direct. Mixing IEC 61131-3 PLC Languages
-
Control Engineering. PLC object orientation guidelines published
-
Automation World. The Power of Modular PLC Code
-
RealPars. Ladder Logic Debugging: Solving Problems in PLC Programs
#PLCProgramming #IEC61131 #StructuredText #LadderLogic #ModularProgramming #PLCCodeQuality #AutomationEngineering #Siemens #Rockwell #Delta #Mitsubishi #Omron #ABB #PLCDebugging #IndustrialAutomation #PLCERA