Siemens S7 One-Button Toggle Latch: STL Flip-Flop Implementation

David Krause15 min read
HMI ProgrammingSiemensTechnical Reference
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

Siemens S7 One-Button Toggle Latch: STL Flip-Flop Implementation

Implementing a single pushbutton that alternately sets and resets a digital output is one of the most common requests on industrial automation desks. The hardware on a Siemens S7-200, S7-300, S7-1200, or S7-1500 only exposes a momentary contact, so the toggling behavior must be created in software. This reference covers correct Statement List (STL) flip-flop patterns, why naive latch circuits self-reset, the modern TIA Portal equivalents, and the input-debouncing rules that keep the output stable on the shop floor.

Operating principle: Every push of the button must produce exactly one transition of the output. That requires edge detection, not level scanning. Scanning a level is the root cause of self-resetting latches.

Problem Definition and Behavior Specification

The functional requirement is unambiguous:

  • Press 1 of the momentary pushbutton (wired to I0.0 in the examples below): output Q0.0 transitions from OFF to ON.
  • Press 2: Q0.0 transitions from ON to OFF.
  • Press 3: Q0.0 returns to ON.
  • The output must hold its state indefinitely between presses, including through power cycles of the input contact bounce.
  • The output must be immune to the button being held down (no chatter, no extra toggles).

This is a single-bit T-type flip-flop (toggle) in PLC terms. The classic Siemens nomenclature is Flip-Flop, Toggle, or Stromstoßschalter (impulse switch). All three search terms yield the same hardware semantics.

Why the Naive Latch Code Self-Resets

The pattern that fails most often looks correct on paper and is, in fact, the root cause of almost every "my latch turns on for one scan then turns off" report:

| M0.1                              |
|----[ I0.1 ]----( P )----( S ) Q0.0 |
|                                    |
| M0.1                              |
|----[ I0.1 ]--[ M0.1 ]--( P )--( R ) Q0.0 |

The second network resets the output the same scan the first network set it, because the latched M0.1 is already TRUE when the P (rising edge) contact closes. The Set instruction and the Reset instruction evaluate in the same PLC scan cycle, and Reset wins because it is physically the last write to the output bit in the OB1 cycle. The output therefore pulses high for one scan (or, depending on scan time, never appears at the process image at all).

This is the same pattern the OP in the original field report was attempting. The fix is to gate the Set and Reset with the opposite state of the output, so only one of the two branches can ever fire.

Edge Detection Prerequisites in S7 STL

Both correct solutions rely on the Siemens FP instruction (Flanke Positive, rising edge). The behavior of FP is described in the Siemens S7-200 System Manual and the S7-300 Programmable Controller System Manual:

Instruction Operand 1 Operand 2 Function
FP (Flanke Positive) Boolean input bit Edge memory bit (Merker/Flag) Generates a one-scan TRUE pulse on a 0→1 transition of the input
FN (Flanke Negative) Boolean input bit Edge memory bit (Merker/Flag) Generates a one-scan TRUE pulse on a 1→0 transition of the input
A Boolean AND; reads TRUE if operand is 1
AN Boolean AND NOT; reads TRUE if operand is 0
S Boolean Set (latch) the operand; remains until R overwrites it
R Boolean Reset (unlatch) the operand
= Boolean Assign current RLO to the operand (single-scan write)
JCN Jump label Jump if RLO is 0 (i.e. no rising edge present)
NOP 0 No operation; target for the jump label
Edge memory bit requirement: The operand of FP must be a non-volatile flag (M on S7-200/300) or a static DB bit. Never use I, Q, or T as the edge memory. Doing so causes the edge to either never fire or fire continuously.

Working STL Toggle Flip-Flop (S7-200 / S7-300)

The following STL block implements a clean T-type flip-flop. The two FP instructions are deliberately addressed to different edge memory bytes so that the Set and Reset pulses never collide on the same scan, and the Set branch is gated by AN Q0.0 while the Reset branch is gated by A Q0.0.

NETWORK 1    // Set on rising edge when output is OFF
      A     I      0.0        // momentary pushbutton
      FP    M      0.0        // rising edge pulse, 1 scan wide
      AN    M      0.2        // interlock: only when internal flag is 0
      S     M      0.2        // latch internal flag

NETWORK 2    // Reset on rising edge when output is ON
      A     I      0.0        // same pushbutton
      FP    M      0.1        // second edge memory byte (independent)
      AN    Q      0.0        // interlock: only when output is 1
      R     M      0.2        // unlatch internal flag

NETWORK 3    // Drive the output
      A     M      0.2
      =     Q      0.0

