Fixing FN Negative Edge Detection in Siemens FC Blocks

David Krause17 min read
HMI ProgrammingSiemensTroubleshooting
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

Fixing FN Negative Edge Detection in Siemens FC Blocks

The Siemens FN (negative/falling edge) instruction is one of the most-used bit-logic operations in STEP 7 and TIA Portal, but it is also one of the most common sources of intermittent control faults. Engineers frequently report that the edge bit "never fires," that the output Q is pulsed only once during commissioning but never again in RUN, or that the falling-edge behavior disappears the moment the function is called from a different OB. In nearly every case, the root cause is the same: the edge memory bit has been placed in the TEMP area of an FC (Function), where Siemens reinitializes it on every call and the previous state is therefore lost between scan cycles.

This reference covers the underlying memory model, three production-ready fixes, the S7-1200/1500 TIA Portal behavior that is different from S7-300/400, and a complete worked example for a 3-phase motor forward/reverse starter with a 5-second off-delay between direction changes. The procedures below are aligned with the SIMATIC S7-1200 manual collection on positive/negative edge instructions and standard STEP 7 programming guides.

Field note: The same root cause affects FP (positive/rising edge) and the R_TRIG/F_TRIG IEC instances when they are placed inside an FC without persistent backing. Treat both edges as a single class of problem.

1. Problem Statement

Symptom: A falling-edge evaluation inside a Function (FC) behaves as if the input never changes, or the output Q flickers on for one scan after every cold restart but never again during steady-state operation.

Typical user code that fails:

// INSIDE an FC - this is the broken pattern VAR_TEMP prev_dir_cmd : Bool; // <-- WRONG: reinitialized every call END_VAR IF dir_cmd XOR prev_dir_cmd THEN // always FALSE on second call off_delay_timer(IN := TRUE, PT := T#5s); END_IF; prev_dir_cmd := dir_cmd;

The author observes that the FN instruction "does not work inside the function" and that "the memory bit loses its value between FC calls." This is correct: the FC's TEMP area is a scratchpad, not memory.

2. Root Cause Analysis

Siemens S7 PLCs segment the variable memory into three distinct lifetimes:

Variable Class Location Lifetime Available In
TEMP Local stack (L stack) One call only; undefined on entry FC, FB, OB
STAT Instance DB (iDB) Persistent across calls and power-cycle (retain attribute) FB only
Global (M, DB, I, Q, PIW, PQW) Process image / global DB / Merker area Persistent for the configured retention time Everywhere

Edge detection in a Boolean sense is a two-point memory: you must remember the input's state from the previous execution to compare against the current state. The FN instruction's Q output is high for exactly one scan when current_input = 0 and previous_state = 1. After Q is set, the instruction latches previous_state := current_input for the next scan.

If previous_state is a TEMP variable, it is reallocated on the L stack on every OB1 cycle. The compiler may even reuse the same L-stack byte for unrelated TEMPs in different code sections, so the value visible at the start of the FN evaluation is essentially random. Result: Q is never asserted during normal RUN because the comparison always sees previous_state = current_input.

Why it appears to work once: On a cold restart, the L stack is zero-initialized. If current_input = 1 at first call, the FN sees previous = 0 and current = 1, which is the rising-edge case, not the falling edge. Q is asserted only on the inverse transition, which often never occurs because the FC's "memory" has been clobbered by then. The single observed pulse is the result of a lucky alignment between L-stack init and the first real input change.

3. FC vs FB: Why Multi-Instance Programming Exists

The Siemens "multi-instance" model was introduced in STEP 7 V5 to eliminate the need for one instance DB per FB. Instead of DB_Motor1, DB_Motor2, DB_Motor3, a single parent FB holds a STAT array of motor FBs that share one instance DB. This is the architecture Siemens documents as the default for new code, and it is the recommended structure for any program that reuses the same logic on multiple instances (valves, drives, conveyors, pumps).

Property FC (Function) FB (Function Block)
Has its own instance DB No Yes (single or multi-instance)
STAT variables allowed No Yes
TEMP lifetime One call One call
Multi-instance (one DB holds many FBs) No Yes (since STEP 7 V5)
Suitable for edge / counter / timer memory No (use In/Out or global) Yes (use STAT)
Reusable as "function library" Yes (with In/Out discipline) Yes (with multi-instance)

Siemens' own recommendation, repeated in the SIMATIC programming style guide, is to use an FB with STAT variables for any block that needs internal memory. FBs are intended for "stateful" code, FCs for "pure" calculations. The user question in the source thread is therefore a classic case of using the wrong block type for a stateful requirement.

