Best Practices
10 PLC Programming Best Practices for Industrial Automation
Write better PLC code with these 10 industry-proven best practices for Structured Text programming in industrial automation.
Why Best Practices Matter
In industrial automation, PLC code controls real machinery. Poor code quality doesn't just cause bugs — it can cause safety hazards, production downtime, and maintenance nightmares. These ten practices will make your Structured Text code safer, more readable, and easier to maintain.
1. Use Consistent Naming Conventions
Adopt a clear naming scheme and stick to it across every project:
// ✅ Good: Clear, descriptive, consistent
pump1_StartCmd : BOOL;
pump1_RunFeedback : BOOL;
pump1_FaultAlarm : BOOL;
tank1_LevelPercent : REAL;// ❌ Bad: Inconsistent, unclear
p1Start : BOOL;
PUMP_running : BOOL;
x42 : BOOL;
temp : REAL; // Temperature? Temporary?
Tip: Hungarian notation (prefixing with type) is common in PLC: bRunning (BOOL), iCount (INT), rTemperature (REAL).
2. Structure Code with Function Blocks
Break logic into reusable Function Blocks rather than writing monolithic programs:
// One FB per equipment type
pump1Controller : FB_PumpControl;
pump2Controller : FB_PumpControl;
mixerController : FB_MixerControl;// Clean, readable main program
pump1Controller(Cmd := hmi_pump1Start, FB := di_pump1Running);
pump2Controller(Cmd := hmi_pump2Start, FB := di_pump2Running);
mixerController(Cmd := autoMode, Speed := recipe_mixSpeed);
3. Always Handle Error States
Never assume inputs will be valid or equipment will behave correctly:
// ✅ Good: Explicit error handling
IF sensorValue < SENSOR_MIN OR sensorValue > SENSOR_MAX THEN
sensorFault := TRUE;
processValue := lastGoodValue; // Use last known good value
ELSE
sensorFault := FALSE;
lastGoodValue := sensorValue;
processValue := sensorValue;
END_IF;
4. Use Constants, Not Magic Numbers
// ❌ Bad: What do these numbers mean?
IF temperature > 85.0 THEN
output := 4095;
END_IF;// ✅ Good: Self-documenting
VAR CONSTANT
TEMP_HIGH_ALARM : REAL := 85.0;
DAC_MAX_OUTPUT : INT := 4095;
END_VAR
IF temperature > TEMP_HIGH_ALARM THEN
output := DAC_MAX_OUTPUT;
END_IF;
5. Implement State Machines Properly
Use CASE statements with defined states for sequential processes:
Declarations (VAR panel):
PROGRAM Main
VAR
machineState : INT;
startCommand : BOOL;
motorRunning : BOOL;
startMotor : BOOL;
stopCommand : BOOL;
faultReset : BOOL;
END_VAR
Logic (PROGRAM panel):
CASE machineState OF
0: // IDLE
IF startCommand THEN machineState := 10; END_IF; 10: // STARTING
startMotor := TRUE;
IF motorRunning THEN machineState := 20; END_IF;
20: // RUNNING
// Production logic
IF stopCommand THEN machineState := 30; END_IF;
30: // STOPPING
startMotor := FALSE;
IF NOT motorRunning THEN machineState := 0; END_IF;
99: // FAULT
startMotor := FALSE;
IF faultReset THEN machineState := 0; END_IF;
END_CASE;
Tip: Use multiples of 10 for states so you can insert intermediate steps later.
6. Comment Your Intent, Not Your Code
// ❌ Bad: Restates the obvious
// Set motorRun to TRUE
motorRun := TRUE;// ✅ Good: Explains WHY
// Bypass start delay during manual jog mode for maintenance
motorRun := TRUE;
💡 Syntax fragment. Declare motorRun : BOOL; in the VAR panel to run this.
7. Limit Scan-Time Impact
Avoid operations that spike scan time unpredictably:
Declarations (VAR panel):
PROGRAM Main
VAR
i, chunkStart : INT;
processData, rawData : ARRAY[1..10000] OF REAL;
scaleFactor : REAL;
END_VAR
Logic (PROGRAM panel):
// ❌ Bad: Processing entire array every scan
FOR i := 1 TO 10000 DO
processData[i] := rawData[i] * scaleFactor;
END_FOR;// ✅ Good: Process a chunk per scan
FOR i := chunkStart TO chunkStart + 99 DO
IF i <= 10000 THEN
processData[i] := rawData[i] * scaleFactor;
END_IF;
END_FOR;
chunkStart := chunkStart + 100;
IF chunkStart > 10000 THEN chunkStart := 1; END_IF;
8. Separate HMI Interface from Logic
Keep HMI-facing tags in a thin "interface" layer; let your logic FB own the real state.
Declarations (VAR panel):
PROGRAM Main
VAR
// HMI-facing tags (Modbus/OPC UA mapped)
hmi_tankLevel : REAL;
hmi_pumpStatus : BOOL;
hmi_alarmActive : BOOL;
hmi_startButton : BOOL;
hmi_stopButton : BOOL; // Logic owns the real state — ProcessController is your own FB
processController : ProcessController;
END_VAR
Logic (PROGRAM panel):
// ✅ Good: Clear boundary between HMI and logic
// HMI interface variables (read by HMI)
hmi_tankLevel := processController.CurrentLevel;
hmi_pumpStatus := processController.PumpRunning;
hmi_alarmActive := processController.HasFault;// HMI commands (written by HMI)
processController.StartCmd := hmi_startButton;
processController.StopCmd := hmi_stopButton;
9. Use Version Control and Backups
Project_v2.3_AddedPumpFaultLogic10. Test Edge Cases
Before commissioning, verify your code handles:
Practice These Principles
Open our PLC simulator and practice writing clean, well-structured Structured Text code. Our project templates demonstrate many of these patterns in real-world scenarios.