Scheduling S7-300/400 Outputs by Day-of-Week with SFC1 READ_CLK

David Krause15 min read
S7-300SiemensTutorial / 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

Overview

Time-window output scheduling on a SIMATIC S7-300 or S7-400 controller is one of the most common application patterns in HVAC, irrigation, lighting, batch release, and machine-shift control. The requirement is to drive a digital output, e.g. Q1.1, to a high logical level between a configured day-of-week and time (Friday 13:00:00) and a second configured day-of-week and time (Monday 15:30:00), and to repeat the cycle every week indefinitely. The implementation requires careful handling of the 8-byte DATE_AND_TIME BCD structure, the time-of-day interrupt OBs, and the edge case where the active window crosses midnight or the week boundary.

Two production-grade methods exist on the S7-300/400 platform:

  1. Polling method: call SFC1 READ_CLK from OB1 on every scan, compare the returned DATE_AND_TIME against a configured window, and set/reset the output with S / R.
  2. Time-of-Day interrupt method: configure OB10 through OB17 in HW Config to fire at the start and end of the window, and use SFC28 SET_TINT and SFC30 ACT_TINT to arm them. The OBs each contain a single S Q x.y or R Q x.y statement.

The polling method is more flexible, supports arbitrary windows, and survives CPU restart without re-arming. The time-of-day interrupt method is more deterministic and CPU-efficient, but only supports up to eight hard-wired OB slots per CPU (OB10 only on S7-300; OB10–OB17 on S7-400). Most production applications use the polling method in OB1 wrapped inside an FB for reusability across multiple outputs.

Important — DATE_AND_TIME offsets are CPU-dependent. The 8 bytes returned by SFC1 are written to the local DATE_AND_TIME variable you supply. The byte offsets shown below assume the variable starts at the address you declared it at. When using absolute LB addressing on the OB1 local stack, confirm the exact offset for your CPU firmware version; offsets shown in older community examples are not always portable.

Prerequisites

  • STEP 7 V5.5 SP4 or later (or TIA Portal V13 SP1 / V15 / V16 with the S7-300/400 add-on installed) for SFC0 / SFC1 / SFC28–SFC31.
  • CPU 31x or CPU 41x with integrated real-time clock. All standard CPU 312 through CPU 319 and CPU 412 through CPU 417 include a hardware RTC backed by the front-panel battery or super-capacitor. See the SIMATIC S7-300 CPU 31xC and CPU 31x Operating Instructions and the SIMATIC S7-400 CPU 41x Manual for the RTC backup specifications of your specific CPU.
  • CPU battery in service — the RTC retains time only while the backup power source is functional. On CPU 31xC and CPU 31x the backup is a single CR 1025 lithium cell; replace every 3–5 years. A depleted battery on cold restart causes the time to revert to 01.01.1990 00:00:00 and your scheduler to misbehave silently.
  • HW Config project loaded to the CPU so the time-of-day OB numbers are recognized in the CPU firmware.
  • A configured output address, e.g. Q1.1 on an SM 322 DO16 digital output module, or a built-in CPU output on CPU 31xC.

DATE_AND_TIME (DT) Data Type Specification

STEP 7 represents date and time as a fixed 8-byte BCD-encoded structure. The layout follows the IEC 61131-3 DT definition used by S7-300/400 and WinAC:

Byte Offset Field Range (BCD) Meaning
+0 Year 00 – 99 2000 – 2099 (90 – 99 = 1990 – 1999)
+1 Month 01 – 12 Calendar month
+2 Day 01 – 31 Day of month (CPU does not enforce BCD validity)
+3 Hour 00 – 23 24-hour clock
+4 Minute 00 – 59 Minute of hour
+5 Second 00 – 59 Second of minute
+6 ms_hi + Weekday 0 / 1 – 7 High nibble = ms hundreds/thousands (0–9); low nibble = weekday (1 = Sunday, 2 = Monday, …, 7 = Saturday)
+7 ms_lo 00 – 99 Milliseconds tens and ones (BCD)

The weekday field is critical: a direct BCD comparison against a literal such as 6 for Friday will fail because the high nibble of byte 6 contains part of the milliseconds counter. You must mask the byte with W#16#0F (or B#16#0F for a byte-AND) before comparing the weekday.

