S7-300 STL: 5-Second Timer Snapshot Comparison on CPU 313C

David Krause23 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: Snapshot Comparison in S7-300 STL

Snapshot comparison is a classic pattern in PLC programs: latch a process value at a known moment, latch it again after a defined interval, and emit a Boolean result that downstream logic can use to detect a freeze, a stalled update, or a value match. On a SIMATIC S7-300 with a CPU 313C, the pattern is implemented cleanly in Statement List (STL) using only the integrated clock memory bit, an up-counter, two memory words, edge detection, and a single integer comparison. The code runs inside the standard OB1 cyclic task, requires no additional hardware, and consumes fewer than 20 STL statements.

This article documents the working pattern end to end: the clock memory byte that supplies the periodic heartbeat, the up-counter that derives the 5-second window, the positive-edge detector that converts a level-triggered threshold into a momentary snapshot pulse, the load/transfer pair that stores and refreshes the snapshot pair in MW50 and MW52, and the <>I instruction that drives Q0.0 whenever the two snapshots disagree. The implementation is targeted at STEP 7 V5.5 SP4 or STEP 7 V5.6 with a CPU 313C (6ES7313-5BF03-0AB0 or later) and an existing Modbus TCP/RTU link delivering the timer value to a memory word such as MW50.

STL availability: STL is supported on the S7-300/400 family including the CPU 313C. STL is not supported on the S7-1200 or S7-1500 families. If the target platform is S7-1200, port the routine to SCL, LAD, or FBD using the equivalent edge and comparison blocks.

Two timing options are presented. The published reference code uses the 5 Hz clock bit M100.5 with a counter threshold of 5; the cleaner 1 Hz form uses M100.3 with the same threshold. Both produce a 5-second snapshot interval. The two forms are functionally identical; the choice depends on whether you want more resolution in the counter or a slower toggling heartbeat.

Prerequisites and Hardware Configuration

Before the STL code can be loaded and run, confirm that the CPU 313C is configured to provide a usable clock memory byte. The clock memory byte is a free byte in the M area (default MB100) whose individual bits toggle at fixed frequencies when the operating system is running. The byte is updated once per OB1 cycle, so the actual edge instants are quantised by the OB1 scan time.

  • CPU 313C, firmware V2.6 or higher recommended; V3.3 is typical for the 6ES7313-5BG04 series.
  • STEP 7 V5.5 SP4 or STEP 7 V5.6 with the S7-300 optional package.
  • Configured Modbus link delivering a 16-bit holding register into MW50 every cycle or every N cycles.
  • Clock memory byte enabled in HW Config under CPU properties, on the Cycle/Clock Memory tab.
  • OB1 as the cyclic main; the routine runs entirely in OB1 and needs no OB35 segmentation.

In HW Config, double-click the CPU 313C, select the Cycle/Clock Memory tab, and tick Clock memory. The Memory byte parameter defaults to 100; the eight bits of MB100 then toggle at the rates shown in the table below when the OB1 cycle time is close to the default 100 ms.

Bit Period Frequency Typical use
M100.0 10 s 0.1 Hz Slow sequencer
M100.1 5 s 0.2 Hz Slow toggle, level-timed window
M100.2 2 s 0.5 Hz Heartbeat LED
M100.3 1 s 1 Hz 1-second tick (recommended for 5 s window)
M100.4 500 ms 2 Hz Half-second tick
M100.5 200 ms 5 Hz Fast counter clock (reference code default)
M100.6 100 ms 10 Hz Fast tick
M100.7 50 ms 20 Hz Cycle synchroniser

For a true 5-second window, the bit at M100.1 (period 5 s) is the natural fit if the window can be measured by the level rather than by a counter. The reference implementation in this article uses M100.5 at 5 Hz as a fast up-counter clock because the counter performs the actual 5-second accumulation; using a faster clock simply gives more resolution in the count value while still producing a 5-second window.

Decoding the Modbus Timer Value into PLC Memory

The source PLC has an existing Modbus link that reads a timer value from a remote device into a memory word. The two typical Modbus function codes used in S7-300 user programs are FC 03 (Read Holding Registers) and FC 04 (Read Input Registers). Both return 16-bit unsigned values in the high-byte-then-low-byte order that STEP 7 word loads interpret correctly when the FC is configured for word access. Refer to the Siemens Industry Online Support portal for the S7-300 CP340/CP341 Modbus master example block, or to the Modbus RTU/TCP master FB shipped with STEP 7 V5.5.

