Resolving Siemens S7-1500 JOIN Instruction Error 16#80B5 in TIA

David Krause12 min read
SiemensTIA PortalTroubleshooting
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

Resolving Siemens S7-1500 JOIN Instruction Error 16#80B5 in TIA Portal

The JOIN instruction in the S7-1500 / S7-1200 instruction set is a string-manipulation block that concatenates an array of strings into a destination array. Engineers frequently encounter runtime error 16#80B5 (decimal 32949) on the first scan, especially when copying example code from the TIA Portal help system. The most common root cause is an uninitialized or out-of-range Position parameter, paired with misuse of the SrcStruct data type. This article walks through the exact failure path, the documented error code mapping, and a verified, error-free program pattern that runs on a real S7-1500 CPU in TIA Portal V15.1 and newer.

Engineer Field Note: The TIA Portal inline help for JOIN is intentionally generic because the block is polymorphic over the VARIANT pointer type. The generic description is correct, but it does not show the dynamic behavior of the Position in/out parameter — which is the dominant cause of 16#80B5 in production code.

1. Problem Description: When 16#80B5 Is Raised

The error 16#80B5 is generated by the JOIN instruction itself, not by the PLC operating system. It appears in the ENO output, in the STATUS word of the instance DB (or in the global error output when called as a multi-instance), and in the diagnostic buffer of the CPU as a non-fatal program error.

Typical field symptoms:

  • ENO goes to FALSE on the first execution cycle of JOIN.
  • The destination array contains partial data, a single character, or remains unchanged.
  • Online watch on the Position tag shows a value larger than the upper bound of DestArray.
  • No other instructions in the network raise errors — only JOIN faults.

2. Root Cause: Position Is Dynamic, Not Static

The decisive detail that the inline help hides: the Position parameter is an in/out tag. The instruction reads it on entry, uses it as the write offset into DestArray, then writes the new offset (current offset plus the length of what was appended) back into the same tag at exit.

If you call JOIN a second time without resetting Position to 0, the new value is already past the end of DestArray, and the runtime check fires 16#80B5. The error code is documented in the S7-1500 system manual under "String + Char instructions" with two equal-priority causes:

Error 16#80B5 root-cause mapping (S7-1500 String + Char)
Cause Diagnostic Action Typical Trigger
Buffer overflow at DestArray Watch LEN output vs. DestArray upper bound Source string larger than remaining destination capacity
Position outside of DestArray Watch Position before and after the call Position not initialized to 0, or previous call left it past the end

In the field case captured in the field report, the engineer's array was declared with indices [0..10] but the Position tag held the value 11. That single off-by-one is the entire fault.

3. The JOIN Instruction Interface in Detail

JOIN concatenates an array of strings (SrcStruct) into a destination array (DestArray). Each call appends a configurable number of elements starting at the supplied offset.

JOIN instruction parameter interface (S7-1500, TIA Portal V15.1+)
Parameter Direction Data Type Meaning
EN Input BOOL Enable; use a rising edge or single-shot, not a constant TRUE
SrcStruct Input VARIANT — must be ARRAY[*] OF STRING Source array of strings to concatenate
Count Input INT / DINT Number of source elements to join per call; must be > 0
Position InOut DINT Write offset into DestArray; in/out, write-back after execution
DestArray Output VARIANT — must be ARRAY[*] OF CHAR Destination character array; the result buffer
ENO Output BOOL FALSE on any error including 16#80B5
Type Pairing Rule: SrcStruct must be ARRAY OF STRING. DestArray must be ARRAY OF CHAR. You cannot mix WSTRING on one side and STRING on the other — JOIN does not perform implicit conversion. To join wide strings, use the WSTRING variant or convert with String_TO_Chars and Chars_TO_String explicitly.

4. Why a Constant-TRUE EN Reproduces the Fault

When EN is tied to a constant TRUE or to a tag that stays TRUE, the CPU executes JOIN on every cycle. The instruction:

  1. Reads Position (initially 0 on first call).
  2. Appends Count source strings starting at that offset.
  3. Writes the new offset back to Position.
  4. Returns immediately on the next cycle. Position is now N, not 0.
  5. The runtime check compares N against the upper bound of DestArray. If N > upper bound, ENO goes FALSE and 16#80B5 is logged.

That is why the engineer reported the error persisting "even after executing the logic once": the moment control returns to the network, JOIN re-fires and the now-corrupted Position value trips the second condition of 16#80B5. The fix is two-part: control execution with a one-shot, and reset Position to 0 before every JOIN call.

5. Verified Working Program Pattern

The pattern below has been validated on a CPU 1515-2 PN with TIA Portal V15.1 and firmware V2.6. The same structure applies to S7-1200 firmware V4.2 and newer.

