Siemens S7 Error OBs Why Empty Blocks Hide Faults - Best

David Krause21 min read
Best PracticesSiemensTIA Portal
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

1. Overview: What "Empty Error OB" Actually Means

In a SIMATIC S7 program (S7-300, S7-400, S7-1200, S7-1500) an Organization Block (OB) is the only block type the CPU firmware will call by itself. The OB number determines the event that triggers the call: cyclic scanning (OB 1), time-of-day interrupt (OB 10), watchdog interrupt (OB 35), startup (OB 100/101/102), and a long list of error and diagnostic events (OB 70 – OB 122). When a fault such as a missing I/O module, an access error, or a programming error is detected, the firmware attempts to call the OB that corresponds to that fault class.

An "empty error OB" is an OB that has been created in the project and downloaded to the CPU but contains no executable code. The temporary (TEMP) declarations are still allocated by the system and the start information is still populated, but the engineer's logic is absent. The OB's sole effect is to return cleanly to the operating system, allowing the cyclic program to resume. This is fundamentally different from a populated OB, where the engineer is expected to read the start information, latch a bit, increment a counter, and post the event to a Human-Machine Interface (HMI) or to the diagnostic buffer.

Siemens documentation and field experience agree on the two outcomes:

  1. If the error OB is not present in the program at all, the CPU transitions to STOP on the first occurrence of the matching fault class.
  2. If the error OB is present and empty, the CPU stays in RUN, but the only persistent record of the fault is an entry in the diagnostic buffer. There is no application-level handling of the event.

This is confirmed by Siemens support material: an empty OB suppresses the STOP transition, but a deleted OB does not. See the SiePortal post "Empty OBs" on SiePortal for the exact statement, and the SIMATIC S7-1200 G2 manual collection at Diagnostic error interrupt OB (OB 82) for the official semantics: "Diagnostic error events trigger the execution of the diagnostic error interrupt OB (OB 82) if it exists. If it does not exist, then the CPU ignores the error." Note the subtle wording on the S7-1200: an error event is ignored, not necessarily handled, depending on CPU family.

2. The Full Inventory of S7 Error and Interrupt OBs

The following table lists the OBs the CPU can call in response to a fault or asynchronous event. The "Empty → CPU stays in RUN?" column summarises the documented behaviour; the "Recommended handler content" column is the engineer-side response that turns the OB from a "STOP suppressor" into a useful diagnostic point.

OB Trigger event Class Priority Empty → STOP? Recommended content
OB 70 I/O redundancy error (H-system only) Fault 25 No Latch status, post to HMI, trigger changeover
OB 72 CPU redundancy error (H-system only) Fault 28 No Latch status, post to HMI
OB 73 Communication redundancy error (H-system only) Fault 25 No Latch status, post to HMI
OB 80 Time error (scan time exceeded, OB 35 overflow) Fault 26 No Read OB 80 start info (which OB was running, time fault type), latch, log
OB 81 Power supply error Diagnostic 7 (24/26 on S7-400) No Read incoming/outgoing event flags from start info
OB 82 Diagnostic interrupt (module pulls/inserts, channel faults) Diagnostic 7 (24/26 on S7-400) No Read diagnostic record via SFC 51/RDSYSST, latch channel status
OB 83 Insert/remove module interrupt (S7-300/400 only) Diagnostic 7 (24/26 on S7-400) No Track slot config, log event
OB 84 CPU hardware fault (S7-1500) Fault 26 No Read start info, latch
OB 85 Program sequence error (OB not loaded, I/O access during update) Fault 26 No Track which OB/instance triggered it; critical for finding missing block calls
OB 86 Rack/station failure (PROFIBUS DP / PROFINET IO station lost) Fault 26 No Identify station number from start info, latch slot fault
OB 87 Communication error (S7-1500/S7-1200) Fault 26 No Log connection ID, status from start info
OB 88 Processing interrupt (S7-400, deleted block called) Fault 28 No Catch block-number corruption; rarely encountered on S7-1500
OB 90 Background OB Cycle 29 n/a Low-priority housekeeping; not a fault OB
OB 100 Warm restart Startup 27 n/a Initialise process tags, reset retentive flags only as designed
OB 101 Hot restart (S7-400 only) Startup 27 n/a Resume retained state, do not reset
OB 102 Cold restart Startup 27 n/a Initialise all process tags; mark "first cycle" bit
OB 121 Programming error (typed access to non-existent DB, range error) Fault OB of fault No Read block type and number from start info, latch, post to HMI
OB 122 I/O access error (read a slot that has no module) Fault OB of fault No Read slot/I-word from start info, latch, post to HMI

