S7-1500 Global Error Handling: OB121 Configuration Guide

David Krause10 min read
S7-1200SiemensTechnical 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

1. Overview of S7-1500 Error Handling Architecture

The S7-1500 CPU family (firmware V1.0 through V20) implements a two-tier error model that engineers must understand before commissioning any application involving arithmetic, I/O access, or pointer manipulation:

  • Local error handling – the offending block traps the error itself and returns control to the caller.
  • Global error handling – the offending block does not trap the error; the CPU invokes the corresponding priority class (OB 121 for programming errors, OB 122 for I/O access errors, OB 80 for time errors, OB 82 for diagnostic errors, OB 83 for module removal/insertion, OB 86 for rack failure, OB 121 for programming, OB 122 for I/O access).

When no global error OB of the appropriate class is loaded, the S7-1500 CPU transitions to STOP with diagnostic buffer entry 3581 / 3582 (firmware-dependent). This is the documented fail-safe behavior. See the official Siemens TIA Portal V20 Tips for Error Avoidance and Error Handling for the complete rules matrix.

Engineering rule: An empty OB 121 is not a no-op. A programmed-but-empty OB tells the CPU to swallow the error and continue scan, whereas the absence of OB 121 forces STOP. Decide deliberately which behavior your process safety case requires.

2. Block Attribute: "Handle Errors Within Block"

The toggle that selects between local and global handling lives in the block properties of every compiled block (OB, FB, FC, DB, UDT, PLC data type). In TIA Portal V14 through V20:

  1. Right-click the block in the project tree → Properties.
  2. Open the Attributes tab.
  3. Locate the checkbox "Handle errors within block".
  4. Unchecked (default) → global error handling is active for that block.
  5. Checked → the block uses local error handling via the system function GET_ERROR or GET_ERR_ID and OB 121 will not be called.
Setting OB 121 Called? CPU Stop on Error? Use Case
Handle errors within block = FALSE (default) Yes, if OB 121 exists Yes, if OB 121 absent Process-wide diagnostic collection
Handle errors within block = TRUE No No Library blocks that must self-recover
OB 121 programmed + global handling Yes No (OB decides) Logged recovery, set flags, return to scan
OB 121 absent + global handling N/A Yes Safety-critical STOP on any programming fault

3. Programming Error OB (OB 121) Behavior on S7-1500

Per the official Siemens documentation entry ID 109742272 – Programming error OB (S7-1500):

"Global error handling must be set if you want to enable the S7-1500 CPU to call the programming error OB."

The trigger sources for OB 121 include, but are not limited to:

  • Arithmetic overflow on a typed conversion (e.g., INT_TO_REAL with NaN input).
  • Division by zero on integer and real operands.
  • Invalid array index / range violation on a fully-qualified DB access.
  • Invalid pointer / Variant unwrap.
  • Type conversion error in SCL MOVE or assignment.

The OB 121 interface provides the temporary OB_EV_CLASS, OB_SW_FLT, OB_FLT_ID, OB_PRG_ADDR, and block-type information (OB_BLOCK_TYPE) tags. Sample read:

#iFaultId := OB_FLT_ID; // 16#80xx subevent code #iBlockType := OB_BLOCK_TYPE; // 16#38 = OB, 16#08 = FB, 16#0C = FC #iPrgAddr := OB_PRG_ADDR; // offset inside block

4. Local vs Global Error Handling – Detailed Comparison

Aspect Global (default) Local (Handle within block = TRUE)
Error OB called OB 121 (programming), OB 122 (I/O) None
Detection primitive CPU scan GET_ERROR / GET_ERR_ID in caller
SCL behavior OB 121 raised; status bits set GET_ERROR returns the error struct
LAD/FBD behavior ENO = 0 on the failing instruction ENO still propagated; must check downstream
STL behavior Status bits OV, OS, CC0/CC1 set Same flags, no OB
Diagnostic buffer entry Always written Optional
CPU default on absence STOP RUN

5. Language-Specific Error Behavior

5.1 LAD and FBD

When a math box (ADD, SUB, MUL, DIV, MOVE with type conversion) detects an error, the Enable Output (ENO) of that box is cleared to FALSE. Downstream operations that branch on ENO can be skipped. The diagnostic buffer also receives a non-OB event if OB 121 is present.

5.2 STL (Statement List)

Arithmetic errors set the CPU status bits in the same way as S7-300/400. Read these in the user program:

// Standard bit addresses (S7-1500, word-serial view): OV == "OV" // Overflow (latched) OS == "OS" // Stored overflow (sticky) CC0, CC1 // Condition codes after comparison/arithmetic