4. Solution 1: Convert the FC to a Multi-Instance FB

The cleanest fix is to migrate the FC to an FB and place the edge memory in a STAT variable. If the FC is called from OB1, simply change the block type and the editor will prompt to create an instance DB (single-instance) or to nest it as a multi-instance under an existing parent FB.

SCL for the motor-direction FB:

FUNCTION_BLOCK "Motor_DirChange_FB" VAR EdgeMemoryFwd : Bool; // STAT - holds previous forward cmd EdgeMemoryRev : Bool; // STAT - holds previous reverse cmd OffDelay : TON; // STAT - timer instance (also persistent) END_VAR VAR_INPUT CmdFwd : Bool; CmdRev : Bool; Enable : Bool; END_VAR VAR_OUTPUT RunFwd : Bool; RunRev : Bool; TimerDone : Bool; END_VAR VAR_TEMP ti : SInt; END_VAR BEGIN // Falling edge on the active direction command IF Enable THEN // Detect 1 -> 0 transition on the command input IF (NOT CmdFwd) AND EdgeMemoryFwd THEN OffDelay(IN := TRUE, PT := T#5s); ELSIF (NOT CmdRev) AND EdgeMemoryRev THEN OffDelay(IN := TRUE, PT := T#5s); END_IF; // Latch current state for next scan EdgeMemoryFwd := CmdFwd; EdgeMemoryRev := CmdRev; // Direction outputs only when timer is done and command is stable TimerDone := OffDelay.Q; RunFwd := CmdFwd AND (NOT CmdRev) AND TimerDone; RunRev := CmdRev AND (NOT CmdFwd) AND TimerDone; ELSE RunFwd := FALSE; RunRev := FALSE; OffDelay(IN := FALSE); END_IF; END_FUNCTION_BLOCK

Because EdgeMemoryFwd, EdgeMemoryRev, and the OffDelay instance are all STAT, they live in the instance DB and survive between calls. Marking the FB with {S7_Optimized_Access := 'TRUE'} (S7-1500/1200) or with retain flags on the relevant STATs makes the data survive a power cycle.

5. Solution 2: Keep the FC and Pass an In/Out Edge Bit

If a full FB migration is not feasible (e.g., the FC is shared across many programs and you cannot change its interface), pass the edge bit in as an VAR_IN_OUT parameter and back it with a STAT in the calling block, or with a global Merker byte.

FUNCTION "Motor_DirChange_FC" : Void VAR_INPUT CmdFwd : Bool; CmdRev : Bool; Enable : Bool; END_VAR VAR_IN_OUT EdgeMemoryFwd : Bool; // <-- persistent caller-owned memory EdgeMemoryRev : Bool; // <-- persistent caller-owned memory TimerAccum : Time; // <-- elapsed-time accumulator END_VAR VAR_TEMP FallFwd : Bool; FallRev : Bool; END_VAR BEGIN FallFwd := (NOT CmdFwd) AND EdgeMemoryFwd; FallRev := (NOT CmdRev) AND EdgeMemoryRev; IF (FallFwd OR FallRev) AND Enable THEN TimerAccum := T#0s; // arm the 5 s gap END_IF; IF TimerAccum < T#5s THEN TimerAccum := TimerAccum + T#10ms; // OB1 cycle = 10 ms END_IF; // Caller FB links its own STATs to these In/Out EdgeMemoryFwd := CmdFwd; EdgeMemoryRev := CmdRev; END_FUNCTION

The caller wires the In/Out pins to its own STATs:

// In the parent FB, e.g. "Machine_OB1" VAR statEdgeFwd : Bool; statEdgeRev : Bool; statTimerAcc : Time; instMotor1 : Motor_DirChange_FB; // FB instance (preferred) END_VAR // OR, for FCs: "Motor_DirChange_FC"( CmdFwd := i_CmdFwd, CmdRev := i_CmdRev, Enable := i_Enable, EdgeMemoryFwd := statEdgeFwd, // wire to STAT EdgeMemoryRev := statEdgeRev, TimerAccum := statTimerAcc );
Discipline check: If the same FC is called more than once (e.g., for Motor1, Motor2, Motor3), each call must be wired to its own STAT set. Sharing one Merker byte across multiple FC calls causes edges from Motor1 to trigger Motor2's off-delay. This is the most common secondary fault when an FC is used to back the edge memory.