The S7-300 Modbus master block deposits the received register into a configured data area. Map the destination as follows.

Modbus address (remote) Function code PLC destination Width Content
40001 + N FC 03 MW50 16-bit word Live timer value T-1
(local) (internal) MW52 16-bit word Previous snapshot (owned by routine)
(local) (internal) MB10 8-bit byte Edge memory; cleared on each window

Because the routine only references MW50 as the live input and MW52 as the snapshot, the snapshot cell is owned by the STL routine. The Modbus master should never write to MW52; protect this by reserving a separate DB area for the Modbus receive buffer and copy the value into MW50 at the start of OB1, ahead of the snapshot logic. If the receive buffer is in a DB, use L DBWxx and T MW50 at the very top of OB1 to mirror the value into the snapshot cell.

Endianness: STEP 7 stores words in big-endian. Modbus is big-endian at the register level, but the byte order inside a 16-bit register is device-specific. If the remote device is little-endian, byte-swap inside the receive block with CAW or two SLW/SRW shifts; the snapshot routine then sees the swapped value.

CPU Clock Memory Bit and 1 Hz Heartbeat

The first statement of the reference routine, A M100.5, treats the clock memory bit as a Boolean contact. In a clean 5-second implementation it is more common to use A M100.3 (1 Hz); the published implementation uses M100.5 (5 Hz) so the counter increments more frequently and the threshold of 5 still produces a one-second-equivalent window in the original code. To match the published timing exactly, leave the bit at M100.5 and set the counter threshold to 25; to use a clean 1 Hz heartbeat, change the bit to M100.3 and keep the threshold at 5.

The clock memory byte is updated by the operating system once per OB1 cycle. The bit pattern is generated by a hardware-driven counter inside the CPU and is not affected by user program execution time beyond the precision of the period. For sub-millisecond accuracy, use a hardware timer (SFB 4 / SFB 5 / IEC timers) or an OB35 time-of-day interrupt at 1000 ms; the snapshot pattern does not require that level of accuracy.

OB1 scan time is the practical limiter. With a 100 ms OB1 cycle, the 1 Hz clock M100.3 has a 10x margin against missed edges, so the count is reliable. With a 250 ms OB1 cycle the margin shrinks; with a 1 s OB1 cycle the clock bit is invisible to the up-counter. If the OB1 scan time is variable or long, drive the snapshot from OB35 (cyclic interrupt) or from an IEC timer block instead of the clock memory.

Counter-Based Five-Second Interval Generation

The up-counter instruction CU C1 increments counter C1 on every rising edge of the clock bit. With a 1 Hz input and a threshold comparison of CV >= 5, the counter reaches the threshold after five rising edges, that is, after five seconds. The threshold comparison is implemented with the load-arithmetic pair:

A(
L   C1              // ACCU1 := current counter value (binary)
L   5               // ACCU1 := 5, ACCU2 := old CV
>=I                // CC1 := (ACCU2 >= ACCU1) i.e. CV_old >= 5
)
FP  M10.0           // Edge flag: one OB1 cycle TRUE when CV first crosses 5
=   M10.1           // Latched trigger for the snapshot and compare

The CU instruction counts on the rising edge of the operand contact (the clock bit). Because OB1 runs every 100 ms or so, multiple clock bit edges can be visible per OB1 cycle; the up-counter's internal edge handling ensures that only one increment is recorded per edge of the input regardless of OB1 scan time, so the count is correct even if the OB1 scan is slow.

Counter type: C1 is a standard SIMATIC counter (CTU). For a 5-second window an integer counter is more than sufficient; the maximum count of 999 from BCD counters or 32767 from binary counters is never approached. On the S7-300 the counter area is shared with the timer area at low addresses, so confirm C1 is not also being used as a timer at the same word.

The threshold itself is a level, not an edge. The level is TRUE whenever the counter is at 5, 6, 7, and so on. Without edge detection the snapshot would fire on every cycle while the level is high, which is the wrong behaviour. The next section converts the level into a one-cycle pulse.

