S7-1200 Hourly Trigger: RD_SYS_T, DTL_TO_TOD, MOD 3600 Logic

David Krause13 min read
S7-1200SiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. Overview

Generating a deterministic boolean trigger on every full hour (HH:00:00) of an S7-1200 CPU real-time clock is a recurring engineering task for shift-change logging, hourly report aggregation, energy-meter freeze registers, and time-of-use (TOU) tariff switching. This reference documents a compact SCL implementation that reads the CPU clock with RD_SYS_T, converts it to milliseconds-since-midnight, and uses the modulo operator to detect the exact hour boundary. The technique is scan-time tolerant, requires no external library, and ports to any S7-1200/1500 project built with TIA Portal V14 or later.

The complete one-line condition that produces the trigger is:

#Trig := ((TOD_TO_DINT(DTL_TO_TOD(RD_SYS_T(#t))) MOD 3600000) = 0);

The remainder of this document breaks down the math, the underlying data types, the edge cases (power-up behavior, scan period, 23:59:59 to 00:00:00 wrap), and the production-grade alternatives including the Siemens Schedule library and time-of-day hardware interrupts.

Why this technique replaces a "clock bit": S7-300/400 classic clock-memory bits (for example M0.5 for 1 Hz, M0.4 for 0.5 Hz) do not exist on S7-1200/1500. A 1-Hz flag is available in some firmware versions but does not provide a clean hourly trigger. Converting the system clock to seconds and applying a modulo test is the standard, scan-independent solution.

2. Prerequisites

Item Required Notes
CPU S7-1200 any firmware 2.0 or later RD_SYS_T available from V1.0; DTL/TOD available from V1.0
Engineering tool STEP 7 Basic / Professional V14 or later (TIA Portal) V15.1 or later recommended for current library versions
Language SCL (Structured Control Language) Code below compiles in STL/LAD with equivalent boolean math but SCL is most readable
OB1 cycle Any up to 1000 ms Faster cycles produce cleaner pulses; OB35 (100 ms) is a typical choice
Clock source Internal RTC or NTP For drift-free hourly triggers use NTP (S7-1200 V4.4+ supports NTP client)

3. Data Types: DTL and TOD

3.1 DTL (Date and Time, Long)

DTL is a 12-byte structured data type that stores the full calendar date and time with nanosecond resolution. The CPU writes a DTL value when RD_SYS_T is called. Layout (byte offsets in the PLC memory image):

Byte offset Field Size (bytes) Range
0 YEAR 2 (INT) 1970 to 2554
2 MONTH 1 (USINT) 1 to 12
3 DAY 1 (USINT) 1 to 31
4 WEEKDAY 1 (USINT) 1 (Sun) to 7 (Sat)
5 HOUR 1 (USINT) 0 to 23
6 MINUTE 1 (USINT) 0 to 59
7 SECOND 1 (USINT) 0 to 59
8 NANOSECOND 4 (UDINT) 0 to 999,999,999

3.2 TOD (Time of Day)

TOD is a 4-byte IEC 61131-3 type that holds milliseconds elapsed since midnight (00:00:00.000). It does not carry the date. Range: 0 to 86,399,999 ms which equals 24 hours minus 1 ms.

3.3 Conversion Path

The trigger logic chains three conversions:

  1. DTL to TOD with DTL_TO_TOD which strips YEAR/MONTH/DAY/WEEKDAY/NANOSECOND and returns the time-of-day in milliseconds.
  2. TOD to DINT with TOD_TO_DINT which returns the millisecond count as a 32-bit integer.
  3. Arithmetic: apply MOD 3600000 directly on the millisecond count. The remainder is 0 only during the second 00 to 999 of every hour, that is HH:00:00.000 to HH:00:00.999.

Alternative equivalent form: divide by 1000 first to obtain integer seconds, then test MOD 3600 = 0. Both forms yield the same boolean for an exact boundary; the MOD 3600000 form avoids a divide and matches the canonical Siemens example.

4. RD_SYS_T Instruction Reference

RD_SYS_T reads the current local time of the CPU's real-time clock and writes it to a DTL tag. Signature:

RD_SYS_T(OUT := <DTL tag>, RET_VAL := <status word>);
Parameter Declaration Type Description
OUT Output DTL Receives the current local time
RET_VAL Return INT / WORD 0 = no error; 80A1 = time-of-day invalid (for example RTC battery low on early S7-1200)

The instruction is non-blocking and executes in a few microseconds. It must be called at least once per scan to refresh the working variable; the variable does not auto-update.

