S7-300 Clock to Data Block: SFC1 READ_CLK and DATE_AND_TIME

David Krause12 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

Storing the PLC clock into a data block (DB) is a routine requirement on the SIMATIC S7-300/400 platform, particularly on F-CPUs such as the CPU 319F-3 PN/DP (6ES7318-3FC01-0AB0) where event time-stamping, audit trail logging, and batch reporting depend on a consistent, PLC-resident time reference. The field report covers two implementation paths on STEP 7 V5.6 + SP2:

  1. Use the combined DATE_AND_TIME (DT) format with SFC1 READ_CLK and extract date and time-of-day parts with the standard IEC conversion functions.
  2. Read the 8-byte BCD clock buffer directly and convert each byte with BCD_TO_INT to obtain usable integers in the DB.

This reference implements both methods, documents the byte-level structure of the BCD clock buffer, and adds commissioning checks for the 319F-3 hardware clock, SFC64 tick count, and the additional standard library blocks (DT_DATE, DT_TOD, AD_DT_TM, D_TOD_DT, DT_DAY) required to obtain local time.

Target range for the S7-300/400 hardware clock: years 1990 to 2089 encoded in BCD (byte 0 = 90..89 = 1990..2089). The CPU battery-backed RTC must be installed and healthy, otherwise SFC1 returns a 0x00 year byte after power-up.

Prerequisites

  • STEP 7 V5.6 + SP2 (or later) with the Standard Library containing IEC Function Blocks and System Function Blocks.
  • CPU 319F-3 PN/DP (or any S7-300 CPU; SFC0/SFC1/SFC64 are available in all 31x/31xF/31xC/31xT CPUs from firmware V2.0 onward).
  • An instance DB or a project DB (DB1 minimum for project source approach) declared with the target time format.
  • The additional standard functions DT_DATE, DT_TOD, DT_DAY, AD_DT_TM, and D_TOD_DT from the Standard Library > IEC Function Blocks folder must be present in the project S7 program. Without them, the local-time conversion path returns a runtime error (OB121).

Clock Data Types on S7-300/400

STEP 7 V5.x provides three relevant time data types. The S7-1200/1500 DTL type referenced in the TIA Portal manual collection is not available on S7-300/400; on those platforms the equivalent is DATE_AND_TIME.

Type Length Format Range Use case
DATE 2 bytes (DINT days since 01.01.1990) IEC date 01.01.1990 to 31.12.2168 Calendar arithmetic, days offset
TIME_OF_DAY (TOD) 4 bytes (DINT ms since 00:00) IEC time-of-day 00:00:00.000 to 23:59:59.999 Daily scheduling, time-of-day math
DATE_AND_TIME (DT) 8 bytes BCD BCD wall-clock 01.01.1990 00:00:00.000 to 31.12.2089 23:59:59.999 Hardware clock buffer from SFC1

BYTE layout of DATE_AND_TIME (8 bytes BCD)

Byte offset Content Encoding Example (14.03.2024 09:41:23.456 Thursday)
Byte 0 Year (BCD) 90..89h = 1990..2089 24h = 36 dec
Byte 1 Month (BCD) 01..12 03h
Byte 2 Day (BCD) 01..31 14h
Byte 3 Hour (BCD) 00..23 09h
Byte 4 Minute (BCD) 00..59 41h
Byte 5 Seconds (BCD) 00..59 23h
Byte 6 Milliseconds (BCD, top nibble = 100s, low = 10s, low byte = units) 000..999 45h / 60h (456 ms)
Byte 7 Day of week + reserved 1=Sunday .. 7=Saturday 05h (Thursday)
Encoding is BCD, not binary. A byte value of 16#24 on a 319F-3 is the year 2024, not decimal 36. Reading the buffer as INT will produce wrong results until you run each byte through BCD_TO_INT (FC 93 in the Standard Library, or built-in BTI in older code).

System Functions for Clock Access

SFC Name Function Input Output Available on
SFC0 SET_CLK Set hardware clock (and 1..3 slaves) PDT (pointer to DATE_AND_TIME), SET (BOOL) RET_VAL (INT) S7-300/400, WinAC
SFC1 READ_CLK Read hardware clock RET_VAL (INT) PDT (pointer to DATE_AND_TIME) S7-300/400, WinAC
SFC64 TIME_TCK Read tick counter (10 ns ticks or 1 ms ticks depending on CPU) RET_VAL OUT (TIME, 32-bit) S7-300/400

For S7-300 CPUs the tick counter of SFC64 advances in 1 ms increments (TIME format, 32-bit). The value rolls over approximately every 49.7 days (2^31 ms). Reset behavior: on STOP-to-RUN the counter is reset to 0, so it does not persist across restart; it is intended only for delta time measurement inside a continuous RUN.

