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.
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.
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.
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
- Open SIMATIC Manager and the target S7 program.
- 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.
- Open the Symbol Table and assign names such as
OB10_Count,OB35_Count,OB86_Countto data words MW110 through MW124 (one per OB). Use 32-bit ranges (MD) for OB10 and OB35 if long-term accumulation is needed. - Open OB35 and insert the increment code in network 1:
L "OB35_Count" // MW110
L 1
+I
T "OB35_Count"
- 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.
- 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.
- 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.
- Compile (Program → Compile All) and download (PLC → Download) the program to the CPU. Switch the CPU to RUN.
- 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. - 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
- Open STEP 7 Online → Monitor/Modify and force the CPU to RUN.
- Add
OB35_Countto 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. - Trigger OB86 by powering off a DP slave.
OB86_Countshould 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). - Trigger OB121 by writing a value > 9 999 into a BCD-converted variable.
OB121_Countshould advance by 1. - 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.