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:
- IEC On-Delay Timer (TON) in a Function Block static variable - the simplest method, returning a TIME value with millisecond resolution.
- Cumulative PLC cycle time accumulated in OB1 into a REAL tag - sub-cycle granularity when movement time is shorter than the OB1 scan period.
- RUNTIME system function - returns elapsed seconds between two calls without consuming a timer resource.
- 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 |
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.
- Create a new Function Block
FB_WindowCyclewith 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 |
- 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;
- 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;
- 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;
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.
- Add an FB or OB1 logic that reads
INFOfrom 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;
- On the stop edge, copy
LiftCycleTimeto 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.
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.
- Insert the
DataLogCreate,DataLogWrite,DataLogCloseinstructions from Extended instructions > DataLog. - On each
q_DonePulse, callDataLogWritewith the row(Index, ElapsedTime_ms, StartStamp). - 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.
- 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));
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.
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:
-
Offline simulation: in TIA Portal, set up a PLCSIM instance of the 1212C and force
i_StartTRUE for 5 seconds, theni_Stop. Readq_ElapsedTime; expect T#5s_000ms ± one scan. -
Online watch table: connect with a live CPU, drive
q_UpCmdwith a known-good digital output (e.g., a lamp) and time it with a stopwatch; expect match within 100 ms. -
DataLog sanity: open the user-defined web page and confirm rows appear with monotonically increasing
CycleIndex. -
Buffer wrap: set
s_Indexto 999 manually (in OB100 startup) and force a cycle; verify the next row overwrites index 0, not a memory access violation. - 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:
- SIMATIC S7-1200 Programmable Controller System Manual - covers CPU 1212C hardware, web server, and DataLog
- STEP 7 Basic V13 SP1 / TIA Portal timer instructions - TON, TOF, TP, and IEC_TIMER instance behaviour
- RD_SYS_T and T_DIFF instruction help - DTL handling and clock synchronisation
- S7-1200 Web server User-defined pages - HTML fragment structure and JSON API
- DataLog function block reference for S7-1200 - record format, error codes, and limits
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.