Edge Detection for Snapshot Latching

The threshold comparison returns a level (TRUE whenever CV is greater than or equal to 5), but the snapshot must occur exactly once per 5-second window, on the first cycle in which the condition becomes true. The positive-edge detector FP M10.0 converts the level into a single-cycle pulse by storing the previous state of the comparison in M10.0 and emitting TRUE for one OB1 scan when the level transitions from FALSE to TRUE.

The pulse M10.1 drives two actions in the routine:

  1. The compare-and-update block: load MW50 and MW52, compare with <>I, write the result to Q0.0, then transfer MW50 into MW52 for the next window.
  2. The reset block: zero MB10 (the edge memory byte) and reset counter C1, so that the next 5-second window starts from a clean state.

The edge bit M10.0 is the helper flag of the FP instruction. STEP 7 requires that the helper flag be in a memory area; never use it in a process image or in a DB bit if the area is also being touched by another FP/FN on a different condition, because the edge bit would be aliased. If the application has many FP/FN calls, reserve a dedicated edge flag byte (for example MB20) and assign each edge detector a unique bit.

Snapshot Storage and Memory Word Management

Two memory words hold the snapshots:

  • MW50 — live value, written by the Modbus master block at the top of OB1 (or mirrored from a DB receive buffer).
  • MW52 — aged value, refreshed by the snapshot routine on each 5-second boundary.

The transfer L MW50 / T MW52 copies the live value into the aged slot. The order matters: the compare happens before the transfer, so Q0.0 reflects the state of “live differs from previous aged value at the instant the boundary fires.” If the live value has been stable for two consecutive windows, Q0.0 will be FALSE on the boundary; if it has changed, Q0.0 will be TRUE for one OB1 cycle.

Because Q0.0 is a one-cycle pulse, downstream logic that needs a sticky “value changed in the last 5 s” signal should set an SR latch on the rising edge of Q0.0 and clear it on a separate condition (for example operator reset, or a second edge from a “value is now stable” check). The published routine does not provide the latch; that is a design decision left to the calling program.

If the live value is wider than 16 bits (32-bit millisecond count, for example), promote the loads to double-word form L MD50 and L MD52 and use <>D instead of <>I. The same code shape applies; only the operand width and the comparison mnemonic change.

Integer Comparison with the <>I Instruction

The comparison <>I tests the two 16-bit words for inequality as signed integers. The result sets the RLO and the CC1/CC0 status bits; the subsequent = Q0.0 coil writes the RLO to the output. The semantics of <>I in S7-300 STL are:

ACCU2 vs ACCU1 CC1 CC0 RLO result of <>I
ACCU2 == ACCU1 0 0 0 (FALSE)
ACCU2 != ACCU1 0 1 1 (TRUE)

The load sequence L MW50 then L MW52 places the second loaded value in ACCU1 and the first in ACCU2. The <>I instruction is commutative in terms of inequality, so the order of the two loads does not affect the result; the order does matter for the ==I, >I, and <I forms. For pure equality/inequality, either order is fine.

If the timer value is unsigned but you want a strict inequality check across the full 0..65535 range, you can mask both words with UW 0FFFF before the comparison. For most timer read-backs from a Modbus device the value is small (0..3600 seconds is typical) and the signed form is sufficient.

Complete STL Implementation (Annotated)

The full routine, line by line with comments matching the address notation used in the original publication.

// =====================================================
//  S7-300 STL: 5-second timer snapshot comparison
//  Target: CPU 313C, STEP 7 V5.5/5.6, executed in OB1
// =====================================================

// --- 1 Hz / 5 Hz up-counter on the clock memory bit ---
A   M100.5         // 5 Hz clock bit; for 1 Hz use M100.3
CU  C1             // C1 counts rising edges of the clock

// --- Threshold compare and edge detection ---
A(
L   C1             // ACCU1 = CV (binary)
L   5              // ACCU1 = 5, ACCU2 = CV
>=I               // CC1 set when CV >= 5
)
FP  M10.0          // pulse on the 0->1 transition
=   M10.1          // M10.1 is TRUE for one OB1 cycle

