Measuring Cycle Time on S7-1200 with TON, RUNTIME, and RD_SYS_T

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

Overview

On a Siemens SIMATIC S7-1200, capturing the duration of a single motion cycle - such as the time a window lifter output is asserted from start position to end position - is a routine measurement task. The challenge is that the value is short (typically 2 to 10 seconds), must be accurate to roughly 100 ms, and must be retained per cycle for later logging to the integrated web server of the CPU 1212C DC/DC/Rly. The IEC 61131-3 timer instructions that ship with TIA Portal V13 SP1 (and subsequent versions up through V20) cover this requirement directly without the need for external counters or dedicated measurement modules.

This reference consolidates four practical approaches used in production test stands:

  1. IEC On-Delay Timer (TON) in a Function Block static variable - the simplest method, returning a TIME value with millisecond resolution.
  2. Cumulative PLC cycle time accumulated in OB1 into a REAL tag - sub-cycle granularity when movement time is shorter than the OB1 scan period.
  3. RUNTIME system function - returns elapsed seconds between two calls without consuming a timer resource.
  4. RD_SYS_T with DTL timestamps and T_DIFF subtraction - wall-clock-based measurement using the CPU real-time clock.

All four methods produce data that can be pushed to the standard DataLog function blocks and exposed through the CPU web server, satisfying the test-stand logging requirement without external SCADA software.

Prerequisites

Implement this article on the following hardware and software stack. Earlier or later firmware behaves identically for the timing instructions described.

