Siemens Step 7 OB35 FC Call Order: Troubleshooting Non-Execution

David Krause19 min read
S7-300SiemensTroubleshooting
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

Problem Description

A common failure pattern in Siemens SIMATIC S7-300 / S7-400 programming with STEP 7 V5.x is that an FC called inside OB35 (the cyclic interrupt organization block) does not execute, executes only partially, or behaves differently depending on its position in the network chain. The user-reported symptom is reproducible: two FCs (FC1 and FC2) are both called in OB35. When FC2 is in network 1 and FC1 in network 2, FC1 does not run. When FC1 is in network 1 and FC2 in network 2, both run correctly. The behavior is order-dependent, which is the most diagnostic clue.

The reported OB35 cycle is 500 ms. The default for OB35 in STEP 7 is 100 ms (configurable from 1 ms to 60 s in HW Config → CPU Properties → Cyclic Interrupts). The runtime behavior is the same regardless of the period; the failure is independent of the configured interrupt interval.

Critical: A symptom where "FC1 works alone, FC2 works alone, but together they fail in a specific order" almost always points to a phase error, a local stack collision, an EN/ENO fault, or an SFC/SFB call inside one of the FCs that uses the same instance DB or the same system resource that the other FC also needs.

Root Cause Analysis

OB35 runs at priority class 12 (default) and is a time-driven execution context. Unlike OB1, OB35 has a strict, deterministic time slice: if the OB has not finished when the next interrupt fires, the CPU raises a time error (OB80) or, in the worst case, the PLC goes to STOP. Anything inside OB35 must therefore be lean, deterministic, and free of long-winded communications or nested interrupts.

The four root causes that match the user's description, ranked by frequency of occurrence in field service tickets:

# Root Cause How It Produces the Symptom
1 Phase error (OB121 / OB122) inside FC1 triggered by an indirect addressing or DB fault When FC2 runs first and writes a value FC1 later reads with a wrong offset, FC1 detonates the I/O access error. The CPU jumps into OB122 and the remainder of the OB35 cycle is consumed or aborted.
2 ENO drop on the first call If FC1 uses EN/ENO and the call instruction inside the FC encounters a math error, divide-by-zero, or invalid BCD conversion, ENO becomes 0. The next network's call of FC2 is then skipped because the rung condition is lost.
3 Local (TEMP) stack overwrite between the two FCs STEP 7 reuses the L stack between blocks in the same OB. If both FCs declare identical TEMP variable names with different sizes, or if a TEMP pointer is not initialized, the second FC sees corrupted addresses.
4 Shared instance DB or unmastered SFB inside FC1 An SFB like SFB0 (CTU), SFB1 (TON), or a PID FB may use the same DB the second FC also opens. Re-initialization of one corrupts state for the other.

For the case where "swapping the order makes both FCs work," Cause #1 (phase error / OB122) or Cause #2 (ENO propagation) is statistically the most likely explanation. The rest of this article walks through how to confirm which one is active on the bench.

OB35 Cyclic Interrupt Architecture

OB35 is one of the cyclic interrupt OBs (OB30–OB38) on the S7-300/400. Each runs at a fixed hardware-timer-driven interval that is independent of OB1's scan. This is what makes OB35 attractive for closed-loop control loops, time-slice polling, and high-priority housekeeping tasks.

OB Default Period Priority Class Typical Use
OB30 5 s 7 Free running background
OB31 2 s 8 Free running background
OB32 1 s 9 Slow process scans
OB33 500 ms 10 Slow control loops
OB34 200 ms 11 Medium-speed control
OB35 100 ms 12 Standard control loops (PID)
OB36 50 ms 13 Fast loops
OB37 20 ms 14 Fast loops
OB38 10 ms 15 Fastest cyclic interrupt

The configured period is set in HW Config → CPU Properties → Cyclic Interrupts. When the period is changed, the new value is downloaded with the hardware configuration, not with the program. After a download of new HW Config, the CPU performs a restart and OB35 begins running with the new period. See the official CPU manual at the SIMATIC S7-300 CPU 31xC and CPU 31x Reference Manual.