// --- On the pulse: compare and update the snapshot ---
A   M10.1          // gate the snapshot action
JCN one            // if no pulse, jump to the cycle end

// Compare: live (MW50) vs previous snapshot (MW52)
L   MW50           // ACCU1 = live timer
L   MW52           // ACCU1 = snapshot, ACCU2 = live
<>I               // RLO = (snapshot != live)
=   Q0.0           // Q0.0 high for one cycle on a change

// Update the snapshot for the next window
L   MW50           // ACCU1 = live timer
T   MW52           // snapshot := live

// --- Cycle reset: counter and edge memory back to 0 ---
SET                // RLO = 1
R   C1             // counter C1 := 0
L   0
T   MB10           // M10.0..M10.7 := 0

one: NOP 0         // jump target

The published reference uses counter C1, edge memory M10.0, snapshot memory words MW50/MW52, and the first output byte Q0.0. None of these addresses clash with the integrated I/O of the CPU 313C, which reserves I0.0..I0.23 / I1.0..I1.15 (digital inputs), O0.0..O0.15 (digital outputs), and the analogue/peripheral areas; the standard M area starts at MB0 and is fully available for user flags.

Operand scope: The 16 integrated digital outputs of the CPU 313C start at byte 0. Q0.0 is one of those 16 outputs and is therefore a physical terminal on the front connector. If Q0.0 is wired to a load, the load will see a 100 ms pulse every 5 s whenever the snapshots disagree. If a physical pulse is not wanted, route the comparison result to a flag (M) or a static DB bit and remove the = Q0.0 coil.

Cycle Reset and Continuous Loop Operation

The trailing block — SET R C1, L 0 T MB10 — re-arms the entire sequence for the next 5-second window. SET forces the RLO to 1, R resets the addressed counter or bit, and the L 0 / T MB10 pair clears the edge memory byte so that the next FP detection is clean. Without the byte clear, the edge flag would stay latched in its previous state until the next comparison, which would suppress the next pulse if the threshold were crossed more than once in quick succession.

The cycle reset is intentionally inside the gated block (A M10.1 JCN one skips it when the gate is false), so the counter keeps counting and the threshold can be reached again on the next window. If the application requires a “fire-once” behaviour, replace the reset with a halt label at the end and remove the jump.

Resetting the counter and the edge byte only when the threshold is reached keeps the counter in a known state for the next window. If the reset were outside the gate, the counter would be cleared on every cycle and would never reach the threshold; if it were unconditional after the gate, the edge flag would never be cleared and the next FP would not see a transition. The gated reset is the correct placement for a one-shot per window.

Variant: One-Shot Comparison with Halt

For a single comparison at startup or on a manual trigger, replace the continuous loop with a halt and a one-shot RS latch. The trigger can be I 0.0 (a pushbutton on the first digital input of the CPU 313C), and the result latches into a flag that the rest of the program reads. The pattern is:

// Manual trigger from I 0.0
A   I 0.0          // manual capture button
FP  M11.0          // rising edge of the trigger
=   M11.1          // one-cycle pulse

A   M11.1
JCN end

// Snapshot action: same compare/transfer as before
L   MW50
L   MW52
<>I
S   M12.0          // sticky "value changed" flag
L   MW50
T   MW52
SET
R   M11.0
end: NOP 0

To clear the sticky flag, evaluate a “value is now stable” condition and reset M12.0, or use the operator's reset pushbutton on I 0.1. The variant keeps the live/aged memory layout intact and only changes the trigger source and the latching style of the output.

Variant: SCL Equivalent (S7-300/400 with S7-SCL Add-on)

For sites that prefer a structured text, the SCL equivalent of the same routine is:

// SCL for S7-300/400 with S7-SCL
IF M100.3 THEN                     // 1 Hz clock bit
    C1 := C1 + 1;                  // CTU
END_IF;

IF (C1 >= 5) AND (NOT M10_0_old) THEN
    Q0_0 := (MW50 <> MW52);
    MW52 := MW50;
    C1 := 0;
    M10_0_old := TRUE;
END_IF;

IF (C1 < 5) THEN
    M10_0_old := FALSE;
END_IF;

Although the CPU 313C supports the S7-SCL optional package, the STL form remains the most compact and the most portable across S7-300/400 STEP 7 versions. SCL on S7-300/400 is compiled into STL/MC7 by the SCL compiler, so the runtime behaviour is functionally identical.

