Problem Overview
When programming a multi-instance Function Block (FB) in STEP 7 Classic (S7-300/S7-400), engineers frequently encounter a fault where the preset time PT of a TON (on-delay timer) declared as a static variable of type TON, TP, or TOF cannot be assigned in the LAD/FBD editor. Typical symptoms include:
- The MOVE box on the timer's
PTinput is rejected by the compiler with "Type check error" or "Invalid data type". - The
PTinput on the CALL coil shows as a missing connection and the block will not compile to the PLC. - Modifying the static instance
#STAT0.PTdirectly succeeds in STL but is flagged as bad practice and may behave inconsistently in LAD/FBD. - The
PTvalue appears correctly in the instance DB after compilation, but the timer does not produce the expectedQpulse because the time was never latched into the timer's working area before the firstCALL.
This is one of the most common compile-time errors encountered when converting a single-instance TON program to a reusable multi-instance FB architecture.
Root Cause Analysis
The root cause is a violation of the FB encapsulation rule established by STEP 7 since V5.x. The rules are:
- Static data belonging to an instance (declared in the
STATsection of an FB) is private to that FB. It may be read and written only by the code body of the FB that owns the instance. - All input, output, and in-out data of an FB must be exchanged through its formal parameter interface (
IN,OUT,IN_OUT,STAT,TEMP) using the parameter syntax of theCALLinstruction. - Direct symbolic access to a static component from outside the FB (e.g., from OB1 or from another FB's body) is treated as a compile-time error when type checking is enabled.
When you declare a multi-instance timer in the STAT section:
VAR
STAT0 : TON; // multi-instance timer
STAT1 : TP; // pulse timer
STAT2 : TOF; // off-delay timer
END_VAR
…you must drive STAT0.IN, STAT0.PT, and read STAT0.Q/STAT0.ET through the FB's CALL interface, not by addressing the components directly. The LAD/FBD editor enforces this rule and refuses to wire a MOVE box to #STAT0.PT in the parent FB's network unless type checking is suppressed.
Why the error is intentional
Allowing direct writes to STAT0.PT from outside the FB breaks several design guarantees:
-
Reentrancy: Multi-instance FBs share code but each call uses its own instance data. External code that writes
STAT0.PTmay collide with the FB's internal scheduling. - Maintainability: An outside caller can corrupt timer state without the FB knowing, producing intermittent faults that are extremely difficult to trace.
- Reusability: Encapsulation lets the same FB be copied between projects without dragging hidden dependencies on the parent's instance DB layout.
Affected Platforms and Firmware
| Controller family | STEP 7 version | Editor | Behavior |
|---|---|---|---|
| S7-300 (CPU 312C–319F) | V5.4 / V5.5 / V5.6 | LAD/FBD/STL | Type check active by default; compile error in LAD/FBD |
| S7-400 (CPU 412–417) | V5.4 / V5.5 / V5.6 | LAD/FBD/STL | Same as S7-300 |
| WinAC RTX | V5.4 / V5.5 | LAD/FBD/STL | Same as S7-300 |
| S7-1200 (TIA Portal V13–V20) | TIA Portal | LAD/FBD/SCL | Multi-instance IEC timers; same encapsulation rules, but SCL is the preferred text language |
| S7-1500 (TIA Portal V13–V20) | TIA Portal | LAD/FBD/SCL | Multi-instance IEC timers; optimized DB access for IEC timers in firmware ≥ V2.0 |
TON instruction is documented in the TIA Portal help under "Timer operations S7-1200/S7-1500 / TON: Generate on-delay". The conceptual rule — drive IN and PT through the formal call interface, not by writing to the instance components directly — is unchanged. See the official Siemens reference at TON: Generate on-delay (S7-1200, S7-1500) - TIA Portal V20.Solution 1 — Pass PT Through the FB Interface (Recommended)
Declare PT as a formal IN parameter on the multi-instance FB and assign the time constant at the call site. This is the engineering best practice and the only method that compiles cleanly in LAD, FBD, and STL with type checking enabled.
FB interface (declaration view)
VAR_INPUT
Start : BOOL; // trigger input
Duration : TIME; // preset time
END_VAR
VAR_OUTPUT
Running : BOOL; // Q of TON
Elapsed : TIME; // ET of TON
END_VAR
VAR
STAT0 : TON; // multi-instance timer
END_VAR
BEGIN
STAT0(IN := Start, PT := Duration, Q => Running, ET => Elapsed);
END_FUNCTION_BLOCK
Calling OB1 / parent FB
CALL FB_TimerControl
Start := I0.0
Duration := T#500ms
Running := Q0.0
Elapsed := MW10
Or, using the IEC-style parameter call supported in SCL and modern LAD/FBD editors:
%FB1_DB(
Start := I0.0,
Duration := T#500ms,
Running => Q0.0,
Elapsed => MW10
);
Why this works
The CALL instruction copies the value of Duration into the PT field of the instance owned by FB_TimerControl before the timer logic executes. The timer body itself reads the new PT on the next scan, so the Q output is produced after exactly the requested interval. Because the FB declares Duration as an IN, external code cannot accidentally overwrite STAT0.PT.
Solution 2 — STL Direct Assignment (Legacy Workaround)
STEP 7 STL does not enforce the LAD/FBD type-check restriction, so you can write directly to the static instance:
A I 0.0
= #STAT0.IN
L T#500ms
T #STAT0.PT
CALL #STAT0
IN:=
PT:=
Q :=
ET:=
This compiles, downloads, and runs. However, it is considered legacy code and is explicitly discouraged in modern Siemens programming style guides. Use it only when:
- You are maintaining an existing project and cannot change the FB interface.
- You are running an older CPU with firmware that does not fully support formal parameter CALL in the desired editor.
- You need to test timer behavior in isolation before adding the FB interface.
Solution 3 — Intermediate TIME Variable in TEMP
To stay in LAD/FBD without disabling the type check, declare a TEMP variable of type TIME and use it as a buffer:
VAR_TEMP
tmp_pt : TIME;
END_VAR
BEGIN
tmp_pt := T#500ms; // MOVE box accepts TIME constant in LAD/FBD
CALL #STAT0
IN := I0.0
PT := tmp_pt
Q => M0.0
ET => MW2
END_FUNCTION_BLOCK
The compiler accepts tmp_pt as a TIME, so the formal parameter wire is type-correct. This is the cleanest "stay in graphical editor" workaround.
Solution 4 — Initialize PT via the Instance DB (VAT)
If the PT value is constant and known at commissioning time, write it once into the instance DB using the Variable Table (VAT) tool:
- Open the multi-instance FB's instance DB in STEP 7.
- Open Monitor / Modify > Variable Table (VAT).
- Enter the path:
DB100.DBX[STAT0.PT offset]or, symbolically,"DB_Timer".STAT0.PT. - Type the value:
T#500ms(orT#1m30s, etc.). - Click Modify to write the constant into the loaded DB.
The timer then runs with that preset on every subsequent call. This is the only solution that does not require modifying the FB source — useful when the FB is in a protected library block (know-how protected, for example) and you cannot change its code.
Solution 5 — Disable Type Check (Last Resort)
- Open the LAD/FBD editor.
- Menu: Options > Customize > LAD/FBD.
- Uncheck "Type check of addresses".
- Click OK and recompile.
After this, you can wire a MOVE box directly to #STAT0.PT in the parent FB. Re-enable the type check once the prototype is complete, then refactor the code into one of the solutions above.
Multi-Instance FB Concept (Background)
A multi-instance FB is an FB that contains another FB (or IEC timer) as a static component. STEP 7 supports this to allow code reuse without creating a separate instance DB for every reusable block. The key rules:
| Property | Single instance | Multi-instance |
|---|---|---|
| Instance DB | Own DB, one per call site | Shares the parent FB's instance DB |
| Data layout | Root of the instance | Nested in the parent's static area |
| Variable name in caller | DB number (e.g., DB100) |
Symbolic static name (e.g., STAT0) |
| Call syntax | CALL FB 100 , DB100 |
CALL #STAT0 |
| Encapsulation | Same — components are private to FB | Same — components are private to FB |
Declaration example for a multi-instance IEC timer
FUNCTION_BLOCK FB_TimerControl
VAR
STAT0 : TON; // on-delay, multi-instance
STAT1 : TP; // pulse, multi-instance
STAT2 : TOF; // off-delay, multi-instance
STAT3 : TONR; // retentive on-delay (S7-1500 only)
END_VAR
Instance DB Layout
When a multi-instance FB is called, its instance DB is laid out with a fixed offset for each static component. For an S7-300 with the declaration above, the offsets (in bytes) of the timer fields are:
| Offset (byte) | Symbol | Type | Width (bytes) |
|---|---|---|---|
| 0.0 | STAT0.IN | BOOL | 1 |
| 2.0 | STAT0.PT | TIME | 4 |
| 6.0 | STAT0.Q | BOOL | 1 |
| 8.0 | STAT0.ET | TIME | 4 |
| 12.0 | STAT1.IN | BOOL | 1 |
| 14.0 | STAT1.PT | TIME | 4 |
| 18.0 | STAT1.Q | BOOL | 1 |
| 20.0 | STAT1.ET | TIME | 4 |
| 24.0 | STAT2.IN / PT / Q / ET | ... | 12 |
Offsets are calculated by STEP 7 at compile time. Do not hard-code them; always use symbolic addresses (#STAT0.PT) so that re-declaration does not break the offsets.
Verification and Diagnostics
After applying one of the solutions above, validate the timer behavior in the following order:
- Compile check. In LAD/FBD, ensure no red boxes remain. In STL, ensure the compiler reports no warnings for instance access.
-
Download to PLC. Use PLC > Download with the instance DB included. A partial download that omits the DB will not pick up new
PTdefaults. -
Online monitor. Open the instance DB in Monitor/Modify and watch
STAT0.IN,STAT0.PT,STAT0.Q, andSTAT0.ETlive. -
Functional test. Set
I0.0 = TRUEand verify thatQ0.0transitions toTRUEafter exactly the requestedPT. Use a stopwatch or the PLC's scan-time stamping to confirm. -
Edge cases:
- Set
PT := T#0ms— the timer should passQimmediately on the next scan. - Toggle
INrapidly —ETshould reset toT#0mson each falling edge. - Power-cycle the CPU — the timer state in the instance DB is volatile unless you have declared it retentive (S7-1500 only); expect
ETto start at zero.
- Set
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Compile error "Data type not allowed" on PT | LAD/FBD type check is on and PT is being wired directly | Use Solution 1, 3, or 5 |
| Timer never reaches Q | PT written after CALL, or PT=0 | Move PT assignment above CALL; verify T# value |
| Q flickers after exactly one scan | PT is set inside the timer's own body each call, so the time never accumulates | Keep PT as a constant or FB input; do not reassign every cycle |
| ET does not count up | Wrong instance is being monitored (e.g., DB101 vs DB102) | Verify the instance DB number matches the call site |
| Compile warning "Instance component accessed externally" | STL code reads/writes STAT outside the FB body | Refactor into Solution 1 (parameter interface) |
| T# constant not accepted in MOVE | Source operand is a different data type (INT, WORD) | Cast via TIME intermediate or use L T#500ms / T MWxx in STL |
Related Timer Operations
| IEC type | Behavior | Fields | Notes |
|---|---|---|---|
| TON (IEC) | Delays rising edge of Q by PT | IN, PT, Q, ET | ET reset on falling IN |
| TOF (IEC) | Delays falling edge of Q by PT | IN, PT, Q, ET | ET reset on rising IN |
| TP (IEC) | Pulse of width PT on rising IN | IN, PT, Q, ET | Ignores IN changes during pulse |
| TONR (IEC, S7-1500) | Accumulates ET, reset by RES input | IN, PT, RES, Q, ET | Retentive across power-cycle if DB is retentive |
| S_PULSE / S_PEXT / S_ODT / S_ODTS / S_OFFDT (legacy) | Classic SIMATIC timers | S, TV, R, BCD/DW, Q, BI/DE | Stored in system memory areas; not multi-instanceable |
All five IEC timer types follow the same encapsulation rule described in this article. The same parameter-passing pattern applies to TP, TOF, and TONR identically.
Migration Notes: S7-300/400 to S7-1200/1500 (TIA Portal)
If you are porting the multi-instance FB to a TIA Portal project, the rules remain the same. Two practical differences:
- SCL is the preferred text language for multi-instance work in TIA Portal. The IEC call syntax is more compact and avoids the early L/T dance required in STEP 7 STL.
-
Optimized block access (default for new S7-1500 DBs) means you cannot rely on fixed byte offsets for
PTinitialization in a VAT. Use symbolic names only.
Reference: TON: Generate on-delay (S7-1200, S7-1500) — TIA Portal V20 documentation.
Frequently Asked Questions
Why does the LAD editor reject a MOVE box wired to the PT of a multi-instance TON?
Because the LAD/FBD type checker treats STAT components of an FB as private. You must pass PT through the FB's formal input interface (Solution 1) or use a TEMP TIME buffer (Solution 3). Direct symbolic writes to #STAT0.PT are reserved for STL.
Can I assign T#500ms directly to PT in a multi-instance FB on S7-300/400?
Not from a graphical editor with type checking enabled. Declare Duration : TIME as a VAR_INPUT, wire the constant to it at the call site, and pass Duration into the timer's PT via the formal CALL interface.
Is direct STL access to #STAT0.PT safe in production code?
It works, but Siemens explicitly discourages it because it breaks encapsulation, hurts reusability, and produces faults that are hard to trace. Use it only for legacy maintenance or quick prototypes, and refactor to the parameter interface before commissioning.
How do I initialize PT to a fixed value without recompiling the FB?
Open the instance DB in the Variable Table (VAT) and write the desired TIME constant to the symbolic address "DB_name".STAT0.PT. The value is latched into the DB and used on the next call. This is the only way to change PT for a know-how-protected FB that you cannot edit.
Does the same rule apply to TP, TOF, and TONR multi-instance timers?
Yes. All IEC timer types (TON, TOF, TP, and TONR on S7-1500) declared in the STAT section of an FB are private. The CALL interface and the same five solutions in this article apply to every one of them.