1. Problem Overview and Engineering Context
In SIMATIC Manager (STEP 7 Classic, V5.5/V5.6) on a SIMATIC S7-300 or S7-400 controller, programmers often need to drive a Merker (flag) bit — for example M200.0 — whose address is stored numerically inside a data block. The typical use case is an HMI recipe that writes a single integer into DB1.DBW70 and the PLC logic is expected to translate that integer into the corresponding bit or byte address in the M-memory area and execute a Set/Reset on that location.
The naive expectation that writing 1600 as a decimal value into a DB INT will automatically point to M200.0 is incorrect, because the CPU never interprets a plain INT as a memory address. The address must be packed into a proper S7 pointer (POINTER, ANY, or area-crossing DWORD) before it can be used with an indirect statement such as S M [AR1, P#0.0] or U M [AR1, P#0.0].
This article demonstrates the correct procedure using Programming with STEP 7 as the primary reference, the system manual S7-300 CPU 31xC and CPU 31x: Technical Specifications, and the standard reference System Software for S7-300/400 – System and Standard Functions for the pointer format definitions.
2. Prerequisites
- SIMATIC STEP 7 V5.5 or V5.6 (also valid in older V5.4 projects) installed on Windows 7/10 with administrator rights.
- A configured S7-300 station (CPU 31x) or S7-400 station (CPU 41x) with at least 512 bytes of work memory free; the OB1 cycle time must remain below 100 ms with the new logic included.
- Knowledge of STL or LAD/FBD editing; the indirect statements used in this article are only available in STL. Convert blocks via View → STL if needed.
- An HMI or programmer connection to write test values into
DB1.DBW70using Monitor/Modify (Ctrl+F7) or Variable Table (VAT). - Library blocks
FC36 "BIT_BF"/FC37 "BIT_BL"andSFC20 "BLKMOV"are recommended if the project must remain portable to S7-1200/S7-1500 later (see Bool, Byte, Word, and DWord data types for cross-platform reference).
3. S7 Memory Areas and Their Identifiers
Before constructing a pointer, identify which memory area must be addressed. The S7-300/400 reserves distinct address areas, each with an 8-bit identifier used in the cross-area pointer (Section 4). The numeric ID is part of the 32-bit pointer value and is required whenever the destination area differs from the source area.
| Area ID (hex) | Area Symbol | English / German name | Typical access |
|---|---|---|---|
| 0x81 | I / E | Process image of the inputs | Bit, byte, word, dword |
| 0x82 | Q / A | Process image of the outputs | Bit, byte, word, dword |
| 0x83 | M / M | Merker (flags) | Bit, byte, word, dword |
| 0x84 | DB | Global data blocks | Bit, byte, word, dword |
| 0x85 | DI | Instance data blocks | Bit, byte, word, dword |
| 0x86 | L / L | Local (temporary) data | Bit, byte, word, dword |
| 0x80 | PIPE / PIB/PQB/... | Periphery (direct I/O) | Byte, word, dword only |
1600 stored in the source DB cannot be used as a pointer directly. 1600 in decimal equals 0x0640, which contains the wrong area ID and bit-field structure. Section 4 explains the correct bit layout.4. Pointer Formats in STEP 7
STEP 7 uses three pointer widths: the 32-bit area-internal pointer (P#Byte.Bit), the 48-bit POINTER data type (DB number + 32-bit pointer), and the 80-bit ANY data type (repetition factor, data type, length, DB number, area-internal pointer). For the M-bit application, the 32-bit area-crossing DWORD is the smallest structure that still carries the area identifier.
| Byte offset | Bits 31..24 | Bits 23..16 | Bits 15..8 | Bits 7..3 | Bits 2..0 |
|---|---|---|---|---|---|
| Function | Area ID (0x83 for M) | Byte number high | Byte number low | Reserved (0) | Bit number 0..7 |
| M200.0 | 0x83 | 0x00 | 0xC8 | 0x00 | 0x00 |
| M200.7 | 0x83 | 0x00 | 0xC8 | 0x00 | 0x07 |
| M5.3 | 0x83 | 0x00 | 0x05 | 0x00 | 0x03 |
The 32-bit value is therefore 0x8300C800 for P#M200.0. The bit number is encoded in the lower three bits; the byte number is encoded in the next 13 bits. This layout is documented in the System and Standard Functions reference manual linked above and in the Beijer Electronics S7 ISO-over-TCP/IP driver manual which re-prints the same table for driver integrators.
1600. To translate 1600 (which is a flat bit index in the M area) into the correct pointer: byte = 1600 / 8 = 200, bit = 1600 mod 8 = 0, resulting in P#M200.0 = 16#8300_C800.5. Solution Architecture
Two practical solutions exist:
-
Solution A (recommended): Store the complete 32-bit cross-area pointer in a DB DWORD, then use it directly with
LAR1and a memory-indirect statement. - Solution B (legacy): Store a 16-bit "flat bit index" (e.g., 1600) in a DB INT, and let an FC compute the byte/bit split and the area ID at runtime.
Solution A is faster (no arithmetic in OB1) and easier to commission because the pointer can be observed directly with Monitor. Solution B is friendlier for HMI operators who only know decimal bit positions. Both are implemented below in STL and exported to LAD for documentation.
6. Solution A — Store the Full 32-bit Pointer in the DB
6.1 DB definition
DATA_BLOCK DB1
TITLE = Pointer Table for M-Bits
VERSION : 0.1
STRUCT
i_MBit_Pointer : DWORD; // 16#8300_C800 = P#M200.0
i_MBit_Command : BOOL; // 1 = Set, 0 = Reset
b_Result_OK : BOOL; // 1 = Access successful
w_Diagnostic : WORD; // 16#0001 = OK, 16#8001 = Area out of range
END_STRUCT;
END_DATA_BLOCK
6.2 STL code in OB1 (Solution A)
// OB1 — Network 1: Set/Reset M-bit using pointer stored in DB1
L DB1.DBD 0 // Load 32-bit pointer P#Mx.y (area ID 0x83)
LAR1 // Transfer to Address Register 1
L DB1.DBX 4.0 // Load command bit (1 = set)
U M [AR1, P#0.0] // Read current state (RLO unchanged)
SPB _SET // If 0 and command=1, jump to SET
U DB1.DBX 4.0 // Reload command bit
SPB _RES // If 1 and command=0, jump to RESET
BEA // Otherwise leave
_SET: S M [AR1, P#0.0] // Set M-bit pointed to by AR1
SET
S DB1.DBX 5.0 // Set result bit
BEA
_RES: R M [AR1, P#0.0] // Reset M-bit pointed to by AR1
SET
S DB1.DBX 5.0
BEA
M [AR1, P#0.0] is the memory-indirect, area-internal access. Because the area ID is already in the lower 32 bits of AR1 (bits 24-31 = 0x83), STEP 7 automatically selects the M area. The P#0.0 offset is added to AR1 for byte/bit displacement; it must remain zero for this application to keep the pointer exact.6.3 Converting the HMI integer to the pointer value
Configure the HMI tag for DB1.DBD0 as a 32-bit unsigned value. The operator enters the value 16#8300_C800 in the HMI input field; STEP 7 monitors DB1.DBD0 in hex. For a quick lookup table that the HMI can use as a selection list, see Section 8.
7. Solution B — FC Computes the Pointer from a Bit Index
7.1 FC interface
FUNCTION FC100 : VOID
TITLE = Compute M-bit pointer from flat bit index
VERSION : 1.0
VAR_INPUT
i_BitIndex : INT; // 0..8191 for CPU 31x (1 KiB M area)
END_VAR
VAR_OUTPUT
o_Ready : BOOL;
o_Error : BOOL;
END_VAR
VAR_TEMP
dw_Pointer : DWORD; // Holds P#Mx.y with area ID 0x83
w_Byte : WORD;
b_Bit : BYTE;
END_VAR
BEGIN
NETWORK 1 // Range check
L #i_BitIndex
L 0
<I // Negative?
SPB _ERR
L #i_BitIndex
L 8191
>I // Larger than last M bit?
SPB _ERR
NETWORK 2 // Byte = index / 8, Bit = index mod 8
L #i_BitIndex
SRD 3 // Arithmetic shift right 3 bits = divide by 8
T #w_Byte
L #i_BitIndex
SRD 3
SLW 3 // Multiply byte by 8
TAK // Swap ACCU1 and ACCU2
-I // Remainder = index - 8*byte
T #b_Bit
NETWORK 3 // Build DWORD: 0x83 0x00 byte bit
L #w_Byte
SLD 8 // Shift byte address into bits 8..15
OW // (ACCU1 already zero in high half)
L #b_Bit
OW // OR bit number into bits 0..2
OD // OR with area ID placeholder
L 16#83000000 // Area ID for Merker
OD
T #dw_Pointer
T DB1.DBD 0 // Publish to DB
SET
= #o_Ready
R #o_Error
BEU
_ERR: CLR
= #o_Ready
S #o_Error
END_FUNCTION
7.2 OB1 call
// OB1 — call FC100 once per cycle
CALL FC100
i_BitIndex := DB1.DBW 70 // From HMI, e.g., 1600
o_Ready := DB1.DBX 5.0
o_Error := DB1.DBX 5.1
// Then continue with Network 1 from Solution A (use DB1.DBD0 as pointer)
8. Reference Table: Bit Index to M-Bit
Engineers commissioning the HMI often need a quick lookup. The 1 KiB M-area of an S7-300 CPU 31x covers bit indices 0..8191. The values below are most frequently used.
| HMI integer (DBW70) | Byte / Bit | Real M-bit | Pointer DWORD (hex) |
|---|---|---|---|
| 0 | 0.0 | M0.0 | 16#8300_0000 |
| 1 | 0.1 | M0.1 | 16#8300_0001 |
| 8 | 1.0 | M1.0 | 16#8300_0100 |
| 64 | 8.0 | M8.0 | 16#8300_0800 |
| 1600 | 200.0 | M200.0 | 16#8300_C800 |
| 1601 | 200.1 | M200.1 | 16#8300_C801 |
| 1607 | 200.7 | M200.7 | 16#8300_C807 |
| 8000 | 1000.0 | M1000.0 | 16#8303_E800 |
| 8191 | 1023.7 | M1023.7 | 16#8303_FFFF |
9. Ladder Logic (LAD) Equivalent
STEP 7 does not allow direct memory-indirect bit access in the LAD editor. To stay in LAD while keeping the indirection, wrap the STL inside a custom FC and call the FC from a LAD network. The body of the FC must be written in STL (View → STL). The call site in LAD looks like this:
| CALL FC100 |
| i_BitIndex := DB1.DBW70 |
| o_Ready := DB1.DBX5.0 |
| o_Error := DB1.DBX5.1 |
| |
| U DB1.DBX5.0 |
| SPB NEXT |
| S M [AR1, P#0.0] | // inside FC101
|NEXT:BE
10. Verification and Commissioning Procedure
- Download the project (Ctrl+D) to the CPU and switch to RUN. The CPU diagnostic buffer must show No errors; if SF lights up, the pointer value is outside the M area (most often 0x84xx was entered instead of 0x83xx).
- Open a VAT (PLC → Monitor/Modify Variable) and add
DB1.DBD0,DB1.DBW70,M200.0, andDB1.DBX5.0. - Force
DB1.DBD0 = 16#8300_C800andDB1.DBX4.0 = 1. Verify thatM200.0transitions to TRUE within one OB1 cycle (max 100 ms). - Switch
DB1.DBX4.0to 0.M200.0must return to FALSE. - Test the boundary case: set
DB1.DBD0 = 16#8303_FFFFand confirmM1023.7toggles. If the CPU raises a range-check OB121, the area ID or byte offset is invalid — check that bits 19..23 are zero. - Restore
DB1.DBD0to the HMI-driven value before going online with the operator panel.
11. Diagnostic and Troubleshooting Matrix
| Symptom | Likely cause | Verification | Corrective action |
|---|---|---|---|
| CPU goes to STOP, SF lit, diagnostic buffer: Area length error writing M | Byte number in pointer exceeds the configured M-area size | Check HW Config → CPU → Properties → Merker. Default is 1 KiB (0..1023) | Reduce bit index, or expand the M area in HW Config and download a new hardware configuration |
| Bit toggles one cycle later than expected |
LAR1 called after the indirect statement |
Cross-reference in the STL editor | Move LAR1 immediately before the S/R instruction |
| Bit never changes despite correct pointer | AR1 is overwritten by a called FB | Use Cross Reference → Address Register | Use TAR1 / LAR1 save/restore around the FB call, or use AR2 |
| Operator writes 1600 but nothing happens | HMI tag is bound to DBW70 (Solution B), not DBD0 (Solution A) |
Inspect tag list in WinCC flexible / TIA Portal | Bind the HMI tag to the correct address, or add the FC100 call |
| CPU raises OB121 (programming error) | Pointer area ID is 0 (uninitialized DB DWORD) | Monitor DB1.DBD0; initial value in DB must be 16#83000000 for M0.0 |
Set an initial value in the DB declaration or initialize with L 16#83000000; T DB1.DBD0 in OB100 |
| Bit toggles in the wrong area (I or Q) | Area ID byte (bits 24-31) is 0x81 or 0x82 instead of 0x83 | Display DB1.DBD0 in hex; verify high byte |
Re-load pointer with area ID 0x83 (M) or use the lookup table in Section 8 |
12. Portability Notes: S7-300/400 vs. S7-1200/1500
The same 32-bit pointer layout is used in TIA Portal. However, the S7-1200/1500 prefers the POINTER data type with explicit DB number handling and the new VARIANT data type (see TIA Portal data types). The legacy AR1/AR2 registers are retained for compatibility; new code should consider PEEK/POKE instructions from the Extended Instructions palette as a more readable alternative for byte/word-level indirection. For bit-level indirection across areas, the classic L P#Mx.y; LAR1; S M[AR1, P#0.0] pattern remains the shortest expression and is fully supported through V17 of TIA Portal.
13. Performance and Cycle Time Impact
The pointer load and indirect access require roughly 12 μs on a CPU 315-2 PN/DP and 4 μs on a CPU 416-3 PN/DP. Adding the FC100 math (Solution B) increases the time by an additional 18 μs on a CPU 315 and 6 μs on a CPU 416. Neither impact is significant for OB1 cycle times of 20..100 ms, but in a high-priority OB35 (< 2 ms) the indirect statement should be removed from the cyclic class and executed from OB1 only.
14. Summary of Field-Proven Practices
- Always declare the pointer DWORD with the correct initial value; an uninitialized DWORD means area ID = 0, which causes OB121.
- Range-check the byte component in the FC; never trust an HMI integer blindly.
- Document the pointer layout in the DB header comment so that commissioning engineers do not have to decode 0x8300C800 manually.
- For S7-300, protect bits M0.0..M0.7 (startup / clock-mem overlap risk) and any retained bits the CPU uses internally.
- When migrating to S7-1200/1500, replace the cross-area DWORD with the formal
POINTERorVARIANTtype and rewrite the indirect statement asPEEK_BOOL(area := M, byteOffset := ..., bitOffset := ...).
What is the difference between area-internal pointer P#200.0 and cross-area pointer 16#8300C800?
Area-internal pointer P#200.0 is a 32-bit value without an area identifier and only works when the memory-indirect statement already specifies the area (for example S M [AR1, P#0.0]). The cross-area DWORD 16#8300C800 embeds the area ID 0x83 (M) in the upper byte, so it can be loaded into AR1 and used in any statement regardless of the operand prefix.
Why does writing the integer 1600 to a DB word not point to M200.0?
STEP 7 never interprets a plain INT as an address. The integer 1600 is just a numeric value 0x0640 with no area ID and no bit-field encoding. The DB DWORD must be loaded with the encoded 32-bit pointer 16#8300C800, or an FC must compute the byte/bit split from 1600 before the address is loaded into AR1.
Can I use memory-indirect bit access in LAD and FBD?
No. The syntax M [AR1, P#0.0] is only available in STL and SCL. In LAD/FBD, create a custom FC written in STL and call it from a LAD network. The LAD call site shows the input and output wiring, while the body of the FC performs the indirection.
Which AR register should I use for the pointer in a multi-instance FB?
Use AR1 for the local pointer inside a non-multi-instance FC. Inside a multi-instance FB, save and restore AR1 around the call with TAR1 / LAR1 because the calling FB chain may already use AR1. Alternatively, use AR2 which is reserved for FB-instance handling but can be repurposed inside a dedicated FC if no FB is in the call stack.
What happens if the M area is too small for the pointer byte offset?
The CPU raises an area-length error and calls OB121 (programming error). If OB121 is not loaded, the CPU goes to STOP. In the VAT, the diagnostic buffer shows entry Area length error when writing. The fix is either to reduce the bit index in the HMI or to expand the Merker area in HW Config to a larger value such as 4 KiB and download the new hardware configuration.