Commissioning and Verification

Bring the routine up in a controlled sequence so that each step can be observed in the STEP 7 monitor / online view.

  1. Download and start. Download HW Config and the STL block to the CPU 313C. Place the CPU in RUN. Open OB1 in the LAD/STL/FBD editor and switch to Monitor mode.
  2. Verify the clock bit. Confirm the checkbox in HW Config → CPU Properties → Cycle/Clock Memory is ticked. In the monitor, watch M100.5 toggle every 200 ms; M100.3 should toggle every 1 s.
  3. Verify the counter. Watch C1 increment on the clock edges. Confirm the value climbs toward 5 and resets on the threshold.
  4. Verify the edge flag. Watch M10.0 and M10.1. M10.0 should be FALSE except for the one cycle after the threshold is crossed; M10.1 should pulse TRUE for one cycle on that same boundary.
  5. Verify the comparison. Force a value into MW50 using a variable table (VAT). Set MW50 = 100, let the cycle run, then set MW50 = 200 just after a boundary. Q0.0 should pulse TRUE on the next boundary. Setting MW50 back to the previous value before the next boundary should leave Q0.0 FALSE.
  6. Verify the live input. Replace the VAT-forced value with the actual Modbus receive. Confirm the receive block updates MW50 at the expected rate (every cycle, every N cycles, or on a separate OB35 tick).

For long-term verification, write a trace buffer that records MW50, MW52, and Q0.0 on every edge of M10.1. The trace confirms the snapshot is being latched at the right interval and that the comparison reflects the real data stream. STEP 7's trace function (Tools → Traces) can be configured to trigger on M10.1 and record the three signals at 100 ms intervals for the duration of the test.

Troubleshooting Matrix

Symptom Likely cause Fix
Q0.0 never pulses Clock memory not enabled in HW Config; M100.5 is stuck at 0 Re-open CPU properties → Cycle/Clock Memory, tick the box, re-download HW Config
Q0.0 pulses every cycle Edge flag M10.0 not cleared; M10.1 stays TRUE Confirm the cycle reset (L 0 / T MB10) is inside the gated block; check for a second writer to MB10
Q0.0 pulses every 5 s regardless of value MW50 and MW52 are aliased; transfer happens before the compare Reorder: L MW50 / L MW52 / <>I first, then L MW50 / T MW52 second
Counter never reaches 5 Clock bit too slow; OB1 scan time longer than the clock period Use M100.3 (1 Hz) or reduce the threshold; verify the clock memory bit is updating
Counter overflows (BCD mode) Counter left in BCD format; binary range respected but BCD range is 0..999 Set C1 as a binary counter in the symbol table; CV of 5 is fine in both formats
MW50 is overwritten by the Modbus block Modbus master FB writes into the same data area as the snapshot Move the Modbus receive buffer into a separate DB; copy the value into MW50 at the start of OB1
Snapshot drifts over time Clock memory period is gated by OB1 scan time, not by a hardware timer If drift matters, drive the snapshot from OB35 (1000 ms) or from a hardware time-of-day interrupt
STL editor shows "address not valid" on C1 Counter not declared; or wrong area (S7-300 C area is shared with timer area at low numbers) Declare C1 in the symbol table as "Counter_5s" with address C1; confirm C1 is not already used as a timer at T1
Compiler warns "Area crossing" on MB10 T MB10 touches the edge flag M10.0; the warning is informational Accept the warning or move the edge flag to a dedicated byte (e.g. M20.0) and clear MB20 instead
Snapshot fires on power-up with garbage value MW52 is uninitialised on cold restart; the first comparison is against 0 Initialise MW52 in OB100 (warm restart) or in the startup section of OB1 to the current value of MW50