RET_VAL handling: A non-zero return indicates the DTL value is invalid. The trigger must be forced to FALSE in this case to avoid a spurious pulse on power-up with an uninitialized RTC. Wire the Error_Status output to the HMI alarm system on early S7-1200 hardware where the RTC battery is socketed.

5. FB Interface Definition

Create a new function block (for example FB_HourTrigger) with the following interface in TIA Portal:

Name Type Direction Description
t DTL Static Working variable written by RD_SYS_T
Time_Of_Day TOD Static Working variable after DTL_TO_TOD
Trig Bool Output TRUE during the second HH:00:00
Trig_Pulse Bool Output One-scan TRUE on the rising edge of Trig
Trig_Prev Bool Static Previous cycle value of Trig for edge detection
Error_Status Word Output Return value of RD_SYS_T (0 = OK)
R_Trig_Inst R_TRIG Static Multi-instance rising-edge detector

6. Implementation Method 1 - Explicit CASE Statement

The first method enumerates the 24 possible seconds-since-midnight values that represent an hour boundary. This form is easy to audit line-by-line in code review:

// FB_HourTrigger - Method 1 (verbose)
#Error_Status := RD_SYS_T(#t);
#Time_Of_Day := DTL_TO_TOD(#t);
#Trig := FALSE;
CASE (TOD_TO_DINT(#Time_Of_Day) / 1000) OF
    0,      // 00:00:00
    3600,   // 01:00:00
    7200,   // 02:00:00
    10800,  // 03:00:00
    14400,  // 04:00:00
    18000,  // 05:00:00
    21600,  // 06:00:00
    25200,  // 07:00:00
    28800,  // 08:00:00
    32400,  // 09:00:00
    36000,  // 10:00:00
    39600,  // 11:00:00
    43200,  // 12:00:00
    46800,  // 13:00:00
    50400,  // 14:00:00
    54000,  // 15:00:00
    57600,  // 16:00:00
    61200,  // 17:00:00
    64800,  // 18:00:00
    68400,  // 19:00:00
    72000,  // 20:00:00
    75600,  // 21:00:00
    79200,  // 22:00:00
    82800:  // 23:00:00
        #Trig := TRUE;
END_CASE;

This method has zero math beyond integer division and is straightforward. The drawback is verbosity: 24 constants must be maintained. If the same FB is used for 15-minute or 30-minute triggers the constant list grows proportionally.

7. Implementation Method 2 - MOD 3600 (Recommended)

The modulo operator reduces the 24-element CASE list to a single expression. This is the production-grade form:

// FB_HourTrigger - Method 2 (recommended, full SCL source)
#Error_Status := RD_SYS_T(#t);
#Time_Of_Day := DTL_TO_TOD(#t);
#Trig := ((TOD_TO_DINT(#Time_Of_Day) MOD 3600000) = 0);

// Rising-edge detection for single-scan pulse
#R_Trig_Inst(CLK := #Trig, Q => #Trig_Pulse);

The trigger boolean is TRUE during the entire 1000 ms interval starting at HH:00:00.000. The R_TRIG instance converts this 1-second-wide pulse into a one-scan pulse suitable for counters, SOE records, or edge-triggered logs.

OB1 cycle Scans with Trig=TRUE per hour Scans with Trig_Pulse=TRUE per hour
10 ms 100 1
50 ms 20 1
100 ms (OB35) 10 1
200 ms 5 1
500 ms 2 1
1000 ms (edge case) 1 (may miss the 999 ms sub-second) 0 or 1
Cycle 1000 ms hazard: If the OB1 cycle equals or exceeds 1000 ms there is a non-zero probability that the trigger will be missed entirely because the scan crosses the HH:00:00 boundary in a single iteration. For critical applications use OB35 (100 ms) or configure OB10 (time-of-day interrupt).

8. Timing Behavior - SVG Diagram

CPU clock (DTL seconds field) Trig (1-s pulse) Trig_Pulse (rising edge) OB1 cycle (100 ms) 09:59:58 09:59:59 10:00:00 10:00:01 10:00:02

9. Power-Up and Cold-Start Behavior

S7-1200 retains the RTC across power cycles if the optional battery card is installed (6ES7297-0AX30-0XA0 for early CPUs; integrated on later models). Without a battery, the RTC restarts at 01.01.2000 00:00:00 on power-up. The trigger will fire once at that wall-clock value, which is usually not the desired behavior.

Two practical remedies:

  1. Enable NTP synchronization. Configure the CPU NTP client in Device Configuration > Time of Day > NTP mode (S7-1200 V4.4 and later). The first trigger after boot may be late, but subsequent triggers are accurate to within the NTP poll interval.
  2. Suppress the first N triggers. Use a startup counter that ignores the first N hourly triggers after a STOP to RUN transition. Counter initial value should equal the suppression count; decrement on each Trig_Pulse; only allow downstream action when counter reaches 0.
// Suppress first 2 triggers after startup
IF "FirstScan" THEN
    #SuppressCount := 2;
END_IF;
#Trig_Pulse := FALSE;
IF #R_Trig_Inst.Q AND #SuppressCount = 0 THEN
    #Trig_Pulse := TRUE;
END_IF;
IF #R_Trig_Inst.Q AND #SuppressCount > 0 THEN
    #SuppressCount := #SuppressCount - 1;
END_IF;

10. Alternative - Siemens Schedule Library (LGF_Schedule)

For shift schedules, weekend/holiday suppression, and multi-event cron-style timing, the Siemens LGF_Schedule block from the Library of General Functions (LGF) is the production-grade choice. Reference: Siemens Support entry 109479728 - LGF Schedule function block.

The LGF block accepts a structured schedule (start time, end time, weekdays, dates) and returns a boolean "active" output. It is heavier than the MOD technique (kilobytes of code versus tens of bytes) but eliminates the maintenance burden of listing 24 constants and supports complex weekly and monthly patterns out of the box.

11. Alternative - Time-of-Day Hardware Interrupt (OB10)

For applications that need the absolute minimum jitter (sub-scan accuracy), configure OB10 as a time-of-day interrupt and arm it to fire on the next HH:00:00. The CPU schedules the OB within plus or minus 1 ms of the requested time, irrespective of the OB1 cycle. Configuration is in Device Configuration > Properties > Time of Day Interrupts in TIA Portal.

OB10 is appropriate when:

  • The action must happen at a precise instant (for example TOU tariff switch at midnight).
  • Multiple time-of-day events are needed (arm OB10 for each, then re-arm inside the OB itself).
  • OB1 is long (for example 500 ms) and the consumer cannot tolerate the latency.

For sub-hourly precision (for example firing every 15 minutes on a 60 ms OB1), OB10 is not the right tool - use a cyclic interrupt OB (OB30 to OB38) with the appropriate phase offset.

12. Cross-Platform Portability

The MOD-3600 technique translates directly to other IEC 61131-3 platforms by swapping the system-clock read instruction:

Platform Read clock Type Convert Modulo test
Siemens S7-1200/1500 RD_SYS_T DTL DTL_TO_TOD, TOD_TO_DINT MOD 3600000 (ms)
Siemens S7-300/400 SFC 1 READ_CLK DT (8-byte) FC 8 DT_TOD, DTB to DINT MOD 3600000 (ms)
Allen-Bradley CompactLogix GSV WallClockTime DATE_AND_TIME Subtract 1970 epoch, convert to seconds MOD 3600 (s)
Schneider M340/M580 RRTC_DT DATE_AND_TIME Substring TOD, convert ms MOD 3600000 (ms)
CODESYS V3 RTC (function block) DATE_AND_TIME / DT Same chain as Siemens MOD 3600000 (ms)
Beckhoff TwinCAT 3 FB_LocalSystemTime TIMESTRUCT Convert TOD to ms MOD 3600000 (ms)

The modulo test is platform-agnostic; only the clock source and conversion differ.

13. Variant Triggers - Minutes, Quarters, Days

The same FB supports any divisor of 86,400,000 ms (24 hours):

Pattern Modulo divisor (ms) Trigger condition
Every minute (XX:YY:00) 60,000 (TOD ms) MOD 60000 = 0
Every 15 minutes 900,000 (TOD ms) MOD 900000 = 0
Every 30 minutes 1,800,000 (TOD ms) MOD 1800000 = 0
Every hour 3,600,000 (TOD ms) MOD 3600000 = 0
Midnight only n/a (TOD ms) = 0

For non-divisor intervals (for example "every 7 minutes" or "every 17 minutes") the LGF_Schedule library is required.

14. Verification and Commissioning

  1. Watch table test. In TIA Portal, open the FB instance DB and add Trig, Trig_Pulse, Error_Status, and Trig_Prev to a watch table. Force the CPU clock to within seconds of HH:00:00 and observe the boolean transitions.
  2. Cross-check with raw clock. Use RD_SYS_T directly in the watch table to confirm the displayed clock matches the expected wall-clock time.
  3. Long-duration test. Set the PLC clock to 23:59:55, run for 10 seconds, and confirm exactly one trigger occurs on the 00:00:00 boundary (midnight transition).
  4. Scatter-gather trace. In the trace tool, record Trig, Trig_Pulse, and the raw t.HOUR / t.MINUTE / t.SECOND fields across one full hour to confirm pulse width and edge behavior.
  5. NTP drift verification. With NTP enabled, run for 72 hours and log the wall-clock time of each trigger; drift should be within plus or minus 1 second of the requested boundary.

15. Troubleshooting Matrix

Symptom Likely cause Fix
Trigger never fires OB1 stopped, RD_SYS_T not called, scan time above 1000 ms missing the second Place the FB call in OB1 or a cyclic OB; verify OB1 is running; shorten cycle to 100 ms (OB35)
Trigger fires on every scan Constant 3600000 mistyped as 0; logic inversion wrong Check DINT constant; use PLC variable table to inspect intermediate values
Spurious trigger at power-up RTC defaults to 2000-01-01 00:00:00 on un-batteried CPU Install battery, enable NTP, or suppress first N triggers
Trigger drifts by minutes over weeks Internal RTC drift (typically plus or minus 2 s/day) Enable NTP synchronization in device configuration
Trigger fires at HH:00:00.500 Operator manually set clock mid-second; trigger remains TRUE for full second Expected behavior; use Trig_Pulse for single-scan consumer
Compiler error: TOD_TO_DINT unknown SCL source not refreshed after re-import of types Right-click project > Compile > Software (rebuild all)
Trigger fires twice within one second OB1 cycle crosses the 999 to 0 ms boundary mid-scan and reads both states Wrap the modulo result in a hysteresis test; use OB35 with 100 ms
Trigger absent on spring-forward day DST skipped the 02:00 to 03:00 hour Acceptable for UTC-based clocks; document the behavior in the consumer

16. Safety and Operational Notes

Do not use hourly triggers for safety-critical functions. The trigger is software-generated and depends on CPU clock integrity, OB1 execution, and the absence of firmware faults. Safety-rated shutdown, E-stop processing, and protective interlocks must use dedicated safety I/O and SIL-rated logic per IEC 61508 / IEC 62061. Use this technique for non-safety functions such as logging, billing, and reporting only.
CPU clock security. S7-1200 access protection (CPU password, know-how protection) does not prevent the operator from writing the clock via HMI or online functions. If the trigger drives financial or compliance-relevant actions (tariff switch, audit log), restrict write access to the time-of-day to authorized roles only.

17. Frequently Asked Questions

Why is the test on milliseconds (MOD 3600000) instead of seconds (MOD 3600)?

Both forms produce the same boolean for an exact HH:00:00.000 boundary, but MOD 3600000 avoids an explicit divide-by-1000 step. The DTL to TOD to DINT path returns milliseconds since midnight, and 3,600,000 ms = 1 hour, so the modulo test runs on the native scale of the data and the compiler does not have to optimize a divide away.

Will the trigger fire on the first scan after power-up?

Only if the RTC happens to be exactly on HH:00:00 at that instant. On S7-1200 CPUs without a battery the RTC restarts at 2000-01-01 00:00:00, so a single spurious trigger will occur on the first scan. Suppress it with a startup counter, install a battery, or enable NTP synchronization.

How do I generate triggers every 15 minutes or every 30 minutes?

Replace 3,600,000 with 900,000 (15 minutes) or 1,800,000 (30 minutes). The same FB works for any divisor of 24 hours. For arbitrary intervals that are not factors of 86,400,000 ms, use the LGF_Schedule library referenced above.

Does daylight saving time affect the trigger?

The S7-1200 internal RTC stores local time without DST handling. If DST is implemented at the HMI or SCADA layer by skipping 02:00 to 03:00, the trigger at 02:00:00 will simply not occur on the spring-forward day. Plan the consumer logic for the missing trigger or migrate to a UTC-based clock with NTP.

Can I use this technique on S7-300/400 with classic STL?

Yes. Read the system clock with SFC 1 (READ_CLK) into a DT (DATE_AND_TIME) buffer, use FC 8 (DT_TOD) to extract the TOD into a TIME buffer, convert the TIME to a DINT in milliseconds, then apply MOD 3,600,000. The math is identical to the S7-1200 path.

What is the worst-case latency between wall-clock HH:00:00 and the trigger boolean going TRUE?

For an OB1 cycle of 100 ms the worst-case latency is one full cycle (100 ms). For an OB10 time-of-day interrupt the worst-case is plus or minus 1 ms plus the OB10 priority-class latency, typically well under 5 ms on a CPU with light load. The MOD technique cannot beat OB10 for jitter; use OB10 when sub-10 ms accuracy is required.

Back to blog