SCL: Reading 256 Bytes Input Data into ARRAY on S7-1500

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

Many third-party fieldbus gateways (HMS Anybus, Prosoft, etc.) expose a fixed-length process-data area - commonly 256 bytes per direction - to a Siemens S7-1200 or S7-1500 controller. The SCL program must then pick, decode, and convert the bytes that carry payload (e.g. ASCII status, packed DINT, vendor-specific protocol fields) into usable PLC tags.

This reference covers three production-grade methods to bring those 256 bytes into an SCL ARRAY[*] OF BYTE (or CHAR) and to convert selected slices to STRING and DINT values:

  1. POKE_BLK - runtime copy from the I/O process image into a non-optimized DB.
  2. UDT mapping - symbolic, direct access to the gateway I/O area (preferred for native Siemens and HMS PROFINET/Slave gateways).
  3. AT overlay - on optimized blocks (the S7-1500 default), overlay the bytes with a STRUCT or ARRAY at the same memory location.
Memory budget. 256 bytes equals the maximum STRING length in S7-1200/S7-1500 (254 payload + 2-byte header = 256 bytes total). If a single value may exceed 254 characters, it cannot be held in a single STRING and must be split across multiple strings or moved to a WSTRING / ARRAY OF WCHAR. See the Siemens S7-1200 character and string data types reference.

Prerequisites

Item Requirement
Controller SIMATIC S7-1200 (FW 4.2+) or S7-1500 (FW 1.8+ recommended, 2.9+ for ARRAY[*] bounds)
Engineering TIA Portal V15.1 or later (V17 / V18 recommended for current firmware)
Gateway HMS Anybus X-gateway / Communicator or compatible PROFINET / PROFIBUS slave exposing 256 bytes of input process data
GSD file Installed in TIA Portal (Options → Manage General Station Description Files)
Block knowledge SCL syntax, optimized vs. non-optimized block access, AT construct
License STEP 7 Professional (required to edit SCL sources)

Gateway Data Layout and I/O Addresses

After the GSD is installed, the gateway appears under Devices & Networks as a PROFINET IO device. Its input slot defines the byte length; for a 256-byte gateway you will see one slot of type Input with length 256. The I/O addresses assigned by TIA look like %IB256 … %IB511 for inputs and %QB256 … %QB511 for outputs (offset depends on the slot configuration; verify in the device properties, IO tags tab).

Two important points noted in field use:

  1. Third-party gateways do not expose the slot's I/O tags as symbolic names inside the Properties → IO tags page. Selecting the gateway in the project tree and opening the Details view (or the Device view → Inspector window → IO tags) shows the address range and the start offset. Use the start offset and length to build a UDT that exactly covers 256 bytes.
  2. Always confirm with the gateway's user manual (e.g. HMS Anybus X-gateway installation guide) which sub-range is cyclic process data vs. acyclic parameter data. Only cyclic data is updated every bus cycle and is safe to read into an ARRAY for fast SCL processing.

Method 1 - POKE_BLK into a Non-Optimized DB

POKE_BLK is the classic approach when the user mandates a single SCL block that ingests the entire 256-byte input area and then extracts fields. It requires a non-optimized data block (a hard requirement - the function writes by absolute byte offset).

Step 1 - Create a Non-Optimized DB

  1. Add new Data Block → name DB_Gateway_Buffer.
  2. Right-click the DB → Properties → Attributes → clear the check Optimized block access.
  3. Declare a single tag: Buffer : ARRAY[0..255] OF BYTE; (starts at offset 0.0).
  4. Compile and note the absolute start address from the DB properties (e.g. DB200.DBX0.0).

Step 2 - Call POKE_BLK in SCL

// FB "FbGatewayDecode" - called in OB1 / cyclic task
VAR
    arrInputBuffer : ARRAY[0..255] OF BYTE;   // staging area, OPTIONAL
    sStatus        : STRING;                  // 254-char max
    diValue        : DINT;
END_VAR

// 1. Copy 256 bytes from process image (%IB256) into non-optimized DB
IF NOT POKE_BLK(
        area_src   := 16#81,        // 0x81 = inputs (process image)
        db_src     := 0,            // 0 for PII, DB number for DB
        byte_src   := 256,          // start byte in PI
        area_dst   := 16#84,        // 0x84 = data block
        db_dst     := 200,          // DB200 = DB_Gateway_Buffer
        byte_dst   := 0,            // offset 0.0 in DB200
        count      := 256) THEN
    // POKE_BLK returns BOOL - handle error
    "dbAlarm".bPokeError := TRUE;
    RETURN;
