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:
- Variables explicitly declared as
BOOL,BYTE,WORD, orDWORDmay be addressed as a whole, but the grammar does not provide aVariable.Bitform unless the variable is wrapped in a structured type that has the bit as a named component. - 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
ATconstruct).
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.
% 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.
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. |
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
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.
- Force a single leaf condition (e.g.
Fault_Code[1] = TRUEor set the equivalent input bit). - Observe the DWORD in the watch table. Confirm bit 1 is set and the numeric value reads 2 (or 0x0000_0002).
- Force a second leaf condition (bit 2). Confirm the DWORD reads 6 (0x0000_0006) and that the General Fault is asserted.
- Clear all leaf conditions. Confirm the DWORD reads 0 and General Fault is FALSE.
- 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).
- 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
Retaincompiler 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
ORandANDin 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
Faultalone; in SCL it collides with theFAULTkeyword family on some firmware versions. UseFault_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
DWORDtoUInt32by convention. -
Cross-block use of
AT. ADWORDvariable and its AT overlay must live in the same block. A global DB and a separate FB that each declare anAToverlay on the same memory area will compile, but the optimizer may reorder reads; consolidate the declaration in one block.
10. Related Siemens Resources
- TIA Portal V21 - DWORD data type
- Siemens support entry ID 19362106: "S7-SCL and S7-GRAPH - working with bit-mask operations on a DWORD" (legacy S7-300/400 SCL examples).
- Word (computer architecture) for the WORD/DWORD/QWORD naming convention.
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.