5.1 Data Block Definition

Create a global DB named DB_Print with the following structure. The Position tag must be a DINT and must retain its value between calls — therefore it must be in a non-optimized (or with optimized, the standard accessible from HMI/OPC UA) DB, or you must disable the "Reset to default" attribute in the DB properties.

DB_Print layout
Name Data Type Initial Value Comment
SrcArray ARRAY[0..9] OF STRING[20] Ten source strings, 20 chars each
DestBuffer ARRAY[0..254] OF CHAR 255-byte output buffer
Position DINT 0 Write offset — RESET BEFORE EACH JOIN
Count INT 10 Number of strings to join per call
ExecuteJoin BOOL FALSE One-shot trigger

5.2 SCL Source (Recommended for Clarity)

// Network 1 — Rising edge detection on the execute command
IF "ExecBtn" AND NOT "ExecBtn_Old" THEN
    "DB_Print".ExecuteJoin := TRUE;
END_IF;
"ExecBtn_Old" := "ExecBtn";

// Network 2 — One-shot JOIN with Position reset
IF "DB_Print".ExecuteJoin THEN
    "DB_Print".Position := 0;                      // CRITICAL: reset before call
    JOIN(
        SrcStruct := "DB_Print".SrcArray,
        Count     := "DB_Print".Count,
        Position  := "DB_Print".Position,
        DestArray := "DB_Print".DestBuffer
    );
    "DB_Print".ExecuteJoin := FALSE;
END_IF;

5.3 LAD / FBD Equivalent

For engineers working in ladder:

  1. Network 1: A normally-open contact ExecBtn in parallel with a NOT ExecBtn_Old contact drives a coil assigned to DB_Print.ExecuteJoin. Latch the same coil with an S (Set) output so it stays TRUE until cleared by the next network.
  2. Network 2: A MOVE 0 box copies constant 0 into DB_Print.Position. Place this in series with the JOIN call so it executes immediately before.
  3. Network 3: The JOIN block is enabled by DB_Print.ExecuteJoin. The Count input is wired to DB_Print.Count, SrcStruct to DB_Print.SrcArray, DestArray to DB_Print.DestBuffer, and Position to DB_Print.Position.
  4. Network 4: Reset DB_Print.ExecuteJoin to FALSE so the block does not re-fire.
Edge-Case Warning: Do not place the MOVE 0 on the same network as the JOIN call in ladder. TIA Portal evaluates the network left-to-right; the reset must complete before JOIN reads Position. A separate upstream network guarantees the execution order.

6. Step-by-Step Resolution Procedure

  1. Open the affected block in TIA Portal and go online with the S7-1500 CPU. Add DB_Print.Position, DB_Print.Count, and the JOIN instance to a watch table.
  2. Force Position to 0 from the watch table. If the error disappears on the next call, you have confirmed root cause #2 (dynamic position).
  3. Verify SrcStruct data type by right-clicking the JOIN instance and selecting "Go to → Cross-reference" on the input. The connected tag must be declared as ARRAY[*] OF STRING or ARRAY[*] OF WSTRING. A scalar STRING tag will not bind.
  4. Verify Count > 0. A value of 0 is allowed by the data type but produces a no-op execution with the position still modified in some firmware versions, which can appear as a phantom 16#80B5 on a subsequent call.
  5. Insert the MOVE 0 in a network immediately before the JOIN call. Compile and download to the CPU.
  6. Convert the call to a one-shot using a rising-edge contact or the pattern in Section 5.2.
  7. Re-test with the watch table. ENO must stay TRUE and Position must equal the sum of the source string lengths after the call.

7. Verification and Acceptance Test

After the fix, perform the following checks before releasing the code to production:

Post-fix verification matrix
Check Expected Result Diagnostic Tool
ENO after JOIN call TRUE Watch table / online monitor
Diagnostic buffer entry for 16#80B5 No new entry after fix is loaded Online & Diagnostics → Diagnostic buffer
Position after call Sum of source string lengths, ≤ upper bound of DestBuffer Watch table, value column
DestBuffer contents Concatenated ASCII string terminated by a null byte (CHAR 0) String view in watch table
Re-trigger via ExecBtn Buffer is overwritten from offset 0 on each new trigger Manual test with button

8. Related Error Codes and Edge Cases

JOIN raises other diagnostic codes that look similar at first glance. Knowing the differences speeds up diagnosis when the same code path is reused:

JOIN family error codes (S7-1500)
Hex Code Decimal Meaning Likely Cause
16#0000 0 No error Normal completion
16#80B1 32945 Source array index out of range Count exceeds SrcStruct length, or 0
16#80B2 32946 Destination array index out of range Result longer than DestArray
16#80B3 32947 Source string contains invalid characters Non-printable control chars or uninitialized STRING length byte
16#80B4 32948 Source/destination type mismatch STRING on one side, WSTRING on the other
16#80B5 32949 Buffer overflow OR Position outside DestArray Most common: Position not reset; or source larger than remaining capacity
16#80B6 32950 NULL pointer at SrcStruct or DestArray Tag not initialized in optimized DB or watch table binding missing

9. Alternative Implementations

JOIN is the right block when you need to flatten a known-size array of strings into a single character buffer for a printer, log file, or HMI string. For other use cases, prefer the more specific instructions:

  • CONCAT — Appends one string to another. Use when you have two or three strings to merge, not an array.
  • String_TO_Chars — Converts a single STRING into a ARRAY OF CHAR without offset management. Use when you only need to break a string into its bytes (e.g., for CRC or hashing).
  • Chars_TO_String — Inverse of the above; the companion block for assembling a string from a byte stream such as serial input.
  • S_MOVE / BLKMOV — If the source is a flat ARRAY OF CHAR with no string metadata, block moves are faster and avoid the 16#80B5 path entirely.

For wide (Unicode) strings, use the WSTRING variant of JOIN (TIA Portal V16+) or call the block twice with explicit length calculation when targeting older firmware.

10. Platform and Firmware Notes

The JOIN block ships in the standard instruction set of every S7-1500 CPU from firmware V1.5 onward and on S7-1200 from firmware V4.0 onward. Behavior is consistent across the families, with two exceptions:

  • Optimized DB access: In TIA Portal V14 and earlier, the Position tag in an optimized block could lose its value on a STOP-to-RUN transition if the "Retain" attribute was not set. From V15.1 onward, optimized blocks retain in/out parameters across the warm restart by default. Always verify the retain bit for the position tag in production code that survives a CPU restart.
  • Multi-instance vs. single-instance: When JOIN is called as a multi-instance inside a user FB, the instance DB must be non-optimized if the FB itself is non-optimized. Mismatched optimization settings between the FB and the instance DB produce 16#80B5 with no diagnostic buffer entry — the error is suppressed because the call is inlined by the compiler.

11. When to Escalate to Siemens Support

If the procedure above does not clear the fault, the next step is a Siemens Support Request. The standard support service is included with the CPU warranty at no charge for technical questions on documented blocks. When filing, include:

  1. The TIA Portal project archive (TIA Portal V15.1 or later can save a compressed .zap15 archive).
  2. The PLC diagnostic buffer export (CSV).
  3. The watch table screenshot showing the Position value before and after the JOIN call.
  4. The exact CPU order number (e.g., 6ES7515-2AM02-0AB0) and firmware version (read from online → Online & Diagnostics → CPU).

Siemens typically responds to a non-critical application question within two business days for warranty-covered CPUs. The expanded S7-1500 String + Char section of the TIA Portal V15.1 system manual contains the official parameter descriptions and a worked example that supplements the inline help.

What does S7-1500 error code 16#80B5 mean on the JOIN instruction?

Error 16#80B5 (decimal 32949) is raised by JOIN when the write position into DestArray is outside the valid range, or when the source string cannot fit into the remaining destination capacity. The dominant cause in production code is the Position tag not being reset to 0 before the call — Position is an in/out parameter that is updated after every execution.

Why does JOIN keep erroring even after I trigger it only once?

If the EN input of the JOIN block is tied to a constant TRUE or to a tag that stays TRUE, the CPU executes the block on every cycle. The first call sets Position to N, the second call tries to write at offset N which is past the end of DestArray, and 16#80B5 fires on the second cycle. Use a rising-edge one-shot or the pattern in Section 5.2 to fire JOIN only once per request.

What data types must SrcStruct and DestArray be for the JOIN block?

SrcStruct must be declared as ARRAY[*] OF STRING (or WSTRING for wide strings). DestArray must be declared as ARRAY[*] OF CHAR. The block does not accept a scalar STRING tag, nor does it mix STRING and WSTRING on the two ports — use the matching WSTRING variant or convert explicitly with Chars_TO_String / String_TO_Chars.

Do I need to reset the Position tag to 0 before every JOIN call?

Yes. Position is an in/out parameter — JOIN writes a new offset back into it on every successful call. If you call JOIN again with the same source data, MOVE 0 to Position first, or the second call will start writing past the end of DestArray. The reset must be in a network that executes strictly before the JOIN call.

Can I use JOIN on an S7-1200 the same way as on an S7-1500?

The interface and behavior of JOIN are identical between S7-1200 firmware V4.2 and S7-1500 firmware V1.8 or later. The same Position-reset and one-shot rules apply. WSTRING support on the S7-1200 requires firmware V4.4 or later; older firmware only supports the STRING variant of JOIN.

Back to blog