Siemens STL FP Positive Edge with JC/JU Jump on S7-300/400

David Krause11 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

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 BOOL can 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 BOOL marker — global MX.Y (M-bit), DBX (data word), or STAT (static area of the calling FB) — never the local TEMP area.
Critical: The 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_mem declared in the FB's STAT section. Best practice for re-usable / multi-instance FBs.
  • Data word bit — DBX, DBB, DBW, DBD inside 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:

  • TEMP variables — 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

  1. Open the FB in STL source view (STEP 7: View > STL; TIA Portal: switch the language to STL in the block properties).
  2. Confirm the order of statements is A <input> → FP <EMB> → JC <label> → JU <label> → <label>: body.
  3. In the VAT (Variable Table) or a watch table, force #END_OF_CYCLE = TRUE for exactly one scan, then back to FALSE — confirm #COUNTER increments by exactly 1.
  4. Force #END_OF_CYCLE = TRUE for 100 consecutive scans — confirm #COUNTER still increments by exactly 1 (not 100).
  5. Toggle #END_OF_CYCLE ten times manually — confirm #COUNTER increments by exactly 10.
  6. Use Monitor/Modify with a trigger on #END_OF_CYCLE and verify the EMB bit transitions: FP M0.0 means 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. FP on 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 = 0 in 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 if MD20 contains a valid pointer to a BOOL area at runtime; STEP 7 will not type-check this at compile time. Prefer symbolic FP #edge_mem for 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 0 is a display-only instruction and is harmless, but if you remove it the program executes identically. Some legacy code uses BLD 1...BLD 255 for 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.

Back to blog