A screen full of alarms, one cause
Picture a packaging line that stops in the middle of a shift. The alarm screen shows guard door open, jam detected, and low air pressure, all active. The operator does the obvious thing: clears the jam, closes the door, and restarts. A few minutes later it stops again, with the same alarms.
What actually happened is that the plant air dipped first. A pneumatic gate sagged, the product jammed behind it, and the operator opened the guard door to clear the jam. The air dip was the cause. The other two were the line reacting to it, plus the operator reacting to the line. But the alarm list sorts by time, newest first, and the cause sits at the bottom looking like just another alarm.
This is where guessing starts. One person blames the photoeye, another resets the drive, and the line runs fine until it doesn't. Without a first-out, the next trip gets diagnosed from memory.
The idea: latch the first one
A first-out (sometimes called a first-up or first-fault indicator) is simple:
- Give every interlock a bit in one word, so "anything tripped" is just word not equal to zero.
- The first scan that sees a non-zero word, record which bit it was, copy the whole word, take the time, and set a latch. While the latch is on, nothing else can overwrite the record.
- The operator resets the first-out from the HMI, and the PLC only accepts it once every interlock is healthy again. Then it's armed for the next trip.
It doesn't replace the alarm list. It adds one line on top of it that answers the question everyone asks first: what stopped it?
In the PLC (Studio 5000 / Logix)
The example uses six interlocks on one line so the tag names stay short. Map your own interlocks into the Trip bits in rung 0; the rest doesn't change. Lower bit numbers win a tie, so put the interlocks most likely to be the real cause (air, power, E-stop) in the lowest bits.
Both versions below do the same thing in the same order, with the same tag names. Ladder is shown first. Switch to Structured Text if that's your plant standard.
Ladder
Four rungs in one ladder routine, top to bottom. Open "as text" under any rung to copy it into the Studio 5000 rung editor.
Drawn the way the Studio 5000 ladder editor lays it out: inputs on the left, outputs pushed to the right rail. The small numbers with arrows are where the editor shows live values; they read 0 here because nothing is online.
On a phone, swipe sideways to see the whole rung.
Rung 0 as text
[XIO(EStop_OK)OTE(Trip.0),XIO(Guard_Closed)OTE(Trip.1),XIO(Motor_OL_OK)OTE(Trip.2),XIC(VFD_Faulted)OTE(Trip.3),XIO(Air_OK)OTE(Trip.4),XIC(Jam_Detected)OTE(Trip.5)];Rung 1 as text
XIO(FirstOutCaptured)NEQ(Trip,0)[MOV(Trip,FirstOutBits),GSV(WallClockTime,,DateTime,FirstOutTime[0]),CLR(FirstOut),XIC(Trip.0)EQU(FirstOut,0)MOV(1,FirstOut),XIC(Trip.1)EQU(FirstOut,0)MOV(2,FirstOut),XIC(Trip.2)EQU(FirstOut,0)MOV(3,FirstOut),XIC(Trip.3)EQU(FirstOut,0)MOV(4,FirstOut),XIC(Trip.4)EQU(FirstOut,0)MOV(5,FirstOut),XIC(Trip.5)EQU(FirstOut,0)MOV(6,FirstOut),OTL(FirstOutCaptured)];Rung 2 as text
XIC(FO_ResetReq)EQU(Trip,0)[CLR(FirstOutBits),CLR(FirstOut),OTU(FirstOutCaptured)];Rung 3 as text
XIC(FO_ResetReq)OTU(FO_ResetReq);All 4 rungs as text
[XIO(EStop_OK)OTE(Trip.0),XIO(Guard_Closed)OTE(Trip.1),XIO(Motor_OL_OK)OTE(Trip.2),XIC(VFD_Faulted)OTE(Trip.3),XIO(Air_OK)OTE(Trip.4),XIC(Jam_Detected)OTE(Trip.5)];
XIO(FirstOutCaptured)NEQ(Trip,0)[MOV(Trip,FirstOutBits),GSV(WallClockTime,,DateTime,FirstOutTime[0]),CLR(FirstOut),XIC(Trip.0)EQU(FirstOut,0)MOV(1,FirstOut),XIC(Trip.1)EQU(FirstOut,0)MOV(2,FirstOut),XIC(Trip.2)EQU(FirstOut,0)MOV(3,FirstOut),XIC(Trip.3)EQU(FirstOut,0)MOV(4,FirstOut),XIC(Trip.4)EQU(FirstOut,0)MOV(5,FirstOut),XIC(Trip.5)EQU(FirstOut,0)MOV(6,FirstOut),OTL(FirstOutCaptured)];
XIC(FO_ResetReq)EQU(Trip,0)[CLR(FirstOutBits),CLR(FirstOut),OTU(FirstOutCaptured)];
XIC(FO_ResetReq)OTU(FO_ResetReq);How the ladder lines up with the structured text
- Rung 0 is the six
Trip.n :=lines. AnXIOis theNOT: the bit turns on when a "healthy" input goes off. - Rung 1 is the
IF NOT FirstOutCaptured AND Trip <> 0block. Branch legs run top to bottom, soCLR FirstOutruns before the six decode legs, andOTL FirstOutCapturedruns last. - The
EQU FirstOut 0on each decode leg is theELSIF. Once a leg writes a number, every leg below it sees a non-zeroFirstOutand skips. That's what makes the lowest bit win a same-scan tie. Don't reorder the legs. - Rungs 2 and 3 are the nested
IF FO_ResetReqblock, split in two so each rung reads plainly. Keep rung 3 below rung 2, or rung 2 never sees the request.
Structured Text
(* First-out fault capture: which interlock tripped first *)
(* 1. Interlocks to trip bits. 1 = tripped. *)
Trip.0 := NOT EStop_OK; (* safety controller status: monitor only *)
Trip.1 := NOT Guard_Closed;
Trip.2 := NOT Motor_OL_OK;
Trip.3 := VFD_Faulted;
Trip.4 := NOT Air_OK;
Trip.5 := Jam_Detected;
(* 2. First trip since the last reset: record it once *)
IF NOT FirstOutCaptured AND Trip <> 0 THEN
FirstOutBits := Trip; (* everything that tripped in this scan *)
GSV(WallClockTime, , DateTime, FirstOutTime[0]); (* UTC on v16+ *)
FirstOut := 0;
IF Trip.0 THEN FirstOut := 1; (* lowest bit wins a same-scan tie *)
ELSIF Trip.1 THEN FirstOut := 2;
ELSIF Trip.2 THEN FirstOut := 3;
ELSIF Trip.3 THEN FirstOut := 4;
ELSIF Trip.4 THEN FirstOut := 5;
ELSIF Trip.5 THEN FirstOut := 6;
END_IF;
FirstOutCaptured := 1;
END_IF;
(* 3. Operator reset: only when every interlock is healthy *)
IF FO_ResetReq THEN
IF Trip = 0 THEN
FirstOutBits := 0;
FirstOut := 0;
FirstOutCaptured := 0;
END_IF;
FO_ResetReq := 0; (* always clear it, so a refused reset can't fire later *)
END_IF;
One routine, any task. If the line's run logic is in another routine, schedule this one ahead of it (see gotchas).
Nothing in this logic stops or starts the machine. It only watches. Your existing interlock logic still does the stopping, and the safety system still does the safety functions.
In Ignition
Bring FirstOut, FirstOutBits, and FO_ResetReq in as OPC tags (in a Line1 folder in these examples). Then build a small banner near the top of the line's main screen:
- The first-out text. A Label with an expression binding on its text. Keep the names short enough to read from across the aisle:
// Label text: which interlock tripped first
case({[default]Line1/FirstOut},
1, "E-stop",
2, "Guard door open",
3, "Main motor overload",
4, "Drive fault",
5, "Low air pressure",
6, "Jam detected",
"No trip")
- When it happened. A second Label, bound to the timestamp of the
FirstOuttag:
// Label text: when Ignition saw it (only while there is a first-out)
if({[default]Line1/FirstOut} > 0,
"since " + dateFormat({[default]Line1/FirstOut.timestamp}, "MMM d, h:mm:ss a"),
"")
That's when Ignition saw the change, so it's as close as your tag group's poll rate. The exact controller time is in FirstOutTime (UTC on v16+) if you need it for a report.
- Same-scan ties. A small badge whose
visibleproperty uses this expression. It shows when more than one bit was on at capture:
// Visible: more than one interlock tripped in the same scan
{[default]Line1/FirstOut} > 0 &&
{[default]Line1/FirstOutBits} != (1 << ({[default]Line1/FirstOut} - 1))
- The reset button. A Button with a script action on
onActionPerformed. Set the Write Permissions onFO_ResetReqto your operator role, so only signed-in users can reset it:
# Reset button > Events > Component > onActionPerformed (script action)
def runAction(self, event):
system.tag.writeBlocking(["[default]Line1/FO_ResetReq"], [True])
The PLC decides whether the reset is accepted. If an interlock is still tripped, the request is cleared and the first-out stays up, which is what you want: the banner shouldn't go blank while the cause is still there.
To see patterns over time, historize FirstOut on change, or add an alarm on it (mode Not Equal, setpoint 0) so each first-out lands in the alarm journal. After a few weeks you can see which interlock really stops the line most often, instead of which one people remember.
Gotchas
Scan order decides "first"
The PLC sees inputs once per scan (more exactly, as each module's RPI updates them), so "first" means first scan, not first millisecond. Two trips closer together than that look simultaneous. If the line's run logic is in another routine, schedule this one ahead of it, so it sees the trip before anything reacts to it.
Same-scan ties
When two bits come on in the same scan, the lower bit number wins and FirstOutBits keeps the rest. Put the likely root causes (air, power, E-stop) in the low bits. If you truly need the order inside one scan, that's a job for timestamped sequence-of-events input modules, not ladder logic.
Reset only when healthy
The reset is refused while any trip bit is on, and the request is always cleared, so a press made too early doesn't sit there and fire later. Decide with operations whether the line should also require a first-out reset before restart. That's fine in standard logic, as long as it isn't part of a safety function.
Latch vs. fault array
This latches one first-out until someone resets it, which is what an operator needs. If you want a history of every trip in order, that's a different tool: a small FIFO of fault codes and times, or the Ignition alarm journal. Many lines use both.
Don't mask safety circuits
The E-stop and guard bits here are status bits read from the safety controller or a monitoring contact, for display only. Never route a safety function through first-out logic, and never let an HMI reset touch a safety reset. Safety-rated functions stay in the safety system.
Stopped machines trip too
Opening a guard door on a stopped line will capture a first-out that didn't stop anything. If you only care about trips that stopped a running machine, add your run bit to the capture condition, and make sure this routine runs before the logic that drops that run bit.
The short version
Put every interlock in one word, latch the first non-zero bit with a timestamp, keep a copy of the whole word for ties, and only let the operator reset it once everything is healthy. Then put it at the top of the screen, so the first question after a stop is answered before anyone starts guessing.