Overview
Run-hour metering is a foundational requirement in any preventive-maintenance scheme for conveyors, elevators, valves, fans, pumps, and motor starters. The principle is straightforward: capture the moment a device enters the running state (driven by a Boolean status tag from the PLC), accumulate the elapsed time until the device stops, compare the cumulative hours against a configurable setpoint, and raise an alarm when the threshold is exceeded so the operator can trigger service.
In a Siemens environment the practical split is to keep the time-counting logic inside the S7 CPU and let WinCC only display and alarm. Doing the math on the HMI/SCADA side is fragile because:
- Loss of the WinCC station, a panel reboot, or a SQL/archive server outage will zero the accumulated value.
- CPU-to-HMI communication is asynchronous; one-second polling on a 100 ms tag change can produce large drift.
- Most S7 CPUs already maintain a buffered real-time clock and provide dedicated system functions for time arithmetic, so the work belongs there.
This guide covers a complete reference implementation for SIMATIC S7-1500 with WinCC in TIA Portal, plus an alternative path using the WinCC add-on PM-Maint for larger fleets.
Architecture and Tag Convention
The source problem statement uses two Boolean tags per device to encode state:
-
tag1– Running feedback (TRUE = motor contactor closed / drive enabled) -
tag2– Out-of-service or fault flag (TRUE = device unavailable)
The logical combination tag1 == 1 AND tag2 == 0 defines the device as actively running and countable. Alarm states can be derived independently from tag1 == 1 AND tag2 == 1 (running with fault) or tag1 == 0 AND tag2 == 0 (idle/ready).
| tag1 | tag2 | Device State | Count Time? |
|---|---|---|---|
| 0 | 0 | Idle / ready | No |
| 1 | 0 | Running healthy | Yes |
| 1 | 1 | Running with fault | Yes (typically flagged separately) |
| 0 | 1 | Out of service / tripped | No |
Prerequisites
- STEP 7 V17 or later in TIA Portal (V18/V19 recommended for current S7-1500 firmware).
- S7-1500 CPU with firmware ≥ V2.9 (for the IEC timers and TIME data type usage shown). S7-1200 with firmware ≥ V4.4 is interchangeable for this code.
- WinCC Professional / WinCC Comfort / WinCC Advanced on the engineering station.
- HMI connection established between the PLC project and the HMI project (HMI tags pointing to the PLC data block).
- Optional: WinCC PM-Maint add-on license for large fleets.
PLC Data Block Design
Create a global data block DB_Runtime with a UDT for each device so the array scales cleanly:
TYPE UDT_DeviceRuntime
STRUCT
bRunning : BOOL; // copy of tag1
bOutOfService : BOOL; // copy of tag2
bPrevRunning : BOOL; // edge-detection flag
bAlarm : BOOL; // setpoint exceeded
bAck : BOOL; // operator acknowledge
dtStart : DATE_AND_TIME; // DTL stamp at run-start
tAccumulated : TIME; // total running time
tSetpoint : TIME; // operator setpoint
rHoursDisplay : REAL; // HMI display value in hours
END_STRUCT
END_TYPE
Instantiate an array sized to the fleet:
DATA_BLOCK DB_Runtime
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
NON_RETAIN
STRUCT
aDevices : ARRAY[1..64] OF UDT_DeviceRuntime; // up to 64 assets
END_STRUCT
END_DATA_BLOCK
tAccumulated, tSetpoint, and the alarm bit must be retained across power cycles. Either mark the DB as RETAIN or place the specific fields in a separate RETAIN area. The dtStart stamp and the edge flags can be non-retain.Reading the CPU Clock with SFCs
The classic S7-300/400 approach uses SFC0 (SET_CLK) and SFC1 (READ_CLK) to read and set the real-time clock. The S7-1500 equivalent uses the DTL data type and the RD_SYS_T / WR_SYS_T instructions, which are symbolically called from user code.
| Platform | Read Clock Instruction | Set Clock Instruction | Time Format |
|---|---|---|---|
| S7-300/400 | SFC1 READ_CLK | SFC0 SET_CLK | DATE_AND_TIME (8 bytes BCD) |
| S7-1200/1500 | RD_SYS_T | WR_SYS_T | DTL (16 bytes structured) |
For run-hour metering you normally only need to read the clock. Capture it once per OB1 cycle into a temporary variable and subtract from the previous reading to get the cycle delta:
// In OB1 or a cyclic interrupt OB (e.g. OB35 at 1 s)
#tNow := RD_SYS_T(); // current DTL timestamp
#tCycleDelta := #tNow - #tPrev; // returns TIME
#tPrev := #tNow;
Cyclic Accumulator in SCL
The cleanest implementation places the accumulator logic in a cyclic OB (OB30–OB38) with a known period, typically 1 second. Using OB1 is fine but it runs faster than the meter resolution and wastes CPU. The pattern below handles 64 devices in one call:
// FB_RuntimeAccumulator - called from OB35 (1 s cycle)
FUNCTION_BLOCK FB_RuntimeAccumulator
VAR
i : INT;
tDelta : TIME;
END_VAR
BEGIN
// Read system time once per cycle
#tDelta := RD_SYS_T() - #tPrevCycle;
#tPrevCycle := RD_SYS_T();
FOR #i := 1 TO 64 DO
// Update raw inputs (these are mapped by HMI or by the device FB)
// aDevices[i].bRunning := "device".running[i];
// aDevices[i].bOutOfService := "device".out_of_service[i];
IF #aDevices[#i].bRunning AND NOT #aDevices[#i].bOutOfService THEN
// Device is healthy and running -> accumulate
#aDevices[#i].tAccumulated := #aDevices[#i].tAccumulated + #tDelta;
// Rising-edge capture (optional, for diagnostics)
IF NOT #aDevices[#i].bPrevRunning THEN
#aDevices[#i].dtStart := RD_SYS_T();
END_IF;
END_IF;
#aDevices[#i].bPrevRunning := #aDevices[#i].bRunning;
// Alarm evaluation
IF #aDevices[#i].tAccumulated >= #aDevices[#i].tSetpoint
AND #aDevices[#i].tSetpoint > T#0s THEN
#aDevices[#i].bAlarm := TRUE;
END_IF;
// Operator ack resets the alarm but not the counter
IF #aDevices[#i].bAck THEN
#aDevices[#i].bAlarm := FALSE;
#aDevices[#i].bAck := FALSE;
END_IF;
// Display value: tAccumulated is ms, convert to hours
#aDevices[#i].rHoursDisplay := DWORD_TO_REAL(TIME_TO_DWORD(#aDevices[#i].tAccumulated)) / 3600000.0;
END_FOR;
END_FUNCTION_BLOCK
Ladder Equivalent
If your site standard mandates LAD/FBD, the same logic translates into a network that uses an IEC timer or an add instruction. The preferred pattern uses ADD_TIME on a register of TIME type, since plain counters (CTU) operate on integers and require manual conversion.
Network 1 - Capture cycle delta (1 s OB)
RD_SYS_T EN ENO MOVE
|---RET_VAL-->| |---OUT-->#tNow-| |-->#tDeltaPrev
SUB_TIME
IN1:=#tNow, IN2:=#tDeltaPrev, OUT=>#tDeltaSec
Network 2 - Accumulator (one rung per device)
aDevices[i].bRunning aDevices[i].bOutOfService
| | | |
|--| |----------------|/|------(ADD_TIME)
| IN1:=#tDeltaSec
| IN2:=aDevices[i].tAccumulated
| OUT=>aDevices[i].tAccumulated
Network 3 - Alarm
aDevices[i].tAccumulated aDevices[i].tSetpoint
| | | |
|--[GE_TIME]--------------[>0s]-----S aDevices[i].bAlarm
Network 4 - Ack reset
aDevices[i].bAck
| |
|--[/]--R aDevices[i].bAlarm
| |
|------S aDevices[i].bAck (auto-clear pulse)
WinCC HMI Configuration
Once the PLC block is built, expose three fields per device to the HMI:
| PLC Tag | Direction | HMI Variable | WinCC Element |
|---|---|---|---|
DB_Runtime.aDevices[i].tAccumulated |
Read | Hours_Display[i] |
IO field (output, REAL, "%.1f h") |
DB_Runtime.aDevices[i].tSetpoint |
Read/Write | Setpoint_h[i] |
IO field (input/output, REAL) |
DB_Runtime.aDevices[i].bAlarm |
Read | Alarm_Hours[i] |
Alarm bit + indicator lamp |
DB_Runtime.aDevices[i].bAck |
Write | Ack_Hours[i] |
Button |
For WinCC Professional / Comfort use the standard PLC tag interface; for WinCC on a PC station the connection uses S7ONLINE (TCP) at the default port 102. Update cycles of 1 s are sufficient because the meter resolution is already 1 s.
WinCC Alarm Wiring
Configure a discrete alarm in the HMI alarms editor that triggers on the bAlarm bit:
- Open HMI Alarms > Discrete Alarms.
- Create a new alarm, e.g.
RA_Runtime_Exceeded, text "Device &[i] reached maintenance hours". - Set the trigger tag to
Alarm_Hours[i]with state = 1. - Assign the alarm to class Warning with acknowledge model Single acknowledgment.
- Wire the acknowledge button to the
Ack_Hours[i]HMI tag (the PLC auto-clears the ack pulse).
WinCC User Archive Path
If you want historical records (every shift, every maintenance), use the WinCC User Archive as the persistence layer. Two archives are typical:
- Runtime_Log – appended every time the alarm fires, fields: timestamp, device index, accumulated hours, setpoint, operator.
- Setpoint_Log – appended when the operator changes a setpoint from the HMI.
A small VBScript triggered by the alarm tag writes the row:
' Trigger: tag Alarm_Hours_001 changes 0 -> 1
Dim oUA, oRec
Set oUA = HMIRuntime.Tags("@UA_Runtime_Log").GetObject ' user archive reference
Set oRec = oUA.Data.Create
oRec.Fields("TimeStamp").Value = Now
oRec.Fields("Device").Value = 1
oRec.Fields("Hours").Value = HMIRuntime.Tags("Hours_Display_001").Value
oRec.Fields("Setpoint").Value = HMIRuntime.Tags("Setpoint_h_001").Value
oRec.Fields("Operator").Value = HMIRuntime.Tags("@CurrentUser").Value
oUA.Data.Insert oRec
HMIRuntime.Trace "Runtime alarm logged for device 1" & vbCrLf
Schedule a second script to run once per hour that scans the archive for devices within 5% of their setpoint and pre-emptively warns the operator.
PM-Maint Alternative for Large Fleets
When the device count exceeds roughly 20, hand-rolled DB arrays and scripts become hard to maintain. The Siemens PM-Maint (Plant Maintenance) add-on for WinCC provides:
- Pre-built faceplates for runtime, cycle counts, and wear-based triggers.
- SQL Server persistence of maintenance plans, work orders, and history.
- Automatic alarm generation based on PM schedules.
- Operator and maintenance role separation via WinCC user administration.
PM-Maint consumes the same boolean running signals; internally it reads the HMI clock and stores cumulative hours. For plants that already have WinCC Professional and an MES/SQL backend it is usually cheaper than custom code.
PowerFlex, MicroLogix, and Non-S7 PLCs
If the fleet mixes controllers – for example Allen-Bradley PowerFlex drives with run feedback and a Modicon M340 for valves – the same WinCC alarm tag can subscribe to OPC UA items from each controller. The accumulator logic then runs in a small WinCC C or VBScript, but this is a downgrade compared to the PLC-based approach because:
- WinCC must remain online continuously.
- Tags lost during communication failure are not extrapolated.
- You lose the cleanliness of one source of truth.
If the PLC mix is unavoidable, the closest equivalent to RD_SYS_T on Allen-Bradley is the GSV instruction reading the WallClockTime attribute from the WallClockTime object.
Edge Cases and Field-Proven Caveats
- Time base drift: Using OB1 with RD_SYS_T introduces a small jitter because OB1 is not strictly periodic. Use OB30–OB38 configured to 1000 ms, or a self-correcting mechanism that subtracts the previous timestamp rather than assuming a 1 s delta.
- DST and time zone changes: If the plant uses daylight saving, the meter can lose or gain an hour at the switch. Either disable DST on the CPU clock or freeze the meter during the switch window.
-
Setpoint zero: Always guard against
tSetpoint = T#0s; otherwise every device trips immediately. The code above checkstSetpoint > T#0sbefore raising the alarm. - Counter rollover: TIME on S7-1500 is a DINT in milliseconds with a max of 2,147,483,647 ms (~24.8 days of continuous run). For long-running meters use LREAL or a DWord counter of hours, then convert for display.
- Buffered vs unbuffered DBs: On S7-1500 the new optimized block access model uses non-retentive by default. Confirm the retention bit on the cumulative field; otherwise a power-cycle resets every meter.
-
Start-up alignment: When the CPU boots,
RD_SYS_Treturns the buffered time only if a battery or capacitor-backed RTC is fitted. Without one, the time starts at the last compiled default until synchronized via NTP.
Verification and Commissioning
- Force
bRunning = TRUEon device 1 from the watch table and observerHoursDisplayincrease at ~3600 units per hour. Watch for at least 60 s to confirm 0.0167 h increments. - Force the setpoint to 0.01 h (36 s). Confirm the alarm bit sets within the next accumulator cycle.
- Acknowledge from the HMI. Confirm the alarm clears and the counter continues to grow (do not reset on ack).
- Power-cycle the CPU. Confirm the cumulative value survives.
- Disconnect the HMI connection. Confirm the PLC counter continues to increment – this is the whole point of putting logic in the PLC.
- Reconnect the HMI. Confirm the displayed hours match the PLC value exactly.
- Simulate an out-of-service flag during a run. Confirm the counter pauses while the flag is set.
Frequently Asked Questions
Should I calculate running hours in WinCC or in the S7 PLC?
Always in the PLC. The CPU's real-time clock is buffered, deterministic, and immune to WinCC station crashes, panel reboots, or SQL server outages. WinCC should only display the value and fire the alarm.
Which SFC or instruction reads the CPU clock on S7-1500?
Use the RD_SYS_T instruction. It returns a 16-byte DTL timestamp. On S7-300/400 the equivalent is SFC1 (READ_CLK), which returns an 8-byte DATE_AND_TIME value in BCD.
What is the maximum value of a TIME accumulator before it rolls over?
TIME is a 32-bit signed integer in milliseconds, so the maximum is 2,147,483,647 ms – about 24.8 days of continuous run. For long-term meters convert to a DWord or LREAL counter of whole hours.
How do I keep the cumulative value across a power cycle?
Mark the relevant data block fields as RETAIN in the TIA Portal, or use a separate RETAIN DB. Without this flag the S7-1500 non-retentive default will zero the counter on every restart.
Can I use WinCC PM-Maint instead of writing custom code?
Yes. PM-Maint is a licensed add-on for WinCC that includes ready-made faceplates, SQL-backed history, and automatic alarm generation. It is the recommended approach for fleets above ~20 devices where custom UDTs and scripts become hard to maintain.