Walk-through of a press cycle (initial state Q0.0 = 0, M0.2 = 0):

  1. Operator presses the button. I0.0 goes 0→1.
  2. FP M0.0 fires for one scan. RLO of Network 1 = TRUE (because AN M0.2 is also TRUE).
  3. S M0.2 latches M0.2 to 1.
  4. Network 2: FP M0.1 also fires, but AN Q0.0 = FALSE (because the process image of Q0.0 is still 0 from the previous scan), so R M0.2 does not run. This is the critical detail: the Reset branch is gated by the output state, not by the internal flag, so Set and Reset are mutually exclusive.
  5. Network 3: M0.2 = 1, Q0.0 is written 1. End of scan.
  6. Next scan: I0.0 is still 1 (operator still holding button). FP M0.0 does not fire (no new edge). FP M0.1 also does not fire. The output stays ON. The button-hold case is correctly handled.
  7. Operator releases. I0.0 = 0. No edges.
  8. Operator presses again. FP M0.1 fires, AN Q0.0 = TRUE (because Q0.0 is 1), R M0.2 clears M0.2. Network 1: AN M0.2 = FALSE, so no Set. Network 3: Q0.0 is written 0.

The dual edge memory bytes (M0.0 and M0.1) are intentional. Using a single byte for both networks causes the second FP to consume the pulse the first one generated, and the toggle becomes non-deterministic. Siemens documents this constraint in the S7-1200 Programmable Controller System Manual under "Edge Detection Instructions."

Compact Self-Toggling Network

For installations where memory flags are scarce (a real concern on an S7-200 with a 226 CPU and heavy I/O), the network can be reduced to a single edge + jump. The trick is to read Q0.0 and write the inverted value in the same scan, relying on the fact that the new value is not visible to the read until the next process-image update:

NETWORK 1
      A     I      0.0        // momentary pushbutton
      FP    M      0.0        // rising edge
      JCN   _001              // skip on no edge
      AN    Q      0.0        // if output is OFF, this is TRUE
      =     Q      0.0        // assign inverted state back to output
_001: NOP   0
Step Q0.0 before scan AN Q0.0 Q0.0 after scan
Initial 0 TRUE 0
Press 1 (edge) 0 TRUE 1
Hold 1 1 (no edge, JCN jumps)
Press 2 (edge) 1 FALSE 0
Press 3 (edge) 0 TRUE 1
Read-before-write rule: This compact form works only on CPUs that update the process image at the end of OB1. The S7-200 and S7-300/400 do this. The S7-1200/1500 with TIA Portal uses the same model, but if you place the code in an interrupt OB that is configured for direct I/O access, the read and write happen in immediate mode and the toggle will glitch. Always place this pattern in OB1 (the main cyclic OB).

Ladder Logic (LAD) Equivalent for S7-200/300

Engineers who program in LAD can use the same logic. The two-network pattern is the safest for shop-floor deployment because it does not depend on read-before-write ordering:

NETWORK 1    // SET when output is OFF
|  I0.0   P   |
|----[ P ]---+---( S ) Q0.0 |
|            |              |
|            +---[ ]Q0.0----|   // NC contact of Q0.0
|                            |

NETWORK 2    // RESET when output is ON
|  I0.0   P   |
|----[ P ]---[ Q0.0 ]---( R ) Q0.0 |

On the S7-200 in Micro/WIN, the positive-transition contact (|P|) is found under the Bit Logic toolbar; on the S7-300 in STEP 7 V5.5, the equivalent is the contact POS from the "Bit Logic" catalog, addressed with an edge memory bit as a separate contact parameter. The two-contact gating with Q0.0 is the same interlock concept as the STL version.

Modern TIA Portal Implementation (S7-1200 / S7-1500)

On S7-1200 and S7-1500, the legacy FP instruction exists in STL but the recommended approach is the IEC-standard edge flags. The S7-1500 Programmable Controller System Manual documents the modern block-based edge detection.

LAD / FBD (TIA Portal V16 and later):

NETWORK 1
"MyToggle".CLK := "MyButton";        // pulse generator input
// MyToggle is an instance of FB <IE> (rising edge) from "Bit logic"
// Q output is a one-scan pulse

NETWORK 2
IF "MyToggle".Q AND NOT "MyLamp" THEN
    "MyLamp" := TRUE;                // Set
END_IF;

NETWORK 3
IF "MyToggle".Q AND "MyLamp" THEN
    "MyLamp" := FALSE;               // Reset
END_IF;

LAD-only form using standard contacts (no FBs needed):

NETWORK 1    // Set branch
|  MyButton  MyEdge  MyLamp |
|--[ ]--(P)---+---[/]---(S)-|

NETWORK 2    // Reset branch
|  MyButton  MyEdge  MyLamp |
|--[ ]--(P)---+---[ ]---(R)-|

The (P) coil on the rising-edge contact takes a hidden edge memory bit (the MyEdge tag of BOOL data type). Without the explicit memory tag, TIA Portal will warn at compile time that the edge is non-persistent across scans, which is the modern diagnostic that replaces the field experience of the S7-200 era.

