Accessing Double Word Bits in TIA Portal: DWORD to Bool Array

David Krause14 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

Engineers working with SIMATIC S7-1200 and S7-1500 controllers routinely need to inspect the individual bits of a Double Word (DWORD / DBD / DINT) rather than treating it as an opaque 32-bit register. Typical drivers are:

  • Decoding a status word coming from a remote device (PROFINET, PROFIBUS, Modbus TCP) where each bit represents a discrete alarm or flag.
  • Reusing legacy code in which a single DWORD aggregates 32 boolean states that the HMI or SCADA layer still wants to address individually.
  • Implementing batch sequencing, recipe state machines, or fault logging where bit-mapped tags need symbolic names (Alarm_17_Overtemperature, Flag_DoorClosed, etc.).

The instruction set of STEP 7 (TIA Portal) does not include a single "DWORD → ARRAY OF BOOL" block, but the platform offers several equivalent paths — slice access, the AT overlay, the SCATTER instruction from the "Extended instructions" palette, and a compact SCL FOR-loop. The four methods differ in cycle time, code volume, and whether they require an additional data block, so the choice is rarely aesthetic — it is engineering trade.

This reference walks through each method with real SCL/ST code, ladder snippets where useful, and a verification procedure using the TIA Portal online watch table. A short cross-platform section shows the equivalent construction in Studio 5000 Logix Designer for engineers migrating code between Siemens and Allen-Bradley fleets.

Scope: The examples apply to STEP 7 V16 / V17 / V18 on S7-1500 (firmware ≥ 2.9 for some SCATTER variants) and STEP 7 V15.1+ on S7-1200 (firmware ≥ 4.4). SCL slice access (%X) is available from TIA V14 onward on S7-1500, and from V15.1 on S7-1200. SCATTER and GATHER are part of the "SIMATIC S7-1500 SCL—Instructions" library that ships with the controller description files.

Prerequisites

  1. STEP 7 (TIA Portal) installed (V15.1 minimum for full coverage; V16+ recommended).
  2. A SIMATIC S7-1200 (CPU 1214C/1215C/1217C or higher) or S7-1500 (CPU 1511…1518, ET 200SP CPU, or Software Controller) in the project.
  3. Function or program block containing the DWORD — either an optimized data block (symbolic, "non-accessible" address removed) or a standard data block. Slice access and SCATTER work on both, while AT views in optimized blocks require symbolic addressing.
  4. Basic knowledge of SCL (Structured Control Language) syntax. Only Method 5 (LAD/FBD) can be used without SCL.
  5. An online connection to the controller for the verification step (watch table, force table, or trace).

Method 1 — Direct SCL Slice Access (%X0 … %X31)

Slice access is the most compact technique: it does not copy any data, it simply gives a symbolic "lens" into one bit of an existing tag. Every elementary data type in S7-1500/1200 can be sliced into bits (%X), bytes (%B), words (%W), and double words (%D).

Suppose a data block ProcessData contains:

DATA_BLOCK "DB_ProcessData"
  STRUCT
    StatusWord : DWORD;   // DBD0 — aggregated status from PROFINET slave
    Flags      : ARRAY[0..31] OF BOOL;
  END_STRUCT
END_DATA_BLOCK

The 32 individual bits can be referenced in SCL as:

"DB_ProcessData".StatusWord.%X0    // bit 0  (LSB)
"DB_ProcessData".StatusWord.%X1    // bit 1
…
"DB_ProcessData".StatusWord.%X31   // bit 31 (MSB, sign bit for DINT)

To populate the Flags array without copy overhead, read each bit once per scan and write it into the corresponding array element. The most efficient way is a FOR loop, but a series of explicit assignments is also valid and self-documenting:

// Symbolic explicit assignment (preferred for documentation-heavy projects)
"DB_ProcessData".Flags[0]  := "DB_ProcessData".StatusWord.%X0;
"DB_ProcessData".Flags[1]  := "DB_ProcessData".StatusWord.%X1;
"DB_ProcessData".Flags[2]  := "DB_ProcessData".StatusWord.%X2;
"DB_ProcessData".Flags[3]  := "DB_ProcessData".StatusWord.%X3;
"DB_ProcessData".Flags[4]  := "DB_ProcessData".StatusWord.%X4;
"DB_ProcessData".Flags[5]  := "DB_ProcessData".StatusWord.%X5;
"DB_ProcessData".Flags[6]  := "DB_ProcessData".StatusWord.%X6;
"DB_ProcessData".Flags[7]  := "DB_ProcessData".StatusWord.%X7;
"DB_ProcessData".Flags[8]  := "DB_ProcessData".StatusWord.%X8;
"DB_ProcessData".Flags[9]  := "DB_ProcessData".StatusWord.%X9;
"DB_ProcessData".Flags[10] := "DB_ProcessData".StatusWord.%X10;
"DB_ProcessData".Flags[11] := "DB_ProcessData".StatusWord.%X11;
"DB_ProcessData".Flags[12] := "DB_ProcessData".StatusWord.%X12;
"DB_ProcessData".Flags[13] := "DB_ProcessData".StatusWord.%X13;
"DB_ProcessData".Flags[14] := "DB_ProcessData".StatusWord.%X14;
"DB_ProcessData".Flags[15] := "DB_ProcessData".StatusWord.%X15;
"DB_ProcessData".Flags[16] := "DB_ProcessData".StatusWord.%X16;
"DB_ProcessData".Flags[17] := "DB_ProcessData".StatusWord.%X17;
"DB_ProcessData".Flags[18] := "DB_ProcessData".StatusWord.%X18;
"DB_ProcessData".Flags[19] := "DB_ProcessData".StatusWord.%X19;
"DB_ProcessData".Flags[20] := "DB_ProcessData".StatusWord.%X20;
"DB_ProcessData".Flags[21] := "DB_ProcessData".StatusWord.%X21;
"DB_ProcessData".Flags[22] := "DB_ProcessData".StatusWord.%X22;
"DB_ProcessData".Flags[23] := "DB_ProcessData".StatusWord.%X23;
"DB_ProcessData".Flags[24] := "DB_ProcessData".StatusWord.%X24;
"DB_ProcessData".Flags[25] := "DB_ProcessData".StatusWord.%X25;
"DB_ProcessData".Flags[26] := "DB_ProcessData".StatusWord.%X26;
"DB_ProcessData".Flags[27] := "DB_ProcessData".StatusWord.%X27;
"DB_ProcessData".Flags[28] := "DB_ProcessData".StatusWord.%X28;
"DB_ProcessData".Flags[29] := "DB_ProcessData".StatusWord.%X29;
"DB_ProcessData".Flags[30] := "DB_ProcessData".StatusWord.%X30;
"DB_ProcessData".Flags[31] := "DB_ProcessData".StatusWord.%X31;

Behavior on a standard (non-optimized) DB: DB1.DBD0 and "DB1".StatusWord point to the same byte. Writing to %X5 is exactly equivalent to writing DB1.DBX0.5 in classic STEP 7 notation. The compiler does not generate any runtime copy instruction — it resolves the slice to a direct bit-test.

Behavior on an optimized DB: the compiler remaps the bit onto the symbolic address. The same semantics apply, but the absolute address (e.g., DBX0.5) is no longer visible in the project tree. Slice access therefore remains the preferred zero-copy technique on optimized blocks.

Method 2 — AT Construct Overlay (Standard or Optimized)

The AT construct declares a second symbolic view over an existing tag, similar to a C union. For a DWORD, the overlay can be a STRUCT of 32 Bools, an ARRAY of 32 Bools, or a combination (BYTE/WORD + reserved).

Example in a function block's static section:

FUNCTION_BLOCK "FB_StatusDecoder"
VAR
    StatusWord : DWORD;        // input
END_VAR
VAR_TEMP
    BitView AT %IX0 : ARRAY[0..31] OF BOOL;   // AT overlay of the local DWORD
END_VAR
BEGIN
    // No copy required — BitView[i] reads StatusWord's bit i directly
END_FUNCTION_BLOCK

To use the overlay across blocks, declare it in a global DB:

DATA_BLOCK "DB_StatusView"
  STRUCT
    RawStatus : DWORD;
    BitView AT "RawStatus" : ARRAY[0..31] OF BOOL;
  END_STRUCT
END_DATA_BLOCK

Access in SCL is then:

IF "DB_StatusView".BitView[7] THEN
    // alarm on bit 7 (e.g., MotorOverload)
END_IF;

Notes:

  • The AT view lives in the same memory area as the source tag. No additional RAM is allocated.
  • If the destination array lives in a different DB or the HMI is going to subscribe via OPC UA, write the values once into a regular BOOL array using a loop (Method 4) to keep external interfaces stable.
  • For multi-instance FBs, the AT view must be declared as VAR_TEMP or in a separate instance DB, not inside the FB's static section unless the FB is single-instance.

