DWORD Bit Reference in Siemens S7 SCL Three Field-Tested Patterns

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

DWORD Bit Reference in Siemens S7 SCL: Three Field-Tested Patterns

When a Siemens S7 PLC programmer writes Fault_Code.0 inside an SCL block and the compiler returns "Invalid variable; period not allowed", the cause is structural, not syntactic in the general sense. SCL does not expose the <DWordVar>.<bit_index> dot-notation bit selector that exists in ST-on-CoDeSys, in some third-party IEC 61131-3 dialects, or in WinCC tag references. The DWORD is stored as an atomic 32-bit string in the load memory, work memory, and on the HMI interface, and the SCL parser simply has no rule for treating the trailing period as a bit selector.

This article documents the three production-grade patterns Siemens S7 SCL programmers use to map individual Boolean conditions onto bits of a 32-bit fault word, how to roll those bits up into a single aggregated General Fault indicator, and how to verify the mapping on the bench. The patterns are valid for S7-300/400 (STEP 7 V5.x), S7-1200 (firmware V4.0+), and S7-1500 in both TIA Portal V16 through V21. Differences between platforms are called out in the relevant section.

1. Why the Period Syntax Fails in SCL

According to the TIA Portal V21 reference for the DWORD data type, a DWORD is a 32-bit bit string. In the IEC 61131-3 third edition and the Siemens SCL grammar that derives from it, individual bit access on a multi-bit elementary data type is reserved for two cases:

  1. Variables explicitly declared as BOOL, BYTE, WORD, or DWORD may be addressed as a whole, but the grammar does not provide a Variable.Bit form unless the variable is wrapped in a structured type that has the bit as a named component.
  2. Bit access is performed by an expression - typically a bitwise AND between the source and a constant bit mask - or by a typed overlay (the AT construct).

The string Fault_Code.0 is parsed as a partial tag reference, the parser sees a non-numeric character set after the period (the period itself triggers the diagnostic in SCL), and the compiler emits "Invalid variable; period not allowed". The same declaration would compile cleanly on a CoDeSys 3.5 controller, but on Siemens S7 you must rewrite the access path.

Compiler diagnostic reference. The exact wording of the diagnostic in TIA Portal is "Invalid variable; period not allowed in variable name". The same root condition is reported as "Bit access not allowed on this data type" when the programmer uses the % addressing form (e.g. %DB1.DBD2.X0) directly in SCL - the % form is reserved for STL/absolute addressing and is also not legal SCL source.

2. DWORD Properties and Memory Footprint

Before selecting a bit-access pattern, confirm the data-type properties because some patterns (notably AT overlay) consume a different number of bytes in the instance DB or global DB than the DWORD alone.

Property DWORD
Length (bits) 32
Length (bytes) 4
Signed/unsigned Unsigned bit string; arithmetic only via UDINT cast
Value range 0 to 2^32 - 1 (4 294 967 295)
Memory layout Big-endian on Siemens S7 (MSB at lowest byte address in the bit string sense, but byte 0 is least significant in integer sense on S7-1500; on S7-300/400 word-order conventions apply for block-move operations)
HMI representation 32 bit-by-bit indicators, or as a 32-bit unsigned integer

Source: TIA Portal V21 - DWORD data type. Background on the WORD/DWORD/QWORD naming convention used by Intel-compatible and Siemens documentation is summarized in Word (computer architecture).

3. Pattern A: Bitwise AND with Hexadecimal Masks

The classic SCL pattern is to mask the DWORD against a power-of-two constant. The compiler treats 16#0000_0001 (or the legacy form dw#16#1) as a 32-bit constant, performs a 32-bit AND, and the resulting non-zero value is the truthiness of the bit.

3.1 Working Code (SCL, TIA Portal V18+)

FUNCTION_BLOCK SystemControl
VAR
    Fault_Code : DWORD;          // Aggregated fault bit string
    bGenFault  : BOOL;           // General Fault, bit 0
    bInletHi   : BOOL;           // Bit 1
    bInletLo   : BOOL;           // Bit 2
    bDpFiltHi  : BOOL;           // Bit 3
