Problem Statement: Outputs Stay HIGH After Conditional FC/FB Execution Stops
On Siemens S7-300 and S7-400 controllers programmed with STEP 7 V5.5 (Service Pack 2 or later), conditional invocation of code blocks is a common structure for large machines: one FC or FB per operational sequence (Manual, Auto, Setup, Jog, E-Stop handler, etc.), all driven from OB1 by a mode-selecting flag such as M 10.0 or "ModeSelect".Auto. The trap is well known to senior engineers: any output coil, flag, or peripheral write that the skipped block set TRUE on its last execution remains TRUE on the physical output module until something else writes a zero. There is no implicit output reset when a block is not called. There is no automatic PLC sweep that turns off coils belonging to a deselected mode.
A junior programmer asked exactly this question: "How do you ensure outputs called in that block don't remain on when the block is not called anymore — e.g., changing from manual to auto or pressing an E-Stop?" The answer pattern from veteran STEP 7 programmers converges on three reliable techniques, layered around the same architectural principle: read inputs once, write outputs once, and never let a conditional call own a physical output directly. This article walks through the underlying mechanism, the three production-proven solutions, working STL / LAD / SCL implementations, and a verification procedure suitable for FAT and SAT.
Process Image Behavior: Why Outputs Latch
Every S7-300/S7-400 CPU maintains a process image table for inputs (PAE / PII) and outputs (PAA / PIQ). For a CPU 315-2 PN/DP (6ES7315-2EH14-0AB0) the default process image is 128 bytes for both inputs and outputs; the CPU 416-3 PN/DP (6ES7416-3ES06-0AB0) defaults to 512 bytes. Both ranges can be reconfigured in HW Config under CPU Properties → Cycle/Clock Memory → Process Image, up to 4096 bytes per partition.
During the cyclic OB1 execution the following sequence occurs:
- The CPU copies the physical input states into the PII at the start of OB1.
- The user program runs. Each
A I x.yreads the PII; each= Q x.ywrites the PIA (PIQ). - At the end of OB1 (and at each return-from-OB boundary) the CPU copies the PIQ to the physical output modules (e.g., SM 322 DO 32×DC 24V, 6ES7322-1BL00-0AA0).
If a conditional block call is skipped, the PIQ is not touched for any Q address that the skipped block would have controlled. Whatever was last written to PIQ is what the output module sees. The next OB1 sweep continues to read the PII (refreshed from physical inputs) but never refreshes the PIQ except through explicit user code or through an OB that calls the original block. This is the source of the latch.
| CPU Event | PII Refresh? | PIQ Refresh to Module? | PIQ Cleared When Block Skipped? |
|---|---|---|---|
| OB1 start | Yes (full or partial) | No | No |
| Conditional CALL skipped inside OB1 | No | No | No — outputs remain latched |
| OB1 end | No | Yes (full) | No |
| STOP → RUN transition | Yes | No until OB1 completes | No |
| E-Stop hardware interrupt (OB82/OB40) | Per OB rules | Per OB rules | No — explicit reset required |
The Process Image documentation is in the STEP 7 online help under Blocks → Process Image and Bit Memory and in the S7-300/S7-400 System and Standard Functions reference manual (Siemens entry ID 1214574).
FC vs FB Architecture in STEP 7 V5.5
Before selecting a fix, understand what survives between calls.
| Attribute | FC (Function) | FB (Function Block) |
|---|---|---|
| Memory model | No static memory; parameters are passed by value (IN) or by reference (IN_OUT, OUT) | Static memory held in Instance DB (IDB); STAT variables retain values across calls |
| TEMP variables | Stack-allocated; undefined on entry per IEC 61131-3 (Siemens also flags this in STEP 7 online help) | Same: undefined on entry; the compiler will warn but not error |
| Multi-instance | Not applicable | Supported via AR2 / DI register; one FB can host other FBs as STAT |
| Output coil ownership | If the FC is not called, no output is reset | If the FB is not called, no output is reset; STAT values persist in the IDB |
| Best fit for conditional sequence | Pure, stateless transforms with no direct Q writes | Mode/state machines where STAT retains the last mode |
The critical point is identical for both: the FB IDB retains the last STATE, but the OUTPUT COILS on Q addresses are not automatically re-evaluated. Even with an FB, if your code is = Q 8.0 inside a network gated by UC or CC, skipping the call leaves the physical output unchanged. The IDB remembers that Q 8.0 was last written TRUE, but that does not propagate to the PIQ until the next unconditional write.
The Read-Once / Write-Once Philosophy
The architectural rule that solves the problem cleanly is sometimes called the read-once / write-once (RO/WO) or single-source-of-truth pattern. It has three corollaries:
-
Every physical input is read in exactly one place. All other code references either the original symbol or a shadow flag — never a second
A Iinstruction elsewhere. - Every physical output is written in exactly one place. That place is a gateway function that always runs unconditionally each cycle, regardless of mode.
-
Conditional logic operates on memory flags, never on Q addresses. Conditional FC/FBs write
Mbits or DB tags; the unconditional gateway copies those tags to PIQ based on the active mode and interlocks.
This pattern is enforced by structured-text style audits in many DIN/VDI 3696 / VDI/VDE 3696 machine-safety code reviews. It eliminates the entire class of "output stuck on mode change" bugs because the gateway runs whether or not the mode-specific FC was called.
Solution 1 — Shadow Memory Buffers Between Logic and Physical Outputs
Create a shadow memory area — typically the bit memory area (M 0.0 to M 255.7) or a dedicated data block DB 100 — that represents the logical intent of every physical output. The conditional FC/FB writes only to the shadow. A separate, unconditionally-called gateway writes the shadow to the PIQ under safety-relevant interlocks.
Example layout for an 8-output station using bit memory:
| Shadow Symbol | Address | Physical Output | Purpose |
|---|---|---|---|
| shConveyorRun | M 50.0 | Q 8.0 | Conveyor motor contactor |
| shCylinderUp | M 50.1 | Q 8.1 | Pneumatic lift extend |
| shCylinderDn | M 50.2 | Q 8.2 | Pneumatic lift retract |
| shGripper | M 50.3 | Q 8.3 | Gripper close valve |
| shHeaterOn | M 50.4 | Q 8.4 | Process heater SSR |
| shLedGreen | M 50.5 | Q 9.0 | Status indicator green |
| shLedYellow | M 50.6 | Q 9.1 | Status indicator yellow |
| shBuzzer | M 50.7 | Q 9.2 | Audible alarm |
The conditional FC 10 ("Auto Sequence") writes to shConveyorRun, shCylinderUp, etc. A separate unconditionally-called FC 200 ("Output Gateway") copies those bits to PIQ each cycle. When the mode flag changes and FC 10 is no longer called, the shadows retain their last value, but the gateway still runs, still reads them, still copies them — which is exactly the problem you wanted to avoid if the shadows were never zeroed.
The trick: the gateway must include explicit zero-override logic for modes in which the conditional FC is not active. The conditional FC is therefore responsible for clearing all shadow bits it owns before exiting, OR (better) the gateway clears all shadows owned by the inactive mode before copying. The cleanest implementation is Solution 3 below.
Solution 2 — Pre-Call Output Reset
STL implementation in OB1 (English mnemonics; for German, substitute U/O/= for A/O/=):
// Network 1: Mode select
A M 10.0 // Auto mode flag
JC AUTO // If Auto, jump to AUTO label
// Network 2: Reset all outputs the conditional block owns
CLR // ACCU1 = 0
= Q 8.0 // Conveyor
= Q 8.1 // Cylinder up
= Q 8.2 // Cylinder down
= Q 8.3 // Gripper
= Q 8.4 // Heater
= Q 9.0 // LED green
= Q 9.1 // LED yellow
= Q 9.2 // Buzzer
// Network 3: Bypass conditional block entirely
JU END_PROG
AUTO: CALL FC 10 // Auto sequence block
END_PROG: NOP 0
Why this fails in practice:
- Adding a new output requires touching three places: the reset block, the conditional block, and the gateway. Engineers forget the reset block.
- If the reset block sets an output that a different unconditional routine (e.g., the E-Stop handler in OB82) was about to drive, the order of OB execution creates a 1-cycle race condition that can re-latch the output.
- It hides the actual control logic. The reset code looks like duplicate assignments of the same outputs already managed in the conditional FC.
- Code review tools like those used for IEC 61131-3 ST conformity and EN 61131-3 linting flag the duplicate Q writes as a violation of single-source-of-truth.
Use Solution 2 only for very small machines (≤16 I/O) where the cost of a gateway FC exceeds the maintenance cost of dual ownership. Even then, document it explicitly in the block header.
Solution 3 — Conditional Output Binding (Gateway FC Pattern)
This is the production-grade answer used in most large Siemens machine builders' standard libraries. The architecture has four layers:
-
Mode manager (unconditional): an FC or FB that owns the current mode register
"ModeReg".ActiveMode(BYTE) and the request flags. Always runs. -
Sequence blocks (conditional): FC 10 / FC 11 / FC 12 / etc. Each writes to a dedicated section of shadow memory (
DB 100.Mode10,DB 100.Mode11). Each is called conditionally based on the active mode. - Output gateway (unconditional): FC 200 reads the shadow memory of the active mode, applies safety interlocks, then writes the result to PIQ. This FC always runs.
-
Safety interlocks (unconditional, may be in OB82 / OB35 or a dedicated FB): reads hardware E-Stop, guard-door, and light-curtain bits, ORs them into a global
"SafetyOK"flag that the gateway consults before any output write.
The crucial property: the gateway is unconditional, so even when mode changes mid-cycle, the gateway still executes on the next OB1 sweep and writes the shadow of the new mode (or zeros, if no mode is active). The shadows of the previously-active mode are simply no longer read; they can be cleared at mode-change time by a one-shot network in the mode manager.
STL Implementation of FC 200 Output Gateway
FUNCTION FC 200 : VOID
// Output Gateway - unconditionally called from OB1
// Reads shadow memory owned by active mode, applies interlocks, writes PIQ
VAR_TEMP
tMode : BYTE;
END_VAR
BEGIN
NETWORK 1 // Read active mode and safety flag
L DB100.DBX 0.0 // ModeReg.ActiveMode byte load via accumulator
T #tMode
NETWORK 2 // Mode 10 = Manual - bind DB100.Mode10 to PIQ
L #tMode
L 10 // Compare to 10
==I
JCN M11
A "SafetyOK"
JC M10_OK
JU FORCE_OFF
M10_OK: A DB100.DBX 10.0 // shadow: conveyor
= Q 8.0
A DB100.DBX 10.1 // shadow: cylinder up
= Q 8.1
A DB100.DBX 10.2 // shadow: cylinder down
= Q 8.2
A DB100.DBX 10.3 // shadow: gripper
= Q 8.3
JU DONE
FORCE_OFF:
CLR
= Q 8.0
= Q 8.1
= Q 8.2
= Q 8.3
JU DONE
NETWORK 3 // Mode 11 = Auto - bind DB100.Mode11 to PIQ
M11: L #tMode
L 11
==I
JCN M12
A "SafetyOK"
JC M11_OK
JU FORCE_OFF
M11_OK: A DB100.DBX 11.0
= Q 8.0
A DB100.DBX 11.1
= Q 8.1
A DB100.DBX 11.2
= Q 8.2
JU DONE
NETWORK 4 // No valid mode active - force all outputs off
M12: CLR
= Q 8.0
= Q 8.1
= Q 8.2
= Q 8.3
= Q 8.4
= Q 9.0
= Q 9.1
= Q 9.2
DONE: NOP 0
END_FUNCTION
This is dense STL but it illustrates the principle. A real-world gateway typically uses a CASE or computed-jump structure indexed by mode number, with one section per mode. On an S7-400 with 4–6 modes this is the most maintainable form. On S7-300 with many modes, an indexed loop in SCL is preferred.
SCL Implementation of the Same Gateway
FUNCTION FC 200 : VOID
VAR_TEMP
tSafetyOK : BOOL;
tI : INT;
END_VAR
BEGIN
tSafetyOK := "SafetyOK";
// Default: all conditional-owned outputs OFF
FOR tI := 0 TO 7 DO
Q8_BYT[tI] := FALSE; // word-level write not allowed; use byte
END_FOR;
QW8 := 0; // zero output byte 8 (Q 8.0–Q 8.7)
QW10 := 0; // zero output byte 10 (Q 10.0–Q 10.7)
IF NOT tSafetyOK THEN
RETURN; // safety fail: outputs already zero
END_IF;
CASE DB100.ActiveMode OF
10: // Manual mode
Q8.0 := DB100.Mode10.Conveyor;
Q8.1 := DB100.Mode10.CylinderUp;
Q8.2 := DB100.Mode10.CylinderDn;
Q8.3 := DB100.Mode10.Gripper;
Q8.4 := DB100.Mode10.Heater;
11: // Auto mode
Q8.0 := DB100.Mode11.Conveyor;
Q8.1 := DB100.Mode11.CylinderUp;
Q8.2 := DB100.Mode11.CylinderDn;
Q8.3 := DB100.Mode11.Gripper;
12: // Setup mode
Q8.1 := DB100.Mode12.CylinderUp AND DB100.SetupEnable;
Q8.2 := DB100.Mode12.CylinderDn AND DB100.SetupEnable;
ELSE // Unknown mode
; // outputs remain zero (set above)
END_CASE;
END_FUNCTION;
Note the explicit QW8 := 0; at the top of the function. This is the architectural equivalent of Solution 2's pre-call reset, but it lives in the gateway so it cannot be forgotten when a new output is added. Adding a new output Q 8.5 only requires editing the appropriate CASE branch; the zero-default is automatic.
LAD Implementation of FC 10 (Conditional Auto Sequence)
NETWORK 1 // Conditional call from OB1
A "ModeReg.AutoActive"
JCN NOTAUTO
CALL FC 10
NOTAUTO: NOP 0
NETWORK 2 // FC 10 internal - write to shadow only, never to Q
// (Inside FC 10)
A "i_Start"
A "i_AutoReady"
S DB100.DBX 11.0 // shadow: conveyor on
A "i_Start"
AN "i_AutoReady"
R DB100.DBX 11.0 // shadow: conveyor off
Inside FC 10 you may write to DB100.Mode11.* freely without ever touching Q. The gateway owns Q writes exclusively.
FC or FB for Conditional Sequence Logic?
If the conditional block is stateless — pure transformation of inputs to desired outputs — use an FC. Examples: a scaling block, a recipe selector, a checksum calculator. Stateless FCs are easy to test in isolation and have no memory surprises. They can still latch outputs in the sense that any = Q x.y they contain survives until overwritten; just don't put = Q statements inside them.
If the conditional block holds state across cycles — step index in a sequence machine, last mode transition, accumulated counts — use an FB with an Instance DB. This is where multi-instance FBs shine: one parent FB "ModeSequencer" with child FBs "AutoStepper", "ManualStepper", etc. The parent always runs and decides which child to execute. The children operate on shadow memory.
| Criterion | Use FC | Use FB |
|---|---|---|
| Logic is stateless | Yes | No |
| State must persist between calls | No | Yes |
| Block is called from many OB contexts (OB1, OB35, OB82) | Yes | Caution: AR2/DI register conflicts |
| Multi-instance reuse across modes | No | Yes |
| Direct Q writes inside the block | Forbidden — use gateway | Forbidden — use gateway |
For OB35 (100 ms cyclic interrupt) and OB40 (hardware interrupt) calling the same conditional FB that OB1 also calls, watch the DI register. STEP 7 V5.5 saves DI on OB entry and restores on OB exit, so it is safe, but inside a multi-instance FB do not call a sibling FB that uses the same multi-instance — the DI context will be corrupted.
Step-by-Step Refactoring Procedure
Use this checklist when refactoring an existing machine program that exhibits stuck-on outputs.
- Inventory all conditional CALL / UC / CC statements. In SIMATIC Manager, use Options → Cross References → Used In on each FC/FB to find all call sites. Document each call with its condition network.
- Identify every Q address written inside each conditional block. Right-click the block, Go To → Local Variable → Ladder/STL → All Q symbols. Cross-reference these to physical terminals in HW Config.
-
Create shadow memory. For S7-300 with limited DB count, allocate bit memory M 50.0–M 99.7. For S7-400 or large S7-300 (CPU 319, 6ES7319-3FL04-0AB0), create
DB 100with a UDT template per mode. -
Replace each
= Qinside the conditional block with= DB100.ModeXX.Symbol. Use Find/Replace in the LAD/FBD/STL editor across the block. Re-compile; check the consistency check (Station → Check Consistency). - Write FC 200 (or FB 200) gateway. Implement the explicit zero-default and the CASE or jump-table binding as shown above.
- Call FC 200 unconditionally in OB1 at the very end of the cyclic program, after all conditional FCs have run. The gateway must be the last network before OB1 ends so it sees the freshest shadow memory.
-
Add mode-change clearing. One-shot network in the mode manager: when
"ModeReg".ActiveModechanges, zero all DB100.ModeXX sections. Detect change with edge evaluation (FP on"ModeReg".ActiveModeChanged). -
Cross-reference audit. Run Options → Cross References → All Used Locations for each
Qaddress. The output gateway should be the only writer. Any other writer is a violation. - Static analysis. Use a third-party tool such as the Siemens SIMATIC Safety package's S7FCT or a third-party IEC 61131-3 linter to enforce the single-writer rule.
- FAT test plan update. Add a test case: switch modes 100 times in a row, verify no output remains energized after the 100th switch.
Verification, Commissioning, and Online Checks
Verification is split into three phases: offline static review, online observation, and dynamic mode-switch testing.
Offline static review (before powering the machine)
- Open each conditional FC/FB in the LAD/FBD/STL editor. Use View → Symbol Selection and search for the literal "= Q". Any hit must be a gateway function, never a conditional sequence block.
- Cross-reference
QW8,QW10, etc. Each should show exactly one writer: the gateway. - Use the STEP 7 Reference Data → Program Structure view to confirm OB1 → FC 200 is the unconditional binding path and OB1 → FC 10/11/12 are conditional.
Online observation
- Open VAT_1 (Variable Table) and monitor
DB100.Mode10.*,DB100.Mode11.*, andQW8. Force a mode change withModifyonDB100.ActiveMode. Confirm thatQW8tracks the shadow of the new mode (or zeros, if no mode is active) within one OB1 cycle. - Use Monitor/Modify → Modify → Peripheral Outputs to force
QW8 := 0directly to PIQ and confirm the physical output module de-energizes (helps isolate whether the issue is software latching vs. wiring). - Check the scan time in CPU → Information → Scan Cycle Time. The gateway adds approximately 50–200 µs per mode branch on a CPU 315-2 PN/DP; well within a 10 ms cycle. On larger S7-400 stations with 8+ modes and dozens of outputs, expect 1–3 ms of additional cycle time.
Dynamic mode-switch test (mandatory for any safety-relevant machine)
- Start machine in Mode 10 (Manual). Energize one output (e.g., conveyor). Verify physical motion.
- Switch to Mode 11 (Auto). Verify the conveyor output is now driven by Mode 11 shadow, not the Mode 10 latched value.
- Switch to no-mode (e.g.,
ActiveMode := 0). Verify ALL outputs owned by Modes 10–12 are de-energized within one OB1 cycle. - Trip E-Stop. Verify
"SafetyOK"clears, gateway goes to FORCE_OFF path, all outputs drop to 0. Restore E-Stop and confirm outputs do not auto-resume. - Repeat 1000 mode-switch cycles. Verify no output ever remains latched.
Troubleshooting Matrix
| Symptom | Likely Root Cause | Diagnostic | Fix |
|---|---|---|---|
| Output remains HIGH after mode change | Conditional FC wrote directly to Q; not called in new mode | Cross-reference Q address in SIMATIC Manager | Route through gateway FC; conditional FC writes to shadow only |
| Output remains HIGH after E-Stop | E-Stop handler does not zero outputs; conditional block still owns Q writes | Watch PIQ in online monitor during E-Stop test | Add E-Stop override in gateway FORCE_OFF path; verify "SafetyOK" latches off on E-Stop |
| Output flickers at mode change | Gateway zero-default runs BEFORE conditional FC updates shadow in same cycle | Check gateway is last network in OB1 | Move gateway to final network; ensure mode-change clearing runs at TOP of OB1 |
| Output never turns on even in correct mode | Shadow memory never written because conditional FC condition is wrong | Monitor shadow bits online | Verify enable condition for conditional FC; check mode-register one-shot |
| Outputs correct in OB1 but wrong during OB35 (cyclic interrupt 100 ms) | OB35 also calls conditional FC, bypassing gateway | Check OB35 program structure | Either route OB35 outputs through same gateway or use separate shadow region |
| Outputs stuck after PLC STOP→RUN | IDB retains STAT values from previous run; PIQ retains last written value until OB1 fully executes | Check OB100 (warm restart) or OB101 (hot restart) | Initialize shadows in OB100; gateway runs unconditionally on first OB1 cycle |
| Output on for exactly one cycle after mode change | Mode-change clearing zeros shadows, but old shadows still bound by gateway that hasn't switched yet | Add cross-check network | Add one-cycle delay OR have gateway check mode-change flag and force zero |
| Different behavior on CPU 315-2 DP vs CPU 317-2 PN/DP | Process image size or OB priority differs | Compare HW Config process image settings | Match process image layout; verify OB priority (OB1 = 1, OB35 = 12 by default) |
Migrating to TIA Portal S7-1200/1500
The same architectural pattern applies on S7-1200 and S7-1500, but the implementation differs because optimized block access does not use a process image in the classic sense. Outputs in optimized FB instances are addressed symbolically. The gateway pattern translates directly: a "Gateway" FB with the unconditional assignment to the output tags, called from a cyclic OB (e.g., Main OB1 or a 10 ms watchdog OB). On S7-1500 the conditional FCs can be replaced by conditional call of FBs with multi-instance capability inside the parent. The principle — never let a conditional call own a physical output — is the same.
Related Best Practices
- Use
SETandCLRat the top of the gateway to make the default state explicit and lint-friendly. - Reserve a "shadow only" naming convention (prefix
shor suffix_req) so cross-reference searches onQaddresses can be filtered to find violations. - Configure OB35 priority 12 and a 100 ms cycle for periodic housekeeping (e.g., mode-change clearing). Do NOT call the gateway from OB35 — only from OB1 — to avoid scan-time jitter in the binding.
- Document in each conditional block's header comment: "This block writes shadow memory only. No direct Q writes." The STEP 7 block header comment field supports up to 16 lines of ASCII.
- Use UDTs (User-Defined Data Types) for each mode's shadow structure so that adding a new output is a single change to the UDT and the gateway CASE branch.
Why do my outputs stay HIGH in STEP 7 V5.5 when I stop calling an FC conditionally?
The CPU copies the process image of outputs (PIQ) to the physical module only at OB1 end. If a conditional FC is skipped, no instruction writes to PIQ, so the last value remains. The CPU never auto-resets outputs when a block is not called — that is the design of the cyclic process image on S7-300/S7-400.
Can I just call every FC/FB unconditionally from OB1 to avoid this?
Yes, but you lose mode isolation, scan time scales linearly with mode count, and you cannot easily enforce E-Stop priority. The production-grade alternative is to call all sequence blocks conditionally against shadow memory and route the physical outputs through a single unconditionally-called gateway FC that applies interlocks.
What is the read-once / write-once rule for STEP 7 output handling?
Read each physical input (I) in exactly one network and write each physical output (Q) in exactly one network (the gateway). All other code references symbolic tags or shadow memory. This eliminates duplicate Q writes that cause latch conflicts and is the same architectural rule used in EN ISO 13849-1 PL d/e compliant code.
Should I use an FC or an FB for a conditional sequence block?
Use an FC for stateless transforms (scaling, recipe selection). Use an FB with an Instance DB when state must persist across cycles (sequence step index, last mode). Neither should contain a direct = Q instruction — both should write to shadow memory owned by the gateway.
Does the same pattern apply on TIA Portal S7-1200/1500?
Yes. Optimized block access on S7-1200/1500 removes the classic process image but the principle is identical: route all physical output writes through a single unconditionally-called gateway FB that reads symbolic shadow tags. Conditional code writes only to the shadow. The advantage on S7-1500 is symbolic multi-instance FB support which makes the gateway pattern cleaner to scale.