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.
Problem Definition and Behavior Specification
The functional requirement is unambiguous:
- Press 1 of the momentary pushbutton (wired to
I0.0in the examples below): outputQ0.0transitions from OFF to ON. - Press 2:
Q0.0transitions from ON to OFF. - Press 3:
Q0.0returns 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 |
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):
- Operator presses the button.
I0.0goes 0→1. -
FP M0.0fires for one scan. RLO of Network 1 = TRUE (becauseAN M0.2is also TRUE). -
S M0.2latchesM0.2to 1. - Network 2:
FP M0.1also fires, butAN Q0.0= FALSE (because the process image ofQ0.0is still 0 from the previous scan), soR M0.2does 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. - Network 3:
M0.2= 1,Q0.0is written 1. End of scan. - Next scan:
I0.0is still 1 (operator still holding button).FP M0.0does not fire (no new edge).FP M0.1also does not fire. The output stays ON. The button-hold case is correctly handled. - Operator releases.
I0.0= 0. No edges. - Operator presses again.
FP M0.1fires,AN Q0.0= TRUE (becauseQ0.0is 1),R M0.2clearsM0.2. Network 1:AN M0.2= FALSE, so no Set. Network 3:Q0.0is 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 |
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
- 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.
- Open the STL/FBD/LAD editor online. Force
I0.0FALSE. VerifyQ0.0= 0 (initial state). - Toggle the pushbutton once. Verify in the watch table that
FP M0.0= TRUE for exactly one scan, thatM0.2latches to 1, and thatQ0.0transitions to 1. - Hold the button down for 5 seconds. Verify
Q0.0stays at 1. The edge must not re-fire. - Release, wait at least 100 ms (bounce settle), then press again. Verify
Q0.0transitions to 0 and the internal flag clears. - Repeat steps 3-5 ten times. Verify each press produces exactly one output transition in the correct direction.
- 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.
- On S7-1200/1500, open the Online & Diagnostics view of the FB instance DB. Confirm the
sEdgetag 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.