Overview
Siemens Statement List (STL) edge-detection primitives FP (Flanke Positive / rising-edge) and FN (Flanke Negative / falling-edge) behave very differently from Allen-Bradley / Rockwell Automation ONS, OSR one-shots, or the legacy SFP/SFD bit-instructions. The most common failure mode for engineers migrating from RSLogix 5/500/5000 or Studio 5000 to STEP 7 STL is treating the operand that follows FP as the input to monitor. In Siemens STL that operand is the edge memory bit (EMB) — a latched copy of the previous RLO (Result of Logic Operation) state, not the Boolean variable being watched.
This reference reconstructs the canonical implementation pattern for a one-shot counter increment that fires only on the rising edge of an #END_OF_CYCLE tag, using FP together with the conditional jump JC <label> and the unconditional jump JU <label>. The same syntax and semantics apply whether the target controller is an S7-300 CPU 31x, an S7-400 CPU 41x, an ET 200S, an ET 200pro, or WinAC — the bit-logic and jump semantics are part of the STEP 7 STL language definition documented in the STEP 7 - Statement List for S7-300 and S7-400 Programming Reference Manual.
Prerequisites
- STEP 7 V5.5 or STEP 7 Professional (TIA Portal) — STL source editor available for the S7-300/400 target.
- Function Block (FB), Function (FC), or Organization Block (OB) declared with the appropriate instance/static or global symbol area so an edge memory bit of type
BOOLcan be allocated. - Familiarity with STL scan cycle, RLO bit, and status word (BR, CC0, CC1, OV, OS).
- Symbolic tags
#END_OF_CYCLE(BOOL, input) and#COUNTER(INT or DINT, in/out or static). - An unused
BOOLmarker — globalMX.Y(M-bit),DBX(data word), orSTAT(static area of the calling FB) — never the localTEMParea.
TEMP area is re-initialized on every OB1/OBxx cycle. Writing the edge memory to a TEMP variable will produce a randomized one-shot on every scan and break the FP/FN semantics. Use M, DB, or STAT instead.The FP Instruction — Syntax and Edge Memory Bit
Per the Siemens STL reference, FP evaluates the current RLO against the value of the operand that follows it. The operand is not an input; it is the latched previous-cycle state of the signal you already queried with a bit-logic instruction:
A #END_OF_CYCLE // query the input — sets RLO = state of #END_OF_CYCLE
FP M0.0 // M0.0 = previous RLO; FP returns RLO=1 only on 0 -> 1 transition
= M0.1 // optional: forward the one-shot
The pseudo-code model that the STL CPU executes each scan is:
edge_output = current_RLO AND NOT(edge_memory_bit);
edge_memory_bit = current_RLO;
On the first scan where #END_OF_CYCLE is TRUE, the previous-cycle memory is FALSE, so FP returns RLO = 1. On the second and subsequent scans where #END_OF_CYCLE stays TRUE, the memory is also TRUE, so FP returns RLO = 0. This is the textbook one-shot behavior — one PLC scan of activation per rising edge.
| Mnemonic | Name | Edge detected | Operand type |
|---|---|---|---|
FP <EMB> |
Flanke Positive | 0 -> 1 transition of RLO | BOOL marker / static / DB bit |
FN <EMB> |
Flanke Negative | 1 -> 0 transition of RLO | BOOL marker / static / DB bit |
JC and JU Jump Instructions
STL provides two jump mnemonics used to skip or branch around code blocks:
-
JU <label>— Jump Unconditional. The CPU always branches to<label>; the RLO is preserved and not modified by the jump itself. -
JC <label>— Jump Conditional on RLO = 1. If the current RLO is 1 the CPU branches; otherwise it executes the next statement in linear flow.
Jump labels are declared inline and terminated with a colon, e.g. EXEC:, NOEX:. The BLD 0 (Bild / display) instruction that often appears after the false branch is a programmer-device-only placeholder; it consumes no runtime, executes no logic, and exists solely so that the STL editor can re-sync source view to compiled code.
Step-by-Step Implementation
The use case is: when #END_OF_CYCLE rises, increment a once-per-cycle counter exactly one time. Each pattern below is functionally equivalent; pick the one that matches your block-local symbol allocation.
Pattern 1 — Global M-bit as edge memory
A #END_OF_CYCLE // load input into RLO
FP M0.0 // M0.0 = previous RLO; pulse on 0->1
JC EXEC // jump to EXEC if RLO=1 (one-shot true)
JU NOEX // else skip the increment
EXEC: L 1
L #COUNTER
+I
T #COUNTER
NOEX: BLD 0 // display instruction; no runtime effect
Pattern 2 — Static FB variable as edge memory (preferred for re-entrant code)
A #END_OF_CYCLE
FP #edge_mem // declared in STAT as BOOL
JC EXEC
JU NOEX
EXEC: L 1
L #COUNTER
+I
T #COUNTER
NOEX: BLD 0
Pattern 3 — Instance-DB bit as edge memory (FB with multi-instance)
A #END_OF_CYCLE
FP DBX10.0 // bit 0 of data word 10 inside the instance DB
JC EXEC
JU NOEX
EXEC: L 1
L #COUNTER
+I
T #COUNTER
NOEX: BLD 0
Common Mistakes and Why They Fail
| # | Anti-pattern | Why it fails | Fix |
|---|---|---|---|
| 1 |
FP #END_OF_CYCLE with no preceding A
|
RLO is undefined (depends on prior network). FP reads the input as the edge memory, which corrupts the EMB and produces a stream of phantom pulses or no pulses at all. |
Always query the input first: A #END_OF_CYCLE then FP M0.0. |
| 2 |
A #END_OF_CYCLE / = M0.0 / FP M0.0
|
Overwrites M0.0 with the current input instead of the previous RLO. The next FP sees M0.0 == input and never fires after the first scan. |
Remove the = M0.0; let FP own the marker. |
| 3 |
FP #t_bit where #t_bit is a TEMP
|
TEMP is re-initialized each OB1 cycle, so the edge memory never survives. Result: the one-shot fires every scan while the input is TRUE — count grows linearly, not once per event. | Move #t_bit to STAT (preferred), M-area, or a DBX. |
| 4 | Re-using the same EMB across two FBs that run in different OB priority classes | OB1 and OB35 (cyclic interrupt) share M-area unless one FB runs in a different priority class; one FB's FP clobbers the other's edge memory. |
Use a STAT variable inside each FB, or use multi-instance DBs. |
| 5 |
JC EXEC followed by code that modifies the inputs that FP just sampled |
Not a problem for FP per se, but the unconditional JU NOEX that follows must be present, otherwise linear execution continues into the EXEC block. |
Always close the if-else with JU NOEX before the EXEC: label. |
Edge Memory Bit Rules — Where You May Store It
The Siemens STL reference permits the EMB operand of FP and FN to be:
-
Global M-bit —
M0.0,MB10,MW20. Fast, no DB overhead. Avoid sharing across priority classes. -
Static FB variable —
#edge_memdeclared in the FB's STAT section. Best practice for re-usable / multi-instance FBs. -
Data word bit —
DBX,DBB,DBW,DBDinside a global DB or instance DB. - Process image input (PEW/PED) or output (PAW/PAD) — generally discouraged; the I/O update only writes once per scan.
The following are not valid EMB locations:
-
TEMPvariables — re-initialized each call. - Constants, literal BOOLs (
TRUE/FALSE) — no latching semantics possible. - Immediate I/O (
PQB/PIB) used as EMB — write-back is undefined.
Comparison with Allen-Bradley / Rockwell Edge Detection
| Behavior | Siemens STL | Allen-Bradley RSLogix 500 | Studio 5000 / Logix Designer |
|---|---|---|---|
| Mnemonic | FP |
ONS (output latched) or SFP bit-instruction |
OSR single-shot rising |
| Operand after instruction | Edge memory bit (EMB) | Output bit tag | Output bit tag |
| Memory location rule | EMB must be non-volatile across scans (M / STAT / DB) | Output tag must be a B3/ N7 /scratch | Output tag must be a BOOL tag, alias recommended |
| Edge direction |
FP rising, FN falling |
ONS / SFP rising; OSF falling via SFD
|
OSR rising, OSF falling |
| Companion jump |
JC / JU
|
No direct equivalent — usually nested on rung | Same — FBD / ladder sequence handles it |
The conceptual difference engineers miss is that in Allen-Bradley the bit you supply is the one-shot output you consume downstream; in Siemens STL the bit you supply is the previous-cycle snapshot the CPU uses internally to decide whether to pulse the RLO. Once that mental model clicks, the syntax becomes mechanical.
Verification
- Open the FB in STL source view (STEP 7: View > STL; TIA Portal: switch the language to STL in the block properties).
- Confirm the order of statements is
A <input>→FP <EMB>→JC <label>→JU <label>→<label>:body. - In the VAT (Variable Table) or a watch table, force
#END_OF_CYCLE = TRUEfor exactly one scan, then back toFALSE— confirm#COUNTERincrements by exactly 1. - Force
#END_OF_CYCLE = TRUEfor 100 consecutive scans — confirm#COUNTERstill increments by exactly 1 (not 100). - Toggle
#END_OF_CYCLEten times manually — confirm#COUNTERincrements by exactly 10. - Use Monitor/Modify with a trigger on
#END_OF_CYCLEand verify the EMB bit transitions:FP M0.0means M0.0 should mirror the previous cycle's RLO of the input.
Edge Cases and Field-Proven Caveats
-
First scan after restart / OB100: All M-bits and instance DBs initialize to 0 unless you explicitly preset them.
FPon the first TRUE after a warm restart will fire (because the EMB was 0). This is usually desirable, but for fail-safe counters that must start at zero edge events, preset#COUNTER = 0in OB100 and clear the EMB. - Priority class interaction: If the same M-bit is used as EMB by code in OB1 (priority 1) and OB35 (priority 12), the interrupt OB can clobber OB1's edge memory mid-scan. Always allocate a unique EMB per priority class.
-
Indirect addressing:
FP [MD20]is legal only ifMD20contains a valid pointer to a BOOL area at runtime; STEP 7 will not type-check this at compile time. Prefer symbolicFP #edge_memfor clarity. - Multi-instance FBs (S7-400 / S7-300 with FW 2.x+): Use the STAT area inside the FB for the EMB so each instance has its own latched copy automatically. No manual DB management required.
-
SCL vs STL: If you migrate the same logic to SCL, the equivalent is
IF #END_OF_CYCLE AND NOT #edge_mem_last THEN #COUNTER := #COUNTER + 1; #edge_mem_last := #END_OF_CYCLE; END_IF;. The principle is identical; only the surface syntax differs. -
Compiling with BLD after a label:
BLD 0is a display-only instruction and is harmless, but if you remove it the program executes identically. Some legacy code usesBLD 1...BLD 255for paragraph / network separators in the STL editor view.
Troubleshooting Matrix
| Symptom | Likely cause | Diagnostic step | Remediation |
|---|---|---|---|
| JC never fires — counter never increments | EMB is a TEMP variable (re-init each scan), or A missing before FP
|
Watch table: confirm EMB bit toggles each cycle | Move EMB to STAT / M / DB; add A <input> before FP
|
| Counter increments every scan while input is TRUE | EMB reinitialized to 0 each scan (TEMP), or EMB overwritten by = EMB
|
Cross-reference the EMB symbol — confirm only one write site (the FP) | Remove the manual =; relocate EMB out of TEMP |
| Counter increments erratically when OB35 fires | Shared M-bit between OB1 and OB35 | Search all blocks for the EMB tag | Allocate per-priority-class EMB or use STAT |
| First cycle after CPU stop-run doesn't pulse | EMB stuck at 1 from a previous startup | Preset EMB to 0 in OB100 | Add SET / CLR + = #edge_mem in startup OB |
| JC branches even when input is FALSE | Previous network left RLO = 1 (no fresh A) |
Insert CLR before A <input> if needed |
Always start edge networks with a fresh query |
| Compile error "Invalid operand for FP" | EMB is a constant, TEMP, or process-output area | Cross-check operand declaration | Use M, DBX, or STAT only |
Related STL One-Shot Idioms
The FP/JC/JU triad is one of three canonical STL edge patterns. The other two are useful in different contexts:
Idiom A — Edge flag without a jump
A #input
FP #edge_mem
= #one_shot_out // use #one_shot_out anywhere downstream
Idiom B — Negated edge via FN
A #input
FN #edge_mem
JC EXEC
JU NOEX
Idiom C — Counter pulse using CU
A #END_OF_CYCLE
CU C1 // counts every rising edge of RLO
NOP 0
Idiom C is what the original poster explicitly wanted to avoid (because they had to read or write the counter word manually), but it is worth knowing because CU has its own internal edge memory and is sometimes simpler than the explicit FP+JC+JU pattern.
FAQ
Why does FP #END_OF_CYCLE never fire on an S7-300 / S7-400?
Because the operand after FP is not the input — it is the edge memory bit. You must first query the input with a bit-logic instruction (A #END_OF_CYCLE) and then call FP <EMB> with a non-volatile BOOL marker (M, STAT, or DBX). Without the preceding A, the RLO is undefined and the edge memory is corrupted.
Can I use a TEMP variable as the edge memory bit for FP/FN?
No. The TEMP area is re-initialized to 0 on every call of the block, so the previous-cycle state is lost and FP will fire on every scan that the input is TRUE — turning a one-shot into a runaway counter. Use STAT, M, or a DBX instead.
What is the difference between FP and FN in Siemens STL?
FP (Flanke Positive) returns RLO=1 on the 0-to-1 transition of the previously queried input; FN (Flanke Negative) returns RLO=1 on the 1-to-0 transition. Both use the same edge memory bit convention — the operand stores the previous-cycle RLO.
Do I need both JC and JU in the one-shot block?
Yes. JC EXEC branches to the increment only on the rising edge; the immediately following JU NOEX skips over the increment block when the edge is false, preventing linear execution from falling through. Without JU NOEX the counter increments every scan regardless of the edge.
Is FP still available in TIA Portal S7-300/400 STL, or has it been replaced?
The FP and FN mnemonics remain part of the STL language definition for S7-300 and S7-400 targets in both classic STEP 7 V5.5 and TIA Portal. Newer S7-1500 projects still expose STL for compatibility, but most new development for S7-1500 uses SCL or LAD/FBD.