BCD subtlety. The CPU clock writes the time as BCD, so a literal value of decimal 13 in an integer comparison does not match the encoded byte 0x13 in memory. Use the BCD literal B#16#13 (which encodes the same bit pattern), or load the decimal value with L 13 and compare with ==I after explicit conversion. In the official Siemens reference idiom the hour uses B#16#13, minute B#16#0, and second B#16#0 — that is the correct pattern.

SFC0 SET_CLK and SFC1 READ_CLK

Two system functions are needed to read or set the CPU clock. Both are part of the standard library shipped with STEP 7 and require no additional license. Full descriptions are in the SIMATIC S7-300/400 System Software — System and Standard Functions Reference Manual.

SFC Name Function Inputs Outputs
SFC0 SET_CLK Set the CPU real-time clock PDT (pointer to DT to write) RET_VAL (INT — error code; 0 = no error)
SFC1 READ_CLK Read the CPU real-time clock RET_VAL (INT), CDT (current DT)

Typical call in STL or SCL:

CALL "READ_CLK"   // SFC1
     RET_VAL := #iRetVal
     CDT     := #dtNow;     // DATE_AND_TIME, 8 bytes

After the call, #dtNow contains the current date and time. The individual fields are accessible either by symbolic access (if you assign a DATE_AND_TIME symbol in the symbol table) or by absolute byte addressing against the variable's base address. To set the clock from the user program (e.g. for synchronized time from a master clock), call SFC0 with a DT variable pre-loaded with the desired date and time.

Method 1: Polling with SFC1 in OB1 (Reference Implementation)

The cleanest correct implementation reads the clock once per cycle in OB1, masks the weekday field, and performs the window comparison inside an FB so the same logic can be reused for multiple outputs. The following SCL implementation covers both the same-day topology and the cross-week topology with a single bWrap input flag.

FB100 — Weekly Time Window Scheduler (SCL)

FUNCTION_BLOCK FB 100
TITLE   = 'Weekly Time Window Scheduler'
VERSION : '1.0'
VAR
   iStartWd   : INT;    // 1=Sun .. 7=Sat
   iStartHour : INT;    // 0..23
   iStartMin  : INT;    // 0..59
   iStartSec  : INT;    // 0..59
   iEndWd     : INT;
   iEndHour   : INT;
   iEndMin    : INT;
   iEndSec    : INT;
   bWrap      : BOOL;   // TRUE = window crosses midnight or week boundary
   bActive    : BOOL;   // computed output state
   dtNow      : DATE_AND_TIME;
   iRetVal    : INT;
END_VAR
VAR_TEMP
   iWd : INT;
   iH  : INT;
   iM  : INT;
   iS  : INT;