The key architectural facts every OB35 programmer must internalize:

  • OB35 runs to completion before the next interrupt can fire (single-instance per priority class).
  • OB35 can be interrupted by higher-priority OBs (OB80 time error, OB82 diagnostic, OB85 program sequence, OB121/OB122 phase errors via error OBs).
  • If the OB35 runtime exceeds the configured period, OB80 fires. If OB80 is not programmed, the CPU goes to STOP with "OB80 time error, OB not loaded" or "OB not loaded, restart failed."
  • The L stack (local / TEMP variables) is per-OB invocation. It is released when OB35 completes, but a phase error inside an FC can leak out before the OB terminates.

Why FC Execution Order Matters in OB35

Within a single OB, STEP 7 executes networks in linear order from network 1 downward. Each call to a function (FC) is a CALL FC x instruction; the call pushes a new block on the B stack (block stack) and consumes L stack space for the FC's TEMP variables. When the FC returns, the B stack pops and the L stack is logically freed. However, two practical issues can cause order-dependence:

L-Stack Reuse and Temp Initialization

STEP 7 reuses the same L-stack memory between blocks of the same priority class. If FC1 and FC2 both declare a TEMP variable named t_scratch but FC1 uses it as a pointer to a DB and FC2 uses it as an integer, the compiler may overlap their addresses. If FC1 leaves a non-zero value in t_scratch and FC2 reads it as an integer without re-initializing, FC2 produces wrong results. The reverse order would not fail because FC2's write would precede FC1's read. This is documented behavior of the STEP 7 Programming with STEP 7 V5.x manual.

ENO Drops and Rung Continuity

Every FC call in FBD or LAD can be evaluated with EN (enable input) and ENO (enable output). ENO is the equivalent of a BR (binary result) flag for that block call. If a math instruction inside FC1 fails (divide by zero, square root of a negative, invalid BCD conversion, floating-point underflow), the CPU sets BR = 0 inside FC1. Upon return, ENO of the CALL instruction becomes 0. Any subsequent instruction in OB35 that is wired directly after the CALL (in the same network or in networks without an explicit rung condition) sees ENO = 0 and is skipped if it depends on ENO continuity.

This is the most common "order matters" mechanism: if FC1 is first and triggers a math fault, FC2 (second) may be partially skipped. If FC2 is first and FC1 is second, FC2 runs cleanly, then FC1's internal fault stops only FC1's remaining work — not FC2 — so to the user "both work."

Rule of thumb: If "the FC in network 2 never runs," the very first thing to test is what ENO looks like at the output of the FC in network 1. Use --| |--( ENO of FC1 )-- CALL FC2 -- in a network by itself, or monitor BR in VAT.

OB121 / OB122 Phase Errors from Indirect Access

