Extracting Individual Bytes from PROFINET IO Submodules

David Krause12 min read
I/O ModulesSiemensTutorial / How-to
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

Overview

Reading bulk data from a PROFINET IO device on a SIMATIC S7-1200 or S7-1500 returns a contiguous block of bytes (for example, a 56-byte input area exposed as devicename~56_bytes_I_1). When the project only declares the input as a flat HW_IO address or as an ARRAY[..] OF DINT, the programmer loses the ability to symbolically address individual bytes, words, or bits inside the payload. This article documents four field-proven techniques to recover granular access: bit/byte slice notation on a DWORD tag, casting the I/O address to a UDT, telegram-based symbolic I/O assignment, and consistent block reads using the GETIO instruction. Each method is mapped to a specific TIA Portal workflow and to a STEP 7 firmware capability baseline (CPU firmware V4.4 for S7-1200, V2.0+ for S7-1500).

Prerequisites

  • STEP 7 (TIA Portal) V16 or later. V20 is recommended for the most current GETIO documentation set; the GETIO instruction itself has been available since V14 SP1 on S7-1500 and since V14 on S7-1200 firmware V4.1+. Reference: GETIO: Read all inputs of a submodule (S7-1200, S7-1500).
  • S7-1200 CPU with firmware V4.4 or higher, or S7-1500 CPU with firmware V2.0 or higher. Slice access (%B0..%B3) on a DWORD requires the S7-1500 or S7-1200 firmware V4.0+ instruction set.
  • Configured PROFINET IO device in the device view, with at least one submodule assigned to a slot. The device GSD file must be installed in TIA Portal so that the I/O length (e.g., 56 bytes input, 0 bytes output) is visible.
  • Available PLC tags table, plus a data block of type UDT if the UDT-based method is used.
  • Optional: PROFINET IO Connector V1.1 documentation for advanced scenarios involving third-party IO Connector stacks. Reference: PROFINET IO Connector V1.1 PDF (Siemens Support, attachment 109793251).
Note on HW_IO semantics. The HW_IO data type identifies a hardware input by its I/O address. A tag of type HW_IO cannot store process data; it is only an alias. To read data, the tag must be cast to BYTE, WORD, DWORD, ARRAY OF BYTE, or a UDT at the point of consumption.

Method 1: Byte Slice Access on a DWORD (%Bx)

On S7-1200 (FW V4.0+) and S7-1500, a tag declared as DWORD exposes four byte slices %B0, %B1, %B2, and %B3, plus sixteen bit slices %X0..%X15. This is the fastest path to granular reads when the input length is small and fits inside one or more DWORD tags.

  1. In the PLC tag table, add a tag named ioDword_0 of data type DWORD and assign its address to the start of the PROFINET input range, e.g. %ID500.
  2. In the program, read the entire DWORD with L %ID500 / T "ioDword_0" or use symbolic reference directly: "ioDword_0" := "devicename"~56_bytes_I_1[0]; (the slice and cast are implicit in SCL).
  3. Access individual bytes with SCL slice syntax:
// SCL example — direct slice access on a DWORD
"ioByte_0" := "ioDword_0".%B0;   // bits 0..7
"ioByte_1" := "ioDword_0".%B1;   // bits 8..15
"ioByte_2" := "ioDword_0".%B2;   // bits 16..23
"ioByte_3" := "ioDword_0".%B3;   // bits 24..31

For a 56-byte payload (14 DINTs), declare ioDword_0 through ioDword_13 and assign addresses %ID500..%ID552 step 4. Each DWORD yields 4 byte slices and 16 bit slices without any additional MOVE or serialization.

Method 2: Cast the I/O Area to a UDT

Casting the PROFINET input area to a UDT is the most maintainable long-term solution. The UDT documents the slot layout (which byte carries status, which carries encoder position, etc.) and lets the programmer reference fields symbolically throughout the project.

Step-by-step

  1. Open the PLC data types editor in TIA Portal and create a new UDT named UDT_PN_Device with the exact field layout of the 56-byte input. Example for a generic 14-DINT payload:
TYPE UDT_PN_Device :
STRUCT
    StatusWord    : WORD;      // bytes 0..1   (IW500)
    Padding       : ARRAY[0..1] OF BYTE;  // bytes 2..3
    EncPosition   : DINT;      // bytes 4..7   (ID504)
    EncSpeed      : DINT;      // bytes 8..11  (ID508)
    Torque        : DINT;      // bytes 12..15 (ID512)
    Diagnostics   : ARRAY[0..3] OF DINT;  // bytes 16..31
    UserData      : ARRAY[0..7] OF DINT;  // bytes 32..63 (only first 24 used)
END_STRUCT
END_TYPE
  1. In the PLC tag table, add a tag named pnInput of data type UDT_PN_Device and assign its address to the start of the PROFINET input range: %IB500. TIA Portal accepts the start address even when the tag type is a STRUCT; subsequent fields are mapped at %IB500 + offset automatically.
  2. Reference fields symbolically anywhere in the project: "pnInput".EncPosition, "pnInput".UserData[3].
  3. If the device also has outputs, create a second UDT UDT_PN_Outputs and assign it to the output range %QB500.
Tip — incremental UDT creation. The IDE does not expose a UDT assignment directly in the I/O tag dialog. Workaround: first create a Bool tag at the start address, save the project, then change the tag's data type to UDT_PN_Device. The address stays at the original offset, and all UDT members are mapped relative to that offset. This is the same incremental path Siemens describes in the S7-1500 system manual, and it mirrors the AT-overlay pattern familiar from STL/S7-300 programming.

Method 3: Telegram-Based Symbolic I/O Assignment

If the PROFINET device supports a configurable telegram (for example, Siemens SINAMICS drives use telegrams 1, 2, 3, 5, 6, 7, 9, 110), TIA Portal can map the telegram to a UDT directly. This is the cleanest method when a standard telegram is available because the GSD file ships the structure definition.

  1. In the device view, click the PROFINET device and open the device configuration.
  2. Select the submodule that represents the telegram slot. Assign the standard telegram number from the drop-down (e.g., Telegram 105 for SINAMICS S120 free telegram configuration).
  3. In the I/O addresses tab, note the assigned input start address (e.g., %IB500) and the length.
  4. Create a PLC tag of type UDT_SINAMICS_T105 (Siemens provides this UDT in the SINAMICS library), assign it to %IB500, and reference fields symbolically: "drive".SetpointSpeed.
Method Best For Required Firmware Symbolic Access Consistent Read
%Bx slice on DWORD Small payloads (≤16 bytes), ad-hoc debugging S7-1200 FW V4.0+ / S7-1500 FW V2.0+ Yes (via tag) No — read is non-atomic across DINTs
UDT cast on I/O Stable, documented device layout S7-1200 FW V4.0+ / S7-1500 FW V2.0+ Yes No — UDT is read word-by-word
Telegram UDT Drives and devices with PROFINET telegrams S7-1200 FW V4.2+ / S7-1500 FW V2.0+ Yes (Siemens UDT) Partial — depends on telegram mapping
GETIO / DPRD_DAT Consistent submodule snapshot, large payloads S7-1200 FW V4.1+ / S7-1500 FW V1.0+ Via destination UDT Yes — atomic over the entire submodule

Method 4: GETIO for Consistent Submodule Reads

Both DPRD_DAT (read) and DPWR_DAT (write) operate on a contiguous I/O area. When the application needs a consistent snapshot of the entire submodule (for example, to read a 56-byte input atomically with respect to OB1 cycles), use GETIO from the Extended Instructions. GETIO — Read all inputs of a submodule (S7-1200, S7-1500).

// SCL — GETIO usage on a PROFINET submodule
#retVal   := GETIO(
    LADDR       := 500,                      // logical base address of the submodule
    ID          := 0,                        // submodule identifier (0 = whole station)
    LEN         := 56,                       // length in bytes
    RECORD      := "dbInputs".inputArray,    // destination DB of type ARRAY[0..55] OF BYTE
    #busy       := #busyFlag,
    #error      := #errorFlag,
    #status     := #statusWord);

