Overview
This reference demonstrates how to read a value from a Siemens SIMATIC S7-300 (or S7-400) data block when the address of the target word is not known at compile time but is instead calculated at runtime from a process counter. The application domain is pallet sortation on a conveyor where every pallet carries an order code stored in DB1, and an inductive sensor at each editing station must look up the code for the n-th pallet that has passed a reference point.
The technique used is indirect DB addressing via the address registers AR1 and AR2, combined with the byte pointer format P#Byte.Bit. Two equivalent implementations are shown:
- Pointer arithmetic with
LAR1,SLD 3and theB[AR1,P#0.0]operand. - A jump distributor that branches on the pallet index and loads the matching
DBWdirectly.
Both patterns are portable between STL source, LAD/FBD, and SCL with minor adjustments, and they all work on CPU firmware V2.x through V4.x of the S7-300 family and on the S7-400/C7 product lines.
Prerequisites
- STEP 7 V5.5 SPx (or compatible TIA Portal V15+ with S7-300 target) installed and licensed.
- SIMATIC S7-300 CPU 31x or S7-400 CPU 41x (any variant that supports the STL instruction set, including CPU 312C, 313C, 314, 315-2 DP, 317-2, 416, 417).
- Hardware configured in HW Config: at least one digital input module (e.g. 6ES7321-1BH02-0AA0 SM 321 DI16) wired to the pallet-presence sensor on
I0.0. - Data block
DB1declared with anARRAY[0..199] OF INT(or a flat list ofWORD/INTwords) that holds the order code for every pallet index. - Familiarity with the S7 accumulator concept (ACCU1/ACCU2), the status word, and the difference between area-internal and area-crossing pointers.
Pointer Formats in S7 STL
STEP 7 distinguishes two pointer widths; the S7-300/400 always uses a 32-bit (double-word) pointer inside the address registers.
| Format | Bit Layout | Typical Use |
|---|---|---|
P#Byte.Bit area-internal (intra-DB / intra-M) |
0000 0BBB BBBB BBBB Bxxx xxxx xxxx xxxx | Byte-offset pointer, used with B[AR1,P#0.0] after OPN DBx
|
| Area-crossing 32-bit pointer | 1000 0BBB BBBB BBBB Bbbb bbbb bbbb bxxx | Cross-area pointer (rare in S7-300 STL, common in S7-1500 SCL) |
The shift SLD 3 converts a 16-bit word index (e.g. MW10 containing 3) into a 32-bit byte pointer. Three bits are shifted into the bit-position field of the pointer and the lower word becomes the byte address:
L 3 // pallet index in ACCU1-L
SLD 3 // 3 * 8 = 24, ACCU1-L = 0x0000_0018
LAR1 // AR1 = P#24.0, area-internal
After OPN DB1, the operand B[AR1,P#0.0] reads the byte at offset 24 inside the opened DB. To read a 16-bit INT, use W[AR1,P#0.0] instead.
Data Block Layout for the Sortation Application
Create DB1 (absolute or symbolic; the example uses absolute for clarity) with the following structure:
DATA_BLOCK DB 1
TITLE = PalletCodeTable
STRUCT
Code : ARRAY[0..199] OF INT; // Code[i] = order code for pallet i
END_STRUCT
BEGIN
END_DATA_BLOCK
The array is pre-loaded by the upstream edit station. The first word DB1.DBW0 is the order for pallet 0 (used as a sentinel or for pallet 1 depending on numbering convention), DB1.DBW2 for pallet 1, and so on. Element i occupies bytes 2*i and 2*i+1 because INT is two bytes wide.
Step-by-Step: Indirect DB Read in STL
Step 1 - Open the data block
The DB must be opened before any DBx operand is referenced. OPN DB1 stores DB1 in the DB register; subsequent DBW, DBB, DBD or indirect references resolve against that register.
OPN DB1 // open DB1 in DB register
Step 2 - Load a base byte pointer
Always start from a known base. L P#0.0 loads a clean area-internal pointer. LAR1 copies ACCU1 into AR1 without affecting the DB register.
L P#0.0 // base pointer, byte 0, bit 0
LAR1 // AR1 := P#0.0 (area-internal)
Step 3 - Compute the dynamic offset
The current pallet index lives in MW10 (an INT). Two shifts are required because each array element is 2 bytes, not 1:
L MW10 // ACCU1 = pallet index
L 2 // ACCU2 = 2
*I // ACCU1 = index * 2 (byte offset)
SLD 3 // convert WORD to byte pointer
+AR1 // AR1 := AR1 + offset
If the array were BYTE (1 byte per element) the multiplier would be 1 and only SLD 3 would remain. For DWORD/REAL use a multiplier of 4.
Step 4 - Read the value and use it
Read with W[AR1,P#0.0] for a 16-bit INT:
L W[AR1, P#0.0] // ACCU1 := Code[MW10]
T MW12 // copy for downstream compare
Step 5 - Compare the result
The compare is a standard ==I integer comparison. The result bit is stored in M0.0 for use in the next network:
L MW12
L 30 // constant order code, e.g. B#16#1E == 30
==I
= M0.0 // M0.0 = 1 if Code[i] = 30
Complete STL Network for a Single Index
// Network 1 - Count pallets on positive edge of I0.0
A I0.0
FP M100.0 // edge flag
JCN NO_CNT
L MW10
+ 1
T MW10
NO_CNT:NOP 0
// Network 2 - Indirect read of DB1.DBW[MW10*2] into MW12
OPN DB1
L P#0.0
LAR1
L MW10
L 2
*I
SLD 3
+AR1
L W[AR1, P#0.0]
T MW12
// Network 3 - Compare with constant
L MW12
L 30 // change to whatever code you need
==I
= M0.0
Edge Detection Instead of a Counter (ADD + 1 Pattern)
The original application uses an explicit ADD + 1 rather than the CU counter instruction. To prevent double-increment when the pallet dwells on the sensor, add an edge-evaluation bit:
// Network 1 - rising edge detector for I0.0
A I0.0
FP M100.0 // M100.0 holds previous I0.0 state
JCN END1
L MW10 // load current count
+ 1 // ADD +1
T MW10 // write back
END1: NOP 0
The flag M100.0 must be retentive-free (use M, not MB with retain attribute) and must be in the same byte as the edge instruction operand; STEP 7 enforces this rule at compile time.
Alternative: Jump Distributor (Explicit Branch on Index)
For very small index ranges (1..5 in the example) a jump distributor avoids pointer arithmetic entirely and is easier to debug online:
L #COUNTER // or MW10
L 1
==I
JCN TWO
L DB1.DBW0
T MW12
BEU // branch end (unconditional)
TWO: NOP 0
TAK // bring index back into ACCU1
L 2
==I
JCN THR
L DB1.DBW2
T MW12
BEU
THR: NOP 0
TAK
L 3
==I
JCN FOUR
L DB1.DBW4
T MW12
BEU
FOUR: NOP 0
... // continue for N cases
Each label adds about 60 bytes of MC7 code. If N exceeds 20 or the index is unbounded, switch back to the indirect method; otherwise the distributor is more readable for service technicians.
LAD/FBD Equivalent
The same logic can be implemented in LAD using a single move box wired to a computed address. Open the move box, click the IN operand and enter:
DB1.DBW[MW10 * 2] // symbolic entry; STEP 7 evaluates MW10 at runtime
The * 2 factor compensates for the 2-byte size of INT. The result is moved to MW12, and a normal == comparator on MW12 and a constant produces the result bit. This form is preferred when the team is unfamiliar with STL.
Memory Map and Word Allocation
| Address | Symbol | Type | Description |
|---|---|---|---|
MW10 |
i_PalletIndex |
INT | Running count of pallets that have passed I0.0
|
MW12 |
i_LookupValue |
INT | Code read from DB1 for the current index |
M0.0 |
b_CodeMatch |
BOOL | 1 when MW12 equals the comparison constant |
M100.0 |
b_PosEdgeI0 |
BOOL | Edge flag for I0.0 (FP instruction operand) |
DB1.DBW0..DBW398 |
Code[0..199] |
INT[200] | Order code table, 200 elements max |
Resetting the Counter
At the end of a batch (or on a warm restart OB100) the index must be cleared. Add a dedicated reset network and trigger it from the HMI or from a sensor at the discharge end of the line:
A I0.1 // reset pushbutton or end-of-line sensor
JCN END_RST
L 0
T MW10 // pallet index = 0
T MW12 // lookup value = 0
END_RST: NOP 0
For an OB100 (warm restart) reset, place the same code in OB100 so the line starts at index 0 every power-up.
Verification
- Open the block online in STEP 7 (right-click FB/FC, Monitor/Modify).
- Force
I0.0 = 0and observeMW10 = 0,MW12 = DB1.DBW0. - Toggle
I0.0from 0 to 1 once; confirmMW10increments to 1 andMW12updates toDB1.DBW2. - Toggle
I0.0again;MW10 = 2andMW12 = DB1.DBW4. - Pre-load the table in VAT
DB1with the values5, 7, 12, 30, 100at indices 0..4. - Confirm that after the fifth toggle the comparator
M0.0becomes 1 becauseCode[4] = 30. - Use Reference Data > Cross-Reference to confirm no FB/FC outside this block writes to
MW10,MW12, orM0.0.
Troubleshooting Matrix
| Symptom | Likely Root Cause | Remedy |
|---|---|---|
| MW12 always 0 | DB1 not opened before indirect read; OPN DB1 missing or DB1 deactivated |
Insert OPN DB1 in the same network or the immediately preceding one |
| MW12 reads a value but offset by 2 | Multiplier is 1 instead of 2 for INT elements | Insert L 2; *I before the SLD 3
|
| MW10 increments twice per pallet | Edge detection missing; FP bit not in the same byte as operand | Use FP M100.0 and ensure M100.0 is in the same MB as the I0.0 input bit is referenced from |
| SF LED on, CPU in STOP | Pointer arithmetic overflowed outside DB1; MW10 exceeds array high bound |
Add a range check L MW10; L 199; >I; JC ERR before the indirect access, or bind MW10 to a counter with limit |
| MW12 contains the previous-cycle value |
T MW12 missing; load of W[AR1,P#0.0] goes to ACCU1 only |
Verify the explicit T MW12 line; check the ACCU is not popped by a subsequent BLK call |
| Compare bit never goes true | Compare constant type mismatch (BYTE vs INT); CPU treats as signed | Use L 30 integer literal rather than L B#16#1E when comparing an INT
|
| Compiler error "Bit operand not allowed" | FP/OPN placed against a bit outside the process-image area | Use FP M100.0 with M100.0 in MB100
|
Edge Cases and Field-Proven Caveats
-
Signed vs unsigned array index. The pallet count is normally non-negative, but if a recipe load can write a negative value into
MW10, the+AR1will underflow and the CPU will go to STOP with Area length error. Add a lower-bound check. -
Retain behavior. If you power-cycle the line,
MW10resets to 0 because the defaultMarea is non-retentive. ConfigureMB10..MB12as retentive in the CPU properties (Retain Memory > Merker) if the line must resume where it left off. -
Cross-section number. The pointer
P#Byte.Bitformat used withB[AR1,P#0.0]is a DB-internal pointer; it does not encode the DB number. The DB register is taken from the lastOPN DBx. For multi-DB systems use a separateOPN DIfor instance DBs. -
STEP 7 version drift. On TIA Portal V17+ targeting an S7-1500, the instruction
SLD 3is replaced by the typedWORD_TO_UINTand pointer arithmetic is rarely needed; use SCL"db_code".Code[i]instead. - Time-budget for the cycle. A single indirect read costs roughly 1.4 microseconds on a CPU 315-2 DP at 100% OB1 priority. Reading 200 entries once per pallet is therefore well under the 100 ms typical OB1 budget.
Performance and Diagnostics Checklist
- Enable Monitor/Modify with trigger on
I0.0rising edge for live debugging. - Use PLC > Diagnostic Buffer after a STOP to identify the exact STL statement that caused the area-length error.
- Add VAT WatchTable_1 containing
MW10,MW12,M0.0,M100.0for at-a-glance online state. - Document the array high bound in the block header so future engineers know the legal range of
MW10.
FAQ
Why do I need the * 2 multiplier before SLD 3 when the array is INT?
Each INT element occupies 2 bytes in the DB. SLD 3 converts a word index to a byte offset, so the index must first be scaled by the element size: index * 2 for INT/WORD, index * 4 for DWORD/REAL.
Can I use MW10 directly without L 2; *I if I use DBW indexing in LAD?
Yes. In LAD/FBD the entry DB1.DBW[MW10 * 2] handles the byte scaling automatically. The explicit multiply is only required in STL because the raw index is in elements, not bytes.
What happens if MW10 exceeds the array high bound?
The CPU enters STOP with an Area length error when the pointer falls outside DB1. Always bound the counter with a comparison against the array size and latch a fault bit before allowing the indirect access.
Is FP M100.0 mandatory, or can I use the CU counter instruction instead?
Both are valid. CU C0 consumes 2 bytes of counter area and avoids the edge flag, but the count value is read from C0 rather than MW10. The FP pattern keeps the count in MW10 directly and is preferred for sortation logic.
Will this code work on an S7-1200 or S7-1500?
The S7-1200 in TIA Portal does not support the AR1/OPN/SLD 3 sequence in the same way. Use SCL with "DB1".Code[i] or a Variant pointer. The S7-1500 also supports the legacy STL set but Siemens recommends SCL for new code on those platforms.