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:
-
Polling method: call SFC1 READ_CLK from
OB1on every scan, compare the returnedDATE_AND_TIMEagainst a configured window, and set/reset the output withS/R. -
Time-of-Day interrupt method: configure
OB10throughOB17in HW Config to fire at the start and end of the window, and useSFC28SET_TINT andSFC30ACT_TINT to arm them. The OBs each contain a singleS Q x.yorR Q x.ystatement.
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.
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 312throughCPU 319andCPU 412throughCPU 417include 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 31xCandCPU 31xthe backup is a singleCR 1025lithium 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.1on anSM 322 DO16digital output module, or a built-in CPU output onCPU 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.
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
- Open HW Config and select the CPU.
- Open Properties > Time-of-Day Interrupts.
- 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:00on Friday → formatDD-MM-YY HH:MM:SS. - Phase offset (for periodic): leave at 0 for a fixed weekly start time.
- 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.
Step-by-Step Commissioning Procedure
-
Create the FB: insert
FB100with the declarations shown above. Compile with no errors. -
Instantiate the FB in
OB1as a multi-instance or asDB100(instance DB). -
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. -
Wire the output: drive
Q1.1from thebActiveBOOL with two parallelS/Rnetworks inOB1. -
Download the blocks to the CPU and place the CPU in
RUN. -
Set the CPU clock to a value just before the start (e.g., Friday 12:59:50) using
SFC0 SET_CLKor the online Set Time dialog in STEP 7 (PLC > Set Time of Day). -
Monitor
Q1.1in the VAT or in the online block view. Confirm thatQ1.1goes high within one OB1 scan after the clock crosses Friday 13:00:00. -
Advance the clock through the entire active window and confirm
Q1.1drops within one scan after the clock crosses Monday 15:30:00. - 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_Tto readDTL(64-bit structured time), then compare against a configured window. TheDTLlayout 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. NoDATE_AND_TIMEtype; manual byte comparison against BCD literals and manual weekday masking withAND 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.