Problem Overview
The PUT instruction in the Siemens SIMATIC S7-1500 and S7-1200 PLC families is part of the S7 Communication function block set and is used to write data to a remote partner CPU across Ethernet/PROFINET. A common fault encountered when using the PUT instruction with symbolic ANY pointers (rather than fully-qualified absolute addresses) is the return of ERROR = 1 with STATUS = W#16#0004. STATUS code W#16#0004 indicates a pointer error, meaning that the receiving partner rejected the request because the supplied ID, ADDR_1, or SD_i parameter format was invalid for the target CPU's memory model.
This article documents a specific field case:
- Local PLC: SIMATIC S7-1516-3 PN/DP (CPU firmware V2.x or later, TIA Portal V15.1+)
- Remote PLC: SIMATIC S7-1212C AC/DC/Rly
- Data to send: a 1-byte UDT (
PLCDataType "XrayTelegramOut") containing 8BOOLtags - Generated pointer:
P#DB77.DBX48.0 BYTE 2(incorrect) - Expected pointer:
P#DB77.DBX48.0 CHAR 1(correct) - Observed fault: STATUS output of PUT =
W#16#0004 - Counterintuitive finding: the matching GET instruction (2 bytes wide) does not fault
Understanding PUT/GET and the ANY Data Type
The PUT / GET instructions are wrappers around the SIMATIC S7 communication primitives defined in the IEC 61131-3 programming model. They are exposed in TIA Portal under Instructions > Communication > S7 Communication as the function blocks PUT and GET.
| Input | Data Type | Purpose |
|---|---|---|
| REQ | BOOL | Rising edge triggers the operation |
| ID | WORD | Connection ID from the configured S7 connection |
| ADDR_i | VARIANT (S7-1500) / ANY (S7-1200/300/400) | Remote partner area specification |
| SD_i / RD_i | VARIANT (S7-1500) / ANY (S7-1200/300/400) | Local source / destination area |
| DONE / ERROR / STATUS | BOOL / BOOL / WORD | Job status feedback |
The ANY pointer is a structured descriptor that contains the data type code, repetition factor, and DB/offset. It looks like:
P#DBxx.DBXyy.z TYP n
| | | | | |
| | | | | repetition factor (count)
| | | | data type code (BOOL=1, BYTE=2, CHAR=3, INT=4, WORD=5, ...)
| | byte offset within the area
| area type (DB, M, I, Q, etc.)
DB number
The receiving partner validates the SD_i/ADDR_i parameter based on this descriptor. If the data type code does not match a length that the partner can decode, it rejects the request with STATUS = 4.
Root Cause: UDT 16-Bit Memory Alignment
The UDT (User-Defined Data Type, also called PLC Data Type in TIA Portal) is the central element of the fault. Siemens implements a long-standing compiler rule:
BOOL values), the compiler reserves a full 16-bit word for it within a data block.
Concretely, in the affected FB interface, the variable at offset 48 was declared as a BYTE-sized UDT. The next adjacent variable started at byte offset 50 (not 49), proving that the compiler padded the UDT to a 16-bit boundary. When TIA Portal automatically generated the ANY pointer for an inlined SD_i reference, it derived the following:
P#DB77.DBX48.0 BYTE 2 <-- what the compiler generated
P#DB77.DBX48.0 CHAR 1 <-- what the partner actually needs
The PLC chose BYTE 2 because the underlying storage allocation is two bytes wide. The S7-1212C partner, however, decodes BYTE 2 as two unsigned bytes, not as a single character. The application payload is a 1-byte semantic value (an 8-bit packed flag field). Sending BYTE 2 overruns the receiving data area or mismatches the partner's expected type, so the partner rejects the call with STATUS = W#16#0004.
The GET instruction worked because its payload was 16 bits (2 bytes) - coincidentally matching the padded UDT width. There was no semantic/type mismatch on the receive side.
PUT/GET STATUS Error Code Reference
The complete PUT/GET error mapping per the Siemens Function Manuals is:
| STATUS (hex) | Meaning |
|---|---|
| 0000 | No error |
| 0001 | Communication error (e.g., partner unreachable) |
| 0002 | Negative acknowledgement from partner CPU |
| 0003 | No new job can be initiated while an existing one is active |
| 0004 | Pointer error in ID, ADDR_i, RD_i, or SD_i parameter |
| 0005 | Resource error (no free connection / no instance DB available) |
| 0006 | Wrong parameter assignment in ID |
| 0007 | Job terminated; previous job still pending |
| 0081 | Partner CPU in STOP / disconnected |
| 0082 | Partner CPU cannot be reached (timeout) |
| 0083 | Partner CPU rejected the job (protection / resource) |
| 0084 / 0085 | S7 protocol error on the wire |
| 0086 | Partner CPU reports an access error on ADDR_i |
| 0087 | Job aborted by partner (e.g., PUT to read-only area) |
Codes 0004 and 0086 frequently get confused. Diagnostics should follow this rule:
- 0004: the local CPU evaluates the parameter descriptor and finds a length/type code that the partner is not allowed to use - fix the pointer before sending.
- 0086: the partner accepts the call but cannot access the specified address - check the partner DB existence and length.
Diagnostic Procedure
- Open the online & watch table of the local PLC. Put the FB instance DB on watch. Inspect the data offsets of the tags surrounding the UDT. Verify that the UDT starts on an even byte offset and the next tag starts on the next even byte offset.
-
Cross-check the generated ANY pointer. In TIA Portal, hover over the
SD_iinput of the PUT block. The tooltip shows the resolved pointer (e.g.,P#DB77.DBX48.0 BYTE 2). Compare to the partner's expected definition. - Cross-check the partner PLC project. On the S7-1212C, confirm that the destination DB contains the matching UDT and that its declared type is identical. Mismatch on logical type triggers 0004 even when byte counts match.
-
Add an explicit intermediate tag. Declare a temporary variable (e.g.,
tmpBhs : BYTE) inside the FB. Assign#bhsto#tmpBhsand pass#tmpBhstoSD_1. The generated pointer becomes a flatBYTEpointer of width 1, which avoids the UDT alignment issue. -
Use a STRING[1] instead of a UDT-BOOL-pack. Define
bhs : STRING[1]. The generated pointer then becomesCHAR 1, which the partner decodes cleanly. -
Switch the UDT to a 16-bit or wider symbol. If the application allows, pack the 8 BOOLs into a
WORDorINT. The pointer becomesWORD 1orINT 1, and both ends align on a 16-bit boundary by design. -
Inspect the STATUS word online. Use an
INT_TO_HEXconversion to display the value in human-readable form (0004, 0086, etc.).
Solution 1 - Replace the UDT with a Widened Symbol
The cleanest fix is to change the data type of the source variable to a 16-bit scalar. Eight BOOL values can be packed into one BYTE, but a BYTE still aligns on 16-bit boundaries in UDTs. Use a WORD instead:
// In FB interface
VAR
bhs : WORD; // length 2 bytes - matches UDT alignment, no padding
END_VAR
// Pack the 8 flags locally before the PUT
#bhs.%X0 := flag1;
#bhs.%X1 := flag2;
...
#bhs.%X7 := flag8;
// Or use POKE_BOOL / AT view if individual access is required
The PUT then generates:
P#DBxx.DBX48.0 WORD 1
Both the local CPU and the partner decode WORD identically, eliminating the mismatch.
Solution 2 - Explicit Intermediate Buffer
When the payload is required to be exactly 1 byte, route the data through an intermediate scalar:
VAR_TEMP
tmpBhs : BYTE;
END_VAR
BEGIN
// Copy the UDT byte into a scalar
#tmpBhs := %DB77.bhs; // or use AT view on the source UDT
PUT_DB(REQ := #start,
ID := #connectionId,
LEN := 1,
SD_1 := #tmpBhs); // generates P#DB... BYTE 1
END_FUNCTION_BLOCK
This works on S7-1500 controllers. Because of compiler behaviour on the S7-1200 line (PUT on S7-1200 only accepts in-line literal or DB-tag ANY), the intermediate tag must be declared in a global DB or instance DB - not as a TEMP - for S7-1200 projects.
Solution 3 - Pointer Manipulation via POKE/POKE_BLK
For advanced users, the ANY pointer can be constructed manually using the tc_system library, but on TIA Portal this requires the AnyTypeToVariant conversion helpers. This approach is necessary when:
- The pointer width must be less than the UDT padded width
- The DB layout cannot be modified
- Both S7-1500 and S7-1200 controllers share the FB
ANY pointer type natively. Using symbolic ANY pointers from a library designed for S7-1500 will not compile cleanly on S7-1200. Always keep one code branch per controller family, or unify on a VARIANT interface in S7-1500 and convert at the call boundary.
To force the right pointer, declare the destination as CHAR on both ends:
// Local source
VAR
bhs : ARRAY[0..0] OF CHAR; // 1 byte wide, type CHAR
END_VAR
// Remote destination
VAR
bhs : ARRAY[0..0] OF CHAR;
END_VAR
The compiler emits P#DBx.DBXy.z CHAR 1, and the partner interprets CHAR as a single 8-bit ASCII/byte value.
Solution 4 - Verify Connection and Protection Settings
STATUS 0004 may also surface when the connection is configured with mismatched access attributes. Verify:
- In Devices & Networks > S7 Connection, open the properties of the configured S7 connection. Make sure Active connection establishment matches the controller acting as client. The local S7-1516-3 PN/DP must hold the active role.
- On the S7-1212C properties, under Protection & Security > Connection mechanisms, enable PUT/GET access via communication partners. Without this flag, the partner rejects all PUT/GET calls with 0004 or 0086.
- Confirm the partner DB is not optimised (select Optimized Block Access = No) when the receiving address uses absolute symbolic tags. Otherwise the compiler will mask the tag under a different storage model.
Cross-Platform Compatibility: S7-1500 vs S7-1200
Field teams regularly need to deploy a single Function Block across an entire fleet that mixes S7-1200 and S7-1500 PLCs. The following table summarises platform-specific restrictions that affect portable PUT code:
| Feature | S7-1200 (FW 4.x) | S7-1500 (FW 2.x+) |
|---|---|---|
| PUT/GET in TIA Portal library | Yes (limited address types) | Yes (full type coverage) |
| VARIANT type support | No | Yes (since V13 SP1) |
| ANY pointer as in/out | No (constants only at call boundary) | Yes |
| UDT 16-bit alignment | Yes (inherited from S7-300 lineage) | Yes |
| Library portability | Requires type-safe wrappers | Direct reuse |
The implications are practical:
- If the FB interface passes a UDT into
SD_idirectly, the S7-1500 builds a 2-byte ANY while the S7-1200 may not compile the UDT at all in that slot. - A portable FB must accept a flat scalar (
BYTE,CHAR,WORD) and assemble/disassemble the UDT at the call site. - Library-typed reusables with UDT parameters must be generated as separate controller-type specific library copies or consolidated via the new TIA Portal type versions.
Verification Steps After the Fix
- Online watch on STATUS. With the corrected pointer, STATUS after the next PUT call returns 0000 and DONE = TRUE. Any other value means the same root-cause class (pointer or access mismatch) is still present.
-
Trace the SD_i tooltip. Before downloading, hover the
SD_itag. The tooltip should readP#DB<n>.DBX<o>.0 CHAR 1orBYTE 1depending on the chosen solution. - Watch table on the partner PLC. Open the S7-1212C online. Confirm that the receiving DB byte matches the value placed in the local source. Toggle a flag in the source UDT and verify the partner's mirror updates on the next PUT cycle.
- Stress test with REQ cycling. Toggle REQ every 100 ms for five minutes and confirm DONE/STATUS stays at 0000 and no new entry is created in the partner's diagnostic buffer (Diagnostic buffer > Events > Communication errors).
- Force STOP/RUN on the partner. Bring the S7-1212C to STOP, then back to RUN. Verify that the next PUT retries correctly without leaving the FB in ERROR state.
Troubleshooting Matrix
| STATUS | Likely Cause | Action |
|---|---|---|
| 0004 | Wrong ANY pointer type/length, or PUT/GET blocked on partner | Inspect pointer tooltip; enable PUT/GET on partner |
| 0081 | Partner not in RUN | Check partner mode LED |
| 0082 | Network timeout | Verify IP and PROFINET cabling |
| 0086 | Partner DB too small / wrong slot | Check DB number, length, optimised access |
| 0005 | No free connection resource | Check connection configuration limits |
| 0006 | Wrong connection ID | Verify configured S7 connection ID matches |
Best Practices for Symbolic PUT/GET in TIA Portal
- Prefer fully qualified symbolic tags over inline
P#...literals so that re-instancing the FB changes nothing at the call site. - Treat UDTs as 16-bit aligned blocks. Either widen them to a multiple of 2 bytes or store them in non-optimised DBs with explicit offset notation.
- Enable PUT/GET access by communication partner on every CPU that participates in PUT/GET exchange; document this in the security concept.
- For cross-platform libraries (S7-1200 / S7-1500), keep the public interface on flat scalar types. Do the UDT marshalling at the call site, not inside the library.
- Centralise STATUS decoding in a small FC that maps the Word to a textual code; field engineers are far more effective when 0004 is rendered as "Pointer error" instead of an unreadable hex.
- Always run a consistency check after editing the source PLC project: Project > Compile > Software (rebuild all). Stale generated ANY pointers from incremental compiles have caused 0004 errors in TIA Portal V16-V17.
Why does PUT return STATUS 0004 when GET works with the same data layout?
Because the GET payload was 2 bytes, coinciding with the UDT's 16-bit compiler allocation. The PUT was sending a 1-byte UDT, but the compiler generated a 2-byte ANY pointer (BYTE 2) for it. The remote partner decoded the BYTE descriptor differently than expected, hence STATUS = 0004. Matching the partner's expected type/width eliminates the mismatch.
How do I force TIA Portal to build a CHAR 1 pointer instead of BYTE 2?
Declare the source as ARRAY[0..0] OF CHAR, or as STRING[1], or use an intermediate CHAR temp variable. The compiler will then emit the data-type code 0x03 (CHAR) with repetition factor 1.
Can the same FB with PUT be used on both S7-1200 and S7-1500?
Only if the FB interface avoids VARIANT types and in-line ANY pointer manipulation. Use flat scalars (BYTE, CHAR, WORD, DWORD), build any UDT marshalling at the call site, and the same source code compiles on both families.
Is the UDT 16-bit alignment documented officially?
Could STATUS 0004 be caused by the partner rejecting PUT/GET in its security settings?
Yes. If the partner CPU has 'Permit access with PUT/GET communication partners' disabled under Protection & Security, the partner replies with a negative acknowledgement that the local CPU surfaces partly as 0004 (pointer error) or 0086 (access error). Always verify this flag before chasing pointer construction details.