Item Specification Notes
CPU S7-1212C DC/DC/Rly (6ES7212-1BE40-0XB0 or later -B/-C revision) 100 kB work memory, integrated PROFINET, web server
Firmware V4.2 or higher (V4.4 recommended for current TIA Portal versions) RD_SYS_T and T_DIFF available from FW V2.0 onward
Engineering TIA Portal V13 SP1 Upd 9 minimum; V16/V17/V18/V20 supported V13 SP1 is the source baseline; upgrade if possible
Inputs/Outputs Two digital outputs (window up, window down), one analogue input (current) Use transistor outputs if cycle counting exceeds the relay life of the DC/DC/Rly variant
Web server Activated on the CPU under Web server > Activate Enables User-defined web pages or DataLog readback
Time accuracy CPU real-time clock drift typically ±2 s/day without SNTP Enable NTP for absolute timestamps
Resolution caveat: The IEC TIMER data type is a DINT in milliseconds (range T#0 ms to T#2_147_483_647 ms ≈ 24.86 days). 100 ms accuracy is comfortably inside this resolution. If you need <1 ms resolution, accumulate PLC cycle time (OB1) in REAL seconds.

Method 1 - IEC On-Delay Timer (TON) in a Function Block

The TON instruction returns a TIME value equal to the elapsed time since its IN input became TRUE. Place the timer inside an FB so its instance remains persistent across calls; this avoids losing the measurement on every scan.

  1. Create a new Function Block FB_WindowCycle with the following interface:
Section Name Data type Initial value Comment
Input i_Start Bool FALSE Start measurement on rising edge
Input i_Stop Bool FALSE Stop measurement on rising edge
Input i_Reset Bool FALSE Clear buffer and index
Output q_ElapsedTime Time T#0ms Last cycle time
Output q_DonePulse Bool FALSE One-cycle pulse on i_Stop rising edge
Static s_Timer IEC_TIMER / TP / TON / TOF - Holds the running TON instance
Static s_Buffer Array[0..999] of Real 0.0 History of last 1000 cycles in seconds
Static s_Index DInt 0 Next buffer position
Static s_MoveActive Bool FALSE True between start and stop events
  1. Network 1 - Latch completion and reset:
// Network 1 - capture finished cycle, advance buffer index
IF i_Reset THEN
    s_Index := 0;
    s_MoveActive := FALSE;
    q_DonePulse := FALSE;
    FOR k := 0 TO 999 DO s_Buffer[k] := 0.0; END_FOR;
END_IF;

// rising edge of i_Stop freezes the elapsed time
IF i_Stop AND s_MoveActive THEN
    q_ElapsedTime := s_Timer.ET;            // IEC_TIMER exposes ET as Time
    s_Buffer[s_Index] := DINT_TO_REAL(TIME_TO_DINT(q_ElapsedTime)) / 1000.0; // seconds
    s_Index := (s_Index + 1) MOD 1000;
    q_DonePulse := TRUE;
    s_MoveActive := FALSE;
ELSE
    q_DonePulse := FALSE;
END_IF;
  1. Network 2 - Run the TON only while the motion output is high:
// Network 2 - timer body, must execute before the drive command
s_Timer(IN := i_Start AND NOT i_Stop,
        PT := T#24d_20h_31m_23s_647ms);

IF i_Start AND NOT i_Stop THEN
    s_MoveActive := TRUE;
END_IF;
  1. Network 3 - Motion command comes after the timer networks:
// Network 3 - drive window up/down
q_UpCmd := i_Start AND NOT i_Stop;
q_DownCmd := i_StartDown AND NOT i_Stop;
Ordering matters. The TON must be evaluated in the same scan (or earlier) than the output that drives the window motor. If the command logic toggles the output in the same OB1 scan as the timer reset, you will read a one-cycle-low measurement. Place the timer body in network 1 and the drive command in network 3.

The IEC_TIMER instance exposes ET (elapsed time, TIME) and Q (output). When the IN input goes FALSE, ET holds its last value until IN goes TRUE again - which is exactly the behaviour required to log the cycle time after the window reaches the end position.

Method 2 - Cumulative PLC Cycle Time (REAL)

When the cycle duration is comparable to the OB1 scan period (the S7-1212C scans in 1 to 4 ms typical), the TON approach still works, but you can get finer granularity by accumulating the actual cycle time of the PLC instead of counting milliseconds. This approach is recommended for any movement under ~50 ms where you want true sub-millisecond resolution.

  1. Add an FB or OB1 logic that reads INFO from the cyclic interrupt, or use a self-measuring tick:
// In OB1 or a cyclic OB
"DB_CycleLog".CycleTimeLast := "DB_CycleLog".CycleTimeCurrent;
"DB_CycleLog".CycleTimeCurrent := TIME_TO_REAL(OB1_PREV_CYCLE) / 1000.0; // seconds

// While the motion is active, accumulate
IF "i_MoveActive" THEN
    "DB_CycleLog".LiftCycleTime := "DB_CycleLog".LiftCycleTime + "DB_CycleLog".CycleTimeCurrent;
END_IF;
  1. On the stop edge, copy LiftCycleTime to the buffer and reset to 0.0.

OB1_PREV_CYCLE is a system-local TIME tag provided by the S7-1200 CPU firmware - it reflects the previous OB1 scan time with microsecond precision (the value is still stored as a TIME / DINT in ms). For absolute precision, enable Use minimum cycle OB and read OB1_CYCLIME from the OB1 priority class information.

Method 3 - RUNTIME System Function

The RUNTIME instruction (available from TIA Portal V13 onward in the Extended instructions > Date and time palette) returns a DWORD value that increments every millisecond since the CPU powered up. Capture it at start and stop, then subtract.

// First call - latches start value
IF i_Start AND NOT s_StartLatched THEN
    s_StartTick := RUNTIME(ETimeMs := s_ElapsedTmp); // s_StartTick in ms
    s_StartLatched := TRUE;
END_IF;

// Stop edge - compute delta
IF i_Stop AND s_StartLatched THEN
    s_StopTick := RUNTIME(ETimeMs := s_ElapsedTmp);
    s_DeltaMs := DWORD_TO_DINT(s_StopTick - s_StartTick);
    q_ElapsedTime := DINT_TO_TIME(s_DeltaMs);
    s_StartLatched := FALSE;
END_IF;

Advantages: zero timer-resource overhead; immune to PLC cycle jitter; can be called from any OB. Limitation: the DWORD counter wraps after roughly 49.7 days. For a test stand that runs continuously, add a wrap-detection check (IF s_StopTick < s_StartTick THEN s_DeltaMs := s_DeltaMs + 4294967296;) or reset the start tick every midnight.

Method 4 - RD_SYS_T with DTL and T_DIFF

Use the CPU real-time clock when you need an absolute, human-readable timestamp alongside the elapsed time (useful when the test stand runs unattended for weeks). The combination of RD_SYS_T (read system time) and T_DIFF (subtract two DTL values) is the most readable for the data log.

VAR
    s_StartDTL : DTL;
    s_StopDTL  : DTL;
    s_DiffTime : TIME;
END_VAR

// At start - sample the system clock
IF i_Start AND NOT s_MoveActive THEN
    RD_SYS_T(RET_VAL := s_RetValStart, OUT := s_StartDTL);
    s_MoveActive := TRUE;
END_IF;

// At stop - sample again and compute
IF i_Stop AND s_MoveActive THEN
    RD_SYS_T(RET_VAL := s_RetValStop, OUT := s_StopDTL);
    s_DiffTime := T_DIFF(IN1 := s_StopDTL, IN2 := s_StartDTL);
    q_ElapsedTime := s_DiffTime;
    q_StartStamp := s_StartDTL;   // DTL, perfect for DataLog
    q_StopStamp := s_StopDTL;
    s_MoveActive := FALSE;
END_IF;

The DTL data type is 12 bytes long and carries year, month, day, hour, minute, second and nanosecond fields. T_DIFF returns a TIME value with millisecond precision; nanoseconds are truncated.

Clock drift warning: RD_SYS_T depends on the CPU internal clock, which drifts up to ±2 seconds per day if NTP is not configured. For cycle measurements this is irrelevant (deltas are unaffected). For wall-clock audit trails, enable SNTP under CPU properties > Time of day > Time synchronization.

Comparison of the Four Methods

Criterion TON (Method 1) Cycle accumulation (Method 2) RUNTIME (Method 3) RD_SYS_T + T_DIFF (Method 4)
Native data type TIME (DINT, ms) REAL (s) DWORD (ms) TIME (ms) + DTL
Resolution 1 ms (limited by scan) OB1 scan (typically 1 ms) 1 ms 1 ms (ns truncated)
Range T#24d20h31m23s647ms REAL limit ~49.7 days (wrap) Year 1970 to 2554
Cycle accuracy ±1 scan Sub-scan (use OB1_PREV_CYCLE) ±1 ms ±1 ms
Wall-clock stamp No No No Yes (DTL)
Resource cost 1 timer per FB instance None None None
Implementation effort Low Medium Low Medium
Best fit General purpose, short cycles Sub-millisecond precision Wrap-free long runs Audit trails, DataLog timestamp

For the original test stand described - 2 to 10 second window movement on a 1212C - Method 1 (TON) is the most direct. Method 4 is the right pick when the DataLog entry must record an absolute start/stop wall clock.

Logging to the Integrated Web Server

Once the buffer array is populated, push each entry to the S7-1200 DataLog and expose it through the standard web server pages or via User-defined web pages.

  1. Insert the DataLogCreate, DataLogWrite, DataLogClose instructions from Extended instructions > DataLog.
  2. On each q_DonePulse, call DataLogWrite with the row (Index, ElapsedTime_ms, StartStamp).
  3. Under CPU properties > Web server, enable Activate web server on this module and select Permit access only via HTTPS if the test stand is on a corporate network.
  4. Add a User-defined web page fragment (.html) in Web server > User-defined pages that reads the DataLog via the JSON API exposed at /awp/DataLog.html.
// DataLog write - triggered by q_DonePulse
"inst_DataLogWrite"(REQ := q_DonePulse,
                    ID   := 1,
                    DataLogName := 'WindowCycles',
                    Record := "UDT_CycleRow"(CycleIndex := s_Index - 1,
                                             ElapsedMs  := TIME_TO_DINT(q_ElapsedTime),
                                             StartStamp := q_StartStamp));
DataLog size. The S7-1212C allows up to 50 DataLogs and a total record capacity limited by the internal flash (typically 4 MB). For 500 + 500 = 1000 cycles, each row 16 bytes, the DataLog consumes ~16 kB - well within limits.

Implementation Flowchart

The decision tree below maps the source problem (cycle time measurement on a window lifter test stand) to the recommended code path. Render this as an SVG in your HMI documentation if needed.

Cycle > 100 ms? TON in FB (Method 1) Cycle-time accumulation (Method 2) RUNTIME (Method 3) RD_SYS_T + T_DIFF (Method 4) Write to DataLog buffer Expose via Web server

Troubleshooting Matrix

Symptom Likely cause Diagnostic Fix
Cycle time always reads T#0ms i_Stop fires before the TON sees a rising edge on i_Start Monitor s_MoveActive in a watch table Ensure i_Start remains TRUE for at least one full OB1 scan before i_Stop
Cycle time reads exactly one OB1 scan low TON placed after the drive command network Inspect network order in FB Reorder so timer body is in Network 1, drive in Network 3
Measurement drifts by seconds per cycle NTP not configured and CPU clock drift is being captured Check RD_SYS_T output over 1 hour Enable NTP, or use Methods 1/2/3 which are drift-free for deltas
DataLog write error 80B5 / 80C5 DataLog not created or wrong ID Read STATUS of DataLogCreate Call DataLogCreate once in OB100 startup with the right column count
Buffer wraps unexpectedly at 256 Index declared as Byte instead of DInt Inspect s_Index data type Change to DInt
Time always zero after a power cycle FB instance DB has been re-initialised; retentivity not enabled DB properties > Retain Mark s_Index, s_Buffer as retentive if needed; otherwise re-zero on startup
RUNTIME wraparound every 49.7 days Counter overflow Trend s_StartTick Add wrap detection or re-latch on date change
T_DIFF returns negative IN1 and IN2 swapped, or NTP step adjusted the clock backward Check DTL values Swap arguments; T_DIFF(IN1 := Stop, IN2 := Start) returns positive duration

Verification Procedure

Confirm the implementation before placing the test stand into production:

  1. Offline simulation: in TIA Portal, set up a PLCSIM instance of the 1212C and force i_Start TRUE for 5 seconds, then i_Stop. Read q_ElapsedTime; expect T#5s_000ms ± one scan.
  2. Online watch table: connect with a live CPU, drive q_UpCmd with a known-good digital output (e.g., a lamp) and time it with a stopwatch; expect match within 100 ms.
  3. DataLog sanity: open the user-defined web page and confirm rows appear with monotonically increasing CycleIndex.
  4. Buffer wrap: set s_Index to 999 manually (in OB100 startup) and force a cycle; verify the next row overwrites index 0, not a memory access violation.
  5. Retentivity test: power-cycle the CPU and confirm the buffer is either preserved (retentive) or zeroed deterministically, depending on the project requirement.

Common Pitfalls and Field Notes

  • TIA Portal V13 SP1 has a known issue with IEC_TIMER ET initialisation. Upgrade to V13 SP1 Upd 9 or move to V15.1+ to avoid ET returning T#0ms on the first scan after STOP→RUN.
  • Relay contact wear. The DC/DC/Rly variant uses mechanical relays rated 3 A / 30 V DC. With 1000 cycles per test, the relay life (100 000 operations) gives ~100 full test runs before replacement. Switch to the DC/DC/DC transistor variant (6ES7212-1AE40-0XB0) or the relay-output higher-current 1214C/1215C if cycle counts exceed 50 000.
  • Analogue input noise. Window motor inrush spikes the current input. Add a moving average of 8 samples before logging to the web server.
  • Web server refresh. The standard user-defined pages poll every 10 s. For faster updates, lower the polling in the HTML fragment to 1 s, but mind that aggressive polling consumes PROFINET bandwidth.
  • Symbolic vs absolute access. TIA Portal V13 SP1 occasionally loses the symbolic link to the IEC_TIMER ET property after a project migration. Always re-validate the timer instance DB online before commissioning.

Extended Documentation

Refer to the following official Siemens documentation for instruction signatures, error codes, and firmware compatibility:

FAQ

What data type should I store a cycle time in?

Use the IEC TIME type, a 32-bit signed integer counting milliseconds (range T#0 to T#24d_20h_31m_23s_647ms). For wall-clock logging add a DTL timestamp captured with RD_SYS_T. Store histories as Array[0..N] of Real (seconds) or DInt (milliseconds) depending on whether you prefer human-readable conversion in the web server or compact integer storage.

Can I read the timer elapsed time without using a TON instruction?

Yes. The RUNTIME system function returns a DWORD of milliseconds since CPU power-up. Read it at start and at stop, subtract the two values, and convert the difference with DINT_TO_TIME. This avoids consuming one of the limited IEC timer resources (the S7-1212C supports up to 256 simultaneous IEC timer instances).

How do I log cycle times to the S7-1200 web server?

Write each completed cycle to a DataLog using the DataLogWrite FB. Activate the CPU web server under CPU properties > Web server and create a User-defined page (an .htm fragment with AWP commands) that reads the DataLog via the built-in JSON endpoint. The web page can then refresh automatically every few seconds.

Why does my measurement always read zero on the first cycle after power-on?

On TIA Portal V13 SP1, IEC_TIMER ET can return T#0ms on the first scan after STOP to RUN. Upgrade to V13 SP1 Update 9 or newer, or initialise s_Timer in OB100 with a rising edge on its IN input. Alternatively use RUNTIME or RD_SYS_T, both of which return valid values immediately.

What is the accuracy of T_DIFF for sub-millisecond measurements?

T_DIFF returns a TIME value with millisecond resolution; the nanosecond field of the DTL input is truncated. For sub-millisecond resolution, accumulate OB1_PREV_CYCLE (a TIME tag reflecting the previous OB1 scan time) into a REAL variable while the motion output is high. This gives microsecond-equivalent resolution as long as OB1 is configured for a fixed scan period.

Back to blog