Indexed Addressing in Siemens S7 PLCs M Area Arrays and PEEK/POKE

David Krause15 min read
SiemensTechnical ReferenceTIA Portal
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

Indexed Addressing in Siemens S7 PLCs: M Area Arrays and PEEK/POKE

Siemens S7 controllers expose a flag (M) memory area that can be addressed as bits, bytes, words, and double words. When an application needs to walk through a contiguous block of flags using a runtime index—instead of hard-coding every absolute address—the engineer has three separate implementation paths depending on the controller family and the programming environment. Classic STEP 7 V5.x on the S7-300/S7-400, the embedded STEP 7 of TIA Portal targeting the same S7-300/S7-400 CPUs, and TIA Portal targeting the S7-1200/S7-1500 each use a different combination of syntax, data-block layout, and instructions. This reference documents the M-area indexed-access forms that have shipped in fielded programs, the data-type rules that govern symbol-table entries, and the migration path when an old STL routine is ported into modern SCL on an S7-1500.

1. Memory Model Overview: Inputs, Outputs, Flags, and Data Blocks

Every S7 CPU partitions its process image and internal RAM into fixed, byte-aligned areas:

Area Operand identifier Typical size on S7-300/400 Typical size on S7-1200/1500 Retentive option
Process inputs (PII) I / IB / IW / ID Configurable, default 128 B Configurable, default 32 B No
Process outputs (PIQ) Q / QB / QW / QD Configurable, default 128 B Configurable, default 32 B No
Flags (M) M / MB / MW / MD 2 KB to 16 KB depending on CPU 8 KB to 28 KB depending on CPU Yes (partial, configurable)
Instance DB / Global DB DB / DBX / DBB / DBW / DBD Up to 64 KB per DB on S7-400 (more with several) Up to 64 KB (optimized), 1 MB+ with non-optimized layout Yes (per tag)

The flag area is a flat byte array that can be sliced at any byte boundary. M[byte,bit] refers to the bit at (byte × 8) + bit; MW[n] refers to the 16-bit word whose least-significant byte sits at byte offset n. These two notations are the foundation of indexed addressing.

Per the Wikipedia array data structure reference, an array is stored such that the position (memory address) of each element can be computed from its index tuple by a mathematical formula. The S7 M area is precisely such a structure: byte n lives at fixed offset n from the start of the flags, so the address of MW[n] is &M + 2 × n in absolute terms. The PLC firmware performs this multiplication in a single microcycle when the index is a variable.

2. Classic STEP 7 V5.x Indexed Access on S7-300/S7-400

STEP 7 V5.x (the "classic" line) reached its terminal release as V5.7, which still installs on Windows 10 and Windows 11 and is the only officially supported route for legacy S7-300/S7-400 projects. Versions V5.0 through V5.7 are mutually compatible in source code; project files can move forward and backward with little rework.

Within a single Organization Block (OB) and without declaring any Data Block, classic STL accepts direct indexed syntax against the absolute flag area:

// STL, classic STEP 7 V5.x, valid on S7-300/S7-400
L     50                    // load index 50
T     MW    100             // store value at MW[100]
L     MB    [MW 200]        // indirect byte access using MW200 as index
T     MB    10
U     M     [MW 200, 3]     // bit access: byte from MW200, bit 3
=     M     5.0

Two important properties of this syntax:

  1. Type is fixed by the operand identifier. MW[] is always a 16-bit Word; MD[] is always a 32-bit Double Word; MB[] is always an 8-bit Byte. The data type cannot be altered by the index.
  2. No array declaration is needed. The flag area is treated as a raw, pre-existing array. The symbol table can assign a name to MW 100 (for example MotorSetpoint), but it cannot turn MW 100..MW 110 into an ARRAY[0..10] OF WORD declaration in V5.x.

SCL in V5.x uses square-bracket indexing as well but requires a temporary variable of integer type for the index:

// SCL, classic STEP 7 V5.x
VAR
    iIndex   : INT;
    wValue   : WORD;
END_VAR

wValue := MW[iIndex];
IF M[iIndex] THEN            // bit access by integer index
    MW[iIndex] := 0;
END_IF;

The pre-TIA symbol table (Symbol Editor) stores symbolic addresses—a name and an absolute address such as MW100—but it does not support array declarations. Array types in classic STEP 7 are declared inside a Data Block, never in the symbol table. Engineers who need 16-bit indexed scratch buffers must therefore either:

  • Reserve a Data Block with ARRAY[0..n] OF WORD and access it via DB1.DBW[i], or
  • Use the M area as a flat array with MW[idx] syntax, accepting that no symbol-table metadata records its size or type.

3. TIA Portal STEP 7 for S7-300/S7-400

