HMI
HMI Programming Guide: Screens, Alarms, and Tag Mapping That Operators Actually Trust
A practical, vendor-neutral HMI programming guide covering screen hierarchy, ISA-101 layout, alarm management, and tag mapping between the PLC and the operator panel.
HMI programming is what turns a working PLC program into a system an operator can actually run. The PLC closes the loops. The HMI is where people see what is happening, acknowledge alarms, change setpoints and decide whether to keep producing or shut down. A good HMI prevents accidents, and a bad one causes them.
This guide covers what HMI programmers do day to day: the ISA-101 screen hierarchy and colors, navigation, tag mapping between the PLC and the panel, alarms, trends, and the security and testing that a production system needs. It also shows the Structured Text on the PLC side that keeps the screens clean to bind and easy to maintain.
What HMI programming covers
An HMI (Human Machine Interface) is the screen an operator uses to work with a machine or process: a panel-mounted touchscreen, a thin client, or a PC running visualization software. HMI programming is the engineering work that produces it:
The graphics are the visible part, but on a real project they are usually 30–40% of the work. The rest is tag architecture, alarm rationalization and integration testing. The architecture you settle in the first week decides whether the HMI still serves operators in ten years or gets torn out and replaced.
HMI, SCADA and DCS
| System | Scope | Typical Software |
|---|---|---|
| HMI | One machine or skid, local panel | Siemens WinCC Comfort, Rockwell FactoryTalk View ME, Pro-face GP-Pro EX |
| SCADA | Plant-wide supervision, multiple PLCs, historization | Ignition, FactoryTalk View SE, Wonderware/AVEVA, WinCC Unified |
| DCS | Tightly integrated control + visualization for continuous process | Emerson DeltaV, Honeywell Experion, ABB 800xA |
The ISA-101 four-level screen hierarchy
ISA-101 (Human Machine Interfaces for Process Automation Systems) is built on one principle: the HMI should show the process state, not the process equipment. Its four-level display hierarchy has become the de facto standard for industrial HMIs:
| 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 | 500 ms to 1 s |
| L4 | Diagnostic | Trends, tuning, maintenance data | On demand |
Operators should spend 80% of their time on L1 and L2. If they keep drilling into L3 and L4, the design has failed. The usual rookie mistake is the opposite: everything goes on Level 2 and there is no overview, so during an upset the operators lose situational awareness because they cannot see the whole plant on one screen.
Navigation: any parameter in three clicks
An operator should reach any parameter in the plant in three clicks or fewer:
The overview only works if each area tile shows the worst state inside it. Let the PLC work that out, so the HMI only animates a number:
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;
Active alarms make the area red, warnings make it yellow, running makes it green, and otherwise it stays gray.
Colors: gray for normal, color for abnormal
Walk into a control room built before 2015 and you often see screens packed with 3D pipe graphics, rainbow color schemes and hundreds of data points in a single view. Operators watch these for 12-hour shifts and miss critical alarms because the important information is buried in visual noise. New screens still tend to look dramatic, with black backgrounds, bright green pipes and blinking red lights. ISA-101 deliberately rejects that style. Its recommended palette:
When everything is colorful, nothing stands out. With color reserved for abnormal conditions, a single yellow indicator on an otherwise gray screen draws the operator's eye at once. This is called the gray-field approach. If you use green for running equipment, use it sparingly. This approach grew out of the Abnormal Situation Management Consortium's research on operator response, and it is the basis of ISA-101's guidance against traditional "Christmas tree" screens.
Decide the color in the PLC and pass the HMI a state number, so every motor on every screen follows the same rule:
// 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;
Tag mapping: connecting the HMI to the PLC
Every dynamic element on a screen (a numeric value, an animated pump, a button, an alarm) is bound to a tag in the PLC. How you map those tags decides more than anything else whether the HMI project stays maintainable, and tag management is the biggest maintenance headache in HMI work.
Rules for HMI tags
Pump_101.Run or Tank_201.Level_PV, never to %I0.3 or N7:42. If the I/O moves, the screens still work.REAL for analog values, BOOL for digital, INT or an enum for states. Mixing types between PLC and HMI causes silent rounding or truncation.Pump_101.Run, Pump_101.Fault, Pump_101.Hours) so the HMI faceplate binds to the structure once.degC, bar, %) as a tag property so the HMI can display it without hardcoding.Example: a pump faceplate
PLC UDT: PumpControl
Run : BOOL // operator command
Running : BOOL // confirmed feedback
Fault : BOOL // any fault active
FaultCode : INT // 0 = none, 1 = overload, 2 = no flow
RunHours : REAL // accumulated run time
InterlockOK : BOOL // safe to startHMI faceplate bindings:
Start button → Pump_101.Run (write BOOL)
Status lamp → Pump_101.Running (animate fill color)
Fault banner → Pump_101.Fault (visible when TRUE)
Fault message → Pump_101.FaultCode (lookup table → string)
Hours display → Pump_101.RunHours (numeric, 1 decimal)
Start enable → Pump_101.InterlockOK (button greyed out when FALSE)
The start button is greyed out whenever the interlock is not satisfied. That one design choice prevents dozens of "I pressed start and it did not work" support calls. Note also that the operator command (Run) and the confirmed feedback (Running) are separate tags; keep them separate whatever naming you use.
HMI data blocks in Structured Text
Instead of exposing hundreds of individual tags, structure the PLC data into one HMI-ready block per equipment type:
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
The prefixes give every tag a clear direction. STS_ (status) flows from PLC to HMI, CMD_ (command) flows from HMI to PLC, and ALM_ carries alarms to the HMI. Each piece of equipment has one tag group that is easy to find. You design one motor popup and reuse it for every motor, and commissioning is a matter of copying the structure and mapping the I/O.
Instantiate the block for each physical device and map it to and from the control logic:
// 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;
The last two lines reset the start and stop commands after the control logic has read them, so a button press acts as a one-shot command.
Alarms and ISA-18.2
Alarms are the most safety-critical part of an HMI. The relevant standard is ISA-18.2 / IEC 62682, Management of Alarm Systems for the Process Industries, complemented by EEMUA 191 in Europe. A common scheme uses four priorities, and the HMI must make the priority obvious at a glance:
| Priority | Color | Operator Response Time | Typical Use |
|---|---|---|---|
| Low | Yellow | < 30 min | Trend deviation, advisory |
| Medium | Orange | < 10 min | Process value out of range |
| High | Red | < 3 min | Safety-related, production impact |
| Critical | Magenta + flash | Immediate | Shutdown trigger, safety system trip |
Alarm rationalization checklist
Before you add an alarm to the HMI, check it:
A well-rationalized system averages fewer than 1 alarm per 10 minutes per operator in steady state. If your alarm list is full, the answer is rationalization, not bigger screens.
Flashing only the critical alarm
Flashing is reserved for critical alarms that nobody has acknowledged yet. The PLC can drive it with a 0.5 s on, 0.5 s off toggle:
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;
Animation that shows state
HMI animation should tell the operator the state of the equipment. It should not draw attention for its own sake.
Preparing trend data in the PLC
Trends are the most useful diagnostic tool on an HMI. Beyond PV, SP and the alarm limits, let the PLC pre-calculate the derived values: deviation from setpoint, rate of change, and the rolling minimum, maximum and average over the last 5 minutes.
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;
> Guard every division. Controllers handle an unguarded Sum / 0 differently, depending on the platform and the data type: an arithmetic error, a logged fault, an exception that stops the task, or a REAL result of infinity that then spreads into every calculation using it. None of these is what the operator should see on a trend. The TryPLC engine raises a runtime fault and halts the scan, so the editor flags this on the first cycle instead of returning 0 like a calculator. Treat every divisor in your code the 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.
Data for different panel sizes
HMIs run on everything from 24" control room monitors to 7" panel PCs. Give the PLC data two detail levels: a compact tile for small panels and overview screens, and a full record for detail screens.
// 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
CmdAvailable tells the HMI which commands are valid right now, the same idea as greying out the pump's start button when its interlock is not satisfied.
Security and user roles
Modern HMIs support role-based access control. A typical setup:
Log every privileged action with the username, timestamp, old value and new value. This is mandatory for FDA 21 CFR Part 11 in pharma, and increasingly expected in food and beverage and in energy.
Testing your HMI before commissioning
The squint test
ISA-101 practitioners use a quick check: squint at the screen from 6 feet away. If you cannot tell at once which area has a problem, the design needs work. In normal operation the screen should look mostly gray, calm and boring, which is correct. A single alarm should make one area pop with color. With several alarms, the priorities should be visibly ranked, with critical standing out above high.
If the squint shows a rainbow of colors during normal operation, no clear problem area, or decorative elements competing with the process data, redesign. Design the screens for the worst moment of the operator's shift: calm during normal running, and instantly readable when something goes wrong.
A practical learning path
If you are new to HMI programming, work through these in order:
You can practice the PLC side (the tag structures, the interlocks, the alarm conditions) in the TryPLC editor without a physical HMI panel. Once that side is solid, the HMI work is mostly screen design and binding.
Frequently asked questions
What is HMI programming?
HMI programming is the work of designing the screens, alarms, navigation, and tag mappings that let an operator monitor and control a machine or process from a Human Machine Interface panel. It is not just drawing graphics — it covers screen hierarchy, alarm priorities, security, animation logic, and how every on-screen element is bound to a PLC tag.
Which HMI software should I learn first?
For Siemens shops, learn TIA Portal WinCC (Comfort or Unified). For Rockwell, learn FactoryTalk View ME and SE. For vendor-neutral work, Ignition by Inductive Automation is the most portable skill because it is used across many industries. The underlying concepts — screen hierarchy, alarm classes, tag bindings — transfer between all of them.
What is the difference between HMI and SCADA?
An HMI is the local operator interface for a single machine or skid, usually a panel on the equipment. SCADA (Supervisory Control and Data Acquisition) is a plant-wide system that aggregates data from many PLCs and HMIs, handles historization, and provides dashboards for supervisors. SCADA software like Ignition or FactoryTalk View SE can also act as an HMI, which is why the terms are sometimes used interchangeably.
What is ISA-101 and why does it matter for HMI design?
ISA-101 is the international standard for Human Machine Interfaces in process automation. It defines screen hierarchies (Level 1 overview, Level 2 unit, Level 3 detail, Level 4 diagnostic), a muted color palette that reserves bright colors for abnormal conditions, and consistent alarm representation. Following ISA-101 dramatically reduces operator error and is increasingly required by clients in oil and gas, pharma, and food and beverage.
How should I map PLC tags to HMI tags?
Use a single source of truth — a tag database in the PLC with descriptive names (Pump_101_Run, Tank_201_Level_PV) and import them into the HMI rather than retyping. Group tags by area or unit, keep data types consistent (REAL for analog, BOOL for digital, INT for states), and never bind an HMI element directly to a physical I/O address — always go through a symbolic tag so wiring changes do not break screens.
How many alarms should appear on an HMI screen at once?
Per the EEMUA 191 and ISA-18.2 guidelines, a well-designed system averages fewer than 1 alarm per 10 minutes per operator in steady state, and no more than 10 alarms in the first 10 minutes of a major upset. If your screens routinely show dozens of active alarms, the problem is alarm rationalization, not screen design — you need to remove nuisance alarms, set proper priorities, and add deadbands before adding more graphics.
What is the three-click rule for HMI navigation?
An operator should reach any parameter in the plant in three clicks or fewer: select the area on the Level 1 overview, select the equipment on the Level 2 area view, then see and change its parameters on the Level 3 faceplate.
How can I quickly check whether an HMI screen design works?
Use the squint test: squint at the screen from about 6 feet away. In normal operation it should look mostly gray and calm. If you cannot tell at once which area has a problem when an alarm is active, the design needs work.