END_VAR
BEGIN
   // 1. Read CPU clock
   SFC1(CDT := dtNow, RET_VAL := iRetVal);

   // 2. Extract fields. Byte access on DATE_AND_TIME uses BCD;
   //    mask weekday, keep raw BCD for time fields (compared as INT).
   iWd := WORD_TO_INT(dtNow[6] AND W#16#0FFF);   // weekday low nibble
   iH  := BCD_TO_INT(BYTE#16#00 OR dtNow[3]);    // hour
   iM  := BCD_TO_INT(BYTE#16#00 OR dtNow[4]);    // minute
   iS  := BCD_TO_INT(BYTE#16#00 OR dtNow[5]);    // second

   // 3. Compute "in window"
   IF bWrap THEN
      // Topology: start weekday > end weekday (crosses week)
      //           or same weekday but start time > end time (crosses midnight)
      IF (iWd > iStartWd) AND (iWd < iEndWd) THEN
         bActive := TRUE;
      ELSIF (iWd = iStartWd) AND
            ((iH > iStartHour) OR
             (iH = iStartHour AND iM > iStartMin) OR
             (iH = iStartHour AND iM = iStartMin AND iS >= iStartSec)) THEN
         bActive := TRUE;
      ELSIF (iWd = iEndWd) AND
            ((iH < iEndHour) OR
             (iH = iEndHour AND iM < iEndMin) OR
             (iH = iEndHour AND iM = iEndMin AND iS <= iEndSec)) THEN
         bActive := TRUE;
      ELSE
         bActive := FALSE;
      END_IF;
   ELSE
      // Topology: start and end on the same weekday
      IF (iWd = iStartWd)
         AND ((iH > iStartHour)
              OR (iH = iStartHour AND iM > iStartMin)
              OR (iH = iStartHour AND iM = iStartMin AND iS >= iStartSec))
         AND ((iH < iEndHour)
              OR (iH = iEndHour AND iM < iEndMin)
              OR (iH = iEndHour AND iM = iEndMin AND iS <= iEndSec))
      THEN
         bActive := TRUE;
      ELSE
         bActive := FALSE;
      END_IF;
   END_IF;
END_FUNCTION_BLOCK

Instantiate the FB in OB1 as a multi-instance or as a separate instance DB100. For the example, configure DB100 with: iStartWd = 6 (Friday), iStartHour = 13, iStartMin = 0, iStartSec = 0, iEndWd = 2 (Monday), iEndHour = 15, iEndMin = 30, iEndSec = 0, bWrap = TRUE.

Driving Q1.1 from FB100

CALL FB 100 , DB 100 ;
     iStartWd   := 6,
     iStartHour := 13,
     iStartMin  := 0,
     iStartSec  := 0,
     iEndWd     := 2,
     iEndHour   := 15,
     iEndMin    := 30,
     iEndSec    := 0,
     bWrap      := TRUE,
     bActive    := bActiveRun;

A   "DB100".bActive;
S   Q 1.1;
AN  "DB100".bActive;
R   Q 1.1;

Method 2: Time-of-Day Interrupts (OB10 – OB17)

For deterministic, low-overhead scheduling, configure a time-of-day interrupt OB. S7-300 CPUs support OB10 only; S7-400 CPUs support OB10 through OB17 (eight slots). Each OB is configured in HW Config with a start time and periodicity. The OB body contains a single line: S Q x.y for the start OB and R Q x.y for the end OB.

HW Config Settings

  1. Open HW Config and select the CPU.
  2. Open Properties > Time-of-Day Interrupts.
  3. For OB10 (or OB11 – OB17 on S7-400), set:
    • Execution: "Once" for one-shot, or "Every minute / hour / day / week / month" for cyclic.
    • Start time: e.g., 13:00:00 on Friday → format DD-MM-YY HH:MM:SS.
    • Phase offset (for periodic): leave at 0 for a fixed weekly start time.
  4. Save & download the hardware configuration.

Arming from the User Program

After a CPU restart the time-of-day OBs are disarmed by default. To re-arm them from the user program (e.g., from OB100 warm restart), call SFC30 ACT_TINT:

CALL "ACT_TINT"          // SFC30
     OB_NO   := 10
     PERIOD  := W#16#0001     // once; see manual for cyclic values
     RET_VAL := #iRetVal;

To cancel an already-armed time-of-day interrupt, call SFC29 CAN_TINT. To query the status, call SFC31 QRY_TINT, which returns STATUS as a WORD with bit 0 = enabled, bit 1 = active, bit 2 = expired. To change the configured start time without re-running HW Config, call SFC28 SET_TINT with a new SDT parameter.

Cross-Day and Cross-Week Boundary Logic

The most common source of bugs in weekly schedulers is the implicit assumption that the start and end moments fall on the same calendar day. The reference FB100 above handles three topologies:

Topology Start End Active between bWrap
Same day, same hour Mon 10:00:00 Mon 10:30:00 Mon 10:00:00 – 10:30:00 FALSE
Same day, multi-hour Mon 06:00:00 Mon 18:00:00 Mon 06:00:00 – 18:00:00 FALSE
Cross-midnight Mon 22:00:00 Tue 06:00:00 Mon 22:00:00 – Tue 06:00:00 TRUE
Cross-week (the example) Fri 13:00:00 Mon 15:30:00 Fri 13:00:00 – Mon 15:30:00 TRUE

For the wrap branch, the algorithm treats the week as a circular index. A time is "in window" if its weekday lies strictly between the start and end weekdays, OR (if it equals the start weekday) the time-of-day is >= start time, OR (if it equals the end weekday) the time-of-day is <= end time. The iWd > iStartWd AND iWd < iEndWd condition relies on the wrap branch only being entered when iEndWd < iStartWd or when the time-of-day crosses midnight on a single weekday — the strict inequality is then geometrically correct on the circular scale.

Daylight-saving time. S7-300/400 CPUs do not automatically adjust for DST. If your plant crosses a DST boundary, the active window shifts by 1 hour twice per year. Either (a) disable DST on the CPU and synchronize to UTC, (b) add a 1-hour correction in the application logic during the spring/fall transitions, or (c) use a master clock on the network and run the PLC in UTC, converting to local time only at the HMI.

Step-by-Step Commissioning Procedure

  1. Create the FB: insert FB100 with the declarations shown above. Compile with no errors.
  2. Instantiate the FB in OB1 as a multi-instance or as DB100 (instance DB).
  3. Configure the start and end values in the instance DB. For the example: iStartWd = 6 (Friday), iStartHour = 13, iStartMin = 0, iStartSec = 0; iEndWd = 2 (Monday), iEndHour = 15, iEndMin = 30, iEndSec = 0; bWrap = TRUE.
  4. Wire the output: drive Q1.1 from the bActive BOOL with two parallel S / R networks in OB1.
  5. Download the blocks to the CPU and place the CPU in RUN.
  6. Set the CPU clock to a value just before the start (e.g., Friday 12:59:50) using SFC0 SET_CLK or the online Set Time dialog in STEP 7 (PLC > Set Time of Day).
  7. Monitor Q1.1 in the VAT or in the online block view. Confirm that Q1.1 goes high within one OB1 scan after the clock crosses Friday 13:00:00.
  8. Advance the clock through the entire active window and confirm Q1.1 drops within one scan after the clock crosses Monday 15:30:00.
  9. Cycle the CPU power (cold restart) and confirm the schedule resumes from the new current time without re-arming. Polling-based scheduling has no arming state.

Verification

After commissioning, run the following verification matrix. Adjust the CPU clock to each test point using SFC0 SET_CLK and confirm Q1.1 matches the expected state within one OB1 scan (typically 10–100 ms). All ten states must match before the schedule is considered production-ready.

Test # CPU Clock (set) Expected Q1.1 Notes
1 Fri 12:59:59 0 Just before window opens
2 Fri 13:00:00 1 Window opens exactly at the second
3 Sat 00:00:00 1 Mid-window, day rollover
4 Sun 12:00:00 1 Sunday, still active
5 Mon 15:29:59 1 Just before window closes
6 Mon 15:30:00 0 Window closes exactly at the second
7 Tue 10:00:00 0 After window
8 Wed 10:00:00 0 Mid-week, no activity
9 Thu 23:59:59 0 Just before next window opens
10 Fri 12:59:59 (next week) 0 Window must NOT re-open early

If test 1 or test 10 fails, the wrap logic is reversed. If test 5 or test 6 fails, the boundary comparison is off-by-one. If the output toggles intermittently at the boundary, the cycle time is longer than the granularity required — consider moving the FB call from OB1 to OB35 (cyclic interrupt, e.g. 100 ms) to bound the worst-case latency.

Troubleshooting Matrix

Symptom Likely Cause Fix
Output never goes high Weekday field not masked; comparison is against the BCD-encoded byte including milliseconds AND the weekday byte with B#16#0F (or W#16#0FFF on a WORD) before comparing to a decimal literal
Output goes high on the wrong day Weekday numbering reversed — Siemens uses 1=Sun, 7=Sat; some engineers expect 1=Mon Verify with the CPU online time-of-day view; Saturday returns low-nibble = 7
Output never resets End weekday / time comparison never matches because bWrap = FALSE for a cross-week window Set bWrap := TRUE when end weekday < start weekday
Output toggles 1 hour per day CPU is on local time and a DST change occurred Disable DST and run UTC, or adjust the FB to detect DST transitions
SFC1 RET_VAL returns W#16#8081 Wrong CPU type, missing SFC in library, or CPU in STOP Confirm the project target CPU and that the standard library is installed; check CPU diagnostic buffer for OB85 / SF
Output chatters at the start of the window OB1 scan time > 1 s; output is set/reset multiple times across the boundary due to slow scan Use OB35 100 ms cyclic interrupt for the FB call instead of OB1
After CPU restart the output is in the wrong state Instance DB retained, but the FB was not called during restart; no edge triggered Call the FB from OB100 warm restart to force an initial state evaluation
Time-of-day OB never fires OB is configured in HW Config but SFC30 ACT_TINT was not called after restart Add SFC30 call in OB100 or OB101
Schedule is off by 1 hour after power-up CPU battery depleted; clock reverted to 01.01.1990 00:00:00 Replace CR 1025 backup cell and re-synchronize time via SFC0 or NTP master clock
Schedule is correct on weekdays but wrong on Sundays Forgot that Siemens weekday 1 = Sunday (not Monday); an integer >= 2 check is missing Use literal 1 for Sunday in the comparison, not 7

Alternate Platforms

  • S7-1200 / S7-1500 (TIA Portal): use RD_SYS_T to read DTL (64-bit structured time), then compare against a configured window. The DTL layout is similar but uses binary encoding, not BCD, and weekday numbering matches ISO 8601 (1 = Monday, 7 = Sunday). Direct code port from S7-300/400 BCD logic will not work — convert the literals from BCD to decimal before porting.
  • LOGO! 8: built-in weekly timer function block in LOGO! Soft Comfort with on-screen configuration. No user code required; supports up to 3 on/off pairs per weekly timer, with optional second weekly timer for second window.
  • S7-200 (STEP 7 Micro/WIN): use READ_RTC (SBR6) and write the read result into a VB area. No DATE_AND_TIME type; manual byte comparison against BCD literals and manual weekday masking with AND B, 0x0F.
  • WinAC RTX / SoftPLC: same DATE_AND_TIME layout, same SFC1 call, same FB logic. Verified identical behavior on WinAC RTX 2010 and SoftPLC 4.x.

FAQ

What is the correct SFC to read the CPU real-time clock on an S7-300/400?

Use SFC1 READ_CLK. The call takes no inputs and returns RET_VAL (INT) plus CDT (DATE_AND_TIME). It is part of the standard library shipped with STEP 7 V5.5 and requires no additional license.

How is the weekday encoded inside DATE_AND_TIME?

Byte 6 of the 8-byte DATE_AND_TIME structure contains the weekday in its low nibble. The high nibble holds the hundreds and thousands of milliseconds. Siemens numbers Sunday as 1, Monday as 2, …, Saturday as 7. Mask the byte with B#16#0F (or W#16#0FFF on a WORD) before comparing against a decimal literal.

My schedule crosses midnight — why does the comparison fail?

Most sample implementations assume the start and end fall on the same calendar day. For a window that starts on day A and ends on day B (with A ≠ B), add a bWrap flag and switch the comparison logic to a circular-week model: in-window if (weekday is strictly between A and B) OR (weekday = A and time-of-day ≥ start) OR (weekday = B and time-of-day ≤ end). The reference FB100 above implements both topologies.

Can I use OB10 time-of-day interrupt instead of polling SFC1 in OB1?

Yes. S7-300 supports OB10; S7-400 supports OB10 through OB17. Configure the start OB to set the output and the end OB to reset it, then call SFC30 ACT_TINT in OB100/OB101 to re-arm it after a warm restart. This method is more deterministic but limited to 1–8 hard-wired windows per CPU, and requires a hardware configuration download to change the schedule.

Does the S7-300/400 CPU handle daylight-saving time automatically?

No. The integrated RTC does not adjust for DST. If your plant crosses a DST boundary, the schedule shifts by one hour twice per year. Recommended remediation: run the CPU on UTC (no DST) and convert to local time at the HMI, or add a 1-hour correction in the application logic for the transition weeks. CPU 41x-2 PN/DP and CPU 31x-2 PN/DP with firmware ≥ V2.x can be synchronized via NTP using the integrated PN port, which lets the master clock handle DST.

Back to blog