Conditional jumps use JP (positive), JN (negative), JZ (zero), JN, JO (overflow), JOS (latched overflow). A division-by-zero result is reported as invalid rather than as a fatal condition—check OV or OS after the operation. See the STEP 7 Professional manual "Jump if calculation is invalid" reference for the canonical flow.

5.3 SCL (Structured Control Language)

SCL does not implicitly branch on ENO. A failing arithmetic expression will:

  • Raise the event OB 121 if global handling is active and OB 121 exists.
  • Otherwise place a result-dependent invalid value (often 0 or last valid) in the target variable.
  • Set status bits that can be polled with OV / OS.

This is the source of the "I divide by zero and no error is detected" symptom reported by users: with global handling unchecked and OB 121 present, the error is silently absorbed.

6. Division-by-Zero in SCL – The CONTINUE Pattern

The recommended SCL idiom to avoid a division-by-zero programming error mirrors the official S7-1200 system manual example. Place the guard before the division:

FOR x := 0 TO 10 DO IF value[x] = 0 THEN CONTINUE; // skip this index, do not call OB 121 END_IF; p := part / value[x] * 100; s := INT_TO_STRING(p); percent := CONCAT(IN1 := s, IN2 := "%"); END_FOR;

Why CONTINUE rather than EXIT?

  • CONTINUE skips the remainder of the current loop body and proceeds with the next iteration. This is the safe path when only one element is bad.
  • EXIT terminates the entire loop, which abandons all remaining indices—including valid ones.

An alternative pattern is to pre-filter zero entries into a "zero-encounter" counter:

iZeroCount := 0; FOR x := 0 TO 10 DO IF value[x] = 0 THEN iZeroCount := iZeroCount + 1; ELSE p := part / value[x] * 100; // store p END_IF; END_FOR; IF iZeroCount > 0 THEN "dbDiag".ZeroDivSeen := TRUE; // diagnostic flag for HMI END_IF;

7. Status Bits for Calculation Errors

The S7-1500 inherits the S7-300/400 status-bit model. After any integer or floating-point math instruction, evaluate these tags:

Tag Symbol Meaning Latched?
OV Overflow Result out of range No (cleared on next math op)
OS Stored overflow Overflow seen at least once Yes, until cleared with CLR
CC0 / CC1 Condition codes ==, <, >, <> No
BR Binary result ENO of FB box No

To centralize detection, route all critical math through a wrapper FB whose first instruction is A OV; S "dbDiag".OverflowSeen; CLR;.

8. Configuring OB 121 in TIA Portal V20

  1. Project tree → PLC_x → Program blocks → Add new block.
  2. Choose Organization block → Programming error OB. Default name: ProgrammingError (OB 121).
  3. Inside the OB, write the recovery logic. Three patterns are common:
// Pattern A: Log and continue (logged recovery) "dbDiag".LastFaultId := OB_FLT_ID; "dbDiag".LastPrgAddr := OB_PRG_ADDR; "dbDiag".FaultCount := "dbDiag".FaultCount + 1; RETURN; // Pattern B: Hard stop after N faults IF "dbDiag".FaultCount >= 10 THEN STP(); // explicit STOP request from OB 121 END_IF; RETURN; // Pattern C: Bypass and resume CASE OB_FLT_ID OF 16#8001: "dbDiag".MathFault := TRUE; // DIV by 0 16#8002: "dbDiag".RangeFault := TRUE; // range error ELSE "dbDiag".OtherFault := TRUE; END_CASE;
  1. Compile and download. Confirm in the device configuration: Properties → General → System and clock memory that OB 121 is enabled in the runtime profile.
Calling STP() inside OB 121 transitions the CPU to STOP after the OB body completes. The diagnostic buffer will record "Programming error OB has requested STOP" – useful for forensic replay.

9. I/O Access Errors vs Programming Errors

A subtle source of confusion: I/O access errors raise OB 122, not OB 121. The block-attribute Handle errors within block affects both. Field tests show that with global handling the CPU correctly invokes OB 122 on a missing module, while a divide-by-zero in the same scan may go unnoticed because OB 121 is unprogrammed and the user has not implemented the CONTINUE guard. Plan OB programming per fault class:

OB Number Fault Class Default without OB
OB 80 Time error STOP
OB 82 Diagnostic interrupt IGNORE
OB 83 Module pull/plug STOP
OB 121 Programming error STOP
OB 122 I/O access error STOP

10. Verification and Diagnostic Procedure