// After GETIO returns TRUE (done), cast the byte array back to a UDT for symbolic access
"ioStruct".StatusWord  := "dbInputs".inputArray[0..1];
"ioStruct".EncPosition := DWORD_TO_DINT( DWORD_FROM_BYTES("dbInputs".inputArray[4],
                                                         "dbInputs".inputArray[5],
                                                         "dbInputs".inputArray[6],
                                                         "dbInputs".inputArray[7]) );

Key parameters

  • LADDR: Logical base address of the PROFINET IO submodule. For the example device at %IB500, this is 500. Note that LADDR is a WORD; always pass the integer literal or an INT tag, not a hex string.
  • ID: Hardware identifier of the submodule. When the entire station is read, set ID := 0. For partial reads, use the system constant generated by TIA Portal under PLC > System constants > ... > Head station.
  • LEN: Length in bytes. The maximum transfer length per GETIO call is 1024 bytes; for larger submodules, split into multiple calls with different ID values.
  • RECORD: Destination tag. Must be a fully qualified tag of type ARRAY[..] OF BYTE in a non-optimized DB, or a UDT-compatible buffer in a standard DB. Optimized DBs are allowed in S7-1500 FW V2.0+ but require the RETENTIVE attribute to be off and the DB to be configured as non-process-image.

Address Arithmetic: Locating Byte N Inside a Submodule

Byte-N inside a submodule at base IBbase is IBbase + N. For the 56-byte example with a telegram at IB500:

Symbolic Name Address Notes
Byte 0 (first byte of submodule) %IB500 Bit 0 of first byte
Byte 1 %IB501
Byte 4 (start of first DINT) %IB504 Equivalent to %ID504 / %ID501 (alignment-dependent)
Byte 5 %IB505 High byte of %IW502 (little-endian)
Byte 55 (last byte) %IB555 Final byte of 56-byte payload

Siemens S7-1500 stores multi-byte values in little-endian order, so the first byte of a DINT is the least significant byte (LSB) and is placed at the lowest absolute address. A debugger that monitors IB504..IB507 will see the value of ID504 in the order LSB, LSB+1, LSB+2, MSB.

Comparison of the Four Methods

Criterion %Bx Slice UDT Cast Telegram UDT GETIO
Setup effort Very low Medium Low (if Siemens UDT exists) Medium
Code clarity Medium High High High
Read consistency None (slice reads are not atomic across 4 bytes) None (UDT members update per OB1 cycle) Partial (depends on telegram mapping) Full (entire submodule in one call)
Performance (overhead per scan) Negligible Negligible Negligible One system call per cycle (~5-20 µs on S7-1516)
Recommended use Diagnostic, ad-hoc Production code, fixed layout Drives with standard telegrams Large submodules, safety-relevant reads

Step-by-Step Implementation (Recommended Path)

The recommended long-term pattern combines a UDT for symbolic access with GETIO for consistent reads. The procedure below assumes a 56-byte PROFINET input at %IB500.

  1. Create the input UDT. In PLC data types, add UDT_PN_Input with the device-specific layout. Use a tool like a PROFINET frame capture or the device's manual to confirm field offsets.
  2. Create the target DB. Add DB_PN_Buffer containing buffer : ARRAY[0..55] OF BYTE; and parsed : UDT_PN_Input;. Mark the DB as non-optimized (S7-1500) to allow absolute addressing, or as optimized with Accessible from HMI/OPC UA unchecked if the controller supports it.
  3. Call GETIO in OB1 (or a cyclic OB). Use the parameters shown in the snippet above. Wire the DONE, ERROR, and STATUS outputs to evaluate success; on ERROR, evaluate STATUS per the table below.
  4. Parse the byte array into the UDT. After DONE, copy the buffer into the UDT. For known DINTs at fixed offsets, use the conversion pattern:
"parsed".EncPosition := DWORD_TO_DINT(
    SHL_DWORD(BYTE_TO_DWORD("DB_PN_Buffer".buffer[7]), 24) OR
    SHL_DWORD(BYTE_TO_DWORD("DB_PN_Buffer".buffer[6]), 16) OR
    SHL_DWORD(BYTE_TO_DWORD("DB_PN_Buffer".buffer[5]),  8) OR
    BYTE_TO_DWORD("DB_PN_Buffer".buffer[4]) );
  1. Reference fields symbolically. Use "DB_PN_Buffer".parsed.EncPosition in all subsequent code. The buffer is the only place where raw bytes live; the parsed UDT is the source of truth.
  2. Repeat for outputs. Use SETIO for consistent output writes; see the related TIA Portal instruction set.