If FC1 performs an indirect DB read (DB[DBD#offset] with a calculated offset that is out of range) or accesses a non-existent input (PW 1000 on a CPU whose process image ends at 256), the CPU raises an I/O access error (OB122). The default behavior when OB122 is not programmed is to pass the error back to the calling block; if the calling block (OB35) does not handle it, OB85 (program sequence error) is generated. If OB85 is also not programmed, the CPU goes to STOP. If OB85 is programmed, the offending FC stops executing at the failing instruction, and the next FC in the chain runs but the first is already aborted.

Step-by-Step Diagnostic Procedure

  1. Capture the diagnostic buffer. In STEP 7, go online with the CPU, open PLC → Module Information → Diagnostic Buffer. Filter for events with priority 12 (OB35) and any OB80 / OB85 / OB121 / OB122 entries. The buffer time-stamps the exact moment the failure started. See Using the SIMATIC diagnostic buffer (S7-300/S7-400).
  2. Read the operating mode and LED state. If the CPU is in STOP, write down the SF (red) and BF (red) LED state. SF on with a flashing BF usually means a programming error; a steady SF with no BF is typically a hardware fault.
  3. Insert OB121, OB122, OB80, OB85 into the project (if they do not exist) as empty OBs. They are "programming error," "I/O access error," "time error," and "program sequence error" respectively. Re-download the program and put the CPU in RUN. With the error OBs loaded, the CPU no longer goes to STOP on the first fault; instead, the fault is logged in the diagnostic buffer and execution continues in the error OB. The symptom of "FC1 doesn't run" will then change to "FC1 partially runs and then OB122 fires."
  4. Enable the cross-reference (xref) view in STEP 7. Right-click on FC1 → Cross-references. Look at every DB and I/O address FC1 touches. Compare to FC2. Overlap of any DB or I/O area is your prime suspect.
  5. Single-step the OB. In LAD/FBD editor, place a breakpoint inside FC1 at its first network. Put the CPU in RUN-P. With Debug → Breakpoints enabled, the CPU halts on the breakpoint. Step through FC1 and FC2 to see which instruction fails and at which network.
  6. Inspect the L stack at breakpoint. Use PLC → Monitor/Modify to open the local variables view. Check the values of every TEMP in FC1 at the moment before each CALL inside FC1. Any uninitialized pointer is suspect.
  7. Temporarily comment out FC1's body and re-test OB35. If the system now runs cleanly, the failure is inside FC1's code. Restore one network at a time to localize the failing instruction.
  8. Test with a 1-second OB35 period. If the failure disappears at 1 s, the original problem is OB80 (time overrun). That means the FC's runtime exceeds 500 ms. Profile the FC with RD_SYT (SFC 64) at entry and exit to measure the FC's actual scan time.

Common Pitfalls and Their Fixes

Pitfall 1: Uninitialized TEMP Pointer

Symptom: FC1 contains ANY-POINTER or ANY-DB operations, sometimes a P#DBX 0.0 BYTE 10 constructed at runtime. After FC1 returns, the second FC reads garbage in its TEMP region because the same L-stack slot was reused.

Fix: Initialize every TEMP in the declaration table to a known default. For pointers, initialize to P#M 0.0 BYTE 0 or P#DBX 0.0 BYTE 0 at the very first network of the FC.

Pitfall 2: BCD/Integer Conversion Overflow

Symptom: FC1 reads a 16-bit counter value and converts it from BCD to integer. When the counter rolls over, the conversion fails. ENO drops. OB35 is short-circuited at the first FC call.

Fix: Wrap the BCD conversion in an ENO-smart pattern: monitor ENO and fall back to a safe default if it drops. Alternatively, use BTI in a way that traps invalid BCD, or use the modern BCD_I / I_BCD extended variants (S7-300 v3+).

Pitfall 3: Missing Error OBs

Symptom: The CPU unexpectedly goes to STOP the first time FC1 misbehaves. There is no OB80, OB85, OB121, OB122 in the program.

Fix: Always load empty OBs for OB80, OB82, OB85, OB121, OB122 in every S7 program. The empty OB absorbs the fault, logs it in the diagnostic buffer, and keeps the CPU in RUN. This is a defensive programming standard, not a workaround for bad code.

Pitfall 4: Calling SFC / SFB That Uses Internal DB

Symptom: FC1 contains an SFB 0 (CTU) call to instance DB 5. FC2 also opens DB 5 directly. When FC1 returns, DB 5's contents are in an inconsistent state. FC2 reads the wrong count.

Fix: Never use the same DB as both an instance DB and a global DB. Either use DB n exclusively for the SFB, or pass a different instance DB number.

Pitfall 5: OB35 Runtime Exceeds the Period

Symptom: The failure rate is intermittent. Under load (during a WinCC screen refresh or a Profibus DP update), the second FC appears to be skipped. OB80 fires silently because it is not loaded, but the CPU does not STOP because OB35 completes (just barely) the next time.

Fix: Use SFC 64 (RD_SYT) to read the system clock at OB35 entry and exit. Subtract to get the runtime. The runtime must be at least 10% less than the OB35 period to leave headroom. If not, increase the OB35 period in HW Config or move the heavy logic to a slower cyclic OB (OB32, OB33) or a timed task in OB1.

EN/ENO Handling in Interrupt OBs

The EN/ENO model in STEP 7 LAD/FBD is a non-intuitive trap for beginners. An FC call instruction --[ CALL FC1 ]-- has an EN input on the left and an ENO output on the right. If EN is FALSE, the FC is not executed and ENO is also FALSE. If EN is TRUE and the FC's first executable instruction fails, ENO becomes FALSE. The behavior is inherited from the BR (binary result) bit of the last math operation inside the FC.

Critically, if the FC has no math or conversion operations (for example, it only contains assignments and bit logic), ENO retains its previous value from the rung. In LAD, if the first network of OB35 is --[ CALL FC1 ]-- and the second network is --[ CALL FC2 ]-- with no explicit rung condition, STEP 7 treats the second network as a continuation of the first: the ENO of FC1 becomes the EN of FC2. If FC1's ENO is 0 (because of a math fault inside FC1), FC2 is never entered.

The simplest test for this is to insert an empty network between the two calls:

Network 1:     --[ CALL FC1 ]--       // ENO state depends on FC1 internals
Network 2:     --| |-- ( )--          // Empty rung breaks the ENO chain
Network 3:     --[ CALL FC2 ]--       // This now runs regardless of FC1 ENO

Or, more explicitly, force the rung condition with a constant TRUE:

Network 1:     --|M10.0|--[ CALL FC1 ]--   // M10.0 must be TRUE
Network 2:     --|M10.0|--[ CALL FC2 ]--   // Same M10.0 forces execution

Best practice is to make the FC call unconditional, and to handle the ENO state explicitly inside the FC by setting/resetting a status bit on its own.

Local Stack and Temp Variable Behavior

The L stack is a fixed-size memory area in the CPU that holds the TEMP variables of the currently active blocks. For the S7-300, the L-stack size is CPU-specific (e.g., 512 bytes for a CPU 314, 8 KB for a CPU 319). For the S7-400, it is configurable. Within a single OB execution, the L stack is consumed by the OB itself, then by each FC/FB it calls.

The L stack is not zeroed between blocks. STEP 7 just reuses the bytes. The first network of every FC should explicitly initialize its TEMP variables. A common pattern is:

FUNCTION FC1 : VOID
VAR_TEMP
    tIndex : INT;
    tPtr   : POINTER;
    tLimit : INT;
END_VAR

BEGIN
    tIndex := 0;            // Force init
    tLimit := 100;
    tPtr   := P#DBX 0.0;    // Force init to known value
    
    // ... rest of FC1 code ...
END_FUNCTION

If a TEMP is a complex type (DWORD, REAL, POINTER, STRING), the uninitialized value can be anything. The compiler may or may not overlap slots between FC1 and FC2; you cannot rely on it.

Code Examples

Example 1 — Defensive OB35 Pattern

// OB35 - Cyclic Interrupt @ 100 ms (configurable)

NETWORK 1   // Initialize OB-level status
      CLR
      = M 100.0       // Local health flag

NETWORK 2   // Always run FC1 (unconditional CALL)
      SET
      = M 10.0
      A M 10.0
      JNB _skip
      CALL FC 1
_skip: NOP 0

NETWORK 3   // Always run FC2 (unconditional CALL)
      SET
      = M 10.1
      A M 10.1
      JNB _skip2
      CALL FC 2
_skip2: NOP 0

Example 2 — ENO-Safe Math in FC

FUNCTION FC 10 : VOID
VAR_TEMP
    tRaw   : INT;
    tScale : REAL;
    tValid : BOOL;
END_VAR

BEGIN
    tValid := TRUE;
    tRaw   := IW 100;             // Safe analog read
    
    IF tRaw > 27648 OR tRaw < 0 THEN
        tValid := FALSE;
        tScale := 0.0;            // Safe default
    ELSE
        tScale := INT_TO_REAL(tRaw) / 27648.0 * 100.0;
    END_IF;
    
    // Output to process image
    IF tValid THEN
        L tScale;
        T QD 200;                 // Output to drive setpoint
    END_IF;
    
    // Set BR = 1 to keep ENO = 1 on return
    SET ;
    SAVE ;
END_FUNCTION

Example 3 — Runtime Profiling with SFC64

// At OB35 entry
CALL SFC 64                  // RD_SYT - read system time
      RET_VAL := tStart_DT    // DATE_AND_TIME type
      OUT     := tStart_DT

// ... rest of OB35 ...

// At OB35 exit (last network)
CALL SFC 64
      RET_VAL := tEnd_DT
      OUT     := tEnd_DT

// Compute delta in ms (compare with OB35 period)
// tEnd_DT - tStart_DT must be < 0.9 * OB35_period

Verification Tests

  1. Diagnostic buffer clean. With the fixed program online, the diagnostic buffer should show no OB80, OB85, OB121, OB122 events during a 1-hour observation window.
  2. Counter check. Add a counter (DBW 0) at the entry and exit of each FC. After 600 OB35 cycles (60 s at 100 ms), the count should match exactly. If FC1 is at 600 and FC2 is at 0, FC1 is consuming all of OB35's runtime and FC2 is being starved.
  3. Variable scope test. Force the rung condition of both FCs to TRUE in a forced-variable test. Both should run. Remove the force. If the failure returns, the ENO chain is the culprit.
  4. Period stress test. Halve the OB35 period (e.g., 100 ms → 50 ms). If the failure returns, runtime is too high. If it stays clean, runtime is fine and the original failure was logic/ENO related.
  5. Empty-body test. Replace each FC's body with a single SET;SAVE;. The two empty FCs should both run unconditionally in OB35. If they do, the issue is in the FC bodies. If they don't, the issue is in the OB35 wiring itself.

Diagnostic Buffer Analysis

The diagnostic buffer is the single most important tool for S7-300/400 fault analysis. The most common fault codes for OB35-related issues are listed below.

Event ID OB Meaning Action
0x2520 OB80 Time error — OB35 period exceeded Profile runtime; increase period; move heavy code to OB1
0x2521 OB80 Time error — OB35 not loaded (period too short for empty OB) Load OB35; check period value
0x2522 OB80 Cyclic interrupt time stamp fault Reset CPU; check battery
0x3582 OB85 Program sequence error — OB called but not loaded Load the OB that the CPU tried to call
0x3942 OB85 Process image update error Check for SFC26/SFC27 misuse
0x2524 OB121 Programming error — BCD/invalid instruction/area length Inspect the call stack entry in the buffer; fix the FC
0x2525 OB122 I/O access error — bad address or area length Check P# and address ranges; correct the FC
0x2530 OB82 Diagnostic interrupt — module removed/faulted Check the diagnostic interrupt buffer; reseat the module
0x2534 OB83 Insert/remove interrupt Verify module configuration matches actual hardware
0x2550 OB87 Communication error Check Profibus/Profinet diagnostics; check SFC calls

For each event, the diagnostic buffer shows the call stack — the chain of blocks that were active when the fault occurred. If the call stack shows OB35 → FC1 → Instruction at address X, the failing instruction is in FC1 and the address in the call stack gives the byte offset of the instruction. This is the fastest path to the failing line.

For the full specification of event IDs, refer to the SIMATIC S7-300 / S7-400 system and standard functions reference manual.

Recommended Long-Term Architecture

For a production S7-300 program, the recommended pattern is:

  1. OB1 — Slow, non-time-critical logic; runs as fast as the scan allows.
  2. OB35 — Time-critical control loops (PID, fast filtering) at a fixed period of 100 ms or longer. Keep the body under 50% of the period.
  3. OB82, OB85, OB121, OB122 — Always loaded as empty OBs. This is the safety net.
  4. FCs called from OB35 — Modular, time-budgeted, with ENO self-managed by SAVE instructions. Never call SFBs that need their own error OBs not yet loaded.
  5. DB ownership — Each FC uses its own DB; never share an instance DB with a global DB.

If the application requires the FCs to be called only in OB35, the proper structure is shown in the "Defensive OB35 Pattern" code block above. If the FCs are also called from OB1, make sure that the L-stack initialization is done at the top of each FC — STEP 7 does not guarantee a clean L stack between OB1 and OB35 invocations.

FAQ

Why does my FC work in OB1 but not in OB35?

OB35 runs at a fixed higher priority than OB1, has a strict time slice, and shares the L stack with all FCs in the same priority. A latent math error, uninitialized TEMP, or shared DB that is benign in OB1's longer scan becomes a hard fault in OB35. Insert empty OBs for OB80/OB85/OB121/OB122 to keep the CPU in RUN and inspect the diagnostic buffer call stack.

Does FC execution order really matter in OB35?

Yes. Networks execute linearly, and an FC's ENO state can be the implicit rung condition of the next network. If FC1's internal math fails, ENO becomes 0 and FC2 (called in the next network) is skipped. Force unconditional execution with a constant TRUE bit on the EN input of each CALL.

What is the default period of OB35?

The STEP 7 default is 100 ms. The period is set in HW Config → CPU Properties → Cyclic Interrupts and can be from 1 ms to 60 s. The value is downloaded with the hardware configuration, not the program. The OB priority class is 12.

How do I measure OB35 runtime?

Call SFC 64 (RD_SYT) at OB35 entry and exit to read the system time, then subtract. The runtime must be at least 10% less than the configured OB35 period. If it is not, the CPU will raise OB80 (time error) or go to STOP. See the SIMATIC system software manual on system and standard functions for SFC 64 details.

Should I always load OB80, OB85, OB121, and OB122 in my S7-300 program?

Yes. These are the programming, time, sequence, and I/O access error OBs. Loading them as empty OBs prevents the CPU from going to STOP on the first fault. The error is logged in the diagnostic buffer with the full call stack, which is the fastest path to the failing instruction. This is a defensive standard for any S7 program that runs in production.

Back to blog