SCL Structured Text Implementation

For S7-1200/1500 projects written in SCL, the toggle is a single line wrapped in an edge condition:

IF "MyButton" AND NOT "MyEdge" THEN        // rising edge of MyButton
    "MyEdge" := TRUE;
    "MyLamp" := NOT "MyLamp";             // T-flip-flop core
END_IF;
IF NOT "MyButton" THEN
    "MyEdge" := FALSE;                    // release edge memory
END_IF;

Why this works in SCL: the NOT "MyLamp" expression is evaluated against the value at the start of the IF statement and the assignment is committed to the process image at the end of the OB1 cycle. The semantics are identical to the compact STL form above. TIA Portal V17+ optimizers do not re-order the NOT past the assignment because the variable is marked as a process-image tag (default).

Reusable Function Block (FB) for Toggle Logic

For a project with multiple single-button toggles (panel lamps, machine enable, maintenance mode), wrap the logic in a parameterized FB. The block interface is platform-portable from S7-300 to S7-1500 with no code change:

FUNCTION_BLOCK "FB_Toggle"
VAR_INPUT
    iButton : BOOL;        // momentary contact, level
END_VAR
VAR_OUTPUT
    qOut    : BOOL;        // toggled output
END_VAR
VAR
    sEdge   : BOOL;        // edge memory (static, retained by FB instance)
END_VAR

BEGIN
    IF iButton AND NOT sEdge THEN        // rising edge detection
        qOut := NOT qOut;
    END_IF;
    sEdge := iButton;                    // update edge memory
END_FUNCTION_BLOCK

Call the block from OB1 as "DB_Toggle_Lamp1"."iButton" := "I0.0"; "Q0.0" := "DB_Toggle_Lamp1"."qOut"; or use a multi-instance DB inside a parent FB. The Siemens Programming and Operating Manual for S7-1200/1500 documents the multi-instance pattern in chapter 6.4.

Input Debouncing and Scan Time Considerations

A mechanical pushbutton does not transition cleanly from 0 to 1. The contacts bounce for 1 to 20 ms, generating several rising edges in rapid succession. Two scenarios arise:

Scan time Button bounce Behavior of a level-scanned latch Behavior of an edge-scanned toggle
5 ms 10 ms Captures one or two false transitions; output may strobe One transition captured; correct
50 ms 10 ms Bounce resolves within one scan; output stable One transition captured; correct
100 ms 20 ms Bounce resolves within one scan; output stable One transition captured; correct

The edge-based toggle is intrinsically bounce-tolerant: FP only fires on the 0→1 transition, and the contact stays at 1 across the bounce window, so subsequent 1→0→1 transitions of the bounce do not generate new pulses. The block is therefore correct without external debounce hardware, provided the scan time is less than the contact-bounce window. On the S7-200 with a 226 CPU and a 5 ms scan, the 1-bit toggle is robust against a 30 ms bounce, which covers the worst mechanical switches listed in the Phoenix Contact and Eaton-Möller catalogs.

For S7-1200/1500 applications using a fast input (e.g. 100 kHz high-speed counter), the scan time can drop below 1 ms and the bounce may produce multiple edges inside one cycle. In that case, configure the input filter on the input channel (TIA Portal → Device Configuration → Digital Inputs → Input Filter). The default filter of 6.4 ms on S7-1500 is sufficient; do not reduce it below 3.2 ms for mechanical contacts.

Cross-Platform Mapping Table

Function S7-200 STL S7-300/400 STL S7-1200/1500 STL S7-1200/1500 LAD S7-1200/1500 SCL
Read input A I0.0 A I0.0 A "I0.0" Normally-open contact iButton
Rising edge FP M0.0 FP M0.0 FP "M0.0" or P_TRIG FB POS contact or (P) coil iButton AND NOT sEdge
Set output S Q0.0 S Q0.0 S "Q0.0" (S) coil qOut := TRUE
Reset output R Q0.0 R Q0.0 R "Q0.0" (R) coil qOut := FALSE
Edge memory Flag M Flag M or static DB bit Static FB instance, BOOL tag, or P_TRIG instance DB Hidden tag behind (P) coil VAR sEdge in FB

