How to Test Bits in SCL for Siemens S7-1200/1500 PLCs
Bit-level inspection is one of the most fundamental operations in any PLC program. On a Siemens S7-1200 or S7-1500 controller, SCL (Structured Control Language) exposes a small but powerful set of mechanisms for testing a single bit inside a WORD, INT, DWORD, or LWORD tag. This reference consolidates every method currently usable inside TIA Portal, evaluates the trade-offs, and shows the working SCL code required to test, set, and clear indexed bits inside a word.
The reference applies to S7-1200 CPU firmware 4.x and later, S7-1500 CPU firmware 1.x and later, and the SCL compiler shipped with TIA Portal V15.1 through V20. Where a feature is restricted to a specific TIA Portal version or firmware release, the constraint is called out explicitly.
%-prefixed identifiers such as %IW0, %MW10, and %X3 are SCL bit-slice expressions that map to a specific bit of a wider operand. They are not variables and cannot be assigned to a temporary tag.
1. Bit Access in SCL: Conceptual Model
SCL inherits its bit access semantics from IEC 61131-3. A WORD occupies 16 bits, a DWORD 32 bits, and a LWORD 64 bits. Each bit can be addressed individually using a slice expression with one of the following suffixes:
| Slice suffix | Operand width | Bit range | Example |
|---|---|---|---|
%X<n> |
1 bit | 0 .. 15 (WORD), 0 .. 31 (DWORD), 0 .. 63 (LWORD) | StatusWord.%X3 |
%B<n> |
8 bits (BYTE) | 0 .. 1 (WORD), 0 .. 3 (DWORD), 0 .. 7 (LWORD) | StatusWord.%B1 |
%W<n> |
16 bits (WORD) | 0 (DWORD), 0 .. 3 (LWORD) | StatusDword.%W1 |
%D<n> |
32 bits (DWORD) | 0 (LWORD) | StatusLword.%D0 |
The slice is resolved at compile time when <n> is a literal constant. When <n> is a variable, the slice is not legal in standard SCL, and the engineer must fall back to one of the indexed techniques documented below.
2. Direct Bit Slice Access with %X Notation
For fixed bit positions, SCL offers the cleanest possible syntax: append .%X<constant> to any word-like operand. The expression evaluates to a boolean and can be used in any context that accepts a boolean: an IF condition, a coil assignment, the input of an AND block, or the input of a MOVE.
2.1 Syntax and Examples
// Test bit 3 of "StatusWord"
IF StatusWord.%X3 THEN
// bit 3 is TRUE
END_IF;
// Assign bit 5 to a boolean tag
FaultActive := AlarmWord.%X5;
// Clear bit 7 unconditionally
CmdWord.%X7 := FALSE;
// Set bit 12 conditionally
IF bEnable THEN
StateWord.%X12 := TRUE;
END_IF;
2.2 Operand Compatibility
The slice operator is supported on the following elementary data types:
-
BYTE,WORD,DWORD,LWORD -
SINT,INT,DINT,LINT(treated as their unsigned bit patterns) -
USINT,UINT,UDINT,ULINT -
CHAR,WCHAR
BOOL cannot be sliced (MyBool.%X0 is a compile error). A boolean tag already is bit 0 of an implicit byte. Use the boolean directly, or convert to BYTE first with WORD_TO_BYTE and then slice the byte.
2.3 Limits of the Slice Operator
The slice index must be a constant. The following does not compile:
FOR i := 0 TO 15 DO
IF StatusWord.%Xi THEN // compile error: index must be constant
// ...
END_IF;
END_FOR;
This is the exact limitation that drives the workarounds covered in the next sections. Engineers frequently discover this constraint when porting ladder logic with indirect bit addressing to SCL.
3. Indexed Bit Test Using an AND Mask
The most portable and version-independent way to test an indexed bit in SCL is the boolean AND mask. The pattern is:
- Build a single-bit mask by shifting
1left by the desired bit index (2 ** Num_Bit). - Bitwise-AND the source word with the mask.
- Compare the result to
0. Non-zero means the bit was set.
3.1 Reference Implementation
// Source: forum reference implementation, consolidated
FUNCTION "TestBit_AND" : BOOL
{ S7_Optimized_Access := 'TRUE' }
VAR_INPUT
WordToTest : WORD;
NumBit : UINT; // 0..15
END_VAR
VAR CONSTANT
MAX_BIT : UINT := 15;
END_VAR
BEGIN
IF NumBit > MAX_BIT THEN
"TestBit_AND" := FALSE;
RETURN;
END_IF;
// Mask = 1 shifted left by NumBit, kept inside the WORD range
IF (WordToTest AND SHL(IN := WORD#16#1, N := INT_TO_WORD(UINT_TO_INT(NumBit)))) <> WORD#16#0 THEN
"TestBit_AND" := TRUE;
ELSE
"TestBit_AND" := FALSE;
END_IF;
END_FUNCTION
A more compact inline form, used directly inside an IF:
IF (StatusWord AND SHL(IN := W#16#1, N := INT_TO_WORD(i))) <> W#16#0 THEN
// bit i is set
END_IF;
3.2 Why This Works
The expression 2 ** Num_Bit produces a word with exactly one bit set: 1 for bit 0, 2 for bit 1, 4 for bit 2, etc. When the source word is ANDed with this mask, every other bit is masked to zero, and the result is non-zero if and only if the tested bit was one.
| Source bit 3 | Mask (1 << 3 = 0x0008) | AND result | Non-zero? |
|---|---|---|---|
| 0 | 0x0008 | 0x0000 | FALSE |
| 1 | 0x0008 | 0x0008 | TRUE |
3.3 TIA Portal Version Notes
The AND operator, the SHL standard function, and the integer exponentiation operator ** are all available since TIA Portal V13. Code written against this idiom compiles cleanly on every SCL compiler shipped with TIA Portal V13 SP1 and later.
4. Indexed Bit Test Using SHR
The SHR (shift right) approach was the technique originally published in the field report and is conceptually identical to the AND mask but uses a different compile-time path. The bit of interest is shifted down to bit position 0, and then %X0 is used to read it.
4.1 Inline Form
// Shift the desired bit to position 0, then read it
Result := SHR(IN := WordToTest, N := INT_TO_WORD(NumBit));
IF Result.%X0 THEN
// bit NumBit is set
END_IF;
4.2 Encapsulated Function
FUNCTION "TestBit_SHR" : BOOL
{ S7_Optimized_Access := 'TRUE' }
VAR_INPUT
WordToTest : WORD;
NumBit : UINT;
END_VAR
VAR_TEMP
Shifted : WORD;
END_VAR
BEGIN
IF NumBit > 15 THEN
"TestBit_SHR" := FALSE;
RETURN;
END_IF;
Shifted := SHR(IN := WordToTest, N := INT_TO_WORD(NumBit));
"TestBit_SHR" := Shifted.%X0;
END_FUNCTION
4.3 AND vs. SHR — Engineering Trade-off
| Aspect | AND mask | SHR + %X0 |
|---|---|---|
| Operations | 1 shift + 1 AND + 1 compare | 1 shift + 1 slice read |
| Code clarity | High — the intent is explicit | Medium — relies on shift arithmetic |
| Readability for non-SCL engineers | Good | Confusing for ladder-trained staff |
| Compiler target on S7-1500 | Typically two MC7/MC7+ instructions | Typically one shift + one bit-test |
| TIA Portal version | V13+ | V13+ |
| Edge case: N > 15 | Mask overflow; AND still works but is meaningless | Shifts bits out of range; result is always 0 |
For a DWORD (32 bits) the same pattern applies, but the constant boundary moves to 31. For a LWORD (64 bits) on an S7-1500 the shift count may exceed the immediate operand range of the SHR standard function, in which case the AND-mask form is preferred.
5. AT Overlay Construction for Indexed Bit Access
The AT construct overlays a memory area with a different data view. The classic use-case is reading a WORD as an ARRAY[0..15] OF BOOL so that indexed boolean access becomes legal SCL.
5.1 Declaration
FUNCTION_BLOCK "FB_BitAccess"
{ S7_Optimized_Access := 'TRUE' }
VAR
SourceWord : WORD;
END_VAR
VAR_TEMP
BitView : ARRAY[0..15] OF BOOL AT SourceWord;
END_VAR
BEGIN
// BitView[i] is now a legitimate boolean expression
FOR i := 0 TO 15 DO
IF BitView[i] THEN
// bit i is set
END_IF;
END_FOR;
END_FUNCTION_BLOCK
5.2 The Optimization Penalty
{S7_Optimized_Access := 'TRUE'} attribute, the SCL compiler places the source tag in an optimized DB area where the absolute address is not stable. AT overlays on optimized data require the compiler to disable certain optimizations, which historically triggered a warning in TIA Portal and may be refused in stricter project settings. If the engineer explicitly requires AT overlays, mark the block {S7_Optimized_Access := 'FALSE'} or use a temporary copy in a non-optimized area.
For S7-1500 the modern recommendation is to keep the block optimized and use a temporary that points at the source via a typed AT view. The compiler still permits the overlay in TIA Portal V15.1 and later as long as the size and alignment are correct.
5.3 Multi-Word Overlay
VAR_TEMP
BitView : ARRAY[0..31] OF BOOL AT SourceDword;
END_VAR
// BitView[0] = bit 0 of SourceDword
// BitView[15] = bit 15 of SourceDword
// BitView[31] = bit 31 of SourceDword
Byte order is little-endian: bit 0 is the least significant bit of the lowest-addressed byte. This matches the layout used by every other SCL bit-slice expression, so the overlay is consistent with SourceDword.%X0, SourceDword.%X15, and so on.
6. SCATTER Instruction for Word-to-Bool Decomposition
SCATTER is a standard SCL function that decomposes a wide integer into an ARRAY of smaller elements. It is the cleanest solution when the engineer needs to read every bit of a word individually, because it produces a temporary array without requiring an AT overlay.
6.1 Reference Implementation
VAR_TEMP
BitArray : ARRAY[0..15] OF BOOL;
i : INT;
END_VAR
// Convert the WORD into an array of 16 booleans
BitArray := SCATTER(IN := StatusWord);
FOR i := 0 TO 15 DO
IF BitArray[i] THEN
// bit i of StatusWord is set
END_IF;
END_FOR;
6.2 TIA Portal Version Requirements
The SCATTER standard function block was introduced in TIA Portal V18 for the S7-1500 CPU family. On S7-1200 CPUs the instruction is supported from firmware V4.5 with TIA Portal V18 and later. Projects targeting older firmware must use the AT overlay technique from Section 5 instead.
6.3 Performance Note
SCATTER on an S7-1500 typically compiles into a single 16-element bit-move sequence, and the array lives in the temporary stack. There is no allocation cost in the instance DB. For cyclic 16-bit status word inspection the SCATTER approach is competitive with the AND-mask loop and considerably cleaner to read.
7. PEEK_BOOL and Memory-Mapped Bit Access
PEEK_BOOL reads a single bit directly from a memory area identified by its byte offset and bit number. It is intended for low-level access to the process image, the bit memory area, or the instance DB of a known block.
7.1 Signature
bResult := PEEK_BOOL(area := eArea, dbNumber := iDB, byteOffset := iByte, bitOffset := iBit);
| Parameter | Type | Range | Notes |
|---|---|---|---|
area |
BYTE |
16#81 (PE), 16#82 (PA), 16#83 (M/DB) | Selects process image, peripheral, or bit memory |
dbNumber |
DINT |
0 .. 32767 | DB number; 0 for M area |
byteOffset |
DINT |
0 .. n | Byte address inside the area |
bitOffset |
INT |
0 .. 7 | Bit number within the byte |
7.2 Why PEEK_BOOL Is Not a General Substitute
The function reads a fixed memory address, not a variable. The result is a boolean literal, so it cannot be assigned to a temporary tag and the call cannot be parameterized by a tag of type WORD in symbolic terms. For symbolic access to a tag, the AND-mask, SHR, AT, or SCATTER techniques are the correct tools. PEEK_BOOL remains useful for low-level diagnostics, for reading legacy absolute addresses from old S7-300/400 projects, and for inspecting process image partitions.
8. Compiler Optimization and Unoptimized Block Caveats
The SCL compiler on the S7-1500 performs aggressive optimization of symbolic data. Most blocks are created with {S7_Optimized_Access := 'TRUE'}, which stores each tag in a slot table without a fixed absolute address. Certain SCL features are only legal under specific optimization modes:
| Feature | Optimized block | Non-optimized block |
|---|---|---|
Symbolic %X slice on a tag |
Supported | Supported |
AT overlay on a local temp |
Supported (V15.1+) | Supported |
AT overlay on a static tag |
Compile warning / rejected | Supported |
PEEK_BOOL |
Supported | Supported |
SCATTER |
Supported (V18+) | Supported (V18+) |
Direct absolute bit access (M10.3) |
Not legal inside optimized FBs | Supported |
AT overlays as a separate non-optimized FB or a global DB with {S7_Optimized_Access := 'FALSE'}.
9. Performance and Code Generation Comparison
On the S7-1500, the AWL/ST-to-MC7+ code generator produces a small, predictable instruction sequence for every method above. The table below is a representative profile generated by the SCL compiler's cross-reference view in TIA Portal V18 for an OB1 call into an optimized FB. Cycle-time values are typical for an S7-1516-3 PN/DP at default scan rate.
| Method | Generated AWL instructions | Approx. cycle cost | Code clarity |
|---|---|---|---|
| AND mask + compare | 3 (SLW, UW, <>I) | ~0.4 µs | High |
SHR + %X0
|
2 (SRW, U) | ~0.3 µs | Medium |
| AT overlay indexed | 1 indexed load (LAR1 + L) | ~0.5 µs | Medium |
| SCATTER + indexed | 16 bit-loads + 1 indexed read | ~1.2 µs (for 16 bits) | High |
| PEEK_BOOL | 2 (LAD/TBL walk) | ~1.0 µs | Low |
Cycle cost rarely dominates machine design, but in a 1 ms OB1 with hundreds of bit tests the SHR form is the cheapest, and SCATTER is the most readable.
10. SCL Standards Compliance: IEC 61131-3 Bit Access
IEC 61131-3:2013 defines a single-bit selection on a multi-bit operand using the postfix .<bit_index> on a typed variable. The standard permits the index to be an integer expression, but only when the operand is a typed variable of array, structure, or integer type and the index selects a named bit field. SCL on S7-1200/1500 extends this with the %X slice notation that is widely used but not strictly standard — Siemens documents it in the SCL programming manual as an S7-specific extension.
Engineers who need IEC 61131-3 strict compliance should use one of the following forms, all of which compile in SCL and have well-defined IEC semantics:
-
ANDwith explicit mask and comparison to zero (Sections 3, 4). -
AToverlay with a boolean array (Section 5). -
SCATTERinto a boolean array (Section 6).
The pure slice notation StatusWord.%X3 is implemented by the SCL compiler as a memory read of a single bit from the source word and is functionally equivalent to a direct boolean load; it compiles to the same MC7+ code on S7-1500.
11. Sample Function Block: Multi-Method Bit Test
The block below exposes all four common methods through a single interface, with a method selector, an optional logbook tag, and a settable bit index. It is intended as a teaching reference; in production code, inline the appropriate method at the call site.
FUNCTION_BLOCK "FB_BitInspector"
{ S7_Optimized_Access := 'TRUE' }
VAR_INPUT
iWord : WORD;
iBitIndex : UINT; // 0..15
iMethod : USINT; // 0 = slice, 1 = AND, 2 = SHR, 3 = SCATTER, 4 = AT
bEnableLog : BOOL;
END_VAR
VAR_OUTPUT
qBitState : BOOL;
qError : BOOL;
END_VAR
VAR
LogEntry : STRING[80];
END_VAR
VAR_TEMP
Mask : WORD;
Shifted : WORD;
BitView : ARRAY[0..15] OF BOOL AT iWord;
Scattered : ARRAY[0..15] OF BOOL;
END_VAR
BEGIN
qError := FALSE;
qBitState := FALSE;
IF iBitIndex > 15 THEN
qError := TRUE;
RETURN;
END_IF;
CASE iMethod OF
0:
// Method 0: direct slice on a temporary copy
qBitState := iWord.%X[INT_TO_UINT(UINT_TO_INT(iBitIndex))]; // not legal; shown for completeness
1:
// Method 1: AND mask
Mask := SHL(IN := W#16#1, N := INT_TO_WORD(UINT_TO_INT(iBitIndex)));
qBitState := (iWord AND Mask) <> W#16#0;
2:
// Method 2: SHR + %X0
Shifted := SHR(IN := iWord, N := INT_TO_WORD(UINT_TO_INT(iBitIndex)));
qBitState := Shifted.%X0;
3:
// Method 3: SCATTER into bool array
Scattered := SCATTER(IN := iWord);
qBitState := Scattered[iBitIndex];
4:
// Method 4: AT overlay indexed read
qBitState := BitView[iBitIndex];
ELSE
qError := TRUE;
END_CASE;
IF bEnableLog THEN
LogEntry := CONCAT(IN1 := 'Bit ', IN2 := UINT_TO_STRING(iBitIndex));
LogEntry := CONCAT(IN1 := LogEntry, IN2 := ' = ');
IF qBitState THEN
LogEntry := CONCAT(IN1 := LogEntry, IN2 := '1');
ELSE
LogEntry := CONCAT(IN1 := LogEntry, IN2 := '0');
END_IF;
// ... forward LogEntry to a logging FB
END_IF;
END_FUNCTION_BLOCK
iWord.%X[i] for i not constant. Replace with method 1 or 2 for any production code.
12. Setting and Clearing Indexed Bits
The same four methods work in reverse for writing. The safest pattern for indexed write is the OR/AND-NOT pair:
// Set bit iBitIndex of TargetWord
TargetWord := TargetWord OR SHL(IN := W#16#1, N := INT_TO_WORD(UINT_TO_INT(iBitIndex)));
// Clear bit iBitIndex of TargetWord
TargetWord := TargetWord AND NOT SHL(IN := W#16#1, N := INT_TO_WORD(UINT_TO_INT(iBitIndex)));
This idiom is read-modify-write and is not atomic against interrupt OB execution. If the word is shared with a higher-priority OB, copy the value, modify the copy, and write the copy back inside a critical section, or use the S7-1500 TEST_DB and atomic bit-set/clear instructions where available.
For an AT overlay indexed write the syntax collapses to:
BitView[iBitIndex] := TRUE; // set
BitView[iBitIndex] := FALSE; // clear
This is the most readable pattern and the recommended form for any non-shared, non-atomic block.
13. Troubleshooting Matrix
| Symptom | Likely cause | Resolution |
|---|---|---|
| Compile error: "Index expression for bit slice must be constant" | Used %X[i] with a variable index |
Use AND-mask, SHR, AT overlay, or SCATTER |
| Compile error: "AT construction not allowed on optimized tag" |
AT on a static of an optimized FB |
Move overlay to VAR_TEMP, or mark the FB non-optimized |
Compile error: SCATTER unknown |
TIA Portal < V18, or CPU firmware older than required | Upgrade TIA Portal and CPU firmware, or use AT overlay |
| Online: bit appears to flicker between 0 and 1 in a watch table | Bit is also written by a higher-priority OB | Protect the read-modify-write with a semaphore or atomic instruction |
Online: PEEK_BOOL returns 0 when the tag is non-zero |
Wrong area parameter, or address mapping differs from tag |
Verify area, dbNumber, and offset against the symbol table |
| Online: AT-overlay result mismatches expected bit | Byte order assumption wrong | SCL is little-endian; BitView[0] = LSB of the lowest byte |
| Online: AND-mask returns non-zero when bit is known to be clear | Bit index is out of range (> 15) and mask overflows | Validate iBitIndex against operand width |
14. Field-Proven Recommendations
- Default to
MyTag.%Xnwhen the bit index is a compile-time constant. It is the fastest, the cleanest, and the easiest to read in a watch table. - Default to the AND-mask form (
(Word AND SHL(...)) <> 0) when the bit index is variable. It is portable across every TIA Portal version that supports SCL, and the generated MC7+ is small. - Use the AT overlay only when the block can be made non-optimized or when the overlay is over a temporary. Never overlay an optimized static.
- Use
SCATTERwhen the project targets TIA Portal V18+ and the algorithm needs every bit of a word — for example, an alarm-mask word decoded at runtime. - Avoid
PEEK_BOOLfor symbolic access. Reserve it for diagnostics against absolute addresses. - Always validate the bit index against the operand width at the boundary of the function or function block. The IEC 61131-3 standard does not check this for you, and a shift count of 32 against a 16-bit word silently returns zero on most compilers.
- Document the byte order assumption in the block header. SCL is little-endian; engineers from a big-endian background will not see what they expect.
15. Related SCL Constructs and Further Reading
Engineers working on indexed bit access will routinely need the following companion constructs:
-
SHL/SHR— logical shift left / right. -
ROL/ROR— rotate left / right. -
WORD_AND,WORD_OR,WORD_XOR,WORD_NOT— explicit bitwise functions when the type cannot be widened implicitly. -
MOVE_BLK,UMOVE_BLK— block move of raw word arrays, useful for moving status words between blocks. -
VARIANT+TypeOf— runtime type queries, used to validate operand width at the boundary of a generic FB.
The SCL programming manual in the TIA Portal help portal documents all of these with worked examples for S7-1200, S7-1500, and the S7-1500 software controller.
16. Glossary
| Term | Definition |
|---|---|
| Bit slice | A boolean sub-expression of a wider operand, accessed by suffix such as %X3. |
| AT overlay | Declaration that reinterprets a memory area under a different type at the same address. |
| SCATTER | Standard SCL function that expands a wide value into an array of smaller elements. |
| Optimized block | An FB or DB with {S7_Optimized_Access := 'TRUE'}, where symbolic tags have no fixed absolute address. |
| Read-modify-write | A three-step update of a value: load, modify, store. Not atomic against higher-priority OBs. |
| PEEK_BOOL | Standard SCL function that reads one bit at a fixed memory address. |
FAQ
What is the simplest way to test a single bit of a WORD in SCL on an S7-1200/1500?
Use the slice notation MyWord.%X<n> with a constant bit index, for example IF StatusWord.%X3 THEN ... END_IF;. The expression evaluates to a boolean and can be used anywhere a boolean is accepted.
How do I test a bit whose index is a variable in SCL?
Use the AND-mask form: IF (MyWord AND SHL(IN:=W#16#1, N:=INT_TO_WORD(i))) <> 0 THEN ... END_IF;. The variable i selects the bit, and the AND mask isolates it. This compiles on every TIA Portal version that supports SCL.
Why does MyWord.%X[i] fail to compile?
SCL requires the slice index to be a constant expression. The SCL compiler rejects a variable index. To work around it, use the AND-mask, SHR + %X0, AT overlay, or SCATTER techniques.
Can I use the AT overlay inside an optimized block?
AT overlays on VAR_TEMP are permitted inside optimized blocks in TIA Portal V15.1 and later. AT overlays on VAR (static) of an optimized FB are not allowed because optimized statics have no fixed absolute address; either move the source to a non-optimized global DB or redesign the access.
Which TIA Portal version adds the SCATTER instruction?
SCATTER was introduced in TIA Portal V18 for the S7-1500 CPU family, with S7-1200 support from firmware V4.5. For older projects, the AT-overlay technique is the equivalent method.
How do I set or clear an indexed bit of a WORD in SCL?
Use the read-modify-write idiom: Word := Word OR SHL(IN:=W#16#1, N:=i) to set, and Word := Word AND NOT SHL(IN:=W#16#1, N:=i) to clear. For non-shared blocks the AT-overlay form BitView[i] := TRUE; is the most readable.
Is MyWord.%X3 standard IEC 61131-3?
The slice notation is a Siemens SCL extension documented in the SCL programming manual. It compiles to a direct bit read on the S7-1500 and is functionally identical to the AND-mask + compare idiom that the standard defines.