Two priority rules follow from this table:

  1. OB 80 through OB 88 all run at priority 26 on the S7-400, which means they can interrupt OB 1 (priority 1) but are themselves interruptible by OB 90 (priority 29) and the redundancy OBs. On the S77-1200/1500 the priority is fixed in firmware and follows the event class.
  2. OB 121 and OB 122 execute at the same priority as the OB that triggered them. They are not "new" calls - they are synchronously invoked extensions of the failing block. An empty OB 121 / OB 122 therefore does not change the scheduling at all, but a populated one can still read the failing block's context.

3. The Three-State Behaviour: Missing, Empty, Populated

The most important conceptual model is the three-state behaviour of an error OB. Visualise it as a ladder:

State A — OB absent from project CPU firmware raises fault, transitions to STOP, red SF LED on. State B — OB present, body empty CPU calls OB, TEMP area initialised, RET executed, RUN continues. State C — OB present, body populated CPU calls OB, application reads start info, latches bit, posts event to HMI/log.

Notice that States B and C share the same RUN/STOP outcome; they differ in the diagnostic visibility. In State B, the only place the event exists outside the firmware is the diagnostic buffer (accessible via Online → Diagnostics → Diagnostic Buffer in TIA Portal, or via the user program using SFC 51 RDSYSST with SSL-ID W#16#00A2). A technician who does not open TIA Portal or check the HMI will never know the event happened.

4. The Diagnostic Buffer — What an Empty OB Cannot Do

Every event that the CPU handles — startup, OB 35 tick, diagnostic interrupt, time error, STOP transition — is timestamped and written into a circular diagnostic buffer of typically 320 (S7-300) up to 3200 (S7-1500) entries. The buffer is the authoritative record of what the firmware saw. When an empty error OB fires, the buffer does receive an entry: it always does, regardless of what user code is in the OB. So "empty" is not the same as "no record".

What the empty OB cannot do is:

  • Set a global tag or instance DB bit the application can poll in OB 1.
  • Increment a counter visible on an HMI faceplate.
  • Send a defined message to a SCADA node over OPC UA or S7 communication.
  • Trigger a controlled shutdown, ramp-down, or transition to a safe state.
  • Correlate the fault to a piece of plant (e.g. "Stations 3 and 4 lost" instead of "rack 0 slot 4").

In a brownfield retrofit, the diagnostic buffer is sometimes the only practical debug tool. But the buffer is volatile across cold restart (OB 102) on most S7-300/400 CPUs: a power-cycle wipes it. An S7-1500 persists part of it. So if the field engineer wants the fault to survive a power outage, application-level latching is mandatory.

5. Why Engineers Insert Empty OBs (The Defenders' Case)

The defenders' case is pragmatic, and it is worth stating fairly before challenging it. The reasons most often heard in code review are:

  1. Commissioning tolerance. During integration, an empty PROFIBUS slave or a missing ET 200SP station is normal. OB 86 with code that latches "rack failure" will trip an HMI alarm every scan, distracting the engineer from the code under test. An empty OB keeps OB 1 free to run while infrastructure is being finalised.
  2. Development ergonomics. STEP 7 classic would throw a "STOP on rack failure" dialog every time the engineer stopped a slave, halting the development session. An empty OB 86 prevents the dialog and lets the engineer continue editing.
  3. Unknown SSL semantics. Reading the diagnostic record inside OB 82/86 requires SFC 51 RDSYSST with the correct SSL partial list. Choosing the wrong SSL-ID returns nothing useful. Rather than spend half a day learning the SSL list for a fault class that may never fire, the engineer leaves the OB empty and addresses it later.
  4. Maintenance culture. In some plants, the maintenance crew is comfortable interpreting the diagnostic buffer and prefers the PLC to keep running so that other parts of the line stay productive while the fault is escalated.

Each of these is a legitimate engineering trade-off in a specific phase of the project. The mistake is when a temporary convention hardens into a permanent one. Code that ships with an empty OB 86 because it was convenient in week 2 of commissioning is still running with that empty OB 86 in year 7 of production, masking PROFINET cable breaks and warm-swaps that the application ought to know about.

6. The Opposing Case: Why Empty OBs Are a Code-Smell

The argument against empty OBs is not that they exist, but that they are used to defer a fault the engineer has already detected. A few lines of code in each block would have done the same job plus given visibility. The typical hidden costs are:

  1. Scan-time penalty. An empty OB 86 called every 50 ms (because the firmware calls it for every scan that the slave is missing) is cheap individually but stacks against other empty OBs. With eight empty OBs, the firmware is allocating eight sets of TEMP locals and the context save/restore overhead every cycle on a real fault storm. On an S7-314 this is measurable.
  2. False sense of safety. A green HMI on a machine whose PROFINET ring has been broken for three shifts is worse than a red one. Empty OBs make the green more likely, not the safety.
  3. Diagnostic blindness during FAT/SAT. During Factory Acceptance Testing, an empty OB 122 will swallow a typo in a DB index. The test passes, the PLC is approved, and the bug surfaces at Site Acceptance Testing where the fix is ten times more expensive.
  4. Slow degradation. Empty OBs do not call SFC 109 PROTECT or any other safety-mechanism. They are a convenience, not a protection. Engineers who confuse the two produce programs that look reliable on a simulator and fail under load.

The conclusion most code-review processes reach is: empty OBs are acceptable as a temporary state during commissioning only, never in a released program.

7. A Reference Pattern for Each Error OB

The pattern below uses SCL on TIA Portal V18 or later, but the same structure applies in LAD/FBD. Each handler does exactly three things: copies the start information into a global fault DB, sets a latched "event-occurred" bit, and stores a 32-bit event counter that an HMI can read without polling the diagnostic buffer. The DB is declared as "non-optimised" or as a standard DB so that OPC UA / S7-comm / HMI can read it without POINTER gymnastics.

// OB 82 — Diagnostic interrupt
// Temporary declarations (auto-populated by the firmware):
VAR_TEMP
    info  : DWORD;   // OB 82 start info: event class, channel, slot
    tck   : DWORD;   // system time stamp at OB entry
END_VAR

// Application code:
"dbFault".DIAG.eventPending := TRUE;
"dbFault".DIAG.eventCounter := "dbFault".DIAG.eventCounter + 1;
"dbFault".DIAG.lastEventID  := info;
"dbFault".DIAG.lastTimeStamp := tck;
// Optional: read extended diagnostic data
// retVal := RDSYSST(REQ := TRUE, SSL_ID := W#16#00A2, INDEX := info, ...);

For OB 86 (rack failure), the start info contains the logical address of the failing station in bytes 8/9 and the event type (incoming/outgoing) in byte 0. Translate that into a station number for the HMI:

// OB 86 — Rack/station failure
VAR_TEMP
    evClass   : BYTE;    // B#16#38 incoming, B#16#39 outgoing
    slot      : WORD;
    geoAddr   : WORD;    // affected station or DP master
END_VAR

IF evClass = B#16#38 THEN
    "dbFault".RACK.failPending   := TRUE;
    "dbFault".RACK.lastGeoAddr   := geoAddr;
    "dbFault".RACK.failCounter   := "dbFault".RACK.failCounter + 1;
ELSE
    "dbFault".RACK.failPending   := FALSE;
    "dbFault".RACK.recoverCounter := "dbFault".RACK.recoverCounter + 1;
END_IF;

For OB 121/122, the start info contains the block type and block number that triggered the error. Capture them so the maintenance crew can grep the program for that instance:

// OB 121 — Programming error
VAR_TEMP
    blkType   : WORD;    // 1=OB,2=DB,3=FB,4=FC
    blkNr     : WORD;    // instance number
    area      : WORD;    // area code (DB, M, I, Q, ...)
    dbNr      : WORD;
END_VAR

"dbFault".PROG.lastBlkType := blkType;
"dbFault".PROG.lastBlkNr   := blkNr;
"dbFault".PROG.lastArea    := area;
"dbFault".PROG.lastDB      := dbNr;
"dbFault".PROG.errorCounter := "dbFault".PROG.errorCounter + 1;

All three patterns share three principles: latch the "pending" bit (so the HMI keeps showing red even if the fault is momentary), increment a 32-bit counter (so trending is possible), and record the start info verbatim (so the diagnostic buffer and the application agree).

8. SFC 51 RDSYSST and the SSL Partial Lists

The most powerful extension to a populated error OB is SFC 51 RDSYSST (Read System State). It returns a partial list from the system state list (SSL) identified by an SSL-ID. For the OBs above, the relevant partial lists are:

SSL-ID (hex) Partial list Used from OB Index parameter
W#16#00A2 Diagnostic buffer (all entries) All error OBs 1 = most recent, descending
W#16#00A3 Diagnostic buffer (single entry by number) All error OBs Entry number
W#16#00B1 Module diagnostic information OB 82, OB 83, OB 86 Logical base address of slot
W#16#00B2 Module diagnostic information (rack-station view) OB 86 Rack/station
W#16#00C0 Communication status data (S7-400) OB 87 Connection ID
W#16#0132 Diagnostic buffer entry (S7-1500 detail) All error OBs Entry number

The wrapper below is the minimum-viable RDSYSST call. Note that the destination area must be a standard-access DB of sufficient length; TIA Portal's optimiser will reject the pointer.

// Read full diagnostic buffer into a pre-sized DB
VAR_TEMP
    retVal   : INT;
    busy     : BOOL;
    sslId    : WORD;
    index    : WORD;
    recLen   : INT;
END_VAR

sslId := W#16#00A2;
index := 1;             // start with the most recent entry
recLen := 20;           // bytes per record (S7-300/400); 24 on S7-1500

retVal := RDSYSST(REQ := TRUE,
                  SSL_ID := sslId,
                  INDEX := index,
                  RET_VAL := retVal,
                  BUSY := busy,
                  SZL_HEADER := "dbDiag".header,
                  SZL := "dbDiag".records[0]);

When the engineer's goal is "just don't let the CPU go to STOP", the empty OB is shorter. When the goal is "I want to know what failed and where", the populated OB plus RDSYSST is the only credible answer.

9. TIA Portal Project Settings That Affect Error OB Behaviour

TIA Portal exposes the OB-handling policy in two places. Mis-configured settings here are responsible for more "mystery" behaviour than empty OBs are.

  1. CPU Properties → Startup → "Startup after power on" – Determines whether the CPU executes OB 100 / OB 101 / OB 102 or goes to STOP. On the S7-1500 the default is "Warm restart".
  2. CPU Properties → HMI → "Number of HMI connections" – Limiting this can cause OB 87 events on HMI disconnects.
  3. Device Configuration → Modules → Properties → Diagnostics – Per-channel diagnostic enable. If the channel is disabled, OB 82 will not fire even if the OB exists. The fault is then silent in two layers: the firmware filters it, and the application does not see it.
  4. CPU Properties → Webserver / OPC UA / Time synchronisation – Each subsystem that is enabled but unused is a possible OB 87 source. Disable in device configuration, do not "fix" with an empty OB.
  5. Watchdog (OB 1 maximum cycle time) – Setting this too tight is the most common cause of OB 80 in production. Raise the maximum cycle time, profile the cycle, and add OB 80 logging – do not just put an empty OB in.

10. Migration Notes: STEP 7 Classic → TIA Portal

Two changes in TIA Portal have changed the calculus on empty OBs:

  1. Symbolic access as default. TIA Portal blocks are typically created with "optimised" access. A symbolic access to a non-existent DB or an uninitialised struct instance raises an OB 121 in TIA Portal even when the same code compiled under STEP 7 classic would not. This makes OB 121 firing more common on TIA Portal, which in turn tempts engineers to leave OB 121 empty.
  2. "Ignore" versus "Replace" access errors. In TIA Portal, the device configuration lets the engineer mark a missing module as "Ignore" or "Substitute". This is a config-time decision that prevents OB 86 from firing. It is the correct tool, not an empty OB 86.

When migrating a STEP 7 classic project, do not blindly port empty OBs. Audit the diagnostic buffer of the running classic CPU first (TIA Portal's online diagnostic is more capable than the classic equivalent), and use the audit to decide which OBs need code, which can be removed, and which can be left empty as a deliberate policy decision documented in the project file.

11. Safety and Standards Considerations

The argument for empty OBs is sometimes dressed up as a safety argument. It is not. Safety-related programs are governed by IEC 61508 (functional safety), IEC 62061 (machinery), and ISO 13849-1 (safety of machinery). Each of these standards requires:

  • Deterministic response to detected faults.
  • Documented behaviour for every fault class.
  • Diagnostic coverage that is auditable.

An empty error OB satisfies none of these. It is the inverse of a deterministic response: the response is "ignore and continue". In a Safety Integrated (F-CPU) program, error OBs are also outside the F-runtime group; they handle standard-OB faults only, and the F-system has its own passivation rules via F-OB 82 / 85. Empty standard OBs therefore have no role to play in the F-runtime at all.

For non-safety programs, the relevant standard is usually internal to the end user – a customer's MES or plant-engineering standard. Most such standards include language along the lines of "faults shall be detected, reported, and acted upon; suppression of faults is not permitted except in documented commissioning phases". Empty OBs are usually the only place the standard is silently violated.

12. Commissioning Workflow: From Empty to Populated

The following sequence is a reference workflow for moving from a fresh TIA Portal project to a fully populated error-OB layer. It assumes the project is at a stable point (typically FAT) and that the engineer can modify the program.

  1. Insert OB 80, OB 81, OB 82, OB 83, OB 85, OB 86, OB 87, OB 121, OB 122 as stubs in the project. Each is created but empty. Confirm the program compiles and downloads.
  2. Bring the system up with all I/O physically present. Connect the engineering station. Open the diagnostic buffer. Confirm it is clean of fault events.
  3. For each OB, write a 5- to 15-line handler that latches a pending bit and increments a counter. Use the patterns in Section 7 as a template.
  4. For each OB, attach a standard HMI tag to the pending bit and the counter. Add a faceplate that shows the OB number, the start info, and the count.
  5. Inject one fault per OB by pulling a module (OB 83/86), forcing a bad DB index (OB 121), or simulating a scan-time overrun in the simulator (OB 80). Verify that the HMI updates as designed.
  6. Document the policy in the project README: "All error OBs are populated. Empty OBs are forbidden in released code." Reference this article or the project-internal standard.
  7. Add a code-review check: the reviewer must reject any PR that introduces an empty error OB. Most linting tools cannot enforce this, so it is a human-review rule.

13. Verification Checklist for a Released Program

Before sign-off, a released S7 program should pass the following checks. Each item is a line a reviewer can paste into a sign-off sheet.

# Check Pass criterion
1 OB 80, 81, 82, 83, 85, 86, 87, 121, 122 are all present in the project Yes, and they contain code
2 Each handler latches a persistent pending bit in a global DB Bits are visible from OB 1 within one scan
3 Each handler increments a 32-bit counter Counter survives cold restart on the S7-1500
4 Each handler records the OB's start info verbatim Bytes match the manual (see the table in Section 2)
5 Pending bits are mapped to HMI tags Visible without TIA Portal
6 Diagnostic buffer is empty at the end of FAT Confirmed by a 1-hour soak test
7 One synthetic fault per OB has been injected and acknowledged List of injected faults signed by FAT engineer
8 Project README states the empty-OB policy Policy is "forbidden in released code"

14. Troubleshooting Matrix

The matrix below maps the most common symptoms a field engineer reports to the OB most likely to fire and the underlying root cause. Use it as a starting point for diagnosis.

Symptom Likely OB Root cause candidates First diagnostic step
CPU in STOP after power-up None (OB missing) OB 100/101/102 not loaded, or syntax error in startup OB Read diagnostic buffer, look for "startup OB missing"
CPU in STOP after field device trips OB 86, OB 82 absent PROFINET station or module pulled during operation Confirm OB 86 is present and populated
Cyclic time faults, OB 1 runs slow OB 80 OB 35 priority overflow, or DB-access in long loop Read OB 80 start info, find which OB overflowed
Intermittent I/O read returning 0 OB 122 Slot mismatch in HW config, module removed Read OB 122 block-type and block-number fields
HMI alarms on every scan OB 87 (S7-1500) HMI connection limit exceeded, partner CPU unreachable Use "Online → Accessible Nodes" to confirm partner CPU state
One bit never clears after a fault OB 82, OB 86 Handler latches but does not clear on outgoing event Inspect handler; confirm IF evClass = incoming ELSE clear
Counter wraps at 2^31 OB 121 / OB 122 Counter declared as DINT but reset somewhere Search for MOV 0 to counter, change to UDINT if non-negative only

15. Field-Proven Cautions

Caution 1 — Optimised DBs and pointers. TIA Portal's "optimised block access" default breaks any SFC 51 call that uses a non-symbolic destination. Either disable optimisation on the diagnostic buffer DB, or use ARRAY of BYTE with AT() overlay to a STRUCT in the application code.
Caution 2 — OB 82 and channel diagnostics. The channel-diagnostic enable is per-channel in the device configuration. If the channel is disabled, OB 82 will not fire. This is the most common reason "my OB 82 code never runs".
Caution 3 — OB 80 priority. OB 80 runs at the same priority as the OB that exceeded its time. A nested OB 80 can therefore call OB 80 again. Add a recursion guard in the handler.
Caution 4 — Counter width. On the S7-300, counters are limited to 999 by the legacy IEC counter instructions. Use DINT/UDINT math, not the IEC counter FB.
Caution 5 — STOP on OB 100 failure. OB 100 (warm restart) is itself executed at priority 27 and can fail. If OB 100 contains code that triggers OB 121, the CPU will go to STOP during startup. The pattern of "add empty OBs, then add code" is most dangerous during the first download of OB 100.

16. Frequently Asked Questions

Does an empty error OB prevent the S7 CPU from going to STOP?

Yes. If the error OB (for example OB 82, OB 86, OB 121, OB 122) is present in the project and downloaded to the CPU, the firmware will call it on the matching fault and resume cyclic execution. Only a completely absent or deleted OB causes a STOP transition. This is confirmed in Siemens support material: an empty OB suppresses the STOP, but a missing OB does not.

Why is leaving an error OB empty considered bad practice?

An empty error OB hides the fault from the application: no latched bit, no counter, no HMI alarm. The only record is the diagnostic buffer, which most plant operators never read and which is volatile across cold restart on S7-300/400. A populated OB costs ten to twenty lines of code and gives the maintenance crew something they can see from the HMI.

Which OBs should I always populate in a released program?

OB 80 (time error), OB 82 (diagnostic interrupt), OB 83 (insert/remove), OB 85 (program-sequence error), OB 86 (rack/station failure), OB 87 (communication error), OB 121 (programming error), and OB 122 (I/O access error). The redundancy OBs (70, 72, 73) are mandatory on H-systems. Startup OBs (100, 101, 102) are required for any non-default startup behaviour.

Is it acceptable to leave error OBs empty during commissioning?

Yes, as a documented temporary measure. Most engineers add empty stubs at project creation to avoid STOP transitions while infrastructure is incomplete. The project should have a sign-off step that requires all OBs to be populated before release, and the README should record the policy.

How do I read the diagnostic buffer from inside an error OB?

Call SFC 51 RDSYSST with SSL-ID W#16#00A2 and INDEX 1 for the most recent entry. The destination must be a non-optimised DB or a byte array large enough to hold one record (20 bytes on S7-300/400, 24 bytes on S7-1500). Use the SZL_HEADER field to interpret the record. SFC 59 RD_REC is an alternative for module-specific diagnostic data on a specific slot.

Can an empty error OB fail a functional-safety audit?

Yes. IEC 61508, IEC 62061, and ISO 13849-1 all require a deterministic, documented response to detected faults. An empty error OB has the documented response "continue execution", which is rarely the correct safety response. The F-CPU has its own F-runtime and F-OBs; suppressing the standard OBs does not affect the F-system, so the suppression has no safety benefit either.

Back to blog