Counting S7-300/400 OB Executions: HMI/SCADA Implementation Guide

David Krause16 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

Overview

Siemens SIMATIC S7-300/400 Organization Blocks (OBs) form the interface between the CPU operating system and the user program. Each OB class is triggered by a specific event: time-of-day, cyclic, hardware interrupt, error condition, or restart. Tracking the execution count of these OBs is essential for diagnostics, especially for error OBs (OB80-OB87, OB121-OB122) where the count itself signals fault frequency, and for cyclic OBs (OB35) where the count confirms scan integrity on the HMI or SCADA layer.

This reference documents three methods to count OB executions and expose the count to HMI/SCADA: edge detection on a shared flag, direct increment, and timestamp analysis via the OBx_DATE_TIME TEMP variable. All examples apply to S7-300 CPUs (CPU 312, 314, 315-2 DP, 317-2, 319-3) and S7-400 CPUs (CPU 412, 414, 416, 417) running STEP 7 V5.5 or later. For TIA Portal / S7-1500 implementations, the data types and counter methods differ and use the OBxx_PREV_CYC and OBxx_RES attributes of the TCON_IP_RFC family of blocks.

OB Categories and Trigger Conditions

OBs are partitioned into classes that determine the priority and trigger source. According to the S7-300/400 System and Standard Functions Reference Manual, the categories relevant to execution counting are:

OB Class Range Trigger Default Priority
Time-of-Day OB10-OB17 Configured date/time (once or periodic) 2
Delay OB20-OB23 SFC32 (START_SDT) 3-6
Cyclic OB30-OB38 Configurable interval (OB35 default 100 ms) 7-15
Hardware Interrupt OB40-OB47 Module interrupt (rising/falling edge) 16-23
Time Error OB80 Cyclic OB exceeded scan time 26
Power Supply OB81 Battery / 24 V failure 26
Diagnostic Interrupt OB82 Module diagnostic event 26
Remove/Insert OB83 Module removed or inserted 26
CPU Hardware Fault OB84 Interface error to submodule 26
Program Execution OB85 OB not loaded, image update error 26
Rack/Station Failure OB86 DP/PN station lost or returned 26
Communication OB87 Communication error 26
Background OB90 Minimum scan time reserved for OB90 29
Restart OB100/101/102 Warm / hot / cold restart 27
Programming Error OB121 Illegal instruction / type error Inherits from calling OB
I/O Access Error OB122 Direct I/O access fault Inherits from calling OB

OB35 is the standard 100 ms cyclic interrupt on S7-300 CPUs and is the most commonly counted OB for scan verification. The interval is set in HW Config → CPU Properties → Cyclic Interrupts, with a range of 1 ms to 60 000 ms per the S7-300 CPU 31xC and CPU 31x: Technical Data manual. Misconfigured values can cause OB80 to fire when OB35 cannot complete within the interval.

OBs That Force the CPU to STOP Mode

Not every OB must be loaded. The CPU only enters STOP if an OB that handles a specific fault class is not present when the fault is detected. The S7-300/400 System and Standard Functions manual lists the critical OBs in chapter 1:

  • OB80 – Time error (OB1/OB35 scan exceeded; OB90 priority violation)
  • OB81 – Power supply error (battery low, 24 V missing)
  • OB82 – Diagnostic interrupt from DP/PN module
  • OB83 – Insert/remove interrupt during RUN
  • OB84 – CPU hardware fault (MPI/DP interface)
  • OB85 – Program execution error (e.g., referenced OB not loaded, image update error)
  • OB86 – Rack/station failure (DP/PN slave lost)
  • OB87 – Communication error (global data, SFB link failure)
  • OB121 – Programming error (BCD conversion, range, type)
  • OB122 – I/O access error (direct I/O access to missing module)

Time-of-day (OB10-OB17), delay (OB20-OB23), cyclic (OB30-OB38), and hardware interrupt (OB40-OB47) OBs do NOT cause a STOP when not loaded — their events are simply ignored. Startup OBs (OB100-OB102) execute only during the corresponding restart; if the user does not load them, the default behavior (as configured in HW Config → Startup) applies.

Engineering rule: In a production S7-300/400 program, always load OB80, OB82, OB85, OB86, OB121, and OB122 as a minimum. A missing OB85 will STOP the CPU on the first reference to a non-existent OB in the program.

