Problem Details
A STEP 7 classic (S7-300/S7-400) Function (FC) compiled with STEP 7 V5.x or TIA Portal V13+ suddenly changes behavior when a single new local variable is added in the block interface. In S7-PLCSIM, and frequently on the real CPU, an FP (positive edge) instruction in the FC stops generating true edges. Counters and flags (count1, count2, chiller, priority, temp1, pulse1, temp2, pulse2) declared under the block's TEMP interface appear to be overwritten between calls, so the FC increments DB70.DBW12 every cycle instead of once per S5T pulse.
The signature of the bug is unmistakable:
- The FC works correctly with the original TEMP layout.
- Adding any new TEMP variable shifts the rest of the local-data declarations upward in byte offset.
- The
FPdetection on#pulse1(or#pulse2) fires on every scan; downstreamJCconditional jumps therefore trigger every cycle. - If the same FC is invoked from multiple points in OB1, or if any other block of the OB cycle is restructured, the symptom appears spontaneously — without any code change to the FC.
- Replacing the TEMP with a global Merker (e.g.,
M 20.3) restores correct single-pulse behavior.
This class of defect appears in many STEP 7 projects and is documented in the official Siemens training curriculum under "Handling Local Data." It is the most common reason an FP/FN-driven counter ramps up "out of nowhere" after a FC is extended.
Root Cause Analysis
TEMP variables in S7-300/S7-400 STEP 7 are not persistent storage. They exist only for the duration of a single call chain and live in the CPU's Local Data Stack (L-Stack). The L-Stack is a per-priority-class scratch area that the operating system hands out to OB/FC/FB calls. Once the called block returns (or an intermediate block at a higher call depth overwrites the same bytes), the bytes previously associated with your TEMP are not guaranteed to contain the value the FC last wrote.
The positive-edge instruction FP <bit> requires the operand bit to remember the prior RLO state of the same point from the previous scan of this exact signal. STEP 7 implements that "remembered bit" as a normal operand reference — it is not a hidden register. The instruction works in OBs and FBs because OBs always run with a fresh stack frame and FBs own a STAT interface allocated in an Instance DB (referenced by DI/AR2) that survives across scans.
When #pulse1 is declared as TEMP, the following sequence unfolds on each OB1 scan:
- OB1 begins, the L-Stack pointer is positioned.
- Your FC is called;
FP #pulse1reads byte 8.1 of the current L-frame, compares it to the new RLO ofT 11, and writes the new value back to byte 8.1 of this frame. - The FC returns.
- OB1 (or another block in the priority class) reuses the same physical L-Stack region for other blocks called later in the cycle.
- OB1 wraps back to the top; on re-entry into your FC, byte 8.1 of the new L-frame is undefined — possibly zero, possibly the value some other block just left behind.
If the byte happens to come back as 0 when the new RLO is 1, FP fires a "positive edge" again — every single scan. The user's symptom of "#pulse1 is somehow written to 1 every scan" is exactly this: at the moment of the FP comparison, the prior-state bit looks like 0 to the instruction.
L-Stack Allocation Mechanics
The S7-300/S7-400 CPU firmware allocates the L-Stack as a single contiguous memory area for each OB priority class (OB1 = priority class 1 by default). When a block is called, the FC/FB pops a frame from the L-Stack and uses it for:
- TEMP variables of the called block
- TEMP variables created implicitly by STEP 7 to manage transition conditions, edge memories, and P#-style indirect parameters
- The block's local call interface (return address, selector references)
The total L-Stack size is configured in the CPU properties under "Memory". For S7-300 CPUs it is typically fixed (e.g., CPU 315-2 DP has 2048 bytes per priority class); for S7-400 CPUs it can be expanded up to the maximum local-data work memory in HW Config.
STEP 7 lays out your declared TEMP variables in the order they appear in the interface editor, starting at byte 0.0 of the block's L-frame. The compiler does not necessarily map them to "byte 8.1" the way the editor displays them; the editor numbers are symbolic offsets within the L-frame used at compile time. After a code change, the offsets may shift because:
- A new TEMP variable is inserted — the compiler reorders the local frame to satisfy alignment rules.
- STEP 7's optimizer decides differently which auxiliary variables are needed for edge/transition generation and reallocates the implicit temporaries.
- Another FC/FB with overlapping L-frame usage is called earlier or later in OB1, leaving residues.
With a small project, the L-Stack residue at byte 8.1 of the FC's frame often coincidentally equals 0 between OB1 calls — so the FC "works for years." Re-compiling or extending any block in the call tree perturbs the residue and the bug appears from a clear-blue sky.
Why FP Cannot Use TEMP
The STEP 7 reference manual "SIMATIC Programming with STEP 7" and the Ladder Logic (LAD/FBD/STL) reference describe FP as: "FP detects a positive-going edge on the result of logic operation (RLO) and saves the previous state of the RLO in a specified operand bit." The "specified operand" must be a bit-addressable location that survives across scans.
Valid operand locations for FP/FN are:
| Operand | Suitable for FP/FN? | Reason |
|---|---|---|
I, IB, IW, ID (Process Inputs) |
Yes, but input-image refresh can change them | Real-world inputs are valid edge references; however some are latched by the PAE refresh. |
Q / M / global DB bits (DBX) |
Yes | Retained in bit memory or instance data blocks; safe. |
L (Local / TEMP) bits |
NO | L-stack frame does not survive between scans of OB1 unless the FC call is strictly recursive and unchanged, which is not the case in structured STEP 7 code. |
STATIC of an FB |
Yes | Stored in the Instance DB (DI); the FB owns a private data block that retains state. |
The Siemens S7-300 Module Reference (6ES7 instruction set) and the S7-300 Programming Manual (entry ID 1533245) are explicit that TEMP data is not backed up and is initialized by STEP 7 to undefined values on every block call. Any reliance on a TEMP "remembering" state is non-conforming.
Verification of the Defect in STEP 7
Reproduce the fault in S7-PLCSIM as follows:
- Open the project, go online with PLCSIM (via TCP/IP or MPI adapter).
- Open the FC with the temporary interface and force
I0.0 = TRUE. - Insert a Monitor/Modify window for
M20.1(pulse fromSE T11/ S5T#5S),DB70.DBW12(counter being incremented), and the TEMP variablecount1at offset 0.0 /pulse1at offset 8.1. - Set
Monitorwith default trigger conditions. - Activate Modify, press "Monitor (Continuously)".
Expected (broken) observation: DB70.DBW12 increments on every OB1 cycle, not every 5 s. The diagnostic indicates the FP inside your FC is firing on every call. If you temporarily change the FP operand to an M-bit (e.g., FP M 20.3) and recompile, the increment rate returns to one tick per 5-second S5T pulse.
Solution: Use STAT in an FB
The correct architecture is to convert the block into a Function Block (FB) and move every variable that needs to persist (edge memories, counters, latch bits) into the STAT interface. STEP 7 allocates a dedicated Instance Data Block (DI) for the FB, and every STAT variable occupies a fixed address inside the DI that the CPU preserves across scans.
Procedure:
- Rename
FC1toFB100using the menu Edit > Rename in the STEP 7 SIMATIC Manager, or right-click the block in TIA Portal and choose "Change block type." - Open the interface editor. Under STAT, declare:
-
pulse1: BOOL -
pulse2: BOOL -
chiller_pulse: BOOL - any other edge memory or retention bit
-
- Leave TEMP reserved only for true workspace values (loop indices, scratch INTs that do not need persistence).
- Generate an Instance DB for the FB (e.g.,
DB100) that defaults the STAT variables to FALSE / 0. - Replace every
#pulse1call site with the FB instance's STAT (the symbol would be"FB100"static name"pulse1"; from STL it isDB100.DBX0.0or, symbolically,FB100.pulse1).
Re-compile, download the OB/FB/DB trio to PLCSIM, run the test sequence again. The counter now increments exactly once every 5 s.
Solution: Global Merker / DB Bit
If the FC must remain an FC (no encapsulation of state), allocate dedicated global memory:
-
Merker (M) bits — e.g., a user-defined Merker word that the FC reads/writes via IN_OUT parameters. Reserve a range outside the system Merkers (
MB0..MB255; avoid overlap with clock/status bytes). -
Global DB bit — declare a non-optimized DB (e.g.,
DB99 "edge_mems") with retentive bits for each FP/FN operand. -
IN_OUT reference — pass a
POINTERorANYto a static location. Useful for multi-instance code where the caller owns the storage.
This matches the original workaround in the user's project: replacing FP #pulse1 with FP M 20.3 is a valid, efficient fix that requires no FB conversion.
Code Templates
STL – Counter pulsing correctly in an FB:
FUNCTION_BLOCK FB100
VAR
pulse1 : BOOL; // edge memory for FP, retained in DI
chiller_count : INT;
END_VAR
BEGIN
NETWORK 1
A I0.0 // chiller request
AN M20.1 // pulse generator not yet running
L S5T#5S
SE T11
A T11
= M20.1
FP #pulse1 // now uses FB-static edge memory
JC UP
JU END
UP: L DB70.DBW12
+ 1
T DB70.DBW12
END: BE
LAD – Equivalent ladder:
| Segment | Element |
|---|---|
| 1 | Network 1 — Title: 5 s pulse generator [ I 0.0 ]──┐ │ [ M 20.1 ]──┤/├──( SE T11, S5T#5S )──( M20.1 )
|
| 2 | Network 2 — Positive edge on pulse
[ M20.1 ]──| P|──("FP on #pulse1 via DI")──( JC to label "up" ) |
| 3 | Network 3 — Counter increment
[ Label "up" ]── L DB70.DBW12 ─ + 1 ─ T DB70.DBW12 |
FBD – Equivalent function block diagram:
I0.0 ─┐
│
M20.1 ─┤AND NOT├─[SE T11 S5T#5S]─M20.1
T11 ────── #pulse1(edge in DI) ── JC "up"
"up": DB70.DBW12 + 1 → DB70.DBW12
Alternative: A Simpler Counter Design
Even with the FP problem fixed, the original program reinvents the S5T pulse generator — STEP 7 already provides this. A cleaner approach for "add 1 every 5 s while I0.0 = TRUE":
NETWORK 1 // 5 s clock generator
A I0.0
L S5T#5S
SE T11
NETWORK 2 // count on each pulse
A T11
FP M20.3
JC INCR
JU END
INCR: L DB70.DBW12
+ 1
T DB70.DBW12
END: BE
Or use the CPU's clock memory byte (e.g., M100.7, configured in HW Config at 1 Hz / 2 Hz / 5 Hz / 10 Hz) if a 5-Hz standard tick is acceptable. M100.7 for 5 Hz = 200 ms period; pre-scale with a counter. Note that clock-Merker bits do survive between scans since they live in Merker memory.
Verification After the Fix
After recompiling the FB and downloading:
- Set
I0.0 = TRUE, open Monitor/Modify onDB70.DBW12. - Place a cross-reference for
#pulse1via Options > Reference Data > Display. All references must point into the FB's Instance DB, not the L-stack. - Enable "Continuous update" in Monitor/Modify. Confirm that
DB70.DBW12increments by exactly 1 every 5 s, not every cycle. - Force
M20.1temporarily to verify the FP path; then release the force.
If your project was previously an FC and has been converted to an FB, re-run a full reference-data rebuild (Program > Compile All in STEP 7 V5.x; "Compile (Rebuild all blocks)" in TIA Portal) to ensure no orphan FC calls remain.
Best Practices for TEMP vs STAT
| Use TEMP for | Use STAT (FB) or Merker/DB bit for |
|---|---|
| Loop counters inside the FC that the FC owns only for the current call | Edge memories for FP/FN |
| Calculation scratch (e.g., intermediate INT math result) | Up/Down counters that must persist |
| Function-style P# index when indexing an ANY | Sequential state machine flags (e.g., step0/step1) |
| Pass-by-value parameter intermediate | Anything that must survive across OB cycles |
| Never: edge bits, flip-flops, latches that need to remember an RLO across scans | Engineered for that purpose |
Other guidelines:
- Never declare a TEMP variable you suspect should "remember" state.
- Do not use named TEMP "counters" ("Count" in the interface is usually a giveaway — counters need STAT or Merker).
- For multi-instance FB designs, STAT variables of the parent FB are the preferred location for edge memories.
- Increase local-data work memory in HW Config if you begin to see OB121 / OB122 after bigger multi-instance projects.
- Tia Portal blocks (S7-1200/S7-1500) handle TEMP differently on S7-1200 — those TEMPs are cleared every call to 0 by default, which masks the bug. The S7-300/400 L-stack aliasing issue still applies.
Edge Cases and Platform Notes
S7-PLCSIM version differences. PLCSIM V5.4 SP3 and earlier did not perfectly emulate L-stack aliasing, so an FC with TEMP FP could "appear" to work for hours in PLCSIM but fail on real hardware. PLCSIM V5.4 SP5+ follows the real CPU's behavior more closely. On a STEP 7 V13+ project, also confirm that the simulator is not caching the L-frame from an earlier scan (which it never should).
SCL and Graph. In SCL, when you write IF signal AND NOT signal_old THEN ...; signal_old := signal; END_IF;, the signal_old must be a STAT variable or a global; TEMP compiles but produces the identical bug. Siemens S7-GRAPH state machines use STAT for step state, so this issue is largely avoided there.
IEC timing. Using TP (pulse timer) instead of SE does not fix the issue; the input to FP still needs retention.
S7-1200/S7-1500. TIA Portal blocks (FB/FC) reset TEMP to 0 on entry if "Optimized Block Access" is enabled, so the specific symptom disappears — but the underlying rule still holds: TEMP must never be used as an edge memory. Use STAT in FB, or use a global tag in a global DB.
Standards and Documentation References
- Siemens SIMATIC S7-300 Programmable Controller, Hardware and Installation Manual — entry ID
8855739on the Siemens Industry Online Support portal. - Siemens SIMATIC Programming with STEP 7 V5.x manual — entry ID
1533245— chapter "Working with Local Variables." - Siemens Ladder Logic (LAD) for S7-300/400, Programming Reference — FP/FN operand rules.
- Siemens S7-300 Module Reference (instruction set
6ES7 ...) — bit-check instructions. - Siemens S7-400 CPU Specifications Technical Data — local-data stack size per priority class.
- SIMATIC Training Course "S7-PLCSIM" — Handling Local Data module.
https://support.industry.siemens.com for KB articles specific to your CPU's MLFB. Specific error codes triggered by an L-Stack overflow differ by CPU type — reference your CPU's section of the Siemens Online Support portal rather than relying on memory size quoted in this article.
Frequently Asked Questions
Why does FP with a TEMP variable work sometimes and fail after adding a new variable?
TEMP lives in the L-stack. When code grows and the L-frame layout changes, the bytes that store #pulse1 between OB1 scans end up re-used by other blocks or initialized to 0 — so FP looks like there is an edge on every scan. Coincidental residue (the unlucky 0) makes the bug only appear after specific changes.
Can I keep using FC and just change the FP operand to a Merker bit?
Yes. FP M 20.3 is a fully supported solution. Reserve a Merker word for your block to keep ownership clear, or pass the bit in via an IN_OUT pointer so the calling block owns its storage.
What is the difference between TEMP and STAT for an FB?
TEMP is volatile local workspace allocated on the L-stack each call. STAT is persistent storage mapped into the FB's Instance Data Block (DI), and that DB retains its contents across scans. Use STAT (not TEMP) for any state that must survive across OB cycles — counters, edge bits, latches, sequential flags.
Does S7-PLCSIM reproduce the L-stack aliasing reliably?
PLCSIM V5.4 SP5 and later emulate the L-stack behavior of the real CPU closely enough that the FP-on-TEMP bug reproduces. Earlier PLCSIM releases hid the issue, so tests on those versions were not sufficient to detect the defect.
Are there TIM instructions I can use that avoid the FP operand entirely?
Yes. Use TP (pulse) or TP with an explicit edge-bypass: feed a TON/TOF pulse generator into a counter's CV input, and configure the timer's Q output as the "one tick per cycle" signal. Pulse timers still rely on edges inside the block, so the timer must be in the OB (or as STAT of an FB) — not a TEMP at the FC boundary.
Does TIA Portal fix this automatically?
No. TIA Portal reset behavior on TEMP (cleared to 0 on entry when Optimized Block Access is enabled on S7-1200/S7-1500) hides the symptom for FCs, but the underlying rule — never put an FP/FN memory in TEMP — still applies. Use STAT for any FB or convert to STAT-in-FB to be safe across S7-300/S7-400/S7-1200/S7-1500.