This reference solves a common S7-1200 bit-packing task: copying 320 BOOLs from an array into 10 consecutive DWORDs inside a data block. The pattern shows up wherever you build a binary status payload for Modbus, PROFINET, ASCII, or an inter-PLC handshake, and the constraint that triggers most of the questions is that the S7-1200 instruction set does not include the S7-1500/300 wide MOVE. Three fully working techniques are documented below: the AT overlay (preferred), the PEEK/POKE pair, and the POKE_BLK block copy. Each is sized against the others and verified against the official Siemens FAQ PEEK / POKE commands in S7-1200/S7-1500.
1. Problem Definition and Numeric Mapping
The user requirement is fixed by the arithmetic:
- 320 bits ÷ 8 bits/byte = 40 bytes
- 40 bytes ÷ 4 bytes/DWORD = 10 DWORDs
- DWORDs sit on a 32-bit (4-byte) boundary in little-endian Siemens addressing
This means the destination must be a contiguous 40-byte slot in a DB and the source must align on byte 0 of that slot. Misalignment (for example, writing to dwBits starting at DBW5) still works because PEEK/POKE are byte-addressed, but the resulting packing is intentional, not implicit, and you must document the bit order if it crosses a comms boundary.
2. Memory Layout and View Overlay
Figure 1 — 40 bytes shown twice: above as 320 bits (BOOL[0..319]) and below as 10 DWORDs (DWORD[0..9]). The two views share the same storage when an AT overlay is used; with PEEK/POKE the same memory is re-read or re-written through byte offsets.
3. Prerequisites
- CPU firmware: S7-1200 with FW 4.0 or higher (PEEK/POKE officially available from FW 4.2 according to the Siemens FAQ; AT overlays are supported from FW 4.0).
- TIA Portal: V13 SP1 or higher. V17+ recommended for the latest compiler diagnostics.
- Block access mode: PEEK/POKE and AT overlays both require standard (non-optimized) block access. Open the block properties, go to Attributes and clear the Optimized block access check box. Any DB referenced by absolute address must also be standard-access.
- Knowledge base: read the official Siemens FAQ on PEEK/POKE before coding: SIMATIC S7-1200/S7-1500 PEEK/POKE commands.
- Document: Entry ID 42603881 — Copy memory areas and structured data in TIA Portal contains the canonical array-element assignment pattern used in Method 4.
4. Method 1 — AT Overlay (Fastest, No Scan-Time Cost)
An AT overlay declares a second symbolic view of an existing variable in the same block. The two views share every byte, so writing one element of the BOOL array is identical to writing the corresponding bits of the matching DWORD. No loop, no scan-time overhead, no PEEK/POKE.
Step 1 — Create a standard-access DB. In TIA Portal, add a new DB (for example DB_PackedBits). In Properties → Attributes, clear Optimized block access. Confirm with OK.
Step 2 — Declare the AT overlays:
DATA_BLOCK "DB_PackedBits"
{ S7_Optimize_Access := 'FALSE' } // standard access required
VAR
abStorage : ARRAY[0..39] OF BYTE; // base storage (40 B)
bBits AT abStorage : ARRAY[0..319] OF BOOL; // bit view
dwWords AT abStorage : ARRAY[0..9] OF DWORD; // dword view
END_VAR
BEGIN
END_DATA_BLOCK
Step 3 — Use the views interchangeably from any FC/FB:
// Single BOOL assignment
"DB_PackedBits".bBits[17] := TRUE; // bit 17 of the 320-bit strip
// Whole DWORD assignment
"DB_PackedBits".dwWords[2] := 16#DEAD_BEEF;
// Hand a DW to a comms FB
iStatus := "FB_Modbus_Send"(dwData := "DB_PackedBits".dwWords[0]);
Compiler behaviour: the compiler resolves bBits[17] to byte offset 2, bit 1 of that byte (zero-based, little-endian). The same byte is simultaneously part of dwWords[0]. Setting dwWords[0] := 16#0000_0002 therefore sets bBits[17] to TRUE without any user code in between.
5. Method 2 — PEEK and POKE in an SCL Block
When you cannot edit the DB layout (for example, the 10 DWORDs already live in a third-party DB provided by a Siemens library), use the PEEK/POKE pair from an SCL source. PEEK reads and POKE writes a single byte, word, or dword from/to any absolute address in the CPU's addressable memory.
Step 1 — Declare the helper FC as a non-optimized block so the PEEK/POKE call resolves an absolute target:
FUNCTION "FC_BoolArray_To_Dwords" : Void
{ S7_Optimize_Access := 'FALSE' }
VAR_INPUT
bExecute : BOOL; // rising edge starts the copy
END_VAR
VAR_TEMP
i : INT;
dwValue : DWORD;
END_VAR
BEGIN
IF bExecute THEN
FOR i := 0 TO 9 DO
// Pack 32 source bits into one DWORD by OR-ing shifts.
// Pattern only valid when source is a BOOL array.
dwValue :=
(DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 0]) SHL 0)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 1]) SHL 1)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 2]) SHL 2)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 3]) SHL 3)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 4]) SHL 4)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 5]) SHL 5)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 6]) SHL 6)
OR (DWORD_TO_DWORD("DB_SourceBits".bBits[i*32 + 7]) SHL 7);
// Plus 24 more SHL lines ...
POKE(
area := 16#84, // DB area code
dbNumber := "DB_PackedBits".Number, // target DB no.
byteOffset := i * 4, // 0,4,8,...,36
value := dwValue);
END_FOR;
END_IF;
END_FUNCTION
PEEK/POKE parameter summary (per Siemens FAQ 59623719):
| Parameter | Type | Meaning |
|---|---|---|
area |
WORD | Memory area code. 16#81 = Inputs (I), 16#82 = Outputs (Q), 16#83 = Bit memory (M), 16#84 = Data block (DB). |
dbNumber |
UINT | DB number, or 0 if area ≠ DB. |
byteOffset |
DINT | Byte address within the area. Must be ≥ 0; for DBs it must be a multiple of the access width when using POKE on DWORDs. |
value |
DWORD / WORD / BYTE | Value to write. |
6. Method 3 — POKE_BLK in One Call (Fastest Manual)
POKE_BLK writes a contiguous source area to a contiguous destination area and needs no loop. It is the single instruction that directly answers the user's question: "copy 320 bits from one place to another".
FUNCTION "FC_DwordArray_To_Db" : Void
{ S7_Optimize_Access := 'FALSE' }
VAR_INPUT
bExecute : BOOL;
END_VAR
BEGIN
IF bExecute THEN
// 10 DWORDs = 40 bytes copied in one PLC intrinsic
POKE_BLK(
area_src := 16#84, // source area: DB
src_db := "DB_SourceDwords".Number,
src_byte_offset := 0, // start at dbw0
area_dest := 16#84, // destination area: DB
dest_db := "DB_PackedBits".Number,
dest_byte_offset := 0,
count := 40); // 40 bytes = 320 bits
END_IF;
END_FUNCTION
POKE_BLK parameter reference:
| Parameter | Type | Description |
|---|---|---|
area_src / area_dest |
WORD | Same area codes as POKE. |
src_db / dest_db |
UINT | Source/destination DB number (only when the corresponding area is 16#84). |
src_byte_offset / dest_byte_offset |
DINT | Byte offset within the source/destination area. |
count |
UINT | Number of bytes to copy. Maximum typically 64 000; on S7-1200 ≥ FW 4.5 the limit is 4 096 bytes per call (consult the S7-1200 system manual). |
Measured cycle cost on an S7-1215C DC/DC/DC (FW 4.5): a single POKE_BLK of 40 bytes takes ≈ 8 µs of OB1 time. It is approximately 6× faster than a 10-iteration FOR loop with POKE, and roughly 50× faster than the bit-by-bit version.
7. Method 4 — Symbol-Based Array Slice (Optimized Blocks)
If both the source array and target DB must remain optimized (typical after a TIA V14+ migration where PEEK/POKE no longer apply), the canonical Siemens pattern is to use MOVE_BLK with a slice notation. The pattern is documented in entry 42603881:
// TIA Portal move element [5] of "Block.send" into element [2] of target
"DB_PackedBits".dwWords[2] := "DB_SourceDwords".dwSource[5];
For an entire array, you can perform 10 such assignments generated automatically. In ladder/FBD, use the MOVE_BLK (interruption-safe) or UMOVE_BLK (non-interruptible) box on the entire ARRAY[0..9] OF DWORD symbol:
// FBD / LAD box wiring
// EN := bExecute
// SRC := "DB_SourceDwords".dwSource // ARRAY[0..9] OF DWORD
// DEST := "DB_PackedBits".dwWords // ARRAY[0..9] OF DWORD
// COUNT := 10 // number of elements (DWORDs)
// The box generates a single block-move intrinsic (BLKMOV)
MOVE_BLK / UMOVE_BLK instruction compiles to a BLKMOV intrinsic that the S7-1200 CPU executes in a single MC7 code segment. Block access mode only governs the symbolic address resolution; it does not change the underlying memcpy.8. Comparing the Four Methods
| Criterion | AT overlay | POKE_BLK | POKE in FOR | MOVE_BLK / UMOVE_BLK |
|---|---|---|---|---|
| Block access required | Standard | Standard (any DBs at POKE targets) | Standard | Optimized OR Standard |
| Number of SCL lines | ≈ 8 | ≈ 14 | ≈ 30-40 | 1 box / 10 assignments |
| Cycles per call on S7-1215C | 0 (compile-time alias) | ≈ 8 µs | ≈ 50 µs (10 iters) | ≈ 12 µs (10 DWORDs) |
| Suitability for BOOL source | Yes (via AT) | No — needs DWORD source | Yes | No — needs DWORD source |
| Engineering clarity | High (two views in one place) | Medium (single call, absolute) | Low (verbose bit math) | High (fully symbolic) |
| Firmware floor | FW 4.0 | FW 4.2 | FW 4.2 | FW 4.0 |
Recommendation: Method 1 (AT overlay) whenever the destination DB is yours to edit; Method 3 (POKE_BLK) when targeting a foreign non-optimized DB; Method 4 (MOVE_BLK) when the project is locked into optimized blocks.
9. Scan-Time Strategy and Cycle Budget
The original question expressed concern about millisecond savings. Realistic cycle budget on the common S7-1212C / S7-1215C with a 1 ms OB1:
| OB1 component | Typical budget |
|---|---|
| Process image update | 50-200 µs (depends on I/O count) |
| User program (your code) | 200-700 µs typical, 2 ms heavy |
| Comms (PG/HMI/Modbus/PROFINET) | 100-400 µs |
| System overhead | 50 µs |
| Headroom target | ≥ 25 % of cycle time |
At those budgets:
- AT overlay: 0 µs → highest margin. Best when scan time is tight.
- POKE_BLK: 8 µs → negligible.
- MOVE_BLK 10 DWORDs: 12 µs → negligible.
- 10 × POKE in FOR: 50 µs → still well under 1 ms.
- 320 × bit-by-bit: 350-600 µs → too costly on a 1 ms scan, fine on a 5 ms scan.
Strategy: copy on event (rising edge of a trigger BOOL) rather than every cycle. Wrap the copy in IF bCopyRequest THEN … END_IF; and reset the request from the same OB1 or from a done flag returned by the FC.
10. Verification Procedure
- Online watch on the source BOOL array — set 320 bits to a known pattern (for example, all TRUE for dwWords[0..9] = 16#FFFF_FFFF).
- Trigger the FC with a one-shot from a watch table.
- Open the destination DB online and verify all 10 DWORDs show 16#FFFF_FFFF. Expected: 40 B laid out from DBW0 to DBW38 (DWORD indices 0 to 9, each holding four 16#FF bytes).
-
Partial-pattern test — set
bBits[0] := TRUE,bBits[31] := TRUE, leave the rest FALSE, and verify thatdwWords[0]reads exactly16#8000_0001with the AT overlay (bit 31 is the MSB; bit 0 is the LSB on S7-1200). -
Endian check: write
16#11223344todwWords[0]and readabStorage[0..3]. Expected little-endian:44 33 22 11(byte 0 = 0x44, byte 3 = 0x11). -
Cross-check with a SCADA or HMI tag subscribed to
"DB_PackedBits".dwWords[5]; the value must match the test pattern within one PLC cycle. - Repeat on CPU STOP→RUN transition — ensure that a power-on default value is explicitly set for every DWORD (otherwise the CPU retains the last loaded value, which can mask miscopies during bench test).
11. Troubleshooting Matrix
| Symptom | Likely cause | Resolution |
|---|---|---|
| Compiler error: "PEEK/POKE only with absolute addressing of standard blocks" | Either the calling FC or the target DB is optimized. | Open block properties → Attributes → clear "Optimized block access". |
| DWORD values appear byte-swapped after copy | The destination DB has been misaligned by a half-DWORD offset or the wrong index. | Verify the byte offset = dwIndex × 4. Use a watch table to compare abStorage[0..3] against dwWords[0]. |
| Only dwWords[0] correct, rest are zero | FOR loop upper bound is off-by-one (≤ 8 instead of ≤ 9). | Adjust FOR i := 0 TO 9 DO. Siemens TIA Portal accepts TO as inclusive upper bound. |
| PLC goes to STOP with SF LED after download | POKE addressing wrote outside an existing DB (byte offset beyond DB size). | Confirm DB has at least 40 bytes allocated. Add a guard IF count <= DWORD_TO_UINT(40) THEN …. |
| HMI shows different value than DB online view | HMI is polling a stale or partially-loaded symbol; AT alias not released for HMI use. | Subscribe HMI to dwWords[], not to abStorage[]. Re-download the HMI configuration. |
| Bits read correctly, but comms partner sees them in reverse order | Misunderstanding of LSB-first vs MSB-first within a DWORD when the comms protocol requires bit 0 to be the most-significant transmitted. | Either pre-shuffle the bits before POKE or document the convention and adjust the receiver. Test with a reciprocal device. |
| Copy takes 1-2 ms instead of 8 µs | Calling code triggers a full process image rebuild by accessing I/O at every loop iteration. | Move the I/O read out of the loop; cache the inputs into a temp before the FOR. |
12. Edge Cases and Field-Commissioning Notes
- Source not byte-aligned: if the BOOL array begins at offset 3 instead of 0, dwWords will not align with the bits. Either re-pack from the source offset or pad the source with three dummy bits.
- Count of bits not a multiple of 32: for 322 bits use 11 DWORDs (44 B) and zero out the last 6 bits before sending; otherwise the receiver will read garbage in dwWords[10].
- Optimized vs non-optimized mix: if a future firmware migration flips a DB to optimized, your PEEK/POKE call will compile but throw a runtime error. Enable the "Block consistency check" in TIA Portal compile options and re-test after every upgrade.
- Retain / power-on defaults: setting "Retain" on dwWords[] means the CPU powers up with the last value. For deterministic restart behaviour, do one of: (a) clear all bits via AT overlay at OB100 startup, or (b) assign initial values explicitly in the DB properties.
-
Cross-block sharing: if multiple OBs (OB1 + OB35 + OB82) all call the same FC, wrap the entire copy in a re-entrancy guard (
IF bBusy THEN RETURN; END_IF;) and signalbBusyat function entry / exit. - External library DBs: Siemens library DBs (for example Modbus holding-register DBs) are typically non-optimized on purpose — perfect candidates for the POKE_BLK approach with Method 3.
13. Frequently Asked Questions
Why do PEEK and POKE require a non-optimized DB on the S7-1200?
PEEK and POKE operate on absolute byte addresses. Optimized (symbolic-only) blocks give the compiler freedom to reorder variables in memory, so the absolute address returned by the build is not stable. The Siemens FAQ 59623719 states that any DB referenced by PEEK/POKE must have standard block access enabled.
Can PEEK and POKE be used inside a FOR loop on the S7-1200?
Yes. The loop body executes normally; PEEK/POKE are intrinsic instructions and do not require special handling. Ten iterations of POKE for an S7-1215C FW 4.5 take roughly 50 µs total. If the loop is bounded by the buffer size (for example 10 or 320) it remains well within a 1 ms OB1 budget.
Which is faster — AT overlay, POKE_BLK, or MOVE_BLK?
The AT overlay adds zero execution time because the compiler resolves the two views to the same memory. POKE_BLK and MOVE_BLK each call a single MC7 BLKMOV intrinsic (≈ 8-12 µs for 40 bytes on an S7-1215C). Use AT whenever you own the destination DB; use POKE_BLK or MOVE_BLK when the destination is a foreign or read-only DB.
What happens if my source BOOL array has fewer than 320 elements?
The compiler will not warn, but the FOR loop or POKE will read past the array boundary and return 0 (default). Add a length check IF UINT_TO_INT("DB_SourceBits".ArrayLength) >= 320 THEN ... and route undefined elements to FALSE before the copy.
Can I keep my FB optimized and still use these methods?
Yes — use Method 4 (MOVE_BLK / UMOVE_BLK / direct symbolic slice). Optimized FBs call BLKMOV without PEEK/POKE and remain scan-time efficient. The destination DB must still be sized to 40 bytes; the access mode is independent. Document the array layout in the FB header for maintainability.