6. Solution 3: Use Global Memory (M Area or Shared DB)

The simplest patch, suitable for small standalone projects where you can accept the loss of multi-instance discipline, is to use a Merker (M) bit or a global data block bit. STEP 7 reserves the M area (flags) in CPU work memory and persists it across OB1 cycles.

// In the FC body VAR_TEMP FallFwd : Bool; END_VAR BEGIN FallFwd := (NOT "i_CmdFwd") AND "M_cmd_prev_fwd"; IF FallFwd THEN "i_OffDelayStart" := TRUE; END_IF; "M_cmd_prev_fwd" := "i_CmdFwd"; // M bit retains across calls END_FUNCTION

Mapping table for the global-memory approach:

Signal Address (S7-300/400) Address (S7-1200/1500)
Prev forward cmd M 100.0 %M100.0
Prev reverse cmd M 100.1 %M100.1
Direction-change latch M 100.2 %M100.2
5 s off-delay accumulator MW 102 (TIME format) %MW102
5 s timer done flag M 100.3 %M100.3
Reserved M bytes: The S7-300/400 reserves the first 16 M bytes (MB0 to MB15) for system functions in some CPU variants. Always start the user area at MB 16 or higher, and consult the CPU's datasheet for the exact reserved range. On S7-1200/1500 the M area size is configurable from the device configuration and defaults to 4096 bytes in the TIA Portal project tree.

For larger systems, replace the M area with a dedicated global DB (e.g., DB100 "Motor_Internal") so that the variables are documented in the symbol table and can be HMI-tagged without polluting the Merker area.

7. S7-1200/1500 Behavior in TIA Portal

On S7-1200 firmware V4.x and S7-1500, the FP/FN instructions have been modernized. According to the SIMATIC S7-1200 manual collection, bit-logic operations, the edge instructions in TIA Portal:

  • Automatically create an implicit instance DB when the M_BIT operand is omitted, removing the need to declare a STAT variable manually.
  • Expose an ENO output and standardized error handling consistent with other IEC 61131-3 instructions.
  • Can be used inside optimized-access FBs (the default on S7-1200/1500), where the M_BIT is allocated symbolically and is retentive by default.
  • For an FC, the implicit instance DB is still created, but the editor warns that the instance DB lifetime is tied to the FC's calling context. If the FC is called from multiple OBs (e.g., OB1 and OB35), the implicit instance DB can be created in each calling OB's context, leading to inconsistent state.

Best practice on S7-1200/1500 is therefore identical to the S7-300/400 recommendation: use an FB with STAT for any stateful code, including edge bits. The TIA Portal improvements reduce the verbosity, not the underlying memory model.

Platform FN / FP Old Style FN / FP TIA Style Edge in FC works?
S7-300 / S7-400 (STEP 7 V5) FN <in> <M_BIT> = Q Not applicable Only if M_BIT is STAT or global
S7-1200 (TIA V13+) Supported FN (with implicit IDB) Implicit IDB created; not portable
S7-1500 (TIA V13+) Supported FN (with implicit IDB) Implicit IDB created; not portable
S7-1500 Software Controller / ET 200SP CPU Supported FN (with implicit IDB) Same as S7-1500

8. Worked Example: 3-Phase Motor Direction Change with 5 s Off-Delay

A common application of negative edge detection is the forward/reverse motor starter. The control philosophy is:

  1. Operator presses FORWARD (CmdFwd = 1). Contactor K1 closes, motor spins clockwise.
  2. Operator releases FORWARD. CmdFwd falls 1 -> 0. The FC must detect the falling edge and start a 5 s off-delay.
  3. During the 5 s window, neither K1 nor K2 may close, so the motor is allowed to stop and the back-EMF on the DC bus decays. This protects the contactors and any solid-state switching.
  4. After 5 s, the operator may press REVERSE (CmdRev = 1). Contactor K2 closes, motor spins counter-clockwise.
  5. Mechanical and electrical interlocks (e.g., RunFwd XOR RunRev) prevent both contactors being closed simultaneously.

Using a multi-instance FB for the motor block gives a clean, scalable architecture:

// Parent FB: "Line_Control" VAR MotorConveyor1 : "Motor_Direction_FB"; // multi-instance MotorConveyor2 : "Motor_Direction_FB"; // multi-instance EdgeMemFwd1 : Bool; // only needed if using the FC fix END_VAR

