Implementing 7-, 8-, and 30-Day Timers on Siemens S7-300 CPU315-2PN/DP
Long-duration timing requirements are routine in water/wastewater, HVAC, and process plants. A pump that must rotate duties every 30 days, a filter that must backwash 7 days after a high-high level event, or a sterilisation cycle that triggers 8 days after a tank fill all push past the practical limits of any single IEC timer block. This reference covers three production-grade implementations for the S7-300 platform running STEP 7 V5.x (SIMATIC Manager) on a CPU 315-2PN/DP (6ES7 315-2EH14-0AB0 or compatible firmware), explains why the typical first attempts fail, and shows the commissioning checks that prove the design before deployment.
Problem Definition
The target application is a small pumping station controlled by a CPU 315-2PN/DP. Three timing requirements must be met simultaneously, each with independent trigger, reset, and action:
| Function | Trigger | Preset | Action on expiry | Reset source |
|---|---|---|---|---|
| Pump duty rotation | Pump-1 motor contactor closed | 30 days (720 h) | Stop Pump-1, start Pump-2 | Pump-2 confirmed running |
| Process-1 auto-start | Tank level = LHH (high-high) | 7 days (168 h) | Start Process-1 (e.g., transfer pump) | Operator acknowledge |
| Process-2 auto-start | Tank level = LHH (high-high) | 8 days (192 h) | Start Process-2 (e.g., backwash) | Operator acknowledge |
All three timers must operate independently, must be resettable on command from the HMI, and must optionally survive a CPU STOP/RUN transition or loss of power without losing their accumulated value. The first two requirements drive the code structure; the third determines whether the values live in retentive M-markers or retentive DB words. The reference S7-300 CPU 31xC and CPU 31x Manual documents the IEC timer data type limits that bound the problem.
Why a Single IEC Timer Cannot Reach 30 Days
The S7-300/400 family offers three IEC timer function blocks: SFB 3 / TP (pulse), SFB 4 / TON (on-delay), and SFB 5 / TOF (off-delay). Their preset and elapsed values are stored in the IEC TIME data type (DWORD, 32-bit signed), which the IEC 61131-3 standard defines as a duration in milliseconds. The maximum positive value is 231 − 1 ms = 2,147,483,647 ms, which is approximately 24.86 days. The 30-day requirement exceeds this ceiling by roughly 5 days, so a single TON instance cannot be used even at the mathematical limit.
The legacy S5TIME format used by S_ODT, S_PULSE, S_ODTS, S_OFFDT, and S_PEXT is even more restrictive. The 16-bit BCD count (0–999) is multiplied by a time base selected from 10 ms, 100 ms, 1 s, or 10 s, so the absolute maximum is 999 × 10 s = 9,990 s ≈ 2.77 hours. This explains why first attempts using an IEC timer with MD800 as the preset produced an apparently-correct network that could never reach the desired value - the preset silently saturated. Reference STEP 7 - Working with Timers and Counters tabulates the S5TIME ranges.
Two practical workarounds exist: cascade a 1-minute timer into a minute counter and convert counts to days, or compare real-time-clock (RTC) timestamps against a target DATE_AND_TIME using SFC 1. A third option - SFB 4 CEV (operating-hours counter) - is purpose-built for run-time meters. Each method has trade-offs in precision, retentivity, and code complexity. The full S7-300 Automation System Manual lists every available SFB/SFC and its retentive behaviour.
Method 1 - Cascaded 1-Minute Timer + Counter
This is the simplest, most portable approach and requires no system clock. The architecture is:
- An IEC
TONwith preset = 60,000 ms (= T#1m) generates a one-shot every minute while the run-condition is TRUE. - A rising-edge-detected CV-flag increments an
INTminute counter. - When the minute counter reaches 60 it resets to 0 and a separate hour counter is incremented.
- Hour counters are compared to the day-equivalent preset (e.g., 30 d × 24 h = 720 h).
- When the hour counter reaches the preset, a one-shot "expired" bit is set and the process is started.
For a pump that runs essentially 24/7 the run-condition is "Pump-1 contactor closed" or "Pump-1 current > 5 A" (using a 4–20 mA transducer). For a tank high-high event the run-condition is "LHH TRUE AND Process-1 NOT yet started". Once the expired action has fired the timer should be latched off, and a manual reset bit must be provided to re-arm the cycle.
Data Block Layout
Use a dedicated DB (e.g., DB 50 "Timers") with the following structure so that values are grouped and easy to monitor online in STEP 7 or via the HMI:
DATA_BLOCK "Timers"
VERSION : 0.1
STRUCT
Pump1_RunMinutes : INT; // 0..59
Pump1_RunHours : DINT; // 0..1000 for 41 d
Pump1_PresetHours : DINT; // 720 = 30 d
Pump1_Expired : BOOL;
Pump1_Reset : BOOL;
HH_7d_Minutes : INT;
HH_7d_Hours : DINT;
HH_7d_PresetHours : DINT; // 168 = 7 d
HH_7d_Expired : BOOL;
HH_7d_Reset : BOOL;
HH_8d_Minutes : INT;
HH_8d_Hours : DINT;
HH_8d_PresetHours : DINT; // 192 = 8 d
HH_8d_Expired : BOOL;
HH_8d_Reset : BOOL;
Tick_1min : BOOL;
END_STRUCT;
END_DATA_BLOCK
LAD Network - 1-Minute Tick Generator
The 1-minute tick uses an IEC TON with a 1-minute preset. Only the rising edge of the Q output at expiry is used; the elapsed time is irrelevant.
A M 100.0 // Run-enable flag (TRUE for pump, LHH-latched for tank events)
AN M 100.1 // Inhibit on CPU fault
L T#1m
SD "Tick_1min" // IEC TON in DB 50 / multi-instance
A "Tick_1min"
FP M 100.2 // Detect one rising edge per minute
JC INCR
BEU
INCR: ...
ST Implementation of the Cascade
The cascade is more compact in Structured Text and easier to commission because all the time math is in one place. Place the following in OB 35 (cyclic interrupt, 100 ms by default) or directly in OB 1:
// 1-minute tick detection
IF "Timers".Tick_1min THEN
"Timers".Tick_1min := FALSE;
// -------- Pump-1 30-day run meter --------
IF "Pump1_Running" AND NOT "Timers".Pump1_Expired THEN
"Timers".Pump1_RunMinutes := "Timers".Pump1_RunMinutes + 1;
IF "Timers".Pump1_RunMinutes >= 60 THEN
"Timers".Pump1_RunMinutes := 0;
"Timers".Pump1_RunHours := "Timers".Pump1_RunHours + 1;
IF "Timers".Pump1_RunHours >= "Timers".Pump1_PresetHours THEN
"Timers".Pump1_Expired := TRUE; // Pump-2 start condition
END_IF;
END_IF;
END_IF;
// -------- 7-day Process-1 timer (LHH triggered) --------
IF "Tank_LHH" AND NOT "Timers".HH_7d_Expired THEN
"Timers".HH_7d_Minutes := "Timers".HH_7d_Minutes + 1;
IF "Timers".HH_7d_Minutes >= 60 THEN
"Timers".HH_7d_Minutes := 0;
"Timers".HH_7d_Hours := "Timers".HH_7d_Hours + 1;
IF "Timers".HH_7d_Hours >= "Timers".HH_7d_PresetHours THEN
"Timers".HH_7d_Expired := TRUE;
"Process_1_Cmd" := TRUE;
END_IF;
END_IF;
END_IF;
// -------- 8-day Process-2 timer (same LHH trigger) --------
// identical pattern with preset = 192 h
END_IF;
// Manual reset paths
IF "Timers".Pump1_Reset THEN
"Timers".Pump1_RunMinutes := 0;
"Timers".Pump1_RunHours := 0;
"Timers".Pump1_Expired := FALSE;
"Timers".Pump1_Reset := FALSE;
END_IF;
Note that the 7-day and 8-day timers share the same trigger (LHH) but are independent counters. The 7-day timer fires first; the 8-day timer keeps counting until 192 h have elapsed since the LHH event was first seen, producing a deterministic 24 h spacing between Process-1 and Process-2.
Resolution and Worst-Case Error
Each tick is one minute by definition, so the absolute worst-case timing error is +59 s, or about 0.003 % of a 30-day cycle. This is acceptable for pump rotation but is too coarse for an event-time-of-day requirement. Use Method 2 when you need real-time-clock precision (1 s) and an audit-grade timestamp.
Method 2 - Real-Time Clock Comparison with SFC 1
The CPU 315-2PN/DP contains a hardware-backed real-time clock with typical accuracy of ±2 s/day at 25 °C. SFC 1 READ_CLK reads the current DATE_AND_TIME (8-byte BCD format, base 1990-01-01) into a destination area. By stamping the trigger event and comparing it to the current RTC, the elapsed duration in seconds can be calculated using FC 3 / FC 33 / FC 40 from the STEP 7 standard library.
Data Structures
DATA_BLOCK "RTC_Timers"
VERSION : 0.1
STRUCT
LHH_Event_Time : DATE_AND_TIME; // SFC 1 capture
LHH_Triggered : BOOL;
Process1_Due : BOOL;
Process2_Due : BOOL;
Pump1_Start_Time : DATE_AND_TIME;
Pump1_Running : BOOL;
Pump1_Due : BOOL;
END_STRUCT;
END_DATA_BLOCK
Capturing the LHH Trigger
A "Tank_LHH" // high-high digital input (I 0.7)
FP "LHH_Edge_Mem" // one-shot on rising edge
JCN END1
CALL SFC 1
RET_VAL := MW 200
CDT := "RTC_Timers".LHH_Event_Time
SET
S "RTC_Timers".LHH_Triggered
R "RTC_Timers".Process1_Due
R "RTC_Timers".Process2_Due
END1: NOP 0
Comparing Elapsed Time
S7-300 has no native DATE_AND_TIME subtraction, but FC 3 "D_TD" converts DT to seconds-since-1990-01-01 (DINT), after which straight subtraction and division gives elapsed days or seconds. A user-friendly approach is to encapsulate the math in an FB that returns a DINT of elapsed seconds:
// returns elapsed seconds since LHH_Event_Time
L "RTC_Timers".LHH_Event_Time
T #DT_old
CALL FC 3 // D_TD: DT → DINT seconds
IN := #DT_now_sec
RET_VAL := #elapsed_sec
CALL FC 3
IN := #DT_old
RET_VAL := #DT_old_sec
L #DT_now_sec
L #DT_old_sec
-D
T #elapsed_sec // signed DINT, allow wrap-around
// 7-day check: 7 * 86400 = 604800 s
L 604800
>=D
JC NO7
SET
S "RTC_Timers".Process1_Due
NO7: NOP 0
// 8-day check: 8 * 86400 = 691200 s
L 691200
>=D
JC NO8
SET
S "RTC_Timers".Process2_Due
NO8: NOP 0
Because the comparison is performed against absolute wall-clock time, the timer is inherently retentive. The RTC has its own battery-backed hardware clock and the trigger timestamp lives in a retentive DB. A power loss of any duration does not corrupt the elapsed-time calculation: when the CPU returns to RUN, SFC 1 reports the current time and the comparator immediately sees the correct value.
Method 3 - Pump Run-Time Meter Using SFB 4 (CEV)
For the 30-day pump rotation requirement specifically, the cleanest implementation is to use the operating-hours counter SFB 4 ("CEV"). SFB 4 increments an internal DINT for as long as its CU input is TRUE and automatically resets when the DINT reaches the preset. The instance DB keeps the hours in volatile memory unless explicitly placed in the retentive area via the hardware configuration.
CALL "CEV" , DB72 // SFB 4 instance
CU := "Pump1_Running" // 1 = count, 0 = hold
RESET := "Reset_Button"
PV := L#720 // 30 d × 24 h
Q := "Pump1_HoursDone"
CV := "Pump1_HoursCounted" // current hours, DINT
The CPU 315-2PN/DP requires SFB 4 to be instantiated as a multi-instance or in a dedicated DB. To make the counter survive STOP/RUN, open the CPU Properties in HW Config → Retentive Memory and tick "Retentivity for all DBs" or place the instance DB explicitly in the retentive area. Reference S7-300/400 Standard Software - System and Standard Functions for the full list of CEV/CTV/CU parameters.
Pump Changeover and LHH Process-Start Logic
30-Day Pump Rotation
Once Pump1_HoursDone goes TRUE, the duty-rotation code should:
- Latch Pump-1 off.
- Energise Pump-2 contactor after a 5 s overlap interlock to avoid both pumps starting together.
- Start a mirror SFB 4 CEV for Pump-2 with the same 720 h preset.
- Reset Pump-1's CEV only after Pump-2 has been confirmed running (current-feedback or contactor auxiliary).
A "Pump1_HoursDone"
AN "Pump1_Fault"
AN "Pump2_Running"
S "Pump2_Cmd" // start pump 2
R "Pump1_Cmd" // stop pump 1
// 5 s overlap interlock then pulse the reset
L S5T#5s
SD T 10
A T 10
A "Pump2_Running"
R "Reset_Button" // pulse CEV reset for Pump-1
If the application requires absolute equal wear across multiple pumps, use a third FB that tracks total pump starts, total run-hours, and total starts since last maintenance, then selects the least-worn pump on each call. The PLCopen-compliant FB MC_Power patterns available in TIA Portal also apply on S7-300 with STEP 7 V5.x but require the optional "PLCopen Library" add-on.
7-Day and 8-Day LHH Process Starts
The 7-day timer should fire only the first time the LHH condition is observed after a reset (a new tank cycle). The 8-day timer should fire 24 h later. Code pattern in ST:
// Edge-triggered capture of LHH
IF "Tank_LHH" AND NOT "LHH_Latch" THEN
"LHH_Latch" := TRUE;
SFC1_RET := READ_CLK(CDT := "LHH_Start_DT");
END_IF;
IF NOT "Tank_LHH" THEN
"LHH_Latch" := FALSE;
END_IF;
// 7-day check (uses ELAPSED_DT FB returning DINT seconds)
IF "LHH_Latch" AND NOT "Process1_Done" THEN
IF ELAPSED_DT("LHH_Start_DT", CURRENT_DT) >= 604800 THEN
"Process1_Cmd" := TRUE;
"Process1_Done" := TRUE;
END_IF;
END_IF;
// 8-day check
IF "LHH_Latch" AND NOT "Process2_Done" THEN
IF ELAPSED_DT("LHH_Start_DT", CURRENT_DT) >= 691200 THEN
"Process2_Cmd" := TRUE;
"Process2_Done" := TRUE;
END_IF;
END_IF;
The ELAPSED_DT FB subtracts two DATE_AND_TIME values via FC 3 (D_TD → seconds since 1990-01-01) and returns a DINT result. S7-300's IEC TON cannot directly accept a TIME result longer than 24.86 days, so the elapsed time must be compared as raw seconds (DINT) rather than fed into a TON preset.
Retentive vs Non-Retentive Storage
The CPU 315-2PN/DP allocates retentive areas during hardware configuration. Defaults are MB 0–15 (16 bytes), T 0–127 (128 timers), and C 0–63 (64 counters). For multi-day timing applications where the PLC may experience power dips, the storage location of the counters determines whether they survive.
| Storage area | Volatile | Retentive | Typical use |
|---|---|---|---|
| M 0.0 – M 15.7 | No | Yes (default) | Run-time meter hours counter |
| M 16.0 – M 255.7 | Yes | No | Intermediates, scan flags |
| DB with "Non-retentive" UNCHECKED | No | Yes | Hour / minute counters, presets |
| DB with "Non-retentive" CHECKED | Yes | No | Edge memories, one-shots |
| SFB 4 instance DB (DB 72 above) | Volatile unless marked | Requires tick in HW config | Operating-hours counter |
| Retentive DB 50 above | No | Yes | Cascade counters |
For a pump duty-rotation or filter back-wash that must survive a power outage of indefinite length, place all hour/minute counters in a retentive DB (DB 50 above) and clear the "Non-retentive" checkbox in the DB properties. For a process that should restart its timer on every power-up (e.g., a one-shot 30-day sterilisation cycle), leave the DB non-retentive.
Alternative - Time-of-Day Interrupts (OB 10) for Periodic Schedules
If the 7/8/30-day requirements are periodic (e.g., "every 7 days at 02:00") rather than continuous from a trigger event, OB 10 time-of-day interrupt is the natural choice. OB 10 is called by the CPU operating system at the programmed date and time, regardless of OB 1 execution. Inside OB 10, set the next run time via SFC 28 SET_TINT and execute the desired action. This avoids counters entirely and uses the CPU's RTC scheduler.
// In HW config: enable OB 10, set execution date/time, e.g., 02:00:00 daily
// In OB 10:
CALL SFC 28
OB_NR := 10
SDT := "Next_OB10_DT" // next trigger time (DATE_AND_TIME)
PERIOD := W#16#0000 // once
RET_VAL := "OB10_RetVal"
// User action
A "Pump1_Running"
S "Pump2_Cmd" // rotate pump duties at 02:00
R "Pump1_Cmd"
The CPU 315-2PN/DP supports OB 10–17 (eight time-of-day OBs), each capable of being scheduled once, every minute, hourly, daily, weekly, monthly, or yearly via the PERIOD byte. For multi-day intervals compute the next SDT in seconds and load it as a DATE_AND_TIME constant. The same caveat regarding TIME data-type limits applies when converting days × 86400 into a DINT offset, so use DINT arithmetic.
Commissioning and Verification
Do not run the timer for 30 days during commissioning. Two safe techniques compress the test cycle:
- Preset override. Temporarily change the preset from 720 h to 0 h so the expired bit fires on the first scan. Verify the downstream action (Pump-2 starts, Process-1 starts). Reset to the real value.
- Time compression. Change the 1-minute tick to 1-second (T#1s) and reduce presets by a factor of 60. The 30-day timer fires in 12 minutes, the 7-day in 2.8 min, and the 8-day in 3.2 min.
For the RTC method, temporarily set the CPU clock one week forward via SFC 0 SET_CLK, trigger the LHH event, then set the clock back. Verify the elapsed-time logic behaves as expected without disturbing the battery-buffered time.
| Test | Action | Expected result |
|---|---|---|
| Pump-1 30-day rotation | Start Pump-1, force 1-min tick to 1 s, preset = 12 min | After 12 min Pump-2 starts, Pump-1 stops, Pump-2 hour meter starts |
| 7-day Process-1 start | Force LHH TRUE, monitor DB50.HH_7d_Hours | Counts to 168, then Process_1_Cmd = TRUE |
| 8-day Process-2 start | Same as above with HH_8d_Hours | Counts to 192, then Process_2_Cmd = TRUE; 24 h after Process_1 fired |
| Power-loss retention | At 50 h elapsed, STOP CPU, power down 5 min, power up | Pump1_RunHours still = 50, continues counting from 51 |
| Cold restart (MRES) | MRES, then run | Timers reset to 0 unless retentivity is configured |
| RTC wrap test | Set CPU clock back 2 days, retrigger LHH | Elapsed_DT returns negative; no process start |
| Simultaneous pump prevention | Manually force both Pump1_Cmd and Pump2_Cmd | Interlock holds; only one contactor closed at a time |
Troubleshooting Matrix
| Symptom | Likely root cause | Fix |
|---|---|---|
| 30-day timer never fires; Q stays FALSE | Preset entered as S5TIME constant (max 2.77 h) | Use IEC TON with TIME data type and cascade counter, or use SFC 1 + RTC comparison |
| MD800 shows 30 d but compiler warns "range error" | MD loaded with T#30d overflows IEC TIME 32-bit signed | Switch to DINT day counter and compare with 30, not MD with TIME constant |
| Hour counter resets to 0 on every STOP→RUN | DB not retentive or placed outside retentive area | Open DB properties → uncheck "Non-retentive", or move to MB 0–15 (retentive by default) |
| 1-minute tick fires more than once per minute | Self-reset in same network creates multiple passes | Use edge bit (FP) and reset timer only in the next scan, or use IEC TP with PT = T#1m and clear the input |
| LHH event lost on power dip | LHH was a momentary pulse; latch not retained | Latch LHH in retentive M-marker or DB bit; reset only on manual acknowledge |
| Both Pump-1 and Pump-2 start simultaneously | Reset of Pump-1 CEV happens before Pump-2 contactor closes | Add 5 s overlap interlock; only reset Pump-1 meter after Pump-2 running contactor confirms |
| Process-1 and Process-2 fire at the same time | Single 7-day timer used for both with wrong preset | Implement two independent counters with presets 168 h and 192 h |
| RTC timestamps read as 1994-01-01 | Battery exhausted / CPU cold-started without SFC 0 | Replace battery, run SFC 0 from HMI or NTP at every warm restart, monitor buffer-low bit |
| OB 10 fires immediately on cold start | OB 10 start time left in past | Always set start time forward of current RTC; on firmware < V3.3 add 2 s margin |
| OB 35 cyclic interrupt not called | OB 35 not inserted in OB 1 priority tree or scan time > 100 ms | Insert OB 35 in HW config; verify scan time in OB 1 |
Choosing Between the Three Methods
| Criterion | Cascade counter (Method 1) | RTC comparison (Method 2) | SFB 4 CEV (Method 3) |
|---|---|---|---|
| Maximum duration | Unlimited (counter only) | Unlimited | 2,147,483,647 h (≈ 245,000 y) |
| Resolution | 1 min (default), tunable | 1 s | 1 h |
| Power-loss retentive | Optional (mark DB retentive) | Inherent (battery-backed RTC) | Optional (instance DB) |
| Code complexity | Low | Medium (need DT math) | Very low (one SFB call) |
| Audit-grade timestamp | No | Yes (DATE_AND_TIME) | No (counter only) |
| Best for | Tank-event cascade, multi-stage processes | Audit-trail events, billing, alarms | Pump / motor hour meters |
For this application the recommended mix is SFB 4 CEV for the 30-day pump rotation (cleanest, smallest code, built-in PV comparison) and cascade counter for the 7-day and 8-day LHH timers (independent counters, easy HMI reset). If audit trails are required for compliance, layer the RTC method on top as a parallel timestamp log without removing the counter.
Field-Proven Caveats and Summary
RuntimeMeter instruction in the "Extended instructions" palette.The 30-day, 8-day, and 7-day timing requirements cannot be met with a single S7-300 IEC timer block because the 32-bit signed TIME data type saturates at 24.86 days. Three production-grade workarounds exist: cascade a short IEC timer into a counter for continuous-run meters, compare DATE_AND_TIME stamps using SFC 1 for event-triggered delays, or use SFB 4 CEV for operating-hours accumulation. For this application, combining SFB 4 (pump rotation) with two independent cascade counters (7-day and 8-day) provides the best balance of code clarity, retentivity, and commissioning speed. The retention behaviour is governed by the retentive memory settings in HW Config and by the "Non-retentive" property of each data block. Verify all three timers with time-compressed commissioning before handing over the system, and always re-synchronise the CPU clock from NTP after a battery replacement.
FAQ
Why does my IEC TON never fire when I set the preset to 30 days?
The IEC TIME data type is a 32-bit signed value in milliseconds with a maximum of 2,147,483,647 ms (~24.86 days). A 30-day preset exceeds this limit, so the value is clamped at maximum or rejected by the compiler. Replace the single TON with a cascade of a short timer into a counter, or compare DATE_AND_TIME stamps using SFC 1 / FC 3.
How do I make the 30-day timer survive a power loss?
Place the hour and minute counters in a data block marked as retentive (clear the "Non-retentive" checkbox in the DB properties), or store them in MB 0–15 (retentive by default on the CPU 315-2PN/DP). Alternatively, use the real-time clock method with SFC 1 and a retentive DATE_AND_TIME timestamp; the RTC is battery-backed so wall-clock time is preserved through short outages.
Can I use a single 7-day timer for both Process-1 and Process-2?
No. The two processes must fire 24 h apart, so they need independent counters. Use two DINT hour counters in the same DB with presets 168 h and 192 h respectively, each driven by the same LHH edge-detect block. Once Process-1 fires, latch "Process-1 done" and let the 8-day counter keep counting until 192 h elapsed.
What is the simplest way to rotate two pumps every 30 days?
Use SFB 4 "CEV" with PV = L#720 (30 d × 24 h). Drive its CU input from the Pump-1 contactor-closed flag. When Q rises, start Pump-2, stop Pump-1, and reset the CEV via the RESET input (interlock to prevent both pumps running). Make the CEV instance DB retentive if you need to survive power loss.
Can OB 10 replace the cascade counter?
Yes, if the timing requirement is periodic (e.g., "every 30 days at 02:00"). OB 10 is called by the CPU operating system at the programmed date/time; inside OB 10 you set the next run time via SFC 28 and execute the desired action. For one-shot delays from a trigger event (e.g., "7 days after LHH goes TRUE"), use the cascade counter or RTC-comparison method instead.