END_IF;

// 2. Slice the bytes (e.g. status at offset 8, length 32)
//    Direct symbolic access is preferred on the non-optimized DB
sStatus := '';
FOR #i := 0 TO 31 DO
    sStatus[1 + #i] := CHAR_TO_STRING("DB_Gateway_Buffer".Buffer[8 + #i])[1];
END_FOR;
// (Production code should use the IEC STRNG libraries instead of byte-by-byte copy)

// 3. Convert 4 ASCII digits at offset 16 to DINT
diValue := STRING_TO_DINT(
    CHAR_TO_STRING("DB_Gateway_Buffer".Buffer[16]) +
    CHAR_TO_STRING("DB_Gateway_Buffer".Buffer[17]) +
    CHAR_TO_STRING("DB_Gateway_Buffer".Buffer[18]) +
    CHAR_TO_STRING("DB_Gateway_Buffer".Buffer[19]));
Why POKE_BLK has been deprecated for S7-1500. On optimized blocks the CPU protects the memory layout, so the function can only target DBs with "Optimized block access" disabled. Siemens documents this in the S7-1500 system manual. For new programs prefer Method 2 (UDT mapping) or Method 3 (AT overlay). POKE_BLK remains valid on S7-300/400 and on S7-1500 with non-optimized DBs.

Method 2 - Direct UDT Mapping (Recommended)

Symbolic I/O access is the cleanest approach and is what Siemens documentation recommends.

Step 1 - Build a 256-byte UDT

// UDT "UDT_Gateway_256"
TYPE UDT_Gateway_256 :
STRUCT
    abData : ARRAY[0..255] OF BYTE;   // 256 bytes total
END_STRUCT;
END_TYPE

For a structured payload (e.g. one ASCII status + one packed DINT + one float), declare the UDT with named fields so the SCL can address them symbolically. Example for an HMS Anybus status payload of length 256:

TYPE UDT_Gateway_Status :
STRUCT
    abHeader    : ARRAY[0..7]   OF BYTE;   // protocol header
    sNodeId     : STRING[32];              // offset 8,  34 bytes total
    diCycleTime : DINT;                    // offset 42, 4 bytes
    rVoltage    : REAL;                    // offset 46, 4 bytes
    rCurrent    : REAL;                    // offset 50, 4 bytes
    abTail      : ARRAY[54..255] OF BYTE;  // vendor-specific
END_STRUCT;
END_TYPE
Alignment. A STRING[32] occupies 34 bytes (2-byte header + 32 data), not 32. Likewise DINT/REAL require 4-byte alignment on non-optimized blocks. On optimized blocks the compiler reorders fields for word alignment automatically. Always verify the byte layout against the gateway user manual and use a PLC tag table watch window to confirm offsets before scaling up.

Step 2 - Map the UDT to the IO Address

  1. Open PLC tags → Default tag table and add a new tag: ibGatewayIn with data type UDT_Gateway_256 and address %IB256 (start of the 256-byte input slot).
  2. For the output direction, add qbGatewayOut with the same data type at %QB256.
  3. Compile the hardware configuration and download to the CPU.

Step 3 - Use the UDT in SCL

// Symbolic, type-safe access - no POKE_BLK needed
VAR
    stGw    : "UDT_Gateway_256";     // instance for code-side work
END_VAR

#stGw := "ibGatewayIn";              // snapshot inputs (single MOV)
// Now read fields symbolically
IF #stGw.sNodeId = 'NODE-007' THEN
    "dbCtl".diLastCycle_ms := #stGw.diCycleTime;
END_IF;

Method 3 - AT Overlay for Optimized Blocks

When the surrounding code must live in an optimized FB (the S7-1500 default) and you only have a byte index to work with, use the AT construct to re-interpret the input area as an array or a struct without copying memory.

FUNCTION_BLOCK "FbDecode256"
VAR
    // 256 raw bytes from process image
    abRaw        : ARRAY[0..255] OF BYTE AT %IB256;
    // Overlay the SAME memory as a struct
    stPayload    : "UDT_Gateway_Status" AT %IB256;
END_VAR
BEGIN
    // Read fields directly - no copy, no POKE_BLK
    "dbCtl".diCycle_ms := #stPayload.diCycleTime;
    "dbCtl".rVoltage   := #stPayload.rVoltage;
END_FUNCTION_BLOCK

Key rules for AT overlays:

  • Both variables must reference the same absolute address or the same identifier.
  • Lengths must match exactly - a 256-byte UDT cannot overlay a 200-byte ARRAY.
  • On optimized FBs the Address column in the block interface must be visible (right-click header → show) and the AT must be on a VAR_TEMP, VAR, or VAR_IN_OUT of the same block - not across blocks.

Selecting the Destination Area: TEMP vs Static vs Non-Optimized DB

Field experience strongly prefers non-optimized static or UDT-mapped symbolic I/O over VAR_TEMP for the 256-byte staging area. Reasons:

Location Advantages Disadvantages
VAR_TEMP Local, no symbol table pollution, no retentivity concerns Re-initialized every block call - cannot be inspected live; cannot be bound to HMI; POKE_BLK cannot write here
Non-optimized DB (static) POKE_BLK can write by absolute byte offset; HMI-visible; survives scan Disables optimized access; no symbolic read in newer TIA wizards
Optimized DB (static) Symbolic, type-safe, no offset management No POKE_BLK; field-by-field must match vendor protocol
Direct AT on process image Zero-copy, fastest, no extra DB Address is fixed at compile time; cannot be remapped without FB re-compile

For a single 256-byte input that is consumed by one FB and not remapped at runtime, the direct AT method (Method 3) is the leanest. If the buffer must persist across cycles, copy it into a static VAR or into the same UDT in a DB with Optimized block access enabled.

Converting the Bytes to STRING and DINT

ASCII substring to STRING

The TIA standard library ships CHARS_TO_STRING (IEC) and the legacy STRNG family. A clean, bounds-checked copy of 32 bytes from offset 8 into a STRING[32] looks like:

// Copy offset 8, length 32 from gateway to sStatus (STRING[32])
"LIB_String".Chars_To_Strg_26(
    String   := #sStatus,
    Chars    := #stPayload.sNodeId,   // symbolic - already a STRING
    pChars   := 0,
    Count    := 32);

When the source is still raw bytes (no UDT field), the IEC-standard idiom is to CHAR-cast each byte and concatenate. Avoid per-byte STRING assignment in long loops - it produces quadratic string copies. Use the Chars_To_Strg FB (Blocks library → String + Char) for any payload over 16 bytes.

ASCII digits to DINT

For a 4-byte ASCII representation (e.g. '0042') use the IEC STRING_TO_DINT conversion. The function returns 0 on invalid input - guard the result:

IF #sStatus = '' THEN
    #diValue := 0;
ELSE
    #diValue := STRING_TO_DINT(#sStatus);
    IF ENO = FALSE THEN
        #diValue := 0;
        "dbAlarm".bConvertError := TRUE;
    END_IF;
END_IF;

Raw 4-byte big-endian to DINT

Many gateways return numeric values as big-endian bytes, which is the opposite of S7 native little-endian. Swap bytes before assigning:

// Convert 4 big-endian bytes at offset 16 to DINT
#diValue := SysByteSwap_BEtoLE_DINT(
    BYTE_TO_DWORD(#abRaw[16]) * 16#1000000 +   // byte 0 << 24
    BYTE_TO_DWORD(#abRaw[17]) * 16#10000   +   // byte 1 << 16
    BYTE_TO_DWORD(#abRaw[18]) * 16#100     +   // byte 2 << 8
    BYTE_TO_DWORD(#abRaw[19]));                // byte 3 << 0
Always confirm byte order in the gateway manual. PROFINET devices on the wire are little-endian by default; PROFIBUS DP-V1 slaves vary. Mixing the two is the most common source of "value is rotated" bugs.

Testing the 256-Byte Input in TIA Portal

Hardware-in-the-loop testing with the real gateway present is best, but it is rarely convenient during software bring-up. Use one of the three approaches below.

Approach A - PLCSIM (S7-1500 only)

  1. Start PLCSIM (V16+ supports the S7-1500 process image and PROFINET IO simulation).
  2. Add a virtual PROFINET device and configure a 256-byte input slot.
  3. Drive the input bytes with a watch table forced on a small simulator block.

Approach B - Watch Table with Force

  1. Open Watch and force tables → create a new table.
  2. Add "ibGatewayIn".abData[0] … "ibGatewayIn".abData[255] (or use the Modify / Force pattern with a 256-element ARRAY).
  3. Select all 256 rows, set Modify value to a known test pattern (e.g. 16#01, 16#02, … cycled), apply, and observe the UDT fields.

Approach C - Force M-Flag Mirror for Early Code Bring-up

To validate the SCL logic before the gateway is on the network, mirror the 256 bytes into a M-flag area and have the SCL read from the mirror. Once the gateway is on-line, switch the pointer back to the real input area. This is the most reliable way to unit-test the decode path.

// Pseudo-code for bring-up test switch
IF "dbCfg".bUseTestMirror THEN
    #stPayload AT %MB1024 : "UDT_Gateway_Status";
ELSE
    #stPayload AT %IB256  : "UDT_Gateway_Status";
END_IF;

Verification Checklist

  1. CPU goes to RUN; PROFINET diagnostics shows the gateway as Connected, No faults (or PROFIBUS slave in data exchange).
  2. Watch table shows ibGatewayIn.abData[0..255] updating at the bus cycle rate.
  3. All 256 bytes are accounted for in the UDT (no overlap, no gaps). Use Cross-reference to find stray absolute I/O accesses.
  4. STRING slices contain the expected ASCII (no leading NULs, no truncation past the field's max length).
  5. Numeric conversions produce the expected values; sweep test values from 0 to max.
  6. Cycle-time impact: the SCL block must complete well inside the OB1 / cyclic task budget (typical budget < 5 ms for a 256-byte decode on an S7-1516).
  7. Retentivity: if the buffer lives in a DB, decide whether the data must be retentive; set the Retain property accordingly.

Troubleshooting Matrix

Symptom Likely Root Cause Fix
SF / BF LED on, gateway not in data exchange Wrong GSD, wrong device number, cable/port issue Re-install GSD; verify device name & IP match the project; check PROFINET diagnostics buffer
Inputs always 0 Watch table is on symbolic tag but actual I/O is at a different start byte Confirm slot start offset in device properties; align the UDT base address
POKE_BLK returns FALSE / BOOL stays FALSE Destination is in an optimized DB, or count > available bytes Disable optimized access on target DB; verify count and the source/dest byte ranges
Garbled characters in STRING Length field of the STRING is wrong, or the source is binary not ASCII Re-initialize the STRING header (use the String standard library FBs); verify source encoding with watch table
Numeric value is byte-swapped Endianness mismatch between gateway and S7 Apply byte swap (see snippet above); confirm in gateway manual
Cannot see IO tags in gateway property window Third-party gateway does not export symbolic tag info Open the device's Details view for the address range; build a UDT manually
Compile error: AT requires same identifier AT and target variable have different addresses Point both AT declarations at the same absolute address or use the same source identifier
STRING is truncated to 1 character String was declared without a max length and defaults were not set Declare as STRING[254] for the largest expected payload, or use WSTRING for Unicode

FAQ

Can POKE_BLK write into an optimized DB on S7-1500?

No. POKE_BLK writes by absolute byte offset and is rejected by the optimized-block memory protection. Use a non-optimized DB or, preferably, switch to symbolic UDT access or an AT overlay.

Why don't I see I/O tags for my HMS Anybus gateway in TIA Portal?

Third-party gateways typically do not publish a symbolic tag list in the GSD. Open the gateway in the project tree and check the Details view (or the Inspector window → IO tags) to see the address range. Build a matching 256-byte UDT and bind it to that start address.

What is the maximum STRING length on S7-1200/S7-1500?

A STRING can hold 254 user characters plus a 2-byte header (max length + current length), for a total of 256 bytes. For longer ASCII or Unicode payloads, use a WSTRING, an ARRAY OF CHAR, or split the data across multiple strings.

Should the 256-byte buffer be in VAR_TEMP or in a DB?

Avoid VAR_TEMP for a 256-byte buffer that must be inspected from a watch table or HMI. Put the buffer in a static DB (optimized or non-optimized) or - best - map the gateway's process image directly via UDT / AT and read fields symbolically.

How do I test the decode logic before the gateway is on the network?

Mirror the 256 bytes into an M-flag area, point the SCL at the mirror using an IF / ELSE on a test-mode flag, and force the mirror with a watch table. Once the gateway is live, switch the pointer to the real %IB area and verify with on-line diagnostics.

Back to blog