Method 1: Edge Detection on a Shared Flag

The first method uses a single shared flag (M10.0) toggled by the target OB on every entry, with a counter in OB1 that increments on both rising and falling edges. This produces a count equal to twice the OB entry count, suitable for slow error OBs such as OB86 (rack failure).

STL Logic in the Target OB (OB86 example)

// OB86 - Rack/Station Failure
      AN    M 10.0          // AND NOT M10.0 (invert state)
      =     M 10.0          // Toggle M10.0 on every OB86 entry

On every OB86 entry, M10.0 flips. The flag is global, so OB1 sees the new state on its next scan.

STL Logic in OB1

// OB1 - increment counter on either edge of M10.0
      A(
      A     M 10.0          // Sample M10.0
      FP    M 11.0          // Rising edge of M10.0
      O(
      A     M 10.0          // Sample M10.0
      FN    M 11.1          // Falling edge of M10.0
      )
      )
      CU    C 0             // Counter C0 increments on each edge

Two edge flags (M11.0 rising, M11.1 falling) are required because a single FP cannot see both transitions. The C0 counter therefore advances by 2 per OB86 execution. Divide by 2 in the HMI to recover the actual execution count. Each M area edge-detection bit holds its state across scans; reset M11.0 and M11.1 in the restart OB if you want fresh edge memory after a CPU restart.

Caution: The source explicitly warns against using this pattern for OB35. OB35 runs every 100 ms and the counter C0 (range 0-999) will roll over in less than 50 s. Use a memory word (MW) for fast-cyclic OBs, or switch to Method 2. Additionally, the OB1 dependency means the count can lag by up to one OB1 cycle during heavy main-scan load.

Method 2: Direct Increment in the Target OB

The simplest and most reliable method. Each OB increments its own memory word on entry. No global flag, no OB1 dependency, no edge detection. This is the recommended baseline for production code.

STL Logic in the Target OB

// OB35 (Cyclic Interrupt, 100 ms)
      L     MW 100         // Load current count
      L     1              // Add 1
      +I                   // Integer add (16-bit)
      T     MW 100         // Store result

Equivalent LAD: a single counter coil (CTU) with the CU input driven by a constant TRUE, or a MOVE block plus +1 logic. Equivalent FBD: ADD_I (MW100, 1, MW100). For OB35 at 100 ms, MW100 increments at 10/s. Over 24 hours, the count approaches 864 000, well within the 16-bit integer range (-32 768 to 32 767) only briefly. Use a 32-bit counter (MD) for sustained OB35 counting:

// OB35 with 32-bit double-word counter
      L     MD 102         // Load 32-bit count
      L     L#1            // Load long constant 1
      +D                   // Double-integer add (32-bit)
      T     MD 102         // Store

The 32-bit value reaches 2 147 483 647 in approximately 6.8 years at 10 increments/s, which exceeds the practical service life of the PLC. For arithmetic in SCL, use the equivalent DINT variable:

// SCL version
"OB35_Count" := "OB35_Count" + 1;

Method 3: OBx_DATE_TIME Timestamp

Every error OB and most restart OBs provide a TEMP local variable of data type DATE_AND_TIME named OBxx_DATE_TIME (e.g., OB86_DATE_TIME). This 8-byte value is the CPU wall-clock time at the moment the OB was triggered. Storing the previous timestamp in a static (M) variable and computing the delta gives a precise inter-event interval and can be used to derive a per-window count.

TEMP Variable Layout per OB

OB OBxx_DATE_TIME Present OBxx_RES Available Notes
OB10-OB17 Yes No Auto-populated at OB entry
OB35 Yes Yes PHASE offset in OB35_RES
OB80 Yes No Fault info in OB80_FLT_ID
OB82 Yes No Module diag in OB82_MDL_ADDR
OB86 Yes No Event class in OB86_EV_CLASS
OB100-OB102 Yes No Restart reason in OB100_STRTUP
OB121 No No Block / address in OB121_BLK / OB121_PRG_ADDR
OB122 No No I/O address in OB122_MEM_ADDR

STL Logic in the Target OB (OB86 example)

// OB86 - save timestamp to global area
      L     DBB 12          // Bytes 12-19 of OB86_DATE_TIME
      T     DBB 220         // (TEMP variable is local; persist to MD220)
      L     DBB 13
      T     DBB 221
      L     DBB 14
      T     DBB 222
      ...                  // (simplified - in practice use LAR1 / L DBD)