TIA Portal (current major release line STEP 17) ships an embedded STEP 7 module that can program S7-300, S7-400, S7-1200, and S7-1500 from a single IDE. Two license tiers govern which targets are programmable:

TIA Portal license Programmable CPU families Notes
STEP 7 Basic S7-1200 Entry-level, single-tag PLC only
STEP 7 Professional S7-300, S7-400, S7-1200, S7-1500, ET 200 CPU Required for any S7-x00 or S7-1500 project

The SCL syntax for indexed M-area access on the S7-300/S7-400 remains the same as classic V5.x:

// SCL, TIA Portal targeting S7-300/400
wScratch := MW[iIndex];

The TIA Portal editor, however, applies a stricter parser. Two engineering realities result from this:

  1. Some legacy V5.x idioms that compiled silently (for example, an STL loop that updates the AR1 register by +AR1 P#2.0 and then loads M [AR1, P#0.0]) compile with warnings about pointer use and may be flagged by the SCL compiler as ambiguous.
  2. The IDE encourages migration to Data-Block-resident arrays. When a project migrates from V5.x to TIA, the migration tool will, in many cases, convert a contiguous MW[0..100] scratch range into a global DB containing an ARRAY[0..100] OF WORD.
Backward compatibility caveat: each TIA Portal version drops support for older S7-300/S7-400 CPUs. Before committing a migration, verify that every CPU in the bill of materials is still listed as programmable in the TIA Portal release notes (Help > Installed Software > Readme). A CPU that compiled under V5.5 may be invisible under STEP 17.

4. S7-1200 and S7-1500: PEEK and POKE Replace Indexed M-Area Syntax

The S7-1200 and S7-1500 firmware does not implement indexed absolute operand syntax in the same way as the S7-300/S7-400. The line was deliberately simplified. The official Siemens guidance is captured in the entry In STEP 7 (TIA Portal) and in S7-300/S7-400, how can you implement indirect addressing in an SCL program? on the Siemens Industry Online Support portal:

"In SCL for S7-1200 and S7-1500, indirect access to memory areas is implemented with the PEEK/POKE instructions. Use non-optimized data blocks for absolute addressing."

The runtime implications are significant:

  • PEEK/POKE operate on byte addresses. A PEEK_WORD reads two bytes; POKE_DWORD writes four. The index passed is the byte offset, not the element index, so the engineer must multiply by 2 for WORD and 4 for DWORD.
  • Optimized data blocks must be disabled for the source area. PEEK/POKE use the absolute byte offset in load memory; an optimized block reorganizes tags for speed and the byte offsets become unpredictable. TIA Portal will either reject the access or produce a compile error such as W:Address area of the PEEK/POKE instruction is not a non-optimized block.
  • STL is no longer offered on the S7-1200. The S7-1500 emulates STL register operations for compatibility, with a measurable performance penalty relative to native SCL or LAD/FBD. Indirect addressing via AR1/AR2 in STL should be avoided on the S7-1500 unless absolutely necessary.

4.1 SCL Pattern: PEEK and POKE on the M Area

// SCL, TIA Portal, S7-1500 CPU 1515-2 PN
// Read MW at byte offset iByteOffset (must be even)
wValue := PEEK_WORD(area := 16#83,      // area code 0x83 = M (flags)
                   dbNumber := 0,      // not used for M area
                   byteOffset := iByteOffset);

// Write MD at byte offset (must be multiple of 4)
POKE_DWORD(area := 16#83,
           dbNumber := 0,
           byteOffset := iByteOffset,
           value := dwValue);

The area codes used by PEEK/POKE are documented in the TIA Portal help under PEEK/POKE — area parameter:

Area Hex area code Byte offset range (S7-1500 default)
Inputs (I / PE) 0x81 0 .. (process-image size − 1)
Outputs (Q / PA) 0x82 0 .. (process-image size − 1)
Flags (M) 0x83 0 .. (M area size − 1)
Global DB (non-optimized) 0x84 0 .. (DB size − 1)
Misalignment fault: passing an odd byte offset to PEEK_WORD is rejected at runtime. The CPU enters STOP with diagnostic buffer entry Area length error when reading (event ID 0x2522) and BD / BIE flags cleared. Always guard the index with an even-value assertion or use PEEK_BOOL / PEEK_BYTE to read a single byte and assemble words manually when the index is dynamic.

4.2 STL Pattern on S7-1500 (Compatibility Only)

// STL on S7-1500 — emulated register operations
// Not recommended; left here for migration reference only
LAR1  P##pSource              // load ANY-pointer from in/out
L     W [AR1, P#0.0]          // indirect read
T     MW   100

The S7-1500 firmware maintains AR1/AR2 as 32-bit registers internally; the emulation step adds roughly 0.5–1.5 µs per access compared with the native operand L MW 100. Multiply that by a 1000-iteration loop and the cycle-time penalty becomes visible. Rewrite the routine in SCL with PEEK/POKE or, preferably, with an in-DB array.

5. Data Type Rules in the Symbol Table vs. the DB

The source observation that MW[adr] "will always be in the Word format" is technically correct for the address itself but can mislead readers about what the symbol table accepts. The rules are summarized below.

Location of declaration Supports array? Supports type override? Notes
Symbol table (classic) or PLC tags (TIA) No Yes (you bind a name to MB 100, MW 100, MD 100, or M 100.0) Type is intrinsic to the absolute address; symbol just adds a name.
Global / instance Data Block Yes — ARRAY[lo..hi] OF <type> Yes — element type is free (BOOL, INT, REAL, STRUCT, UDT) Type is declared at design time. Indexed access DB[i] uses element type.
Temporary (TEMP) variables in OB/FB/FC Yes in SCL; LAD/FBD exposes only scalar Yes Scope ends at block exit; not retentive.

For an ARRAY[0..255] OF WORD declared in DB1, the symbol table in TIA Portal can still bind a tag name to it (for example ScratchBuffer) and the address column shows DB1. The data type is set on the DB declaration, not in the symbol table.

6. Recommended Migration Path from Classic M[adr] to Modern Code

Step-by-step procedure for converting a legacy MW[idx] routine running on an S7-300 into an S7-1500 project without losing functionality.

  1. Inventory the M range. In the V5.x project, open Reference Data > Display and export all writes to the M area. Record the byte-offset ranges touched by the indexed loop and the data types (byte / word / dword) actually consumed.
  2. Reserve a non-optimized global DB. In TIA Portal, add a new global DB (for example DB_Scratch) and uncheck Optimized block access in the DB properties. Declare ARRAY[0..n] OF WORD and/or ARRAY[0..m] OF DWORD arrays sized to the inventoried range.
  3. Replace indexed M access with PEEK/POKE if the original code must remain a thin shim. Use the area code 16#83 and pass 2 × idx for WORD offsets. Wrap each PEEK/POKE in a small FC so future cleanup can swap to direct array indexing.
  4. Replace PEEK/POKE with direct array indexing in the long term. Once the legacy shim runs, rewrite the consumer blocks to read DB_Scratch.Scratch[idx]. This is type-checked, watchable in the online monitor, and benefits from the optimized-block acceleration on the S7-1500.
  5. Verify byte ordering. S7 is little-endian; MD 100 occupies bytes 100–103 with the least-significant byte at 100. PEEK_DWORD uses the same order. Confirm on the receiving side that any high-endian device (e.g. some Modbus gateways) does not expect byte-reversed data.
  6. Re-test on the target CPU with the diagnostic buffer open. Watch for event IDs 0x2522 (area length error), 0x2530 (DB not loaded), and 0x2942 (I/O access error) during the run-up sequence.

7. Optimized vs Non-Optimized Blocks: When the Choice Matters

On the S7-1500 (and to a lesser degree the S7-1200), each global DB has a toggle Optimized block access. The runtime effects are documented in the TIA Portal help and reproduced in condensed form here.

Property Optimized block Non-optimized block
Address layout Compiler-chosen; byte offsets not stable Fixed byte offsets, S7-300/400-compatible
Access speed Faster (symbolic, no offset calc) Slower (absolute pointer)
Symbolic only access enforced Yes No — both symbolic and absolute allowed
PEEK/POKE allowed? No Yes
PLC-to-PLC data exchange via PUT/GET with absolute addresses Restricted Full
Retain granularity Per individual tag Whole block

Engineering rule of thumb: keep new S7-1500 projects optimized unless a specific integration (legacy HMI tag poll, indirect PEEK/POKE, or third-party OPC UA mapper) demands absolute addressing. The S7-1500's symbolic access path is consistently 5–15 % faster than the absolute path on benchmark loops.

8. STL Register Operations and AR1/AR2 Indirect Addressing

The classical pointer-in-register style is still compiled by STEP 7 but is firmly in the legacy bucket for S7-1500 work. The relevant instructions are:

Instruction Effect
LAR1 P##src Load address register AR1 with pointer from in/out src (type ANY or POINTER)
TAR1 Transfer AR1 to accumulator
+AR1 P#2.0 Add 16-bit immediate offset to AR1
L W [AR1, P#0.0] Load WORD addressed by AR1 + 0.0
L D [AR1, P#4.0] Load DWORD at AR1 + 4.0

For S7-300/S7-400 STL this remains the fastest indirect-access path. For S7-1500, the same source compiles and runs, but the compiler emits a register-emulation call into firmware, slowing every iteration. Siemens' own guidance in the TIA Portal documentation is to refactor such loops to SCL with array indexing whenever possible.

9. Field-Commissioning Verification Procedure

After deploying a program that uses indexed M-area or PEEK/POKE access, run through the following verification before sign-off.

  1. Static watch table. Open Watch & Force Tables in TIA Portal (or Monitor/Modify in classic). Add a row for the highest-indexed word in your range (for example MW[254]). Force a value from the laptop and confirm it appears on the online value column. Repeat for index = 0 and a mid-range index.
  2. Diagnostic buffer sanity check. With the CPU in RUN, open Online & Diagnostics > Diagnostic Buffer. Verify that no event IDs from the indirect-access failure family (0x2522, 0x2530, 0x2942) have been logged since the program reached the steady state.
  3. Cycle time check. Record the OB1 cycle time from the online diagnostics before and after switching from MW[idx] to PEEK/POKE. Document the delta; an S7-1500 typically shows a 0.3–1.0 ms increase for a 100-iteration loop.
  4. Retain behavior. Power-cycle the CPU and verify that all retain-flagged M bytes reload their pre-power-down values. Indexed access into the retain region follows the same retention rules as direct access; this test catches accidental use of the non-retain sub-range.
  5. HMI tag refresh. If the HMI is bound to MW[idx] by a polled tag, confirm the polling rate does not bottleneck the operator screen. S7-1500 symbolic tag access is faster than absolute polling and is the preferred path for HMI updates.

10. Troubleshooting Matrix

Symptom Likely cause Remedy
Compile error Indexed access only on optimized tags not allowed PEEK/POKE attempted on optimized DB Disable optimized block access on the source DB or switch to symbolic array indexing
CPU STOP with 0x2522 Area length error when reading Byte offset out of range or misaligned Bounds-check the index; enforce even offset for WORD, multiple-of-four for DWORD
Online monitor shows zero for every MW[idx] Index variable uninitialized or wrong scope Initialize iIndex in the block header; verify the index is in TEMP, STAT, or global DB rather than a missing instance
S7-1500 code ported from S7-300 runs but reads garbage Big-endian/little-endian mismatch after PEEK_DWORD Insert byte-swap or use WORD_TO_BLOCK_DB pattern explicitly
Performance regression on S7-1500 versus S7-300 STL register operations still in use Rewrite as SCL with array indexing inside an optimized DB
PEEK compiles but returns zeros at runtime Area code wrong (e.g. 0x82 used where 0x83 needed) Verify area codes from the TIA Portal help table above

11. Frequently Asked Questions

Can I declare an ARRAY in the S7 symbol table?

No. In both classic STEP 7 V5.x and TIA Portal, the symbol table (PLC tags) binds a name to a single absolute address such as MW100. ARRAY declarations live inside Data Blocks (DB) and are referenced from the symbol table by DB name only. Use a global DB with ARRAY[0..n] OF WORD when you need a typed indexed buffer.

Is MW[adr] always a 16-bit Word?

Yes. The operand identifier fixes the type: MW[] is always WORD (16 bits unsigned), MD[] is always DWORD (32 bits), and MB[] is always BYTE. To get a signed 16-bit value, assign the result to an INT variable in SCL; the bit pattern is identical but the symbolic type tracks sign extension.

Why does my S7-1500 reject PEEK/POKE on a global DB?

PEEK/POKE use absolute byte offsets, which only work on non-optimized data blocks. TIA Portal enforces this at compile time. Open the DB properties and uncheck Optimized block access, or replace the PEEK/POKE pattern with symbolic array indexing in SCL.

Does the S7-1200 support STL at all?

No. The S7-1200 product line does not offer the STL editor in TIA Portal; SCL and the graphical editors (LAD/FBD) are the only text and graphical options. Any S7-300 STL routine that relied on indirect addressing must be rewritten in SCL before it can run on an S7-1200.

What is the latest classic STEP 7 version that supports Windows 10/11?

STEP 7 V5.7 is the terminal release of the classic line and is the version officially tested on Windows 10 and Windows 11. Any version earlier than V5.5 is not validated for current Windows releases and may install but lack driver signing for newer USB/PCI adapters.

How do I pick between PEEK_WORD and a DB array index for an S7-1500?

Prefer the DB array index whenever the block is owned by your project and can be declared optimized: it is type-safe, watchable in the online monitor, and 5–15 % faster. Reserve PEEK_WORD for legacy DBs that must remain non-optimized for an external OPC UA/HMI gateway or for absolute-address legacy protocols.

Back to blog