Operational Notes and Field Caveats

  • OB1 scan time matters. The clock memory bit toggles at a frequency that is set in HW Config, but the actual edge of M100.x is sampled at the top of each OB1 cycle. A 200 ms clock with a 150 ms OB1 scan can miss edges. For a 1 Hz clock the tolerance is 50 percent of the period, which is comfortable for most S7-300 scan times. If the scan time is variable or long, move the snapshot to OB35 (cyclic interrupt) or use an IEC timer (SFB 4 / SFB 5).
  • Counter area overlap with timer area on S7-300. The S7-300 has a single “system memory” area for counters and timers. Using C1 is safe as long as T1 is not also using the same physical word. Reserve C1 and T1 as separate symbols; the remote timer at T1 in the source application is a logical timer read via Modbus, not the local T1 of the CPU 313C, so there is no clash with the local C1 used in the routine.
  • Modbus register width. If the remote timer value is encoded as 32-bit (two consecutive Modbus registers), combine the words in the receive block with L MWxx / L MWxx+2 / T MDxx and use the double-word form of the loads in the snapshot routine.
  • Endianness. STEP 7 stores words in big-endian. Modbus is big-endian for the register payload but the byte order within a 16-bit register is device-specific. The receive block must byte-swap if the device is little-endian; the snapshot routine is then applied to the swapped word.
  • One-cycle pulse into a physical output. Wiring a 100 ms pulse to a relay on Q0.0 will not register on a mechanical load. If a sticky signal is needed downstream, latch Q0.0 into M12.0 on the rising edge of M10.1 and reset M12.0 on the falling edge or on a separate condition.
  • Cold restart initialisation. After a power cycle, MW52 is 0 and the first comparison on the first 5-second window will read “live != 0”. If the live timer also starts at 0, the first pulse will be suppressed; if the live timer starts at any non-zero value, the first pulse will fire. Initialise MW52 in OB100 or in the startup section of OB1 to suppress the cold-restart artefact.
  • STEP 7 version compatibility. The instructions used (A, CU, L, T, >=I, <>I, FP, JCN, SET, R, =) are all available from STEP 7 V3.x onwards. The same code compiles unchanged on STEP 7 V5.4, V5.5, and V5.6, and the binary MC7 code runs on the CPU 313C without version checks.

Which Siemens CPUs support this STL pattern as written?

The routine uses STL instructions available across the S7-300/400 family: A, CU, L, T, >=I, <>I, FP, JCN, SET, R, and the absolute bit/word/counter addressing. All of these are supported on the CPU 313C, CPU 314, CPU 315-2 DP, CPU 317-2, and the entire S7-400 range. S7-1200 and S7-1500 do not support STL; port the routine to SCL, LAD, or FBD on those platforms.

How do I change the snapshot interval from 5 s to a different value?

Edit the threshold in the comparison: L 5 for 5 s, L 10 for 10 s, L 60 for 60 s, and so on. The clock memory bit M100.3 toggles once per second when clock memory is enabled, so the threshold in seconds equals the number of edges the counter must accumulate. For sub-second windows use a faster clock bit (M100.4 = 500 ms, M100.5 = 200 ms) and scale the threshold accordingly: 10 for 5 s on M100.5, 2 for 1 s on M100.4.

Can I snapshot more than one process value in the same 5-second window?

Yes. Place a second transfer of the form L MW60 / T MW62 inside the gated block, using its own snapshot words (MW60 for live, MW62 for aged) and its own comparison. Each additional value adds two loads, one transfer, and one comparison; the cycle time impact is negligible. For ten or more values, copy the live values into a DB and use an index loop in SCL; in pure STL, a sequence of gated blocks is the most direct route.

What happens if the Modbus link drops and MW50 stops updating?

The snapshot routine will continue to run. MW50 will hold its last received value, and after the first 5-second boundary the snapshot in MW52 will match MW50 on every subsequent boundary, so Q0.0 will stop pulsing. To detect a stalled link, add a freshness check: a separate timer in the Modbus receive block that resets on every successful receive and sets a “link down” flag if the timer expires before the next receive. The snapshot routine is not the right place for that check; it is the responsibility of the communication layer.

Why is the cycle reset (counter and edge memory) inside the gated block?

Resetting the counter and the edge byte only when the threshold is reached keeps the counter in a known state for the next window. If the reset were outside the gate, the counter would be cleared on every cycle and would never reach the threshold; if it were unconditional after the gate, the edge flag would never be cleared and the next FP would not see a transition. The gated reset is the correct placement for a one-shot per window and ensures the loop restarts cleanly on every 5-second boundary.

Back to blog