To read a 32-bit slice of the DATE_AND_TIME efficiently, use the load double-word command after computing the local address with LAR1 + TAR2. The S7-300/400 System and Standard Functions manual, section 1.x, documents the exact byte offset per OB.

Critical: The OBx_DATE_TIME TEMP variable is only valid inside the OB. To persist the value across OB executions, copy it to a global M area or instance DB before exiting the OB. Otherwise the value is overwritten on the next OB start and the timestamp is lost.

Using the Saved Timestamp

// OB1 - compute interval from previous OB86 timestamp
      CALL  SFC 1           // READ_CLK
      RET_VAL: MW 200
      CDT   : MD 204        // Current time (8 bytes)

      L     MD 220          // Previous OB86 time (low 4 bytes)
      L     MD 204          // Current time (low 4 bytes)
      -D                    // Delta in seconds (approx)
      T     MD 228          // Interval
      L     MD 204
      T     MD 220          // Save current for next pass

This is the technique used by the source contributor: rather than a counter, the OBx_DATE_TIME variable gives the wall-clock time of each OB call. In a WinCC flexible trend view, plot the saved timestamp against the cycle counter to get a Gantt-style fault timeline.

Method Comparison

Method Code Location Counter Storage Range Recommended For Limitations
Edge detection (M flag) Target OB + OB1 S7 Counter C0-C255 0-999 OB86, OB82, OB100 (slow OBs) Not for fast OBs; OB1 dependency; counts by 2
Direct increment (MW/MD) Target OB only MW (16-bit) or MD (32-bit) ±32 767 or ±2.1×10&sup9 All OBs incl. OB35, OB10 No built-in overflow alarm; reset on cold restart
OBx_DATE_TIME timestamp Target OB only M area or instance DB Wall-clock time Diagnostic logging, SLA tracking, fault timeline Requires wall-clock buffer; not a direct counter

STEP 7 Implementation: Step-by-Step

The following procedure implements Method 2 (direct increment) for OB10, OB35, OB80, OB82-OB87, OB100, OB121, and OB122. Refer to the Programming with STEP 7 V5.5 manual for the general OB creation workflow.

Prerequisites

  • STEP 7 V5.5 SP4 or later, with the project containing the target S7-300/400 station.
  • Symbol table access to define counter tags (Symbol Editor → Options → Symbol Table).
  • WinCC flexible 2008 SP3, TIA Portal WinCC, or third-party SCADA (Citect, InTouch, FactoryTalk View) with the relevant HMI tags configured.
  • CPU firmware that supports the target OBs (S7-300 CPU 31x from firmware V2.0 onward; S7-400 CPU 41x from V4.0 onward).

Procedure

  1. Open SIMATIC Manager and the target S7 program.
  2. In S7 Program → Blocks, confirm that the required OBs exist (OB10, OB35, OB80, OB82-OB87, OB100, OB121, OB122). If missing, right-click → Insert New Object → Organization Block and select the number.
  3. Open the Symbol Table and assign names such as OB10_Count, OB35_Count, OB86_Count to data words MW110 through MW124 (one per OB). Use 32-bit ranges (MD) for OB10 and OB35 if long-term accumulation is needed.
  4. Open OB35 and insert the increment code in network 1:
      L     "OB35_Count"     // MW110
      L     1
      +I
      T     "OB35_Count"
  1. Repeat for each target OB. For OB80-OB87, OB100, OB121, OB122, replace the symbol with the matching counter symbol and the same three-line increment block.
  2. For OB10 (time-of-day), configure the interrupt in HW Config → CPU Properties → Time-of-Day Interrupts. Select OB10 and set the execution interval (e.g., every minute, every hour, once daily). OB10 default is once per minute when enabled.
  3. For OB86 specifically, set the priority in HW Config to 26 (default). If you have multiple DP masters, you may have multiple OB86 priorities to cover all master systems.
  4. Compile (Program → Compile All) and download (PLC → Download) the program to the CPU. Switch the CPU to RUN.
  5. Open the HMI project. In the tag table, add a new HMI tag for each counter (e.g., OB35_Count_HMI) with PLC address MW110 and acquisition cycle 1 s.
  6. Insert an I/O field on the HMI screen bound to the tag, set the display format to decimal, and connect to a screen number that is also called from the HMI alarm log (optional).