Wiring diagram (textual):

  +24 V ----[PB_Fwd]---+---- %I0.0 (CmdFwd)
                       |
  +24 V ----[PB_Rev]---+---- %I0.1 (CmdRev)

  Motor FB outputs:
    %Q0.0 (RunFwd) --> K1 forward contactor coil
    %Q0.1 (RunRev) --> K2 reverse contactor coil

  Interlock:
    K1 NC aux contact in series with K2 coil
    K2 NC aux contact in series with K1 coil

The 5 s off-delay must be measured using a real-time source. The simplest implementation uses a TON instance inside the FB, with PT = T#5s and a STAT edge bit that retriggers the timer on every falling edge of either command.

9. Step-by-Step Implementation in TIA Portal V18

  1. Create a new FB. In the project tree, right-click "Program blocks > Add new block > Function Block." Name it Motor_Direction_FB. Choose SCL or LAD/FBD.
  2. Declare the interface. In the FB interface:
    • Inputs: CmdFwd : Bool, CmdRev : Bool, Enable : Bool.
    • Outputs: RunFwd : Bool, RunRev : Bool, TimerDone : Bool.
    • Static: EdgeMemFwd : Bool, EdgeMemRev : Bool, OffDelay : TON with PT preloaded to T#5s.
    • Temp: any local calculation scratchpad.
  3. Write the edge logic. Use the FBD/LAD FN instruction, or in SCL code the manual comparison:
    IF (NOT #CmdFwd) AND #EdgeMemFwd THEN #OffDelay(IN := TRUE); END_IF; #EdgeMemFwd := #CmdFwd;
  4. Compile and download. The TIA Portal will auto-generate an instance DB (e.g., DB_Motor1). Download both the FB and the DB to the PLC.
  5. Call the FB from OB1. Add a call box for Motor_Direction_FB in OB1 and wire the inputs to your I/O tags.
  6. Mark retentive STATs. In the FB interface, right-click the EdgeMemFwd, EdgeMemRev STATs and check "Set in IDB" retain so the edge state survives a CPU stop/start or power cycle.
  7. Test in single-step. Use the TIA Portal online monitor to step through OB1 and confirm that EdgeMemFwd latches the input state across multiple calls.

10. Verification Procedure

  1. Online monitor the edge bit. Open the FB instance DB online and watch the EdgeMemFwd boolean. With CmdFwd held high, the bit must remain 1; the moment CmdFwd goes to 0, the bit stays 0 only for the cycle in which the FN fires.
  2. Force a known pattern. Use the watch table to force CmdFwd = TRUE, observe the output Q assertion only on the 1->0 transition. If Q fires immediately, the FC's implicit instance DB is being re-initialized and you have a TEMP leak.
  3. Cold-restart test. Perform a CPU STOP then RUN. The off-delay timer must start from a defined state, not from a random L-stack residue.
  4. Multi-call test. If two motor FBs are instantiated, force both CmdFwd inputs high, drop them sequentially, and confirm each motor's off-delay starts at the correct instance's falling edge only.
  5. Cycle-time check. Verify the OB1 cycle time is stable (e.g., 10 ms). If the cycle time varies >20%, the implicit TON instance in an FC may be skipped or coalesced. The standard practice is to use TON inside an FB with a defined OB1 cycle period.

11. Best Practices and Program Structure

  • Default to FBs with STATs. Treat every block that holds counters, edges, or timers as an FB. Treat pure calculations (scaling, scaling-to-physical-unit conversions) as FCs.
  • Use multi-instance FBs for repeated equipment. A single instance DB holds the STAT for every valve, motor, or conveyor in the machine. Avoid creating one DB per FB instance.
  • Naming convention for STATs. Prefix every STAT that holds edge memory with Edge_ or Mem_ so that reviewers can identify it during code review (e.g., Edge_CmdFwd).
  • HMI / SCADA segregation. Place HMI-visible tags in a dedicated DB (e.g., DB_HMI) so that the operator panel never references FB instance DBs directly. This decouples the HMI from internal STAT structure.
  • Alarm DB. Use a separate DB_Alarms with structured alarm records. Edge-derived alarm triggers should latch into a STAT in the FB, not into a global M bit.
  • Avoid global Merker for multi-instance code. M bits have no instance context. If the same FC is used for three motors, three M bits must be hand-allocated and reviewed at every change, which is a maintenance burden. FBs eliminate this.
  • Set retain on STATs that must survive restart. In TIA Portal, the default retain setting is "non-retain." For edge memory and timer instances that need restart stability, explicitly set them as retain.

12. Troubleshooting Matrix

Symptom Likely Cause Fix
FN output Q never asserts in RUN M_BIT is a TEMP inside an FC Move M_BIT to a STAT (FB) or VAR_IN_OUT backed by caller STAT
FN output Q asserts once at cold start, then never L-stack zero-initialization gives the rising-edge appearance Same as above: use persistent memory
FN output Q fires on the wrong motor / instance Shared M bit across multiple FC calls Use multi-instance FB with separate STAT per instance
FN output Q is intermittent, correlates with CPU scan time jitter FC called from OB1 and OB35; implicit IDB is recreated Call FC only from one OB, or convert to FB with multi-instance
Off-delay timer never reaches 5 s TON is a TEMP inside the FC; it is re-initialized each call Place TON in STAT of an FB
After STOP->RUN, motor runs immediately without off-delay Retain bit set on the edge memory but not on the timer Set retain on the TON instance STAT as well
Edge works in simulation but not on real CPU PLCSIM uses different memory model; FC TEMP behaves as if STAT Trust the real CPU; fix the FC -> FB migration

13. Alternative-Platform Note: TI Programmable Logic Devices (TPLD)

Outside the Siemens world, similar edge-detection semantics appear on TI Programmable Logic Devices (TPLD). The TI SCLA083 application brief on edge and frequency detection describes rising/falling-edge detectors and frequency comparators implemented in TPLD configurable logic. The TPLD approach differs from S7 in that the edge memory is a dedicated flip-flop inside the device, not a software variable, so the issue of "FC calls losing state" is structurally impossible. This is a useful mental model when justifying the Siemens FB migration to colleagues: a rising-edge detector is a one-bit memory cell, and the same physical property applies regardless of whether the memory is a flip-flop, an instance DB, or a global Merker byte.

14. Summary of the Three Fixes

Fix Effort Scalability Recommended For
Convert FC to multi-instance FB (STAT) Medium (interface migration) Best All new code; refactor of existing code
Keep FC, pass edge bit as In/Out Low OK with discipline Shared FC across many programs
Use M area / global DB Lowest Poor for multi-instance Single-instance, small projects, quick patch

For the motor-direction use case described in the source thread, the recommended path is a multi-instance FB per motor. This gives a documented STAT set per instance, supports retain, and is the pattern documented in the SIMATIC S7-1200 manual on positive and negative edge instructions.

Why does my FN edge instruction never trigger inside an FC?

The FN instruction compares the current input to its memory bit (M_BIT). Inside an FC, any TEMP variable is allocated on the L stack and reinitialized to undefined values on every call, so the previous input state is lost. Move the M_BIT into a STAT variable of an FB, into a VAR_IN_OUT backed by the caller's STAT, or into a global Merker (M) bit. See the S7-1200 manual entry on positive and negative edge instructions for the full semantics.

What is the difference between a Siemens FC and an FB?

An FC (Function) has only TEMP, IN, OUT, and IN_OUT variables. It has no instance DB and no STAT, so any value it calculates is discarded at the end of the call. An FB (Function Block) has a STAT section that is stored in a dedicated instance DB and persists across calls. Since STEP 7 V5, FBs can be nested as multi-instances inside a parent FB, so a single DB can hold the state of dozens of motor or valve blocks.

How do I detect a falling edge on a forward/reverse motor command with a 5 s off-delay?

Build an FB with two STAT boolean edge bits (one for forward, one for reverse), a TON instance with PT = T#5s, and an output that is asserted only when the timer has elapsed. On the 1->0 transition of either command, the FN-equivalent comparison (NOT CmdFwd AND EdgeMemFwd) arms the timer. While the timer is running, neither forward nor reverse output is allowed. After 5 s, the new direction is released.

Can I use a TON timer inside an FC?

Yes, but the TON instance will be reinitialized every call and the accumulated time will be lost. The timer will never reach its preset. Place the TON in a STAT of an FB to give it persistent instance data, or use a global DB to hold an IEC_TIMER variable that the FC reads and writes via In/Out.

Does the TIA Portal V18 FP/FN instruction fix the FC memory problem automatically?

On S7-1200 (firmware V4.x) and S7-1500, the modern FP/FN box creates an implicit instance DB when no M_BIT is wired, which removes the need to manually declare a STAT. However, if the FC is called from multiple OBs, the implicit IDB is created per call context, which can still produce inconsistent state. The clean solution is still to migrate the logic to an FB with multi-instance and use the explicit M_BIT for the edge bit.

Back to blog