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.
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
FALSEon the first execution cycle of JOIN. - The destination array contains partial data, a single character, or remains unchanged.
- Online watch on the
Positiontag shows a value larger than the upper bound ofDestArray. - 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:
| 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.
| 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 |
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:
- Reads
Position(initially 0 on first call). - Appends
Countsource strings starting at that offset. - Writes the new offset back to
Position. - Returns immediately on the next cycle.
Positionis nowN, not 0. - The runtime check compares
Nagainst the upper bound ofDestArray. IfN> 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.
| 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:
- Network 1: A normally-open contact
ExecBtnin parallel with aNOT ExecBtn_Oldcontact drives a coil assigned toDB_Print.ExecuteJoin. Latch the same coil with anS(Set) output so it stays TRUE until cleared by the next network. - Network 2: A
MOVE 0box copies constant 0 intoDB_Print.Position. Place this in series with theJOINcall so it executes immediately before. - Network 3: The
JOINblock is enabled byDB_Print.ExecuteJoin. TheCountinput is wired toDB_Print.Count,SrcStructtoDB_Print.SrcArray,DestArraytoDB_Print.DestBuffer, andPositiontoDB_Print.Position. - Network 4: Reset
DB_Print.ExecuteJointo FALSE so the block does not re-fire.
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
-
Open the affected block in TIA Portal and go online with the S7-1500 CPU. Add
DB_Print.Position,DB_Print.Count, and theJOINinstance to a watch table. -
Force
Positionto 0 from the watch table. If the error disappears on the next call, you have confirmed root cause #2 (dynamic position). -
Verify
SrcStructdata type by right-clicking the JOIN instance and selecting "Go to → Cross-reference" on the input. The connected tag must be declared asARRAY[*] OF STRINGorARRAY[*] OF WSTRING. A scalarSTRINGtag will not bind. -
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. -
Insert the
MOVE 0in a network immediately before the JOIN call. Compile and download to the CPU. - Convert the call to a one-shot using a rising-edge contact or the pattern in Section 5.2.
-
Re-test with the watch table. ENO must stay TRUE and
Positionmust 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:
| 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:
| 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
STRINGinto aARRAY OF CHARwithout 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 CHARwith 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
Positiontag 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:
- The TIA Portal project archive (TIA Portal V15.1 or later can save a compressed .zap15 archive).
- The PLC diagnostic buffer export (CSV).
- The watch table screenshot showing the
Positionvalue before and after the JOIN call. - 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.