Verification

  1. Compile and download the project to the CPU. The TIA Portal online view should show the device as OK in the diagnostics view.
  2. Open the watch table PN_IO_Watch and add tags "DB_PN_Buffer".buffer[0..55], "DB_PN_Buffer".parsed.EncPosition, and "DB_PN_Buffer".parsed.StatusWord.
  3. Toggle a known input on the PROFINET device (e.g., force a value in the device's web server or commissioning tool) and confirm that both the raw byte and the parsed UDT field update on the same OB1 cycle.
  4. Force the controller to STOP and back to RUN. Confirm that GETIO continues to refresh RECORD without an error code in STATUS.
  5. Disconnect and reconnect the PROFINET cable to trigger a station failure and recovery. Verify that STATUS returns 0x0000 on the next successful GETIO call.

Troubleshooting Matrix

Symptom Likely Cause Resolution
Compiler error "Address %IB504 overlaps tag Two tags assigned to the same byte Delete the redundant tag or shift the UDT start address
GETIO returns STATUS = 0x80A1 Invalid LADDR (station not configured or wrong base) Verify the device's logical base address in the device view
GETIO returns STATUS = 0x80B1 LEN longer than configured submodule Reduce LEN or check the GSD-defined submodule length
GETIO returns STATUS = 0x80C3 Resource bottleneck (busy) Wait for DONE before next call; do not retrigger in the same OB1
Slice value flickers between 0 and a constant Process image is updating mid-cycle; the slice read is not atomic Switch to GETIO for a consistent snapshot, or move the slice access into OB1 with a snapshot DB
UDT fields show wrong endianness Manual assumed big-endian, S7 stores little-endian Swap byte order in the DINT conversion, or use SHL/SHR with the documented order
Hardware fault LED on the device after wiring change Submodule mismatch (slot has a different module than the GSD defines) Re-scan the topology in the device view; confirm the configured order matches the physical order
Consistency warning. Reading 14 DINTs as a flat array of DINT is not atomic across the array. The PROFINET update is performed once per PROFINET cycle (typically 1 ms), but the application may read partial updates. For deterministic behavior, always use GETIO into a buffer DB and then parse the buffer.

Frequently Asked Questions

Can I read a single byte directly from a PROFINET IO submodule without copying the whole input range?

Yes. Declare a BYTE tag (or a DWORD tag with %Bx slices) and assign its address to the byte you need, e.g., %IB504 for the 5th byte of a 56-byte input area starting at IB500. The I/O address is mapped to a process image that updates once per PROFINET cycle; no extra copy is required.

Why does my UDT declared at %IB500 not show the right field offsets?

The most common cause is that the UDT was created with the wrong field order or that the start address was assigned before the UDT type was set. TIA Portal maps STRUCT members relative to the tag's start address, so an offset of 1 inside the UDT places the second field at %IB501. Verify offsets against the device manual and recompile the project.

What is the difference between DPRD_DAT and GETIO for PROFINET?

DPRD_DAT reads a contiguous I/O area defined by LADDR and LEN but does not guarantee consistency across the entire area; each word can be updated independently. GETIO reads the entire submodule in a single PROFINET cycle and writes a consistent snapshot into the destination RECORD, which is the recommended method for data larger than 4 bytes.

Do I need a PROFINET telegram to use GETIO or a UDT cast?

No. A telegram is only required for the symbolic Siemens UDT mapping on drives and similar devices. GETIO works on any PROFINET IO submodule regardless of telegram, and a UDT cast on %IB..%Ixx works for any device whose input area is mapped into the process image.

What firmware is required for the %Bx slice notation on a DWORD?

S7-1200 requires firmware V4.0 or later, and S7-1500 requires firmware V2.0 or later. Earlier firmware does not support the %B0..%B3 slice access on a DWORD tag, and the symbol will be flagged in the compiler as an unknown attribute.

Back to blog