Verification

  1. Open STEP 7 Online → Monitor/Modify and force the CPU to RUN.
  2. Add OB35_Count to the watch table. After 10 s, the value should be approximately 100 (10 increments/s × 10 s); allow a tolerance of ±2 for scan jitter.
  3. Trigger OB86 by powering off a DP slave. OB86_Count should advance by 1 (or by 2 if both “lost” and “returned” events fire) within the OB86 priority-class period. Verify with the diagnostic buffer (PLC → Diagnostic Buffer).
  4. Trigger OB121 by writing a value > 9 999 into a BCD-converted variable. OB121_Count should advance by 1.
  5. On the HMI, navigate to the diagnostics screen and confirm the displayed value matches the online watch table to within one scan.

HMI Tag Mapping for WinCC flexible / TIA Portal

Each counter is a 16-bit word; the HMI tag can be configured with a 1 s acquisition cycle. For TIA Portal Unified, use the same PLC tag in the PLC data types and expose it to the HMI via the HMI connection. For WinCC flexible 2008 SP3, the tag address is simply the same MW address used in the PLC:

PLC Tag Address Data Type HMI Tag Acquisition Display
OB10_Count MW 110 INT OB10_Count_HMI 1 s Decimal
OB35_Count MW 112 INT OB35_Count_HMI 1 s Decimal
OB80_Count MW 114 INT OB80_Count_HMI 1 s Decimal
OB82_Count MW 116 INT OB82_Count_HMI 1 s Decimal
OB85_Count MW 118 INT OB85_Count_HMI 1 s Decimal
OB86_Count MW 120 INT OB86_Count_HMI 1 s Decimal
OB100_Count MW 122 INT OB100_Count_HMI 1 s Decimal
OB121_Count MW 124 INT OB121_Count_HMI 1 s Decimal
OB122_Count MW 126 INT OB122_Count_HMI 1 s Decimal

For non-retain counts, the values reset on cold restart. To retain across restart, mark the corresponding MW as retentive in CPU Properties → Retentive Memory, or store the value in an instance DB with the RETAIN attribute. The default retentive range on a CPU 315-2 DP is MB0-MB15 by default; add MW110-MW126 to the retentive bit/byte/word/double-word list as needed.

Best Practices and Caveats

  • Use 32-bit counters (MD) for cyclic OBs. OB35 at 100 ms overruns a 16-bit INT in 54 minutes. A 32-bit DINT overruns in 6.8 years and is the correct choice for any OB that fires more often than once per minute.
  • Avoid S7 counters (C0-C255) for high-frequency OBs. They are 16-bit word counters (0-999), have limited range, and add significant SCL/STL overhead. Use MW/MD unless the application specifically requires the counter instruction.
  • Do not run heavy logic in error OBs. OB86 in particular can fire in bursts during DP station reconnection (rack failure, return, partial failure). Limit the increment to a single L+L+T sequence; defer heavy processing to OB1.
  • Set the OB priority correctly in HW Config. OB35 default priority is 12 on a CPU 315-2 DP, and OB86 default is 26. A higher-priority OB can interrupt OB1, so OB1-based edge detection (Method 1) is not deterministic. Use Method 2 for any OB that may interrupt OB1.
  • Disable the increment for OBs you do not actually load. If OB121 is not in the program, OB85 may fire instead (program execution error) and produce confusing counts. Either load all error OBs or document the omission.
  • Persist the restart counters separately. OB100/101/102 only fire on restart, so their counts are restart-impervious (if stored in MB0-MB15) and act as a service-interval gauge. Use them to track power cycles, operator-initiated restarts, and HMI-forced restarts.
  • Use instance DBs for OB-specific data. Instead of M area, store the count in a dedicated DB (e.g., DB100 → "OB_Counters") with structured tags per OB. This improves HMI tag generation in TIA Portal, gives symbolic access, and avoids M area fragmentation.
  • Reset counters on cold restart only. Use OB102 (cold restart) to clear the MD/MW counters; let them persist through warm restarts (OB100) to retain history across operator-driven warm restarts. Document the reset policy in the project header.
  • Watch the OB1 scan time. A 100 ms OB35 with 80 ms of OB1 work will fire OB80. If OB80_Count advances unexpectedly, the cyclic interrupt cannot complete in time; increase the OB35 interval or offload code to OB90 (background) per the S7-300 CPU manual recommendation.