DB Declaration

Create a shared DB (e.g. DB50 "Clock_DB") with the following structure. The combined DT path uses a single 8-byte element; the dual-tag path uses separate DATE and TOD tags and a scratch INT array for byte-level BCD decoding:

DATA_BLOCK "Clock_DB"
TITLE = PLC clock mirror
VERSION : 1.0
  STRUCT
   WallClock         : DATE_AND_TIME;   // 8 bytes, output of SFC1
   DatePart          : DATE;            // 2 bytes, output of DT_DATE
   TodPart           : TIME_OF_DAY;     // 4 bytes, output of DT_TOD
   DayOfWeek         : INT;             // 1..7, output of DT_DAY
   YearBCD           : BYTE;            // raw byte 0 of SFC1 buffer
   MonthBCD          : BYTE;            // raw byte 1
   DayBCD            : BYTE;            // raw byte 2
   HourBCD           : BYTE;            // raw byte 3
   MinuteBCD         : BYTE;            // raw byte 4
   SecondBCD         : BYTE;            // raw byte 5
   MsHighBCD         : BYTE;            // raw byte 6 (top nibble = 100s)
   MsLowBCD          : BYTE;            // raw byte 6 (low nibble = 10s)
   DOWbyte           : BYTE;            // raw byte 7
   YearInt           : INT;             // BCD_TO_INT result
   MonthInt          : INT;
   DayInt            : INT;
   HourInt           : INT;
   MinuteInt         : INT;
   SecondInt         : INT;
   MsInt             : INT;             // 0..999
   DayOfWeekInt      : INT;
   Tick              : TIME;            // SFC64 output, 1 ms ticks
   ClockStatusOK     : BOOL;            // SFC1 RET_VAL == 0
  END_STRUCT;
END_DATA_BLOCK

Method 1: Read into DATE_AND_TIME and extract parts

This is the recommended path on S7-300/400. One call to SFC1 fills the 8-byte BCD buffer, and the standard conversion functions split it into IEC-native date and TOD values that compare, add, and subtract directly.

STL implementation in OB1

// Call SFC1 READ_CLK, store buffer in DB50.WallClock
CALL "READ_CLK"
     RET_VAL := MW100
     PDT     := "Clock_DB".WallClock;

// Check return value
L     MW100
L     0
==I
=     "Clock_DB".ClockStatusOK;

// Extract date part  (DT_DATE: returns DATE, days since 1990-01-01)
CALL "DT_DATE"
     IN  := "Clock_DB".WallClock
     RET_VAL := "Clock_DB".DatePart;

// Extract time-of-day part (DT_TOD: returns TOD, ms since midnight)
CALL "DT_TOD"
     IN  := "Clock_DB".WallClock
     RET_VAL := "Clock_DB".TodPart;

// Extract day of week  (1=Sunday .. 7=Saturday)
CALL "DT_DAY"
     IN  := "Clock_DB".WallClock
     RET_VAL := "Clock_DB".DayOfWeek;

Reading tick counter for delta time

CALL "TIME_TCK"
     RET_VAL := "Clock_DB".Tick;
Return value semantics for SFC1 on the 319F-3: W#16#0000 = no error. W#16#8081 = internal clock error (RTC battery removed or failed). If 8081 is observed at first power-up after storage, the buffer returns 90-01-01 00:00:00.000 — verify the battery holder and replace the backup battery (3.6 V 1/2 AA, e.g. 6ES7971-1AA00-0AA0 or compatible).

Method 2: Read raw BCD bytes and convert

Use this path when the destination application needs integer values (for ASCII conversion, HMI display, or hand-coded math) and the IEC conversion blocks are not available in the project.