After commissioning, validate global error handling with this 6-step procedure:

  1. Force a known divide-by-zero: write a temporary SCL FB that divides an LREAL by 0.0 with global handling unchecked.
  2. Observe OB 121: if OB 121 is present and non-empty, the CPU stays in RUN and the diagnostic counter in your OB body increments.
  3. Delete OB 121 from the project, recompile, download. Re-trigger the same division. The CPU must transition to STOP with buffer entry 3581.
  4. Re-create OB 121 with STP() inside. Re-trigger: CPU transitions to STOP deliberately, buffer shows "OB 121 STP request".
  5. Enable Handle errors within block on the test FB and re-trigger. CPU stays in RUN, OB 121 is not called.
  6. Read the diagnostic buffer: Online & diagnostics → Diagnostics buffer. The buffer will record whether OB 121, OB 122, or neither fired.

10.1 Online Diagnostic Read Example

// Inside OB 1 start-up, tag reads: #iOB121Count := "dbSysDiag".OB121Count; #iOB122Count := "dbSysDiag".OB122Count; IF #iOB121Count > 0 THEN "dbHmi".LastFaultClass := 'ProgrammingError'; "dbHmi".LastFaultTime := RD_SYS_T(); END_IF;

11. Common Pitfalls and Edge Cases

  • LREAL divide by 0.0 in SCL on a CPU with firmware < V2.0 silently returns +Inf or -Inf rather than raising OB 121. Update firmware or add explicit IF divisor = 0.0 THEN ... END_IF.
  • Typeless constants: result := part / 0; is treated as integer by default and may not trigger floating-point exceptions. Cast explicitly: result := part / LREAL#0.0;.
  • Library blocks with Handle errors within block = TRUE imported from older S7-300 libraries will suppress OB 121; copy the source and reset the attribute if global handling is required.
  • DB access with absolute addressing: invalid slice offsets trigger OB 121 even with Handle errors within block = TRUE. Always validate pointers in PEEK/POKE sequences.
  • Circular reference between OBs: an OB 121 that itself faults will escalate to STOP.

12. Replacement CPU Best Practice

Per the Siemens guidance linked above, always have the configured program archived offline. When replacing a defective S7-1500 CPU:

  1. Download the complete project, not just the changes.
  2. Confirm that OB 121, OB 122, and OB 82 are present and match the expected block-level attribute state.
  3. Verify the device configuration fingerprint matches (article number, firmware version, I/O layout).
  4. Run the divide-by-zero verification above before resuming production.

13. Quick Reference – Decision Flowchart

Program fault occurs
        |
   Handle errors within block?
   /                       \
 YES                       NO
  |                         |
GET_ERROR            OB 121 present?
returns fault       /            \
                    NO             YES
                    |               |
                CPU -> STOP    OB 121 body runs
                                  |
                            STP() inside?
                           /            \
                          NO             YES
                          |               |
                    Return to scan   CPU -> STOP

14. Frequently Asked Questions

Why does my S7-1500 CPU stay in RUN after a divide-by-zero even though I have not programmed OB 121?

Because the default block attribute is "Handle errors within block = FALSE" (global), and if OB 121 is present the error is absorbed silently. Either delete OB 121 to force a STOP, add a CONTINUE guard in SCL, or call STP() from inside OB 121 to halt deliberately. See the Programming error OB entry ID 109742272.

How do I detect a divide-by-zero in SCL without stopping the CPU?

Use the IF divisor = 0 THEN CONTINUE; END_IF; pattern before the division, or increment a counter and post-process. Alternatively, enable "Handle errors within block" on the FB and call GET_ERROR after the divide to read the local error structure. See the SCL CONTINUE example above.

What is the difference between global error handling and "Handle errors within block"?

Global error handling (default) makes the CPU call OB 121 for programming faults or OB 122 for I/O faults when the block attribute is unchecked. With "Handle errors within block = TRUE" the calling block must interrogate the error itself using GET_ERROR or GET_ERR_ID; no OB is called.

Does the S7-1500 behave like the S7-300/400 with status bits OV, OS, CC0, CC1?

Yes. The status bit model is preserved. A divide-by-zero sets OV and OS, with the result marked invalid. Read these in STL/SCL after the arithmetic instruction and use JOS/JO jumps in STL. Refer to the STEP 7 Professional manual section "Jump if calculation is invalid".

Which firmware version first supported the current OB 121 behavior on S7-1500?

OB 121 has been supported since the first S7-1500 CPUs (firmware V1.0, 2013). The block attribute "Handle errors within block" and the GET_ERROR/GET_ERR_ID semantics are documented from STEP 7 Professional V14 onward and remain consistent in TIA Portal V20.

Back to blog