Resolving Siemens PUT Instruction Error 4: Pointer Format Fix

David Krause12 min read
HMI ProgrammingSiemensTroubleshooting
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

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 8 BOOL tags
  • 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:

UDT memory alignment rule: UDT instance declarations allocate memory in 16-bit (2-byte) increments. Even if the UDT logically contains only 1 byte (e.g. eight 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

  1. 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.
  2. Cross-check the generated ANY pointer. In TIA Portal, hover over the SD_i input 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.
  3. 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.
  4. Add an explicit intermediate tag. Declare a temporary variable (e.g., tmpBhs : BYTE) inside the FB. Assign #bhs to #tmpBhs and pass #tmpBhs to SD_1. The generated pointer becomes a flat BYTE pointer of width 1, which avoids the UDT alignment issue.
  5. Use a STRING[1] instead of a UDT-BOOL-pack. Define bhs : STRING[1]. The generated pointer then becomes CHAR 1, which the partner decodes cleanly.
  6. Switch the UDT to a 16-bit or wider symbol. If the application allows, pack the 8 BOOLs into a WORD or INT. The pointer becomes WORD 1 or INT 1, and both ends align on a 16-bit boundary by design.
  7. Inspect the STATUS word online. Use an INT_TO_HEX conversion 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
Compatibility caveat: the S7-1200 family does not support the 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:

  1. 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.
  2. 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.
  3. 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_i directly, 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

  1. 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.
  2. Trace the SD_i tooltip. Before downloading, hover the SD_i tag. The tooltip should read P#DB<n>.DBX<o>.0 CHAR 1 or BYTE 1 depending on the chosen solution.
  3. 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.
  4. 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).
  5. 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?

Yes. It is described in the TIA Portal help under 'Data type BOOL' / 'UDT instance declaration' / 'Memory areas of the data blocks'. Engineers sometimes call it the '2-byte boundary rule'.

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.

"
Back to blog