HMI Design
PLC HMI Screen Design Best Practices: Building Operator Interfaces That Actually Work
Design HMI screens that reduce operator errors and speed up response times — with ISA-101 principles, color psychology, and the Structured Text data structures behind every great display.
The Problem With Most HMI Screens
Walk into any control room built before 2015 and you'll see the same problems: screens packed with 3D pipe graphics, rainbow color schemes, and hundreds of data points crammed into a single view. Operators stare at these displays for 12-hour shifts and miss critical alarms because the important information is buried in visual noise.
The ISA-101 standard (Human Machine Interfaces for Process Automation Systems) was created to fix this. Its core principle is radical: the HMI should show the process state, not the process equipment.
This article covers the design principles, the PLC data structures that feed your screens, and the Structured Text patterns that make HMI binding clean and maintainable.
ISA-101 High-Performance HMI Principles
The Four Levels of Display
| Level | Name | Purpose | Update Rate | | L1 | Overview | Entire plant/area at a glance | 5–10s | | L2 | Process Area | One unit operation with key KPIs | 1–5s | | L3 | Detail | Single equipment item, all parameters | 500ms–1s | | L4 | Diagnostic | Trends, tuning, maintenance data | On demand |
The golden rule: operators should spend 80% of their time on L1 and L2. If they're constantly drilling into L3/L4 screens, your design has failed.
Color Usage: Less Is More
Traditional HMIs use color to decorate. High-performance HMIs use color to communicate:
| Color | Meaning | Usage | | Gray/Dark | Normal operation | Background, pipes, vessels — the "boring" state | | Green | Confirmed running/open | Sparingly — only for active equipment | | Red | Alarm, trip, danger | Abnormal conditions ONLY | | Yellow/Amber | Warning, approaching limit | Pre-alarm conditions | | White | Text, labels, data values | Primary information | | Blue | Informational, selected | Non-critical status, navigation highlights |
Critical rule: If everything is green during normal operation, nothing stands out. Use gray for normal and reserve color for abnormal states. This is called the "gray-field" approach.
// PLC-side: prepare color state for HMI binding
// 0=Gray(Normal), 1=Green(Running), 2=Yellow(Warning), 3=Red(Alarm)
TYPE HMI_ColorState : INT; END_TYPEFUNCTION FC_GetMotorColorState : INT
VAR_INPUT
Running : BOOL;
Warning : BOOL;
Alarm : BOOL;
END_VAR
IF Alarm THEN
FC_GetMotorColorState := 3; // Red
ELSIF Warning THEN
FC_GetMotorColorState := 2; // Yellow
ELSIF Running THEN
FC_GetMotorColorState := 1; // Green
ELSE
FC_GetMotorColorState := 0; // Gray — normal stopped state
END_IF;
Structuring PLC Data for HMI Binding
The biggest maintenance headache in HMI projects is tag management. Instead of exposing hundreds of individual tags, structure your PLC data into HMI-ready blocks:
The HMI Data Block Pattern
TYPE HMI_MotorFaceplate :
STRUCT
// Status (PLC → HMI, read-only)
STS_Running : BOOL;
STS_Ready : BOOL;
STS_Faulted : BOOL;
STS_Local : BOOL; // Local/Remote mode
STS_SpeedPct : REAL;
STS_CurrentAmps : REAL;
STS_RunHours : DINT;
STS_ColorState : INT; // For HMI element coloring // Commands (HMI → PLC, write)
CMD_Start : BOOL;
CMD_Stop : BOOL;
CMD_Reset : BOOL;
CMD_SpeedSP : REAL;
// Alarms (PLC → HMI)
ALM_Overload : BOOL;
ALM_CommFault : BOOL;
ALM_OverTemp : BOOL;
END_STRUCT;
END_TYPE
TYPE HMI_ValveFaceplate :
STRUCT
STS_Open : BOOL;
STS_Closed : BOOL;
STS_Transitioning: BOOL;
STS_Faulted : BOOL;
STS_Position : REAL; // 0–100% for modulating valves
STS_ColorState : INT;
CMD_Open : BOOL;
CMD_Close : BOOL;
CMD_PositionSP : REAL;
ALM_FailToOpen : BOOL;
ALM_FailToClose : BOOL;
ALM_TravelTime : BOOL;
END_STRUCT;
END_TYPE
Why This Pattern Matters
// Instantiate for each physical motor
PROGRAM HMI_DataExchange
VAR
Motor_Pump101 : HMI_MotorFaceplate;
Motor_Pump102 : HMI_MotorFaceplate;
Motor_Agitator : HMI_MotorFaceplate;
Valve_HV101 : HMI_ValveFaceplate;
Valve_CV201 : HMI_ValveFaceplate;
END_VAR// Map from control logic to HMI faceplate
Motor_Pump101.STS_Running := Pump101_FB.Running;
Motor_Pump101.STS_SpeedPct := Pump101_FB.ActualSpeed;
Motor_Pump101.STS_Faulted := Pump101_FB.Faulted;
Motor_Pump101.STS_ColorState := FC_GetMotorColorState(
Running := Pump101_FB.Running,
Warning := Pump101_FB.OverloadWarning,
Alarm := Pump101_FB.Faulted
);
// Map HMI commands back to control
Pump101_FB.CMD_Start := Motor_Pump101.CMD_Start;
Pump101_FB.CMD_Stop := Motor_Pump101.CMD_Stop;
Motor_Pump101.CMD_Start := FALSE; // Auto-reset one-shot commands
Motor_Pump101.CMD_Stop := FALSE;
Navigation Architecture
The Rule of Three Clicks
An operator should reach any parameter in the plant in three clicks or fewer:
Navigation Data Structure
TYPE HMI_AreaStatus :
STRUCT
AreaName : STRING[20];
AreaState : INT; // 0=Off, 1=Running, 2=Warning, 3=Alarm
ActiveAlarmCount : INT;
ProductionRate : REAL;
EquipmentOnline : INT;
EquipmentTotal : INT;
END_STRUCT;
END_TYPEPROGRAM HMI_Overview
VAR
Areas : ARRAY[1..8] OF HMI_AreaStatus;
END_VAR
// Aggregate area status — worst-case alarm state bubbles up
Areas[1].AreaName := 'Raw Materials';
Areas[1].ActiveAlarmCount := AlarmMgr.CountByArea(1);
IF Areas[1].ActiveAlarmCount > 0 THEN
Areas[1].AreaState := 3; // Red — has active alarms
ELSIF AreaWarnings[1] > 0 THEN
Areas[1].AreaState := 2; // Yellow — warnings present
ELSIF AreaRunning[1] THEN
Areas[1].AreaState := 1; // Green — running normally
ELSE
Areas[1].AreaState := 0; // Gray — idle
END_IF;
Alarm Display Best Practices
Alarm Priority and Presentation
ISA-18.2 defines four priority levels. Your HMI must make priority visually obvious:
| Priority | Operator Response Time | Visual Treatment | | Critical | Immediate (< 1 min) | Flashing red, audible alarm, banner | | High | < 5 min | Solid red, prominent position | | Medium | < 30 min | Yellow/amber, alarm list | | Low | Next shift / maintenance | White/gray, logged only |
TYPE HMI_AlarmDisplay :
STRUCT
AlarmID : INT;
Message : STRING[80];
Priority : INT; // 1=Critical, 2=High, 3=Medium, 4=Low
Timestamp : STRING[20];
State : INT; // 0=Normal, 1=Active, 2=Acknowledged, 3=Active+Ack
AreaID : INT;
FlashEnable : BOOL; // Only for critical
END_STRUCT;
END_TYPEFUNCTION_BLOCK FB_HMI_AlarmBanner
VAR
TopAlarms : ARRAY[1..5] OF HMI_AlarmDisplay;
TotalActive : INT;
HighestPriority : INT;
FlashToggle : BOOL;
tmrFlash : TON;
END_VAR
// Flash timer for critical alarms: 0.5s on, 0.5s off
tmrFlash(IN := NOT tmrFlash.Q, PT := T#500MS);
IF tmrFlash.Q THEN
FlashToggle := NOT FlashToggle;
tmrFlash(IN := FALSE);
END_IF;
// Set flash enable only on critical unacknowledged alarms
TopAlarms[1].FlashEnable := (TopAlarms[1].Priority = 1)
AND (TopAlarms[1].State = 1)
AND FlashToggle;
Trend Display Data Preparation
Trends are the most powerful diagnostic tool on an HMI. The PLC should pre-calculate useful derived values:
TYPE HMI_TrendData :
STRUCT
ProcessValue : REAL;
Setpoint : REAL;
ControlOutput : REAL; // PID output %
DeviationPct : REAL; // How far from SP
RateOfChange : REAL; // Units per minute
AvgLast5Min : REAL;
MinLast5Min : REAL;
MaxLast5Min : REAL;
END_STRUCT;
END_TYPEFUNCTION_BLOCK FB_TrendPrep
VAR_INPUT
PV : REAL;
SP : REAL;
CO : REAL;
END_VAR
VAR_OUTPUT
TrendOut : HMI_TrendData;
END_VAR
VAR
PrevPV : REAL;
SampleBuffer : ARRAY[1..300] OF REAL; // 5 min at 1s samples
SampleIndex : INT := 1;
SampleCount : INT := 0;
i : INT;
Sum : REAL;
END_VAR
TrendOut.ProcessValue := PV;
TrendOut.Setpoint := SP;
TrendOut.ControlOutput := CO;
// Deviation
IF ABS(SP) > 0.001 THEN
TrendOut.DeviationPct := ((PV - SP) / SP) * 100.0;
ELSE
TrendOut.DeviationPct := 0.0;
END_IF;
// Rate of change (units per minute, assuming 1s scan)
TrendOut.RateOfChange := (PV - PrevPV) * 60.0;
PrevPV := PV;
// Rolling buffer for min/max/avg
SampleBuffer[SampleIndex] := PV;
SampleIndex := SampleIndex + 1;
IF SampleIndex > 300 THEN SampleIndex := 1; END_IF;
IF SampleCount < 300 THEN SampleCount := SampleCount + 1; END_IF;
// Calculate statistics
Sum := 0.0;
TrendOut.MinLast5Min := 99999.0;
TrendOut.MaxLast5Min := -99999.0;
FOR i := 1 TO SampleCount DO
Sum := Sum + SampleBuffer[i];
IF SampleBuffer[i] < TrendOut.MinLast5Min THEN
TrendOut.MinLast5Min := SampleBuffer[i];
END_IF;
IF SampleBuffer[i] > TrendOut.MaxLast5Min THEN
TrendOut.MaxLast5Min := SampleBuffer[i];
END_IF;
END_FOR;
// Guard the average: first scan SampleCount = 0, and dividing by zero
// is a runtime fault on every real PLC. Don't ship code that relies on
// "the buffer will be full by then" — it won't be on cold start, restart,
// or after a download.
IF SampleCount > 0 THEN
TrendOut.AvgLast5Min := Sum / INT_TO_REAL(SampleCount);
ELSE
TrendOut.AvgLast5Min := PV; // No history yet — current value is the best estimate
END_IF;
> Why the guard matters. On a Siemens S7-1500, an unguarded Sum / 0 triggers an arithmetic error and — without an OB121 programmed error handler — stops the CPU. Rockwell Studio 5000 raises a minor fault and logs the offending rung; chain enough of them and you get a major fault. Beckhoff TwinCAT throws a runtime exception that drops the task. The TryPLC engine now matches that behavior: division by zero raises a runtime fault and halts the scan, so the editor will flag this on the first cycle instead of quietly returning 0 like a calculator. Treat every divisor in your code the same way the ABS(SP) > 0.001 guard handles SP a few lines up — guard it, or prove from the surrounding logic that it can never be zero.
Responsive Layout Patterns
Modern HMIs run on everything from 24" control room monitors to 7" panel PCs. Design your PLC data to support different detail levels:
// Compact data for small panels / overview tiles
TYPE HMI_EquipmentTile :
STRUCT
TagName : STRING[12];
Status : INT; // 0=Off, 1=Run, 2=Warn, 3=Alarm
MainValue : REAL; // One primary KPI
MainUnit : STRING[6]; // Engineering unit
END_STRUCT;
END_TYPE// Full data for detail screens
TYPE HMI_EquipmentDetail :
STRUCT
Tile : HMI_EquipmentTile; // Includes compact data
SecondaryVals: ARRAY[1..6] OF REAL;
SecondaryLbls: ARRAY[1..6] OF STRING[16];
Trend : HMI_TrendData;
Alarms : ARRAY[1..5] OF HMI_AlarmDisplay;
CmdAvailable : ARRAY[1..8] OF BOOL; // Which commands are valid now
END_STRUCT;
END_TYPE
Testing Your HMI: The "Squint Test"
Here's a technique used by ISA-101 practitioners: squint at your screen from 6 feet away. If you can't immediately tell which area has a problem, your design needs work.
What You Should See When Squinting
What You Shouldn't See
Summary
| Principle | Implementation | | Gray-field approach | Normal = gray; reserve color for abnormal states | | Structured data blocks | HMI_MotorFaceplate, HMI_ValveFaceplate — one per equipment type | | Three-click navigation | L1 overview → L2 area → L3 faceplate | | Alarm prioritization | ISA-18.2 levels with flash for critical unacknowledged | | Trend preparation | Pre-calculate deviation, rate of change, rolling min/max/avg | | Responsive data | Compact tiles for overviews, full detail for drill-down | | Testing | The squint test — if you can't see the problem from 6 feet, redesign |
The best HMI is the one operators forget about during normal operation and instantly understand during abnormal situations. Design for the worst moment of their shift, not the best.