LAD/FBD call (single network per byte)

  1. Network 1 — Call SFC1 with PDT pointing to a 8-byte ANY scratch variable (or directly into the DB if the DB layout uses 8 individual BYTEs; in that case the buffer can be sliced at DB level only if the PDT pointer is byte-aligned — declare 8 individual BYTE tags and pass the first tag's address as PDT).
  2. Network 2..7 — Use BTI (BCD to Integer, built-in) on each tag:
      |  "Clock_DB".YearBCD  --- BTI --- "Clock_DB".YearInt
      |  "Clock_DB".MonthBCD --- BTI --- "Clock_DB".MonthInt
      |  "Clock_DB".DayBCD   --- BTI --- "Clock_DB".DayInt
      |  "Clock_DB".HourBCD  --- BTI --- "Clock_DB".HourInt
      |  "Clock_DB".MinuteBCD --- BTI --- "Clock_DB".MinuteInt
      |  "Clock_DB".SecondBCD --- BTI --- "Clock_DB".SecondInt
  3. Network 8 — Decode millisecond field. Byte 6 carries three BCD digits: hundreds (high nibble), tens, units. Convert with the FBD BCD_TO_INT(FB93) from the Standard Library for the full 0..999 value, or split manually:
      L   "Clock_DB".MsHighBCD  // e.g. 16#04 (hundreds)
      L   100
      *I
      L   "Clock_DB".MsLowBCD   // e.g. 16#56 (tens + units)
      +I
      T   "Clock_DB".MsInt

Why BCD conversion is mandatory

Direct byte-to-INT transfer on a BCD clock field produces silently wrong values. Example: the year 2024 is stored as 16#24. A plain L PB0; T MW10 moves 0x24 = 36 decimal. A magazine indexed by year will then show "year 36", or a comparison with constants in the 2000..2089 range will fail in a way that is hard to trace. The fix is one BTI per byte.

Writing the Clock with SFC0

SFC0 is the write counterpart. The 319F-3 supports a master and up to 3 slave clocks. Typical commissioning script:

// Master clock set to system time, slaves only listen (SET=0)
CALL "SET_CLK"
     PDT  := "Clock_DB".WallClock      // source DT, populated manually or by FB
     SET  := TRUE                       // TRUE = this CPU sets its own clock
     RET_VAL := MW110;

Common pattern: a one-shot FB that runs on OB100 (warm restart) and reads the operator's HMI-entered time, writes it with SFC0, and then loops once per minute using SFC64 to verify drift.

Local Time vs UTC

SFC1 returns the CPU's local wall-clock time (i.e. whatever SFC0 last wrote, plus the internal drift compensation). The 319F-3 hardware does not track a separate UTC offset; DST is not automatic. If the application needs UTC, run the conversion in user code:

// 1. Read DT
CALL "READ_CLK"; PDT := "Clock_DB".WallClock;

// 2. Split to DATE + TOD
CALL "DT_DATE"; IN := "Clock_DB".WallClock; RET_VAL := "Clock_DB".DatePart;
CALL "DT_TOD";  IN := "Clock_DB".WallClock; RET_VAL := "Clock_DB".TodPart;

// 3. Add (or subtract) the TZ offset in TOD units (1 TOD unit = 1 ms)
CALL "AD_DT_TM";     // adds TIME to DT   (sum of DATE+TIME)
     T1 := "Clock_DB".WallClock
     T2 := "TZ_Offset"           // TIME, e.g. T#0h0m0s for UTC+0
     RET_VAL := "Clock_DB".UTC_DT;

// 4. Or convert TOD to TIME and add
CALL "D_TOD_DT"; IN := "Clock_DB".TodPart; RET_VAL := MW120;
The Standard Library > IEC Function Blocks must be installed in the S7 program on the 319F-3 for DT_DATE, DT_TOD, DT_DAY, AD_DT_TM, and D_TOD_DT to resolve. If those blocks are missing, OB121 priority-class error is raised and the CPU goes into STOP. Load the IECFC library from Options > Manage System Data if it is not already present.

Verification and Commissioning Checks

  1. Online monitor DB50 in STEP 7 and confirm WallClock increments by exactly 1 s every 1 s when watched in the watch table.
  2. Tick continuity: add a difference calculation Tick - PrevTick in a 1 s OB32 interrupt; expect 1000 ms ± 1 ms (the 319F-3 scans OB32 with a 1 s phase offset).
  3. BCD sanity: at year-end rollover, verify that byte 0 of WallClock changes from 16#23 (2023) to 16#24 (2024), and that YearInt flips to 2024 after the next OB1 scan.
  4. Battery health: power down the CPU for 5 minutes; on power-up, confirm SFC1 RET_VAL = 0 and WallClock is still current. If W#16#8081 is reported, replace the backup battery.
  5. Drift check: log the tick counter at midnight UTC and 24 h later; difference should be 86,400,000 ms ± 1,000 ms on the 319F-3 (typical drift 1..5 s/day at 25 °C).
  6. Time zone: simulate DST transition by adding T#1h with AD_DT_TM at a known trigger; verify the result is exactly 1 hour later in the TOD component.

Troubleshooting Matrix

Symptom Likely cause Diagnostic Remediation
SFC1 RET_VAL = W#16#8081 RTC battery missing/dead Check battery LED, hardware diagnostic buffer Replace 3.6 V 1/2 AA backup battery; reset time via SFC0
Year shows 36 instead of 2024 Reading BCD byte as plain INT Watch table on raw byte 0 = 16#24 Insert BTI / BCD_TO_INT conversion per byte
DT_DATE / DT_TOD blocks not resolved in program IEC Function Blocks library not installed LAD/FBD compiler warning in STEP 7 Open Standard Library > IEC Function Blocks and copy the FCs into the S7 program
OB121 STOP after enabling SFC0/SFC1 PDT pointer is not aligned to a BYTE; or PDT points to a local Temp that is out of scope Diagnostic buffer > I-stack error OB121 cause Use a symbolic DB tag, or a 8-byte ANY pointer to a STAT area
Tick counter resets unexpectedly STOP/RUN transition resets TIME_TCK on 319F Monitor OB100 / OB101 trigger Re-baseline the delta calculation; do not use SFC64 across restart
Local time is one hour off in spring/autumn No DST logic in user code Compare WallClock to UTC reference Add AD_DT_TM call with TZ_Offset TIME tag, or move to a CPU that supports time-of-day zones (S7-1500)
Year shows 89 after power-up Watchdog on SFC0 with the wrong format (binary PDT instead of BCD) Inspect source DT before SFC0 call Always populate PDT with a value obtained from SFC1 or a properly BCD-encoded DT literal

Performance and Cycle Time Impact

On the 319F-3 PN/DP, SFC1 execution is 18..24 µs (typical), SFC0 is 22..30 µs, and SFC64 is 14..20 µs. The IEC conversion blocks DT_DATE, DT_TOD, DT_DAY are each 6..12 µs. The complete Method 1 sequence (SFC1 + three conversions) takes less than 60 µs in OB1 and is well below the 1 ms threshold for any scan cycle. Method 2 (raw bytes + BTI per field) is comparable. Avoid calling the full sequence in fast OB35/OB32 interrupts with a 1 ms phase; cache the DT in a DB and only re-read at OB1 scan or once per second.

Cross-Platform Notes

STEP 7 V5.x S7-300/400 versus TIA Portal S7-1200/1500 use different time models. The S7-1200/1500 DTL type is a 12-byte structure with the same semantic content (year..nanoseconds + day-of-week) but is stored as binary integers, so no BCD conversion is needed. The equivalent call on S7-1500 is RD_SYS_T (reads into a DTL tag) and WR_SYS_T (writes a DTL tag). The Standard Library analog of SFC1 on S7-1500 therefore returns DTL directly, and the BTI path in Method 2 is not required. ABB Automation Builder on AC500 series uses a similar IEC-61131-3 DT and provides DT_TO_DATE / DT_TO_TOD helpers in the standard library. The principle — read the hardware clock into a DB-resident tag, then split — is identical; only the names and byte ordering differ.

Frequently Asked Questions

What is the difference between SFC1 READ_CLK and SFC64 TIME_TCK on the 319F-3?

SFC1 returns the absolute wall-clock time (year, month, day, hour, minute, second, millisecond, day-of-week) encoded as an 8-byte BCD DATE_AND_TIME buffer. SFC64 returns a 32-bit TIME value (milliseconds since the last STOP-to-RUN transition) used for delta time measurement. SFC1 is for "what time is it?" SFC64 is for "how long has this been running?".

Why are the clock bytes in BCD and not binary on S7-300/400?

The SIMATIC S7-300/400 hardware clock buffer follows the legacy BCD encoding defined by the IEC DATE_AND_TIME data type. This allows each field to be displayed directly on a 7-segment display or an OP without conversion. SFC1 writes BCD; reading the bytes as plain binary will yield wrong values (e.g. 16#24 as decimal 36 instead of year 2024). Use BTI or the Standard Library FC93 BCD_TO_INT for each byte.

Do I need SFC0 SET_CLK every cycle, or only once?

Only once at commissioning, or any time the PLC must be re-synchronized. SFC0 sets the CPU's RTC and the slave clocks attached to the MPI/PROFIBUS segment. The 319F-3 keeps the time running on its battery-backed RTC after SFC0 has been executed. Calling SFC0 in every OB1 cycle is unnecessary and can introduce drift if the source pointer contains a time that is not the master reference.

How do I get day-of-week and year integer in a usable format?

Use the IEC standard function block DT_DAY (1=Sunday .. 7=Saturday) for the day-of-week. For year, do not rely on the BCD byte directly; convert the 8-byte DATE_AND_TIME first to a DATE with DT_DATE, then add the year offset (1990) and divide by 365.25 in user code, or just use a CASE ladder on the BCD year byte if you only need decade-level granularity.

What happens to SFC64 TIME_TCK on restart?

On the S7-300 CPU 319F-3 the tick counter resets to 0 on every STOP-to-RUN transition (cold and warm restart). It does not survive OB100. Use the absolute clock (SFC1) for any time value that must persist across restart, and reserve SFC64 for short-interval delta timing inside a single continuous RUN.

Back to blog