Troubleshooting Matrix

Symptom Likely Cause Diagnostic Step Remediation
OB35_Count does not advance OB35 not loaded, or cyclic interrupt disabled in HW Config Online → CPU Information → Cyclic Interrupts Load OB35, verify interval in HW Config (1-60 000 ms)
OB35_Count advances irregularly OB35 scan time exceeds configured interval → OB80 fires Check OB80_Count, monitor OB35 runtime in OB35_PREV_CYC Increase OB35 interval or reduce OB35 code size
OB86_Count increments twice per event DP station failure “came” and “gone” both count as OB86 calls Inspect diagnostic buffer for event class (incoming/outgoing) Expected behavior; divide by 2 in HMI if needed
Counter resets on STOP→RUN MW/MD not marked retentive CPU Properties → Retentive Memory → add MW Define MB0-MBn as retentive bit/byte/word/double-word
CPU goes to STOP despite OB loaded OB contains its own programming error (OB121 inside OB86) Diagnostic buffer → STOP cause analysis Inspect nested OB; keep error OBs minimal (avoid L DBB/T DBB chains)
HMI shows 0 forever HMI tag acquisition cycle or area pointer mismatch WinCC flexible → Connections → Area Pointers Set tag update to 1 s, confirm MW address and rack/slot
OB121_Count spikes after download Block inconsistency during download causes temporary access errors Diagnostic buffer → filter OB121 events Expected once per download; ignore single occurrence or reset in OB100
OB10_Count does not match configured interval Time-of-Day interrupt not enabled in HW Config CPU Properties → Time-of-Day Interrupts → OB10 → Active Activate OB10 in HW Config, set start time and period

Diagnostic Buffer Cross-Reference

For any non-zero counter that is not a cyclic OB, cross-reference the count against the STEP 7 diagnostic buffer (PLC → Diagnostic Buffer). The buffer lists every OB entry with timestamp, OB number, priority, and event class. A one-to-one match between the increment and the buffer entry confirms the counter is accurate. OB80, OB82, OB85, OB86, and OB121 events are logged with full context (module address, slot, fault code) and are the primary source for root-cause analysis beyond the count itself.

FAQ

Which Siemens OBs cause the CPU to enter STOP mode if not loaded?

The error and interrupt OBs: OB80, OB82, OB83, OB84, OB85, OB86, OB87, OB121, and OB122. Time-of-day, delay, cyclic, and hardware interrupt OBs (OB10-OB47) do not force a STOP; their events are simply ignored. Always load the error OBs in any production S7-300/400 program.

Can I use a Siemens counter (C0-C255) inside OB35?

Technically yes, but not recommended. The S7 counter has a range of 0-999. At the default 100 ms OB35 interval the counter rolls over in under 50 s, losing the execution history. Use a 32-bit memory double word (MD) for cyclic OBs and reserve S7 counters for slow error OBs such as OB86.

How do I access the OB start time?

Every error OB (OB80-OB87) and restart OB (OB100-OB102) provides a TEMP variable named OBxx_DATE_TIME of data type DATE_AND_TIME (8 bytes). Copy it to a global M area or instance DB before exiting the OB; the TEMP value is invalidated when the OB completes.

Why does OB86 count twice for one DP failure?

OB86 fires on both the “station failure” event (incoming) and the “station return” event (outgoing). Each event is a separate OB call. Divide the counter by 2 in the HMI to recover the failure count, or use SFC13 (DPNRM_DG) for richer DP slave diagnostics.

What is the difference between OB100, OB101, and OB102?

OB100 is the warm restart (default on S7-300), OB101 is the hot restart (S7-400 only), and OB102 is the cold restart. Each is executed once at the corresponding restart. Counting them provides a per-restart-type history useful for tracking operator-initiated restarts and power cycles.

Why is my OB35 count not exactly 10 per second?

The 100 ms interval is configured, but the cyclic interrupt is triggered by the internal timer, not the OB1 scan. Jitter of ±1-2 counts per second is normal. If the count is consistently low (e.g., 9/s), OB80 is firing and stealing CPU time; check OB80_Count and OB35 runtime.

Back to blog