Modifying ET of a Siemens S7-300 IEC_TIMER (TON) While It Is Running
When you need to inject a value into the elapsed-time word (ET) of a TON block on a Siemens S7-300 (for example a CPU 315F-2 PN/DP), a direct MOVE into the ET tag will appear to "not work": the block immediately overwrites it on the next scan because, in the IEC timer model, ET is an output of the timer function block rather than a writable variable. The remedy is to stop abusing the system block and build a small software timer out of a counter and a clock pulse. The counter's current value (CV) behaves like a writable ET, so the same staged-logic design (0–20 s, 20–40 s, 40–60 s) can be realised and you gain a deterministic preset.
1. Problem Definition
The requirement is concrete and common in process-cell interlocks:
- An FB contains a TON with a 60 s preset (
PT := T#60s). - Three logic branches must be active at distinct time windows:
- Logic A while
ETis in [0 s, 20 s) - Logic B while
ETis in [20 s, 40 s) - Logic C while
ETis in [40 s, 60 s]
- Logic A while
- When a digital trigger (e.g.
M0.0) is asserted, the elapsed-time equivalent must snap to 20 s and continue counting, regardless of the previous value.
What the original author observed is correct: the IEC_TIMER's ET is on the output side of the function-block interface (it is what the block publishes to the outside world). Even if you address the corresponding static variable inside the instance DB, the block's algorithm re-computes ET on every call, so the value you wrote will be replaced the next time the block executes.
TP_TIME, TON_TIME, TOF_TIME) in TIA Portal. The ET semantic is identical in both: read-only output.2. Why the Standard TON Refuses a Mid-Run Preset
The IEC 61131-3 TON contract, as implemented by Siemens, exposes four signals:
| Pin | Direction | Purpose |
|---|---|---|
| IN | Input | Start / enable; level-sensitive |
| PT | Input | Preset time (TIME, e.g. T#60s) |
| Q | Output | TRUE when ET ≥ PT |
| ET | Output | Elapsed time since IN rose; written by the block |
Inside the instance DB, ET is held as a TIME (DWORD, 32-bit, ms resolution). The block updates it on every call. A MOVE from your application into the same DWORD will be overwritten within the same OB1 cycle. The same restriction exists for TP and TOF. PT, by contrast, is an input — you can change it freely while IN is high, and the block's comparison logic honours the new value on the next scan. That is why trying to write to ET looks "frozen" but writing to PT works.
Two options follow from this:
- Build a custom timer whose internal accumulator is a counter or a writable DWORD. This is the recommended path because the timing resolution, scaling, and preset semantics are entirely under your control.
- Pre-load the instance DB byte that holds ET with a non-zero value, then call the SFB, and accept that the block will still add the elapsed ms of the last cycle on top. This is fragile, scan-time dependent, and officially unsupported — do not use it in F-CPUs (CPU 315F-2 PN/DP is a failsafe PLC and the F-runtime will flag an unexpected data change in the safety-relevant instance DB).
3. Architecture of the Software Timer
The replacement timer has three components:
- A tick generator (clock pulse) — typically 1 Hz, derived from a system clock byte or OB35.
- A writable accumulator — a CTU counter, an IEC counter (CTU_UD), or a plain DWORD/INT variable incremented on each tick.
- An overflow / PT comparator that sets the done bit (
Q) when the accumulator ≥ the desired PT.
Because the accumulator is a normal tag, a MOVE into it from anywhere (OB1, another FB, HMI) is honoured. That is the semantic you wanted from ET.
3.1 Tick Source Selection
| Source | Typical period | Where to configure | Notes |
|---|---|---|---|
| System clock byte (M area) | 10 ms / 100 ms / 200 ms / 500 ms / 1 s / 2 s / 5 s / 10 s | HW Config → CPU → Properties → "Clock memory", tick a byte (e.g. MB10) | Cheapest; eight periods from one byte. Bit n is period 2^n × base. |
| OB35 (cyclic interrupt) | 100 ms default, 1 ms – 60 s configurable | HW Config → CPU → Cyclic Interrupts → OB35 | Time-slice priority; tolerant of long OB1 cycles. |
| Programmed pulse generator (timer-pulse train) | User-defined | FB/OB1 | Use only if system clock is disabled or unavailable. |
| S7-300 hardware time (SFC 1 / SFC 0) | 1 ms | Application code | Read into ms DINT and increment; overkill for 1 s resolution. |
For a 1 s tick, enable the system clock byte and pick the period that corresponds to bit 0 at the chosen base. If the base is 100 ms, then bit 3 of the clock byte is 800 ms — wrong. Use the period selector: setting the byte to frequency 1.0 Hz gives you a 1 s tick on bit 7 (or whichever the dialog shows as 1.0 s). The simplest is to use a base of 1.0 s and read bit 0.
4. STEP 7 V5.x Implementation in STL
Create a new FB, e.g. FB100 "SW_Timer", with the following interface:
| Name | Type | Direction | Comment |
|---|---|---|---|
| IN_RUN | BOOL | Input | Run input (replaces TON's IN) |
| IN_PT_s | INT | Input | Preset time in seconds (1 s resolution) |
| IN_PRESET_EN | BOOL | Input | When TRUE, ET := IN_PRESET_s (replaces ET write) |
| IN_PRESET_s | INT | Input | Value to write into ET when IN_PRESET_EN is high |
| OUT_ET_s | INT | Output | Elapsed time, seconds (your custom ET) |
| OUT_Q | BOOL | Output | TRUE when ET ≥ PT |
| STAT_LAST_TICK | BOOL | Static | Edge memory for the 1 s tick |
| STAT_ET_INT | INT | Static | Internal accumulator |
STL body of FB100 (cycle = OB1, 1 s tick from clock byte MB10 bit 0):
// ----- NETWORK 1: Preset override (priority over run) -----
A #IN_PRESET_EN
JCN N1A
L 0
T #STAT_ET_INT // clear accumulator
L #IN_PRESET_s
T #STAT_ET_INT // load preset value
JU N1B
N1A: NOP 0
// ----- NETWORK 2: Rising edge of 1 s tick -----
A M10.0 // 1 Hz system clock bit
FP #STAT_LAST_TICK
= #STAT_ET_INT.DBX0 // RLO true for one cycle on the 1 s edge
N1B: NOP 0
// ----- NETWORK 3: Increment accumulator on the 1 s edge -----
A M10.0
FP #STAT_LAST_TICK
JCN N3A
L #STAT_ET_INT
L 1
+D
T #STAT_ET_INT
N3A: NOP 0
// ----- NETWORK 4: Run gating -----
A #IN_RUN
JCN N4A // when IN_RUN = 0, freeze the accumulator
L 0
T #STAT_ET_INT
JU N4B
N4A: NOP 0
// ----- NETWORK 5: Saturate at preset -----
L #STAT_ET_INT
L #IN_PT_s
>I
JCN N5A
L #IN_PT_s
T #STAT_ET_INT
N5A: NOP 0
// ----- NETWORK 6: Outputs -----
L #STAT_ET_INT
T #OUT_ET_s
L #STAT_ET_INT
L #IN_PT_s
>=
= #OUT_Q
Key points in the STL:
- Network 1 unconditionally writes the preset value into
STAT_ET_INTwhenIN_PRESET_ENis true, before the 1 s tick is processed in the same cycle. The next increment will move it to 21 s on the following edge. - Network 3 uses an FP (rising-edge) detector. Without the edge, the counter would advance by 1 for every OB1 cycle (typically 5 – 50 ms), saturating the timer in microseconds.
- Network 4 demonstrates a freezing behaviour. If you want the timer to keep running while IN = 0, delete N4A and the unconditional reset, or move the freeze into a separate input
IN_HOLD. - Network 5 clamps the accumulator to PT so the integer does not roll over if you never assert
OUT_Q-driven stop logic. Replace the > with a saturatingITD / LIMITif you extend to TIME/DINT.
5. STEP 7 V5.x Implementation in LAD/FBD
If you prefer LAD/FBD, the equivalent is short enough to fit in one network per function:
- Place a CTU (IEC counter, from "Bit logic → Counter") inside the FB.
CU= M10.0 (1 s tick).R= NOT(IN_RUN).PV=IN_PT_s.CV=STAT_ET_INT.Q=OUT_Q. - To "write to ET": add a MOVE from
IN_PRESET_sintoSTAT_ET_INT, gated byIN_PRESET_EN. Place it before the CTU network. Because the counter is reset every cycle byR(network order matters in LAD — the MOVE network must execute first), the next edge will see the preset as the starting point. - For the three-stage branching, add three comparators:
// Logic A: 0 ≤ ET < 20 L #STAT_ET_INT L 0 >=I A( ) L #STAT_ET_INT L 20 <I = #LogicA // Logic B: 20 ≤ ET < 40 L #STAT_ET_INT L 20 >=I A( ) L #STAT_ET_INT L 40 <I = #LogicB // Logic C: 40 ≤ ET ≤ PT L #STAT_ET_INT L 40 >=I A( ) L #STAT_ET_INT L #IN_PT_s <= = #LogicC
6. Structured Text Variant (Portable Across STEP 7 V5.x and TIA Portal)
If the project is migrated to TIA Portal later, porting STL is painful. ST survives. The same FB, in SCL, looks like this:
FUNCTION_BLOCK SW_Timer
VAR_INPUT
IN_RUN : BOOL;
IN_PT_s : INT;
IN_PRESET_EN : BOOL;
IN_PRESET_s : INT;
END_VAR
VAR_OUTPUT
OUT_ET_s : INT;
OUT_Q : BOOL;
END_VAR
VAR
Tick1Hz : BOOL; // wired to MB10.0 in OB1
Tick1Hz_Old : BOOL;
Accumulator_s : INT;
END_VAR
BEGIN
// Preset has priority over the tick
IF IN_PRESET_EN THEN
Accumulator_s := LIMIT(0, IN_PRESET_s, IN_PT_s);
ELSIF NOT IN_RUN THEN
Accumulator_s := 0;
ELSIF Tick1Hz AND NOT Tick1Hz_Old THEN
Accumulator_s := LIMIT(0, Accumulator_s + 1, IN_PT_s);
END_IF;
Tick1Hz_Old := Tick1Hz;
OUT_ET_s := Accumulator_s;
OUT_Q := Accumulator_s >= IN_PT_s;
END_FUNCTION_BLOCK
Tick1Hz AND NOT Tick1Hz_Old) is the IEC 61131-3 equivalent of the R_TRIG function block. The previous-cycle Tick1Hz_Old must live in the static / instance area, not in TEMP, otherwise it is lost on every cycle and the timer increments by one each OB1 scan.7. F-CPU Specific Constraints (CPU 315F-2 PN/DP)
Because the target is a CPU 315F-2 PN/DP (6ES7315-2FH14-0AB0 or 6ES7315-2EH14-0AB0), the F-runtime imposes additional rules:
- Place the software timer outside the F-runtime group (F-FB, F-CB). Mixing standard FBs and F-blocks in the same runtime group is allowed, but the custom timer must not be classified as a safety block, or the F-signature check will treat its non-safety writes as a violation. The standard pattern is to instantiate
SW_Timerin OB1 (or a non-F cyclic OB) and feed only its boolean outputs to the F-programme via the standard → safety gateway. - Do not put the
IN_PRESET_ENorIN_PRESET_stags inside an F-DB. The F-CPU's "modified data" check may flag the runtime write as an integrity violation. Keep preset triggers in the standard process image (e.g.Mor DB from the standard project). - Use the failsafe OB35 (if configured) for time-tick generation only after verifying that your F-programme does not depend on a strict OB1-only call pattern. The F-library
F_ApplicationBlocksv6.x is documented to tolerate OB35-sourced ticks.
8. Step-by-Step Wiring Procedure
- Open HW Config → double-click the CPU 315F-2 PN/DP → tab "Cycle/Clock Memory".
- Tick "Clock memory". Pick a free byte, e.g. MB10. Pick a frequency, e.g. 1.0 s. Click OK and save/compile HW Config.
- Compile the S7 program and download HW Config to the PLC.
- Verify in Monitor / Modify that
M10.0blinks once per second. - Create the FB
SW_Timer(SCL or STL) from §4 or §6. - Create a DB
DB100 "Timer_Inst"with a single instance ofSW_Timer. - In OB1, call the instance:
CALL "Timer_Inst", DB100 IN_RUN := "Start_Button" IN_PT_s := 60 IN_PRESET_EN := "Preset20s_Trigger" IN_PRESET_s := 20 OUT_ET_s := MW200 OUT_Q := M202.0 - Wire the three comparators of §5 to
MW200for stages A, B, C. - Download all blocks to the CPU.
- Go online and use Monitor to watch
MW200climb from 0 → 60 in 1 s steps.
9. Verification
| Test | Procedure | Expected result |
|---|---|---|
| Free run | Set Start_Button = 1, leave Preset20s_Trigger = 0 |
MW200 rises 0, 1, 2, …, 60 s in 1 s steps; M202.0 goes true at 60 s and stays true; LogicA/B/C cycle A → B → C in the right order |
| Preset during run | Set Start_Button = 1, watch MW200 reach 5 s, then pulse Preset20s_Trigger for one cycle |
MW200 jumps to 20 s in the same OB1 cycle, then continues 21, 22, … |
| Preset at PT | Let MW200 reach 60, pulse Preset20s_Trigger
|
MW200 = 20; if Start_Button is still 1, it climbs again from 21 |
| Run off → freeze | Set Start_Button = 0 mid-run |
MW200 holds its last value; no Q |
| Reset | Toggle Start_Button 0 → 1 |
MW200 clears to 0 and starts a new 60 s cycle |
| F-CPU integrity | Force Preset20s_Trigger repeatedly from the HMI |
No F-stop; no diagnostic buffer entry "Data inconsistency in F-DB" |
10. Edge Cases and Caveats
- OB1 scan > 1 s: with a heavily loaded CPU, the system clock byte can skip a tick. Switch the tick source to OB35 (period 1 000 ms) for guaranteed timing regardless of OB1 duration.
-
Power cycle: the static
STAT_ET_INTresides in the instance DB; a cold restart of the CPU resets it to 0 (initial value in the DB). If you need the timer to survive a power dip, mark the DB as non-retentive explicitly and use a separate retentive DWORD as the accumulator. -
Multiple instances of the same FB: each DB has its own static area, so the FP-edge memory is per-instance. Do not share the
STAT_LAST_TICKacross instances. - Time-tick resolution finer than 1 s: replace the INT with a DINT in milliseconds. Drive the CTU from a 100 ms clock bit or from OB35 with period 100 ms. Multiply the comparison thresholds accordingly.
-
Negative preset: protect with
LIMIT(0, IN_PRESET_s, IN_PT_s)to avoid a wrap-around that could trip the comparators. -
S5_TIMER alternative (deprecated): the legacy S5 timer (e.g.
SE,SV,SA,SS,SF) stores its time as a BCD-encoded word in a separate timer word area (T0…T511). That word is technically writable, but Siemens explicitly recommends against modifying it externally; on F-CPUs the F-runtime can detect a "time word modified during run" and raise a diagnostic. The IEC_TIMER / counter approach is the only supported path on a CPU 315F-2 PN/DP.
11. TIA Portal Migration Notes
If the project is later opened in TIA Portal (V16 or newer):
- The SCL source from §6 compiles without changes — copy the FUNCTION_BLOCK into an SCL source file in the TIA Portal project.
- Replace
M10.0with the TIA Portal clock bit configuration (Device Configuration → CPU → Properties → "System and clock memory" → enable "Clock memory", set MB to 10, frequency 1.0 Hz). - The IEC_TIMER data type is available in TIA Portal as
IEC_TIMER/TIME. The semantic of ET is unchanged: still read-only. The custom FBSW_Timerremains the correct replacement.
12. Comparison: Standard IEC_TIMER vs Software Timer
| Property | IEC_TIMER (SFB4 / TON_TIME) | Custom SW_Timer (FB with CTU) |
|---|---|---|
| ET writable mid-run | No (output only) | Yes (MOVE into CV or STAT_ET_INT) |
| Resolution | 1 ms (TIME data type) | User-defined (tick frequency) |
| Multi-instance friendly | Yes (instance DB) | Yes (instance DB) |
| F-CPU compatible | Yes (with F-library) | Yes (if outside F-runtime group) |
| Standalone preset trigger | No (write to PT works only when IN = 0) | Yes, priority-based |
| Internal counter overhead | None | 2 – 4 bytes per instance |
13. Diagnostic Checklist if the Preset "Does Not Work"
- Open the FB instance DB online and confirm that
STAT_ET_INTactually changes immediately after you pulse the preset input. If it does but the outputOUT_ET_sdoes not, the network order in the FB is wrong — the comparator network is reading the value before the MOVE network writes it. Reorder networks. - If the DB value flickers back to the old ET on the next cycle, you are still calling SFB4 / IEC_TIMER and the system block is overwriting the variable. Search the program for the FBs/FCs named "TON" or "IEC_TIMER_DB" instances and remove them.
- Confirm that the system clock byte is enabled. In Monitor, force
M10.0to TRUE in a one-second pattern. If it never toggles, the clock memory has not been downloaded — re-download HW Config. - If the counter increments by more than 1 per second, the edge detector is missing. With an FP on M10.0 in the same network as the CTU, an OB1 scan of 50 ms would add 50 ms × 20 = 1 s of wrong accumulation if you used an unconditional A M10.0 instead of FP. The FP is the fix.
- For the F-CPU, check the diagnostic buffer (CPU → Information → Diagnostic Buffer) for entries mentioning F-runtime data inconsistencies after the preset. If present, the software timer is being called from inside the F-programme — move the call to OB1.
14. Related Building Blocks in the S7-300 Library
- SFB3 (TP) — pulse timer. Same ET semantic, same restriction.
- SFB5 (TOF) — off-delay. Same ET semantic.
- SFC 1 (READ_CLK) and SFC 0 (SET_CLK) — for wall-clock time stamping if the 1 s tick is not adequate.
- SFC 64 (TIME_TCK) — 32-bit ms tick counter; useful if you want ms resolution without system clock bits.
For full reference of the IEC timer SFBs, see the System Software for S7-300/400 System and Standard Functions manual in the Siemens Online Support: S7-300 CPU 31xC and CPU 31x manual (entry ID 109751586) and the SFB4 / TON block description (entry ID 44240604). For the F-variant of the CPU, see S7-300 F-CPU manual (entry ID 109751608).
Why does a MOVE into the ET of an IEC_TIMER have no effect?
ET is a function-block output. The block recomputes ET on every call and overwrites the storage location before your user code can read it back. To get a writable elapsed-time, replace the IEC_TIMER with a software timer built around a CTU counter or a static DWORD incremented by a clock pulse.
Can I simply change PT mid-run on a TON to get a similar effect?
Yes, but only when IN = 0. PT is a function-block input and is honoured on the next scan. If IN is already TRUE, the TON continues with the original PT until IN goes low. That is usually not what people want, and it does not let you "jump the ET to 20 s" while the timer is actively running.
Which clock source is best on the CPU 315F-2 PN/DP?
For 1 s resolution, the system clock byte is the simplest. Enable it in HW Config (CPU → Properties → Cycle/Clock Memory), pick a free MB (e.g. MB10) and frequency 1.0 Hz. For higher accuracy under heavy OB1 load, use OB35 with a 1 000 ms period and set the tick inside the OB35 body.
Is it safe to use this pattern inside the F-programme of the CPU 315F-2 PN/DP?
No. The F-runtime treats non-F writes to F-DBs as a possible integrity violation. Keep the custom timer FB outside the F-runtime group (call it from OB1 or a non-F cyclic OB) and route its boolean outputs to the F-programme through the standard → safety gateway.
Does the S5 timer (T0…T511) allow direct modification of the time word?
Technically yes — the time word is BCD-encoded and sits in the timer word area. Siemens, however, explicitly discourages external modification, and the F-runtime can flag a "time word modified during run" diagnostic. The supported path on the S7-300 platform is the IEC_TIMER / custom counter approach described in this article.