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_TYPE

FUNCTION 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

  • One tag group per equipment — easy to find, easy to maintain
  • Clear direction — STS (status) flows PLC→HMI, CMD (command) flows HMI→PLC
  • HMI faceplate reuse — design one motor popup, reuse it for every motor
  • Commissioning speed — copy the structure, map the I/O, done
  • // 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:

  • Click 1: L1 overview → select an area
  • Click 2: L2 area view → select equipment
  • Click 3: L3 faceplate popup → see/change all parameters
  • 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_TYPE

    PROGRAM 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_TYPE

    FUNCTION_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_TYPE

    FUNCTION_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

  • Normal operation: Mostly gray, calm, boring — this is correct
  • Single alarm: One area pops with color — immediately obvious
  • Multiple alarms: Priority is visually ranked — critical stands out above high
  • What You Shouldn't See

  • Rainbow of colors during normal operation
  • Can't tell which area has the problem
  • Decorative elements competing with process data
  • 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.