END_VAR
BEGIN
    // ---- Bit read via bitwise AND with hex mask ----
    bGenFault  := (Fault_Code AND 16#0000_0001) <> 0;
    bInletHi   := (Fault_Code AND 16#0000_0002) <> 0;
    bInletLo   := (Fault_Code AND 16#0000_0004) <> 0;
    bDpFiltHi  := (Fault_Code AND 16#0000_0008) <> 0;

    // ---- Conditional shutdown on General Fault ----
    IF bGenFault THEN
        // Shutdown system
    END_IF;
END_FUNCTION_BLOCK

3.2 Legacy Form (STEP 7 V5.x SCL)

For projects on STEP 7 V5.5 / S7-300/400 the 16# literal requires the dw# type prefix:

IF ((hd) AND (dw#16#1)) <> 0 THEN
    test1 := 1;            // SHUTDOWN branch
ELSE
    test1 := 0;
END_IF;

IF ((hd) AND (dw#16#2)) <> 0 THEN
    test2 := 1;
ELSE
    test2 := 0;
END_IF;

IF ((hd) AND (dw#16#4)) <> 0 THEN
    test3 := 1;
ELSE
    test3 := 0;
END_IF;

IF ((hd) AND (dw#16#8)) <> 0 THEN
    test4 := 1;
ELSE
    test4 := 0;
END_IF;

IF ((hd) AND (dw#16#10)) <> 0 THEN
    test5 := 1;
ELSE
    test5 := 0;
END_IF;

Note that the mask for bit 4 is 16#10 (decimal 16), not 16#4; 16#4 is the mask for bit 2. This is the most common transcription error when hand-coding 32 masks.

Mask table for quick reference. Bit 0 = 0x1, bit 1 = 0x2, bit 2 = 0x4, bit 3 = 0x8, bit 4 = 0x10, bit 5 = 0x20, bit 6 = 0x40, bit 7 = 0x80, bit 8 = 0x100, ..., bit 31 = 0x8000_0000. Build with 1 SHL N (S7-1500 SCL) to avoid transcription errors: (Fault_Code AND (DWORD#1 SHL 4)) <> 0.

3.3 Pattern A: Pros and Cons

Aspect Assessment
Compiler support S7-300/400/1200/1500, all SCL versions
Memory overhead 4 bytes for the DWORD plus 4 bytes per mirrored BOOL if you materialize flags
HMI friendliness Excellent - one DWORD tag maps to 32 HMI bits in WinCC Unified / TIA HMI without further work
Code density Verbose for > 8 bits; readable for <= 16
Risk Transcription error in hex mask; accidental use of WORD mask (16 bits) on a DWORD (32 bits) by a developer used to 16-bit platforms

4. Pattern B: Array of BOOL (Best for Instanceable FBs)

For a function block that will be instantiated dozens of times - one SystemControl instance per machine section, for example - an ARRAY[0..31] OF BOOL is the cleanest mapping. The HMI side is a derived view: a small FC packs the array into a DWORD on the way out to the panel and unpacks the operator-driven bits on the way in (for resets, mask enables, and so on).

4.1 Working Code

FUNCTION_BLOCK SystemControl
VAR
    Fault_Code : ARRAY[0..31] OF BOOL;   // Bit 0 = General Fault
    dwShadow   : DWORD;                  // Optional packed view for HMI
END_VAR
BEGIN
    // Read: general fault aggregation
    IF Fault_Code[0] THEN
        // Shutdown system
    END_IF;

    // Set a fault from a measured condition
    IF bInletPressureHighSensor THEN
        Fault_Code[1] := TRUE;            // Inlet Air Pressure High Fault
    END_IF;

    IF bInletPressureLowSensor THEN
        Fault_Code[2] := TRUE;            // Inlet Air Pressure Low Fault
    END_IF;
END_FUNCTION_BLOCK

4.2 Packing the Array to a DWORD for HMI

The array of BOOL is convenient inside the FB but cannot be addressed as a single 32-bit tag on the HMI. Add a small FC that performs the pack/unpack. The example below uses bit-test instructions and the SHL / OR combination that the S7-1500 SCL optimizer will emit as a single ROL/OR on the work-memory image.

FUNCTION "FaultPack" : DWORD
VAR_INPUT
    arrFault : ARRAY[0..31] OF BOOL;
END_VAR
VAR
    i : INT;
    dwRet : DWORD;
END_VAR
BEGIN
    dwRet := DWORD#0;
    FOR i := 0 TO 31 DO
        IF arrFault[i] THEN
            dwRet := dwRet OR (DWORD#1 SHL i);
        END_IF;
    END_FOR;
    "FaultPack" := dwRet;
END_FUNCTION

4.3 Pattern B: Pros and Cons

Aspect Assessment
Compiler support All SCL versions; ARRAY[..] OF BOOL is a base construct
Memory overhead 32 bytes per FB instance for the bit string plus 4 bytes for the packed shadow - higher than a raw DWORD but acceptable on S7-1500 with multi-MB work memory
HMI friendliness Indirect - requires the pack/unpack FC
Code density Best when individual bits are written by name in many places inside the FB
Risk Index out of range: SCL does not bounds-check array indices against a constant literal at compile time, so Fault_Code[32] compiles but will trip a runtime OB121 (programming error). On S7-1500 enable bounds checks in the SCL editor settings.
Recommended for instanceable FBs. When the FB will be reused, the readability of Fault_Code[3] over (Fault_Code AND 16#8) <> 0 outweighs the small pack/unpack overhead. The 32-byte instance footprint is irrelevant on S7-1500 (default work memory 150 KB to 3 MB depending on CPU) and is acceptable on S7-1200 (50 KB to 150 KB) for typical machine counts.

5. Pattern C: AT Overlay (S7-1200/1500 SCL Only)

The AT construct is the most direct mapping: it tells the SCL compiler to view the same physical memory as a different data type. In SCL on S7-1200 (firmware V4.0 and later) and on S7-1500, the overlay may be defined in the VAR block of an FB as an array of BOOL, giving both a packed DWORD view and a per-bit named view in a single source block.

5.1 Working Code

FUNCTION_BLOCK SystemControl
VAR
    Fault_Code     : DWORD;                                  // Packed HMI view
    Fault_CodeBits AT Fault_Code : ARRAY[0..31] OF BOOL;     // Per-bit view
END_VAR
BEGIN
    // ---- Write individual bits via the array view ----
    IF bInletPressureHighSensor THEN
        Fault_CodeBits[1] := TRUE;
    END_IF;

    IF bInletPressureLowSensor THEN
        Fault_CodeBits[2] := TRUE;
    END_IF;

    // ---- Read individual bits via the array view ----
    IF Fault_CodeBits[0] THEN
        // General Fault: shutdown system
    END_IF;

    // ---- Read the whole word for the HMI tag ----
    // HMI tag is bound to Fault_Code directly - 32 bits on the panel
END_FUNCTION_BLOCK

5.2 AT Overlay Constraints

Constraint Detail
Platform support S7-1200 firmware V4.0+; S7-1500 all firmware; not available on S7-300/400 SCL
Layout Overlay must occupy exactly the same byte length as the source variable; an array of 32 BOOL is 32 bytes and cannot overlay a 4-byte DWORD - use a STRUCT with bit-precise members, or an ARRAY[0..3] OF BYTE
Valid overlay targets for DWORD ARRAY[0..3] OF BYTE - 4 bytes; STRUCT b0:BOOL; b1:BOOL; ... b31:BOOL END_STRUCT; DWORD onto WORD pair (two AT overlays needed)
Multi-instance compatibility AT overlays work inside multi-instance DBs; the compiler reserves the memory once per instance
Optimization-pass warning Do not pass the same DB address to two different FBs that each define an AT overlay on it; the optimizer may reorder writes

5.3 Working AT Overlay for a DWORD with Per-Bit BOOL Array

Because ARRAY[0..31] OF BOOL is 32 bytes, it cannot overlay a 4-byte DWORD directly. The two valid forms are shown below.

// Form 1: STRUCT with explicit bit members
VAR
    Fault_Code    : DWORD;
    Fault_CodeStr AT Fault_Code : STRUCT
        bGenFault    : BOOL;   // bit 0
        bInletHi     : BOOL;   // bit 1
        bInletLo     : BOOL;   // bit 2
        bDpFiltHi    : BOOL;   // bit 3
        // ...continue to 32 named bits
    END_STRUCT;
END_VAR
// Form 2: byte-granular overlay (for byte-level HMI handling)
VAR
    Fault_Code   : DWORD;
    Fault_CodeBytes AT Fault_Code : ARRAY[0..3] OF BYTE;
END_VAR
Pattern C is the cleanest on S7-1500. Use the STRUCT overlay when the bits have semantic names. Use the BYTE-array overlay when the HMI side needs byte-level access (for example, to send 8 bits per Modbus register). The compiler tracks both views and the runtime cost is zero - the same four bytes in the instance DB are used.

6. Aggregated Fault Word Implementation

The original requirement is the General Fault bit - if any sub-fault is set, the General Fault must reflect it. The three patterns differ in how the aggregation is expressed.

6.1 Pattern A Aggregation

// In an FC, build the DWORD and the General Fault together
Fault_Code := 0;
IF bInletHi   THEN Fault_Code := Fault_Code OR 16#0000_0002; END_IF;
IF bInletLo   THEN Fault_Code := Fault_Code OR 16#0000_0004; END_IF;
IF bDpFiltHi  THEN Fault_Code := Fault_Code OR 16#0000_0008; END_IF;
// ... up to bit 31

bGenFault := (Fault_Code AND 16#0000_0001) <> 0;
// Or, since bit 0 is the OR of all other bits by design,
// recompute bGenFault from the same OR chain:
bGenFault := bInletHi OR bInletLo OR bDpFiltHi OR ... ;

6.2 Pattern B Aggregation

// Set the leaf bits
Fault_Code[1] := bInletHi;
Fault_Code[2] := bInletLo;
Fault_Code[3] := bDpFiltHi;

// Aggregate: General Fault = OR of bits 1..31
Fault_Code[0] := FALSE;
FOR i := 1 TO 31 DO
    Fault_Code[0] := Fault_Code[0] OR Fault_Code[i];
END_FOR;

6.3 Pattern C Aggregation

// Leaf writes are single-bit assignments; aggregation is the same
// OR-loop as Pattern B, but indexed by the STRUCT member:
Fault_CodeStr.bGenFault := FALSE;
Fault_CodeStr.bGenFault := Fault_CodeStr.bInletHi
                        OR Fault_CodeStr.bInletLo
                        OR Fault_CodeStr.bDpFiltHi
                        OR ... ;

7. Pattern Comparison

Criterion A: Bitwise AND mask B: Array of BOOL C: AT overlay (STRUCT)
Platforms S7-300/400/1200/1500 S7-300/400/1200/1500 S7-1200 FW 4.0+ / S7-1500 only
Instance memory 4 bytes per DWORD 32 bytes per array 4 bytes per DWORD (overlay)
HMI single-tag binding Yes - one tag, 32 bits No - requires pack/unpack FC Yes - one tag, 32 bits
Per-bit readability in SCL Low (mask arithmetic) High (indexed by integer) Highest (named members)
Runtime cost (read) 32-bit AND, 1 cycle Index decode, 1 cycle Bit-stride access, 1 cycle
Risk: index transcription Medium High (out-of-range at runtime) Low (compiler checks struct member names)
Recommended for Single-instance global faults; HMI-first designs Many FB instances with no HMI binding per instance S7-1500 designs with named bit semantics

8. Verification and Commissioning

For each pattern, a fixed commissioning sequence catches the most common faults. Run the sequence on a stopped CPU with the watch table open before the line is released to production.

  1. Force a single leaf condition (e.g. Fault_Code[1] = TRUE or set the equivalent input bit).
  2. Observe the DWORD in the watch table. Confirm bit 1 is set and the numeric value reads 2 (or 0x0000_0002).
  3. Force a second leaf condition (bit 2). Confirm the DWORD reads 6 (0x0000_0006) and that the General Fault is asserted.
  4. Clear all leaf conditions. Confirm the DWORD reads 0 and General Fault is FALSE.
  5. Force bit 31 to confirm the mask table is correct at the high end. The DWORD should read 0x8000_0000 (2 147 483 648 unsigned).
  6. Trigger a CPU restart (STOP/RUN). Confirm the bit assignments survive a warm restart (retain / non-retain attribute set correctly on the FB instance).

8.1 Watch-Table Recipe (Pattern A)

// Watch table columns: Name | Display format | Modify value
Fault_Code         | HEX              | 16#0000_0000
Fault_Code.&0      | BIN              | (read only, mask in display? See below)
bGenFault          | BOOL             | (read only)

To view a single bit of Fault_Code in the watch table, bind a separate BOOL tag of the FB and write := (Fault_Code AND 16#1) <> 0; in the FB, then enable that BOOL tag in the watch table. The watch table itself does not provide a "show bit N of DWORD X" form.

8.2 Watch-Table Recipe (Pattern B)

Fault_Code[0]      | BOOL             | (read only)
Fault_Code[1]      | BOOL             | TRUE (force)
Fault_Code[31]     | BOOL             | (read only)

8.3 Watch-Table Recipe (Pattern C)

Fault_CodeStr.bInletHi   | BOOL     | TRUE (force)
Fault_CodeStr.bGenFault  | BOOL     | (read only)
Fault_Code              | HEX      | (should show 0x0000_0002 or 0x0000_0003)

9. Edge Cases and Field-Proven Footguns

  • Retain attribute. A fault word that is supposed to persist across power-cycle must be in a retain-enabled FB instance. On S7-1500 set the Retain compiler setting on the FB; on S7-1200 only certain variables may be retained - see the CPU manual for the limit (typically 4 KB to 10 KB depending on CPU).
  • Endianness and Modbus gateways. If the fault word is passed to a third-party Modbus gateway or to a non-Siemens HMI, byte order may be reversed. A pattern-A mask that is correct on the S7 may display as a different bit on the panel. Verify with the gateway's "byte order" or "word swap" setting.
  • Atomic write width on S7-300/400. The 32-bit OR and AND in SCL are emitted as two 16-bit operations on S7-300/400; on S7-1500 they are single 32-bit operations. If a higher-priority OB (e.g. OB35 at 100 ms) writes to the same DWORD between the two halves, the second half may see a torn read. For a fault word that is only written from OB1 (the main cyclic OB) this is not an issue, but if you write from OB35 and read from OB1 of different cycle phases, switch to the AT-overlay pattern or to an ARRAY to avoid torn writes. On S7-1500 the issue does not exist because the CPU is byte-addressable and aligned 32-bit operations are atomic.
  • Symbian-style naming. Avoid the symbol name Fault alone; in SCL it collides with the FAULT keyword family on some firmware versions. Use Fault_Code, Fault_Word, or a project prefix.
  • OPC UA exposure. When the fault word is exposed as an OPC UA node, expose it as a 32-bit unsigned integer (UInt32) node, not as a ByteString. The OPC UA specification in IEC 62541 maps DWORD to UInt32 by convention.
  • Cross-block use of AT. A DWORD variable and its AT overlay must live in the same block. A global DB and a separate FB that each declare an AT overlay on the same memory area will compile, but the optimizer may reorder reads; consolidate the declaration in one block.

10. Related Siemens Resources

Why does SCL reject Fault_Code.0 with "Invalid variable; period not allowed"?

SCL does not implement a <Variable>.<BitIndex> syntax for elementary bit-string data types such as BOOL, BYTE, WORD, or DWORD. The parser treats the period as the start of a structured-component selector, not a bit selector, and rejects the construct. Use a bitwise AND with a mask, an ARRAY of BOOL, or an AT overlay instead.

What is the simplest SCL pattern to read bit 4 of a DWORD on S7-300/400?

Use ((dwVar) AND (dw#16#10)) <> 0. The mask 0x10 is decimal 16, not 0x4 (which is bit 2). On TIA Portal V16+ you may write the mask as DWORD#16#0010 or with underscore separators as 16#0000_0010.

Can I overlay an ARRAY[0..31] OF BOOL onto a DWORD on S7-1500?

No - the array is 32 bytes and the DWORD is 4 bytes. The valid overlays for a DWORD are ARRAY[0..3] OF BYTE (4 bytes) or a STRUCT of named BOOL members (4 bytes for up to 32 packed bits). For bit-granular access in the same SCL source use the STRUCT overlay form.

Which pattern is best for an FB instantiated 20 times in a machine?

Pattern B (ARRAY[0..31] OF BOOL) for readability and a small pack/unpack FC, or Pattern C (AT overlay with a STRUCT) on S7-1500 to avoid the pack/unpack FC. Avoid Pattern A inside many instances because the mask literals multiply the source-code review surface and obscure which instance is which.

How do I view individual bits of a fault DWORD in a TIA watch table?

The watch table does not provide a "show bit N of DWORD X" column. Either bind dedicated BOOL tags of the FB (one per bit you need to view) and set them from the bitwise-AND expression inside the FB, or use Pattern B / Pattern C so the bits are first-class BOOL tags that can be selected directly. For HMI visualization, bind a 32-element BOOL array of HMI tags to the 32 bits of the DWORD on the panel side.

Back to blog