Verification, Commissioning, and Test Procedure

  1. Wire the momentary pushbutton to a digital input module channel. Verify in the online watch table that the bit transitions 0→1→0 cleanly on each press.
  2. Open the STL/FBD/LAD editor online. Force I0.0 FALSE. Verify Q0.0 = 0 (initial state).
  3. Toggle the pushbutton once. Verify in the watch table that FP M0.0 = TRUE for exactly one scan, that M0.2 latches to 1, and that Q0.0 transitions to 1.
  4. Hold the button down for 5 seconds. Verify Q0.0 stays at 1. The edge must not re-fire.
  5. Release, wait at least 100 ms (bounce settle), then press again. Verify Q0.0 transitions to 0 and the internal flag clears.
  6. Repeat steps 3-5 ten times. Verify each press produces exactly one output transition in the correct direction.
  7. Disconnect and reconnect the input wire (open circuit). Verify the toggle does not spurious-fire during the reconnect transient. If it does, lengthen the input filter on the module channel.
  8. On S7-1200/1500, open the Online & Diagnostics view of the FB instance DB. Confirm the sEdge tag toggles correctly with the input.

Troubleshooting Matrix

Observed symptom Likely root cause Fix
Output strobes on for one scan then off Set and Reset both fire in the same scan (naive latch) Interlock Set with AN Q0.0 and Reset with A Q0.0; or use the dual-edge-memory form
Output never changes Edge memory bit is the same byte for both FP instructions; the second FP consumes the pulse Use distinct edge memory bytes (M0.0 and M0.1) or distinct tags in TIA Portal
Output toggles twice on a single press Mechanical contact bounce generating two rising edges faster than the input filter Increase the digital input filter to ≥6.4 ms; do not rely on PLC scan time alone for very slow scans
Output stays ON after one press and never turns OFF R instruction is missing, or Reset branch is gated by the wrong tag Verify Network 2 has A Q0.0 (not AN Q0.0) on the interlock
Output flickers at scan-rate frequency FP operand is the same as the Set/Reset target (self-referential edge) Use a separate M byte as the edge memory; never use the output Q byte
Toggle works in simulation but not on real hardware OB1 is not the main cyclic OB; the code is running in an interrupt OB with direct I/O Move the toggle to OB1; if an interrupt-driven implementation is required, use a hardware edge input on the F-CPU or the S7-1500 high-speed counter
Compiler warning: "edge flag not persistent" The (P) coil in TIA Portal has no assigned memory bit Right-click the (P) coil and assign a static BOOL tag (typically "Static_1" in the FB instance DB)
Toggle misses every other press on S7-1200/1500 OB1 priority is preempted by a higher-priority OB that writes to Q0.0 Audit the program for any other OB writing to Q0.0; centralize the toggle output in this FB only

Safety and Operational Notes

A toggle circuit of this type is not a substitute for a safety-rated hold-to-run or emergency-stop function. A single pushbutton cannot satisfy the requirements of ISO 13849-1 PL d or higher for a safety stop function, because the operator cannot visually distinguish a stuck button from a healthy one. For SIL/PL applications, use a dedicated F-CPU safety input module (e.g. ET 200SP F-DI) and the S7-1500F's ESTOP1 or FDBACK library blocks. The toggle logic in this article is suitable only for non-safety control functions such as maintenance lights, panel indication, and operator-mode selection.

When the toggle controls an actuator that must not start unexpectedly after a power cycle, configure the output as non-retentive in the OB startup routine: Q0.0 := FALSE; in OB100. The toggle will then always begin in the OFF state on cold start, regardless of the previous run state.

Frequently Asked Questions

Why does my latch turn on for one scan then turn off?

The Set and Reset branches are both firing in the same OB1 cycle. Gate the Set with the OFF state of the output (AN Q0.0) and the Reset with the ON state (A Q0.0), so only one branch can ever win. Using the two-network pattern with a single FP per network is the correct approach on S7-200/300.

Can I use the same edge memory bit for two FP instructions?

No. The FP instruction writes its result into the edge memory bit on every scan; using the same bit for two FP calls causes the second call to overwrite the first within the same scan and the toggle becomes non-deterministic. Assign M0.0 to the Set branch and M0.1 to the Reset branch, or use two separate P_TRIG FBs on S7-1200/1500.

Does the compact "AN Q0.0 = Q0.0" pattern work on S7-1500?

Yes, provided the code is in OB1 and the variable Q0.0 is accessed via the process image (the default). Inside a time-of-day or interrupt OB with direct I/O configured, the read and write happen in immediate mode and the toggle will glitch. Use the two-network gated form for any non-OB1 placement.

What input filter value should I set for a mechanical pushbutton on S7-1500?

Use the default 6.4 ms filter. The Siemens S7-1500 System Manual (function manual section on digital input modules) recommends this value for mechanical contacts. Reducing the filter below 3.2 ms risks double-counting the contact bounce; raising it above 12.8 ms risks missing intentional fast presses in jog-mode applications.

Can I cascade multiple single-button toggles on one CPU?

Yes. Wrap the logic in a parameterized FB (see the "Reusable Function Block" section above) and call it once per output. Each instance gets its own edge memory in its instance DB, so the toggles are independent. This pattern is verified on S7-300 with STEP 7 V5.5 and on S7-1200/1500 with TIA Portal V16 through V18.

Back to blog