Three ways hour meters go wrong
Picture a conveyor drive that runs nearly around the clock. The PM sheet says to change the gearbox oil every 2,000 run hours, and the HMI says the drive is at 8,192.0 hours. It said 8,192.0 last month, too. Nothing is broken. The hour meter just stopped counting, and nobody noticed because the number still looks reasonable.
That's one of three common ways a PLC hour meter quietly goes wrong:
- A REAL that stops adding. Adding one second (1/3600 of an hour) to a 32-bit
REALhour total works at first. But aREALonly holds about seven significant digits, so as the total grows, each small addition gets rounded. From about 1,000 to 4,096 hours the meter runs about 12% slow, from 4,096 hours it runs about 76% fast, and at 8,192 hours the one-second addition rounds to zero and the meter stops for good. Adding scan time instead (say 10 ms per scan) is worse: that version stops at 64 hours. - A timer used as the meter. A Logix
TIMERkeeps its accumulated value in milliseconds in aDINT, so the largest preset is 2,147,483,647 ms, about 24.8 days of run time. A retentive timer (RTO) used directly as an hour meter tops out there and sits with its done bit on. A self-resetting one-secondTONavoids that, but it throws away a little time at every reset, and those losses add up over thousands of hours. - A meter that gets wiped. The count resets to zero at every PM, so the motor's lifetime hours are gone. Or a program download puts back whatever value was saved in the offline file, and nobody notices that a few months of hours just disappeared.
The idea: odometer plus trip meter
Keep the arithmetic in integers and split the count the way a clock does, so no single number ever gets big enough to cause trouble:
- An
RTOruns while the motor's run feedback is on. Every time its accumulated value passes 1,000 ms, take 1,000 off and add 1 to a whole-seconds counter. The timer never gets near its preset, and leftover milliseconds carry over instead of being thrown away. - Every 3,600 seconds, take 3,600 off and add 1 to a whole-hours
DINT. That's the odometer. It never resets, and aDINTcan count more hours than any motor will ever run. - For PM, don't zero anything. When the PM is done, record the odometer reading. Hours since PM is just the odometer minus that reading, like a trip meter, and the motor keeps its lifetime hours.
If you want a decimal number on the screen, calculate it from the integers for display only. Never add to it.
In the PLC (Studio 5000 / Logix)
One copy of this per motor, pump, or fan you want to track. The examples use a single motor so the tag names stay short. For a whole plant, wrap it in an Add-On Instruction (see gotchas).
Ladder: count milliseconds into whole seconds
RTO is a ladder instruction (it isn't available in structured text), so these two rungs go in a ladder routine. Shown as rung text:
// Rung 0: time only while the motor is proven running
XIC(Motor_RunFbk)RTO(RunTmr,10000,0);
// Rung 1: every full second, move 1000 ms into RunSec and keep the remainder
GEQ(RunTmr.ACC,1000)[SUB(RunTmr.ACC,1000,RunTmr.ACC),ADD(RunSec,1,RunSec)];
The preset of 10,000 ms is never reached, because rung 1 takes a second out every time the accumulated value passes 1,000. Subtracting instead of using RES is what keeps the leftover milliseconds, so no time is lost however long the scan is. If RunTmr.DN ever comes on, something stopped rung 1 from running, which makes it a handy fault bit.
Structured text: hours, PM due, and the reset
(* Run hours: integer seconds roll into integer hours, so nothing drifts *)
IF RunSec >= 3600 THEN
RunSec := RunSec - 3600;
RunHrs := RunHrs + 1;
END_IF;
(* Display only. Never add to this value. *)
RunHrsDisp := RunHrs + RunSec / 3600.0;
(* PM due: odometer minus the reading at the last PM *)
PM_LeftHrs := PM_IntervalHrs - (RunHrs - PM_LastHrs);
PM_Soon := PM_LeftHrs <= PM_WarnHrs;
PM_Due := PM_LeftHrs <= 0;
(* PM signed off: record the reading, never zero the meter *)
IF PM_ResetReq THEN
IF PM_ResetPermit THEN
PM_LastHrs := RunHrs;
PM_Count := PM_Count + 1;
GSV(WallClockTime, , DateTime, PM_LastDate[0]); (* UTC on v16+ *)
END_IF;
PM_ResetReq := 0; (* always clear it, so a refused request can't fire later *)
END_IF;
Put the ST routine in the same task as the ladder rungs. RunSec goes up by at most one per scan, so the hour rollover never misses.
The permit check matters. Anyone can press a button, but an hour meter that resets whenever someone gets tired of the alarm isn't a PM program. With the check in the PLC, a stray write from another screen or a script can't sign off a PM either. If the permit comes from the HMI instead of a key switch, clear it in the PLC after each reset so it can't be left on.
Why not a REAL or LREAL?
A REAL is fine for showing hours: RunHrsDisp is recalculated from the integers every scan, so its rounding never builds up. It's only wrong to accumulate in one, as the chart shows. An LREAL, on controllers that support it, pushes the problem out so far that it won't show up in practice, but integer seconds and hours do the same job, work on every Logix controller, and are easier to check by eye.
Power cycles and downloads
A Logix controller normally keeps its tag values through a power cycle, and an RTO keeps its accumulated value when its rung goes false, so a power outage alone doesn't lose hours. Two things do:
- A program download. A download replaces the tag values in the controller with the values in the offline project, so
RunHrsgoes back to whatever it was the last time that file was saved. Before any download, go online and save the project, and answer yes when Studio 5000 asks to upload tag values. On CompactLogix 5380, ControlLogix 5580, and their GuardLogix versions, the Download dialog also has an Options tab with Preserve online tag values. It's off by default and has to be turned on for each download. - An SD card set to load on power-up. If the controller's nonvolatile memory is set to Load Image: On Power Up, every power cycle reloads the stored project, and tag values go back to what they were when the image was stored. Check that setting on any controller that keeps run hours.
Neither one is rare, so don't count on the PLC alone. Keep a copy of the hours in Ignition and alarm if the PLC's number ever goes backwards.
In Ignition
Bring RunHrs, RunHrsDisp, PM_LeftHrs, and PM_Due in as OPC tags, and historize RunHrs and Motor_RunFbk on change. Then:
- PM alarms. On
PM_LeftHrs, add a low-priority "PM due soon" alarm (mode Below Setpoint, setpoint bound to thePM_WarnHrstag, Inclusive) and a high-priority "PM due" alarm (Below Setpoint, setpoint 0, Inclusive). - Who can reset. Set the Write Permissions on
PM_ResetReq(and onPM_ResetPermit, if the HMI sets it) to your maintenance role. Tag security applies everywhere the tag is used, so you don't have to remember to lock down every button. - Catch a reset meter. Add two memory tags next to the OPC tags,
RunHrs_LastGood(integer) andRunHrs_WentBack(boolean, with an alarm: mode Equal, setpoint 1). Then add this Value Changed event script to theRunHrstag:
# Tag Editor > RunHrs > Tag Events > Value Changed
def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
if not currentValue.quality.isGood():
return # comms down: wait for a real value
folder = str(tagPath).rsplit("/", 1)[0]
last = system.tag.readBlocking([folder + "/RunHrs_LastGood"])[0].value
if last is not None and currentValue.value < last:
# Meter went backwards (download, SD reload, or reset).
# Keep LastGood as the number to restore, and raise the alarm.
system.tag.writeBlocking([folder + "/RunHrs_WentBack"], [True])
else:
system.tag.writeBlocking([folder + "/RunHrs_LastGood"], [currentValue.value])
The script skips bad-quality values, so a download, when the PLC's tags briefly go bad quality, is caught as soon as good values come back. It runs on initial values too, so it still catches a reset that happened while the gateway was down. When the alarm comes in, a maintenance user can write the RunHrs_LastGood value back into the PLC and clear RunHrs_WentBack.
For a quick check that the PLC and the historian agree, compare how much RunHrs went up over a week with the historian's run time for the feedback bit:
# Script Console: historian run time for the last 7 days
end = system.date.now()
start = system.date.addDays(end, -7)
ds = system.tag.queryTagCalculations(paths=["[default]Conveyor3/Motor_RunFbk"],
calculations=["DurationOn"],
startDate=start, endDate=end)
print "Historian run hours, last 7 days: %.1f" % (ds.getValueAt(0, 1) / 3600.0)
DurationOn returns the seconds the value was recorded as non-zero, so it's only as complete as the history. A gap in logging shows up as missing hours. If the two numbers are close, you can trust both. If they're not, find out why before the next PM schedule gets built from them.
Gotchas
Count feedback, not the command
Time the motor's proof of running (an auxiliary contact, a drive "running" status, or a current switch), not the output that calls for it. A run command held on while the overload is tripped will rack up hours the motor never ran.
Never zero the odometer
A PM sign-off records the reading. It doesn't reset the meter. That keeps lifetime hours for replacement planning, and PM_Count plus PM_LastDate give you a simple service history in the PLC.
Starting from a mechanical meter
When you add this to an existing motor, write the starting hours into RunHrs once, and set PM_LastHrs to the reading at the last real PM. Then save the project online with tag values, so the next download doesn't undo it.
Many motors: use an AOI
Put the two rungs and the ST in an Add-On Instruction with the timer and counters as local tags, and use one instance per motor. Every motor then counts exactly the same way, and a fix goes into one place instead of fifty.
Display rounding is fine
RunHrsDisp is a REAL, and that's fine because it's rebuilt from the integers every scan. Show one decimal place. A tenth of an hour is six minutes, more resolution than a PM schedule needs.
Newer timer types
Newer firmware on 5380 and 5580 controllers adds a TIMER_T type, which times in microseconds using TIME values. The same idea works there: take a whole second out at a time and keep the remainder. Just match the units.
The short version
Count whole seconds and whole hours in integers, keep the leftover milliseconds, and never reset the meter: record the reading at each PM instead. Then protect the number from downloads and keep a copy in Ignition that alarms if it ever goes backwards.