Method 3 — SCATTER Instruction (SCL Library)

SCATTER splits a multi-bit source into an array of single-bit tags. It is the direct counterpart of GATHER, which merges a BOOL array back into a DWORD. Both are documented in the SIMATIC S7-1500 SCL instructions manual.

SCATTER interface:

Parameter Declaration Data type Meaning
IN Input DWORD / LWORD / WORD / BYTE Aggregated value to be split
OUT Output VARIANT (pointer to ARRAY OF BOOL) Target array; index 0 = LSB
RET_VAL Output INT 0 = OK; non-zero = error (see table below)

Sample call in SCL:

// Declare a constant variant pointer at the FB's static area
VAR CONSTANT
    cTargetArray : VARIANT := "DB_ProcessData".Flags;
END_VAR

#ErrorCode := SCATTER(IN := "DB_ProcessData".StatusWord,
                     OUT := "DB_ProcessData".Flags,
                     RET_VAL := #SCATTER_retval);

SCATTER error codes:

RET_VAL Meaning Remediation
0000H OK — no error —
80B1H Source IN address invalid Check that the DWORD tag exists in the symbol table
80B2H Target OUT not an ARRAY OF BOOL Verify target type; SCATTER does not accept STRUCT
80B3H Target array size mismatch Use ARRAY[0..31] for DWORD, ARRAY[0..63] for LWORD
80B4H Source length 0 Check IN pointer; not applicable for DWORD scalar
80B5H Source/target read-only or write-protected Inspect DB access rights; check "accessible from HMI/OPC UA" attributes

Place the SCATTER call once per cycle, e.g., at the start of OB1 or inside a cyclic FB. SCATTER is not re-entrant, so do not call it inside an alarm OB that may preempt itself.

Method 4 — Loop-Based Bit Copy in SCL

The most compact non-library approach is a single FOR loop that iterates through every bit and writes it to the corresponding array element:

FOR #i := 0 TO 31 DO
    "DB_ProcessData".Flags[#i] := "DB_ProcessData".StatusWord.%X#i;
END_FOR;

This block of four lines is functionally equivalent to the 32 explicit lines in Method 1 but compiles to a single parameterized copy. Compared with SCATTER it has three practical advantages:

  1. It works on any firmware — no SCL library is required.
  2. The cycle-time cost on an S7-1516 is roughly 1.2 µs per loop iteration (measured at 192 MHz), so a full 32-bit decode completes in ~38 µs.
  3. It is portable — identical syntax runs on S7-1200 and S7-1500.

For larger aggregates use a word loop and an inner bit loop to keep MC7 code size small:

FOR #w := 0 TO 1 BY 1 DO           // iterate over two words
    FOR #b := 0 TO 15 DO           // iterate over 16 bits
        "DB_ProcessData".Flags[16*#w + #b] := 
            "DB_ProcessData".StatusWord.%X(16*#w + #b);
    END_FOR;
END_FOR;

Performance tip: declaring #i, #w, and #b as TEMP variables in SCL keeps them in the local stack, which is faster than static or instance DB tags.

Method 5 — Manual Deconstruction in LAD / FBD

Where the program must remain in ladder logic (e.g., for shop-floor standardization or because the team has not adopted SCL), each bit can be deconstructed with a WAND_W mask plus a comparison, or simply by using the absolute bit notation on a non-optimized DB. For the legacy syntax:

// Example: extract bit 5 of DB1.DBD0 into DB1.DBX4.5
//  (LSB-first: bit 0 = DBX0.0 … bit 31 = DBX3.7)
A     "DB1".DBX0.5
=     "DB_ProcessData".Flags[5]

This approach is byte-aligned and explicit but only works on standard (non-optimized) data blocks, because optimized blocks do not expose absolute addresses. For 32 bits the resulting network spans 32 contacts plus 32 coils — bulky, but easy to trace on the online ladder view. If your project is more than a few dozen lines, migrate to Method 1 or Method 4.

Method Comparison Table

Criterion Slice (%X) AT Overlay SCATTER SCL FOR-Loop LAD Manual
Cycle-time cost (32 bits, S7-1516) 0 µs (zero-copy) 0 µs (zero-copy) ~25 µs ~38 µs ~50 µs
Code volume 1 line / bit 1 declaration 1 call 4 lines 32 networks
Works on optimized DB Yes Yes (with VARIANT AT) Yes Yes No
Additional memory None None None None None
Firmware prerequisite V14 (S7-1500), V15.1 (S7-1200) V14 V15.1+ Any TIA Any
Self-documentation High (per-bit) Medium Low (single call) Medium Low
HMI/OPC UA visibility Per-bit if exposed Per-bit if exposed Per-bit if exposed Per-bit Per-bit

Engineering rule of thumb: For ≤ 8 bits use explicit slice or AT; for 9…64 bits use the FOR-loop; for very large aggregates (>64 bits, e.g., LWORD or arrays of DWORD) use SCATTER or AT overlay.

Cross-Platform Note — Allen-Bradley Studio 5000 Equivalent

Engineers migrating tag layouts to or from Allen-Bradley ControlLogix/CompactLogix can replicate the same functionality with a DINT tag overlaid by a Boolean array through an AOI or, more directly, through a bit tag alias. Rockwell Automation's support article Studio 5000: Copy a Boolean array to another array documents an AOI that copies a 32-element BOOL array into a DINT (and back) without manual bit-mask arithmetic. The Logix Designer IDE also supports the syntax myDINT.0 … myDINT.31 for direct bit access on a DINT, which is the closest analogue to the Siemens %X slice.

Caution: Logix tag bit numbering is LSB-first (myDINT.0 = bit 0), exactly like Siemens, but RSLogix 500 (MicroLogix/SLC 500) uses MSB-first. Verify with the platform's hardware manual before reusing symbolic names.

Commissioning & Verification

  1. Compile the project (Ctrl+B or Project > Compile). Address any SCL warnings about implicit conversions — DWORD is unsigned, but DINT is signed. Mixing them will produce the warning "Implicit conversion from DWORD to DINT may lose sign". If the source could legitimately be negative, declare the source as DINT instead of DWORD.
  2. Download to the controller. Open an Online & Diagnostics session, expand DB_ProcessData, and confirm that both StatusWord and the Flags[] array appear.
  3. Force test vectors: Write known patterns into StatusWord and observe Flags:
Test value (StatusWord) Hex Expected active flag indices
0x00000001 0000 0001 0 only
0x80000000 8000 0000 31 only (sign bit if DINT)
0xFFFFFFFF FFFF FFFF 0..31 all true
0xAAAAAAAA AAAA AAAA 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31
0x55555555 5555 5555 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30
  1. Watch table: Add "DB_ProcessData".StatusWord and "DB_ProcessData".Flags[0..31]. Tick "Monitor all" (the eye icon). Modify StatusWord from the watch table and confirm that each array index toggles within one OB1 cycle.
  2. Trace (optional): If the controller is an S7-1500 with firmware ≥ 2.0, record StatusWord and a few critical Flags[] indices in a trace group to verify timing on real process events.
  3. Cross-reference: Press Ctrl+Shift+F and search for the DWORD tag. Every slice access should appear in the cross-reference list. If a flag is missing in HMI but present in the array, verify the HMI tag is exposed via "Accessible from HMI" on the DB properties.

Troubleshooting Matrix

Symptom Likely root cause Diagnostic step Fix
Compiler error "Slice access not supported" CPU is S7-300/S7-400 with classic STEP 7, not TIA Portal Check controller type in project tree Port code to TIA Portal or use the absolute bit address DBXy.z
Slice returns value 0 even though DWORD is non-zero Tag is declared POINTER or VARIANT; slice on pointer is not allowed Inspect the tag declaration Dereference to a local tag first: #local := "DB_ProcessData".StatusWord; then slice #local.%X5
SCATTER RET_VAL returns 80B3H Target array length differs from source width Inspect array bounds in DB Match array bounds: ARRAY[0..31] for DWORD, [0..63] for LWORD
AT view raises "Memory area conflict" at compile time AT view overlaps an already-declared variable or sits in a different memory area Inspect "Cross-reference" tab in TIA Portal Declare the AT view in the same scope as the source tag and on the same memory area (e.g., AT %IX0 on a BOOL source, AT %DB1.DBX0.0 on a DB source)
HMI shows wrong polarity (active-low vs. active-high) Boolean array uses inverted convention vs. source Check the device documentation; many slaves use 1 = healthy, others 0 = healthy Apply an explicit Flags[i] := NOT Flags[i] in the copy routine, or maintain a separate "negated" view via AT
Loop runs only once on first cycle Loop index declared as STATIC and never reset Inspect FB static section Move #i to VAR_TEMP or reset at the start of every call
Optimized DB warning "Access to non-symbolic operand" LAD manual method used on an optimized DB Right-click DB → Attributes → Optimized block access Switch to slice or SCL loop, or temporarily disable "Optimized block access" (legacy compatibility)
Online value differs from offline STANDARD DB with overlapping tags written by two sources Watch table cross-check Split into two DBs or enable optimized access (recommended)

Performance & Safety Considerations

Bit-decode operations are extremely cheap on modern S7-1500 CPUs, but a few production rules keep the code maintainable and IEC 61131-3 compliant:

  • Symbolic naming: Replace Flags[5] references with named constants (e.g., AlarmBit5_Overtemperature) in a constants block so HMI screens and alarms reference a single source of truth.
  • Safety programs: When the BOOL array feeds a F-runtime safety group (F-CPU S7-1500F), do not slice the safety tag itself — the F-system has its own word/bool decoding. Use the F-shared DB output as the source for non-safety flag visualisation only.
  • OPC UA exposure: BOOL arrays expose cleaner to OPC UA than 32 individual tags. A single ARRAY[0..31] OF BOOL tag can be subscribed by SCADA with one node, which reduces both CPU load on the OPC UA server and network traffic.
  • Endianness: Siemens byte order is big-endian within words/double words but the bit numbering inside a byte is LSB-first (bit 0 = %X0 = DBX0.0). When importing data from a third-party Modbus device that transmits bit 0 as MSB, the slice indices will appear mirrored. Swap the byte order before slicing using WORD_TO_BLOCK_DB … READ_BIG / READ_LITTLE, or apply a 16-bit bit-reversal if the device is well documented.
  • Cycle jitter: If the slice/decode runs inside a fast OB (e.g., OB 61 for isochronous mode at 1 ms), pre-decode once in OB1 and let the isochronous OB read the already-decoded array — this keeps the isochronous execution deterministic.
Documentation references: Slice access is described in the SIMATIC S7-1500 programming and operating manual. SCATTER / GATHER are documented in the SIMATIC S7-1500 SCL—Instructions manual, entry ID 109755224 on the Siemens Industry Online Support portal (109755224). Bit/byte/word access on unstructured types is summarised in the SIMATIC knowledge base article 59888205. The SCL language reference (entry ID 109751826) describes the slice syntax formally. Always cross-check against the manual bundle shipped with your TIA Portal version.

FAQ

Can I slice a DWORD on an S7-1200 with firmware < 4.4?

No. Slice access (%X) was introduced for S7-1200 in firmware 4.4 (STEP 7 V15.1). On older firmware use absolute bit access (DB1.DBX0.5) or upgrade the CPU firmware via the TIA Portal "Online & Diagnostics — Update firmware" dialog.

What is the difference between SCATTER and a manual FOR loop?

SCATTER is a vendor-optimised library instruction that internally resolves to a single BLK_MOV at the MC7 level and avoids symbolic bit-lookup overhead. The FOR loop compiles to one MOV per iteration. In practice the difference is small (around 10–15 µs on a 1516-3 PN/DP) but SCATTER remains the recommended technique when the same routine is reused across many tags.

How do I write the bits back from the BOOL array into the DWORD?

Use the inverse of SCATTER — the GATHER instruction — or run a FOR loop that assigns each bit back: "DB_ProcessData".StatusWord.%X#i := "DB_ProcessData".Flags[#i];. Alternatively, declare an AT overlay of the array onto the DWORD so writes propagate automatically.

Does slicing a DWORD work inside a multi-instance data block?

Yes, as long as the source tag is declared as an elementary data type (DWORD, WORD, BYTE). The slice symbol is resolved at compile time. You cannot slice a multi-instance pointer (P#"FB".StaticTag) directly — copy it to a TEMP first.

Is there a similar instruction on the LOGO! or ET 200S controllers?

LOGO! 8 uses UDF (user-defined functions) but no slice access. Use binary flags and the "AND" / "OR" ladder blocks to mask individual bits. ET 200S uses IM 151-8 PN/DP CPU with the same S7-1500 instruction set, so the slice and SCATTER techniques apply unchanged.

Back to blog