1. Problem Overview
The SIMATIC S7 organization block OB 86 is the rack/DP-slave failure OB. Engineers routinely copy its start information into a data block so that an HMI, SCADA, or historian can later display the exact moment a PROFIBUS slave, PROFINET device, or central expansion rack dropped off the network. In SCL, two common patterns fail silently:
- A
BLKMOV(or its SCL synonymMOVE_BLK/MOVE) with source#OB86_DATE_TIMEand destination of typeDATE_AND_TIMEin a DB produces a move of the start-info bytes, but the DB field still readsDT#00-00-00 00:00:00or all zeroes when later read. - A direct assignment in SCL
"Trap_DB".TRAP[1].OB86_DATE_TIME := OB86_DATE_TIME;
compiles without error, runs without a compiler diagnostic, yet at runtime the destination stays at its initial value or shows garbage in the year / month fields.
The fault is not the OB itself. The OB is called correctly by the operating system and OB86_DATE_TIME is a valid temporary of type DATE_AND_TIME. The fault is the way SCL and the older BLKMOV block treat the 8-byte BCD structure in specific firmware/compiler combinations.
2. When the CPU Calls OB 86
Per the official STEP 7 / TIA Portal reference for organization blocks, the operating system invokes OB 86 in the following situations:
- Failure of a central expansion unit is detected (not applicable on S7-300 — applies to S7-400 only, both incoming and outgoing events).
- Failure of a distributed I/O node on PROFIBUS-DP or PROFINET IO is detected — again both at the incoming (fault appears) and outgoing (fault clears) edges.
- Failure of a PROFINET IO submodule is reported through the system diagnostics and propagates to OB 86 only on CPUs that have PROFINET IO system-level diagnostics enabled.
The CPU calls OB 86 in two distinct events: OB 86 starts as the failure appears and OB 86 starts as the failure clears. The temporary OB86_EV_CLASS tells you which edge you are handling (B#16#38 / 39 incoming, B#16#38 / 39 / 3A / 3B outgoing depending on firmware). This distinction matters when you build a circular log, because you may want one row for "incoming" and one for "outgoing" or update an existing row in place.
Reference: Rack failure organization block (OB 86) — STEP 7 / TIA Portal V21 documentation.
3. OB 86 Local Data Layout (Temp Area)
The OB 86 start information occupies 32 bytes of the L stack. The exact byte map is shown below. Sizes are fixed and identical for S7-300, S7-400, and the S7-1500 PROFINET variant.
| Byte | Name | Type | Meaning |
|---|---|---|---|
| 0 | OB86_EV_CLASS | BYTE | Event class — B#16#38 incoming failure, B#16#39 outgoing recovery (PROFIBUS), B#16#3A / 3B PROFINET variants |
| 1 | OB86_FLT_ID | BYTE | Fault ID — B#16#01 DP master failure, B#16#02 DP slave failure, B#16#03 PROFINET IO failure, B#16#04 rack failure (S7-400) |
| 2-3 | OB86_PRIORITY | BYTE | Priority class of the OB |
| 4-5 | OB86_OB_NUMBR | BYTE | OB number 86 |
| 6-7 | OB86_RESERVED_1 | BYTE | Reserved |
| 8-9 | OB86_MDL_ADDR | WORD | Logical address of the affected module / slot (PROFIBUS: slave diagnostic address; PROFINET: device number in low byte) |
| 10-11 | OB86_Z1 / Z2 | WORD / DWORD | Slot or subslot identifier depending on fault ID |
| 12-15 | OB86_Z3 / Z4 | DWORD | Module / channel diagnostic data (PROFINET extended channel status) |
| 16-23 | OB86_DATE_TIME | DATE_AND_TIME | Time stamp of the event — eight BCD-encoded bytes |
| 24-31 | OB86_Z5 / Z6 / reserved | — | Additional fault-specific information; layout depends on fault ID |
The eight bytes of OB86_DATE_TIME encode year, month, day, hour, minute, second, and two "weekday + reserved" nibbles in BCD (binary-coded decimal), the legacy S7 format. SCL and most modern libraries prefer the new DTL type (introduced with S7-1500). On a classic S7-300/400 you must therefore use DATE_AND_TIME as the DB type, or convert explicitly to DTL with a hand-rolled BCD-to-INT conversion.
4. Root Cause — Why BLKMOV and SCL Direct Assignment Can Fail
Three real-world root causes are responsible for the symptom. Engineers should diagnose which one applies before changing code.
4.1 Cause A — Local data has already been overwritten by an FB call
When you call an FB from inside OB 86 and the FB uses DATE_AND_TIME as an IN_OUT parameter, the FB's own temp area is initialised in the L stack at the FB's own offset. On STEP 7 V5.5 and on early TIA Portal V13-V15 firmware, the FB's TEMP initialisation can collide with the OB's TEMP area and the OB86_DATE_TIME bytes you intended to capture end up zero or all-16#FF. Symptom: OB86_DATE_TIME is valid inside the OB (you can move it before calling the FB) but empty after the FB returns. Workaround: BLKMOV / MOVE the value into a global DB before calling any FB.
4.2 Cause B — Wrong interpretation of the second byte of DATE_AND_TIME
The legacy DATE_AND_TIME type uses 8 bytes. The year is stored in the 7th byte (offset 6 of the 8-byte structure) and the low nibble of the 8th byte contains a "weekday" 1 = Sunday … 7 = Saturday. Some SCL compilers on S7-300 with firmware < V3.3 do not support direct assignment between two DATE_AND_TIME symbols and silently promote the source to POINTER. The move completes on the pointer, not on the bytes, so the destination DB field reads an interpreted pointer value. Symptom: OB86_DATE_TIME in the DB shows a non-zero pattern that is not a valid date (year > 99, month > 12). Workaround: use a byte-array AT view, which forces a raw 8-byte copy.
4.3 Cause C — Destination DB field initial value blocks the move
If the DB field is part of a retain block and was previously written by a different OB, SCL's BLKMOV on some firmware versions refuses to write the source value over a non-zero initial value and the destination retains the old timestamp. Symptom: every OB 86 event shows the timestamp of the first event. Workaround: explicitly zero the destination with FILL_BLK (SCL FILL) before the move, or mark the DB as non-retain.
5. Solution 1 — AT-View Byte Copy (SCL)
Most reliable method for S7-300/400 with SCL: declare an AT overlay on both source and destination so the compiler is forced to treat the data as 8 raw bytes. This works on every STEP 7 version from V5.4 SP5 onward and on every TIA Portal version from V13 onward.
FUNCTION_BLOCK FB_Trap86
VAR_TEMP
info : DATE_AND_TIME;
info_bytes AT info : ARRAY[0..7] OF BYTE;
END_VAR
VAR
Trap_DB : "TRAP"; // global DB with structure TRAP_ARRAY[0..63] OF TRAP_REC
END_VAR
BEGIN
// Capture time stamp of OB 86 invocation
info := OB86_DATE_TIME;
// Now do a raw byte copy into the next free entry
Trap_DB.TRAP[DB_INDEX].OB86_DATE_TIME := info;
Trap_DB.TRAP[DB_INDEX].OB86_EV_CLASS := OB86_EV_CLASS;
Trap_DB.TRAP[DB_INDEX].OB86_FLT_ID := OB86_FLT_ID;
Trap_DB.TRAP[DB_INDEX].OB86_MDL_ADDR := OB86_MDL_ADDR;
Trap_DB.TRAP[DB_INDEX].OB86_Z1 := OB86_Z1;
Trap_DB.TRAP[DB_INDEX].OB86_Z2 := OB86_Z2;
END_FUNCTION_BLOCK
The trick is that info_bytes AT info overlays the 8 BCD bytes on the same storage as info. If a compiler is suspicious of a direct DATE_AND_TIME assignment, you can replace the single line with a manual MOVE_BLK:
MOVE_BLK(VARIANT := info_bytes, COUNT := 8, DEST_VARIANT := Trap_DB.TRAP[DB_INDEX].OB86_BYTES);
where OB86_BYTES is an ARRAY[0..7] OF BYTE exposed under OB86_DATE_TIME via another AT overlay in the global DB.
6. Solution 2 — Explicit BCD-to-Integer Conversion
If you want the timestamp in plain integers (so you can compare day, month, year inside SCL), extract each nibble explicitly. This is the only method that gives you a usable date in SCL on S7-300 firmware < V3.3.
FUNCTION FC_BCD_DT_TO_DTL : VOID
VAR_INPUT
src : DATE_AND_TIME;
END_VAR
VAR_OUTPUT
year : INT;
month : INT;
day : INT;
hour : INT;
min : INT;
sec : INT;
ms : INT; // 0 on S7-300, OB does not deliver milliseconds
END_VAR
VAR_TEMP
b AT src : ARRAY[0..7] OF BYTE;
lo : BYTE;
hi : BYTE;
END_VAR
BEGIN
// Bytes 0..6 are BCD; byte 7 is weekday+reserved
lo := b[0] AND 16#0F;
hi := b[0] SHR 4;
year := 2000 + WORD#16#100 * hi + lo;
lo := b[1] AND 16#0F;
hi := b[1] SHR 4;
month := WORD#16#100 * hi + lo;
lo := b[2] AND 16#0F;
hi := b[2] SHR 4;
day := WORD#16#100 * hi + lo;
lo := b[3] AND 16#0F;
hi := b[3] SHR 4;
hour := WORD#16#100 * hi + lo;
lo := b[4] AND 16#0F;
hi := b[4] SHR 4;
min := WORD#16#100 * hi + lo;
lo := b[5] AND 16#0F;
hi := b[5] SHR 4;
sec := WORD#16#100 * hi + lo;
ms := 0;
END_FUNCTION
You then call FC_BCD_DT_TO_DTL from inside OB 86 and store the result as INT fields in the global DB. This eliminates the BCD-encoding ambiguity entirely and is forward-compatible with the S7-1500 DTL structure once you map each INT to a DTL element.
7. Solution 3 — Use RD_SYS_T as a Cross-Check
RD_SYS_T reads the CPU's time-of-day in DTL format on the S7-1500 and in DATE_AND_TIME on the S7-300/400 (SFC 1). Many engineers call it from inside OB 86 to confirm the OB's OB86_DATE_TIME is consistent with the system clock. The two should agree within a few scan cycles:
VAR_TEMP
sys_dt : DATE_AND_TIME;
END_VAR
BEGIN
SFC1(RET_VAL := #ret, CDT := sys_dt);
// Compare sys_dt vs OB86_DATE_TIME - the gap is the OB dispatch latency
IF sys_dt <> OB86_DATE_TIME THEN
Trap_DB.TRAP[idx].DELTA_MS := 1; // see Section 8 for delta calculation
END_IF;
END
This is also a useful verification trick: if OB86_DATE_TIME is stuck at the CPU cold-start date and RD_SYS_T returns the correct current time, you have confirmed that the failure is at the OB-to-DB write path (Cause A, B, or C above) and not at the CPU clock.
8. Building a Multi-Event Trap DB
Capture every event (incoming and outgoing) into a ring buffer of TRAP_REC entries. The recommended DB layout is:
| Symbol | Type | Initial | Comment |
|---|---|---|---|
| TRAP | ARRAY[0..63] OF TRAP_REC | — | 64-entry ring; overwriting is acceptable if the HMI polls faster than the failure rate |
| HEAD | DINT | 0 | Index of next free slot, 0..63 |
| COUNT | DINT | 0 | Monotonically increasing event counter — HMI can use COUNT % 64 to align with TRAP |
| WARN | BOOL | FALSE | Latched fault presence for HMI annunciator |
TRAP_REC structure:
TYPE TRAP_REC
STRUCT
OB86_DATE_TIME : DATE_AND_TIME;
OB86_EV_CLASS : BYTE;
OB86_FLT_ID : BYTE;
OB86_MDL_ADDR : WORD;
OB86_Z1 : WORD;
OB86_Z2 : WORD;
OB86_Z3 : DWORD;
OB86_Z4 : DWORD;
OB86_Z5 : DWORD;
OB86_Z6 : DWORD;
OB86_BYTES AT OB86_DATE_TIME : ARRAY[0..7] OF BYTE;
END_STRUCT
END_TYPE
The AT overlay on the record itself gives every HMI script or WinCC tag access to the raw 8 bytes for direct BCD parsing, while structured access to OB86_FLT_ID etc. remains type-safe in SCL.
9. Step-by-Step Implementation in OB 86
- Insert
OB 86from the right-hand "Standard library > System blocks" tree in TIA Portal / STEP 7. - Open the OB and switch the language to SCL if you are using SCL; leave in LAD/FBD if you prefer
BLKMOVladder segments. - Insert a call to
FB_Trap86(SCL) or to a wrapper FB that contains aBLKMOVnetwork (LAD). - Inside the FB, first move
OB86_DATE_TIMEinto a localDATE_AND_TIMEtemp, then assign the temp toTrap_DB.TRAP[HEAD].OB86_DATE_TIME. - Increment
HEAD := (HEAD + 1) MOD 64andCOUNT := COUNT + 1after the writes, not before. This way, if OB 86 is re-entered because of a burst of diagnostics, the previous slot is still consistent. - Set
WARN := TRUEfor incoming (B#16#38 / 39), clear it on outgoing only if no other events are pending in the ring buffer.
10. Verification Checklist
After the implementation, perform these checks in order. Each check has an explicit pass criterion.
| # | Check | Pass criterion |
|---|---|---|
| 1 | Trigger an OB 86 event by pulling a PROFIBUS connector or simulating with STEP 7's "PLC > Diagnostic/Process > Simulate |
| # | Check | Pass criterion |
|---|---|---|
| 1 | Trigger OB 86 by unplugging a PROFIBUS DP slave diagnostic connector or by using TIA Portal "Online & diagnostics > Simulate module failure". | OB 86 executes, Trap_DB.HEAD advances by 1, Trap_DB.COUNT increments. |
| 2 | Read Trap_DB.TRAP[HEAD-1].OB86_DATE_TIME in the watch table in the format DT#YY-MM-DD HH:MM:SS. |
Value matches the time in the diagnostic buffer (drift < 1 s is acceptable; large drift means the L stack is being clobbered). |
| 3 | Replug the connector and confirm the outgoing event is logged with the same module address and a later time stamp. |
OB86_EV_CLASS = B#16#39 / 3B (outgoing), OB86_MDL_ADDR unchanged, WARN clears if the buffer is empty. |
| 4 | Cross-check OB86_DATE_TIME with SFC 1 / RD_SYS_T called at the same moment. |
Both values are within 1 second of each other. |
| 5 | Trigger a burst: pull two slaves within the same scan. The ring buffer must contain two distinct entries. |
COUNT advances by 2, no TRAP[i].OB86_DATE_TIME is duplicated. |
| 6 | Force a power cycle with the buffer full. After restart, the HMI reads retained COUNT and unwinds correctly. |
WARN state matches the actual present fault. |
11. Common Errors and Their Fixes
| Symptom in DB | Likely cause | Fix |
|---|---|---|
DT#90-12-31 24:63:63 or all bytes = 16#FF |
Cause B: SCL treated DATE_AND_TIME as pointer / array |
Use AT overlay; copy 8 raw bytes with MOVE_BLK
|
| Always the time of first event | Cause C: retain-overwrite behaviour | Set DB attribute "Non-retain" or call FILL_BLK with 0 first |
| Empty until the FB returns, then OK | Cause A: FB TEMP clobbered the OB TEMP | Move into the global DB before calling the FB |
| Random year > 89 | BCD interpretation wrong: you read byte 6 as offset 5 | Remember year is at byte offset 6, not 5 |
| No event at all | OB 86 was deleted; CPU goes into stop on failure | Reinsert OB 86; verify OB priority in CPU properties |
| Event captured but no slot allocated | FB calls itself recursively on OUT event | Distinguish incoming / outgoing with OB86_EV_CLASS and only log one of them |
12. Comparison of Capture Methods
| Method | CPU requirement | Compiler | Reliability | Read-out ease | Notes |
|---|---|---|---|---|---|
SCL direct := OB86_DATE_TIME
|
S7-300 >= V3.3, S7-1500 | STEP 7 V5.5 SP4+, TIA V13+ | Medium | High | Quickest, but fails on Cause A/B |
SCL with AT overlay |
All | All | High | Medium | Recommended default |
LAD/FBD BLKMOV
|
All | All | High on S7-400, medium on S7-300 | High | Watch the enable input — must be TRUE
|
| BCD-to-INT conversion FC | All | All | Very high | Medium | Most portable, also works on S7-1500 with DTL
|
RD_SYS_T + delta check |
S7-300 (SFC 1), S7-1500 (native) | All | High | High | Use for cross-check, not as primary capture |
13. Field-Commissioning Notes
- Always keep a default OB 86 with a single
BEin the project. If you only have an FB version, the CPU will still call the default OB and your FB through the priority class — but only if the default OB exists. Missing default OB stops the CPU on the first PROFIBUS disturbance. - For PROFINET IO, fault IDs are 16#03, 16#04, 16#05, 16#06, 16#07, 16#08 and the
OB86_MDL_ADDRinterpretation changes: low byte = device number, high byte = 0. Confirm in the hardware catalog of the device. - If you migrate the program from S7-300 to S7-1500, switch the DB type from
DATE_AND_TIMEtoDTL. The 8-byte BCD form is still accessible through theAToverlay so your old FCs can keep working during cutover. - Always store the event count modulo the ring size on the HMI. Use the formula
index = COUNT MOD 64to keep the display aligned with the controller's writer.
14. Diagnostic Buffer Cross-Reference
For every entry in the TRAP ring, look up the corresponding entry in the CPU's diagnostic buffer. The buffer ID for OB 86 events is "Event ID 16#38E1 — Rack failure" and "Event ID 16#38E2 — Distributed I/O failure". Comparing the two confirms that you are capturing the same event the operator sees. If they disagree, the FB that wrote to the DB is writing to a wrong address — re-check the DB index calculation.
Why does my SCL direct assignment OB86_DATE_TIME := Trap_DB.TRAP[i].OB86_DATE_TIME leave the destination empty?
On S7-300 firmware < V3.3 and on STEP 7 V5.5 compilers earlier than SP4, the SCL compiler treats DATE_AND_TIME as a structured type and may emit code that re-initialises the destination pointer instead of writing 8 raw bytes. Use an AT-view overlay or a MOVE_BLK of 8 bytes; both force a byte-level copy and are reliable on every firmware from V3.3 onward.
Is BLKMOV the same as the SCL MOVE instruction for an 8-byte DATE_AND_TIME?
At the bit level yes — both copy 8 raw bytes. In the SCL editor the source line is simply "Trap_DB.TRAP[i].OB86_DATE_TIME := OB86_DATE_TIME;" without explicit BLKMOV. In LAD/FBD the BLKMOV block must be enabled permanently and the IN/OUT pointer must reference the OB temp; the OB temp is valid only for the duration of the OB, so call the BLKMOV network before exiting OB 86.
Does OB86_DATE_TIME carry milliseconds on S7-300?
No. The DATE_AND_TIME type on S7-300/400 always stores 0 milliseconds; only S7-1500 DTL (and SFC 1 RD_SYS_T) can deliver milliseconds. If you need ms resolution, read the CPU clock with SFC 1 in OB 86 and treat that value as the OB 86 timestamp; the resolution will be the resolution of the CPU's tick.
Can I rely on the same DATE_AND_TIME value for incoming and outgoing OB 86 events?
No — the operating system updates the temp on every call, so an outgoing OB 86 will have a time stamp close to the recovery moment, not the original failure. If you want to log both edges into one row, update an existing row whose address matches OB86_MDL_ADDR on the outgoing call and add a new row on the incoming call.
What is the correct way to convert DATE_AND_TIME to DTL when migrating an S7-300 trap block to S7-1500?
On the S7-1500 read the OB 86 temp into a DTL tag directly (DTL is supported in start information since firmware V4.0 of S7-1500). On a classic S7-300 program that you want to forward-port, declare an AT view over the 8 BCD bytes, extract year/month/day/hour/minute/second as INTs as shown in Section 6, and assign them to the corresponding DTL elements: .YEAR, .MONTH, .DAY, .HOUR, .MINUTE, .SECOND, .NANOSECOND (set to 0). The conversion is exact — the BCD format and the DTL BCD-style low-level encoding agree for the 6 datetime words.