Overview: What L Memory Is on the IM151-8 CPU
The IM151-8 PN/DP CPU is the integrated PLC of the ET 200S distributed I/O system. It executes a STEP 7 / S7-300 instruction set and is architecturally related to the S7-300 CPU 314 family. The CPU exposes three classes of operands inside the user program:
- I / Q / PI / PQ – process image and direct peripheral I/O
- M – global bit, byte, word, and double-word memory (retainable in parts)
- L – local, temporary data that belongs to a single execution context (the currently active OB and the FC/FB called from it)
L memory is not a globally addressable area. It is a slice of the CPU's local data stack (L-Stack / Lokaldaten-Stack) that is allocated dynamically every time an OB is entered, and it is released when the OB completes. The CPU maintains one physical stack in RAM; the OS simply hands out L addresses from it according to the call hierarchy that is active at the moment a bit/byte/word/double-word is accessed inside the block.
Because the stack is reused, the absolute address you see in the editor (for example LW 22) is meaningful only while that particular code block is executing. The same number will refer to a completely different variable the next time the block is called from a different context, or even the next time it is called from the same OB if other blocks were invoked in between.
L Stack vs. M Memory: The Engineering Difference
Engineers migrating from a relay-ladder background frequently assume that any operand the editor allows must be visible everywhere. On the IM151-8 that is only true for M, DB, inputs, and outputs. L memory has three properties that M memory does not:
| Property | L (Local) | M (Bit Memory) |
|---|---|---|
| Scope | Active OB + blocks called from it | Global across all OBs, FCs, FBs, and the entire scan |
| Retentivity | None – overwritten on every entry, released on OB completion | Configurable per byte (default 16 KB non-retain, partial retain) |
| Allocation | Automatic by the operating system based on declared TEMP and the call stack | Static – addresses are fixed |
| Visible in cross-reference | Only the address of the current instance | Every reference in the program |
| Safe for multi-OB use | Yes – each OB priority class gets an isolated slice | No – concurrent OBs (e.g. OB35 + OB82) can race |
The practical consequence: writing to L0.0 inside FC1 and writing to L0.0 inside FC2 is not a conflict in the source sense, because the L-stack is re-allocated per call. It is, however, a conflict in the maintenance sense – two developers will read the same absolute address and assume they are talking about the same bit. That is why Siemens documentation and the STEP 7 style guide forbid direct L addressing from outside the block where the TEMP variable is declared.
OB Priority Classes and How the L-Stack Is Partitioned
The IM151-8 organises execution by priority class. Every OB has a fixed priority (OB1 = 1, OB10 = 2, OB35 = 12, OB40–OB47 hardware interrupts, OB80–OB87 error OBs, OB100 = 27, OB121/OB122 = priority of the OB that caused the error). Up to 26 priority classes can be active concurrently in the worst case.
For each priority class the operating system reserves a separate slice of the L-Stack. The size of that slice is configured in HW Config (STEP 7) or in the device properties (TIA Portal) under CPU Properties > Local Data / Prioritätsklassen. The default for the IM151-8 family is:
| Priority class | Default local data (STEP 7 / TIA) | Typical use |
|---|---|---|
| 1 (OB1 – cyclic) | 256 bytes | Main scan |
| 2–7 | 256 bytes | Time-of-day, time-delay, watchdog |
| 8–15 | 256 bytes | Cyclic interrupt (OB35 etc.) |
| 16–23 | 256 bytes | Hardware interrupt OBs |
| 24 | 256 bytes | Time error (OB80) |
| 25 (default) | 256 bytes | OB121/OB122 / start-up |
| 26 (start-up) | 256 bytes | OB100 / OB101 / OB102 |
Within one priority class, the OS builds the local data frame by concatenating the local data of every block that is currently on the call stack, starting at byte 0. The frame looks like this when OB1 calls FC1, which in turn calls FC2:
Byte Offset Source
0..19 OB1 TEMP area (20 bytes declared)
20..23 FC1 TEMP area (4 bytes declared)
24..27 FC2 TEMP area (4 bytes declared)
28.. (would belong to FC3 if FC2 called it)
Notice that the offset of FC1's TEMP depends on how many bytes OB1 declared, and the offset of FC2 depends on how many bytes FC1 declared. Change the TEMP footprint of any block in the call chain and every block below it shifts. This is the single most common source of "it worked yesterday" L-stack faults.
Verifying the Allocation: A Walk-Through
Suppose a project contains the following declarations:
-
OB1 – declares 20 bytes of TEMP (for example
tOB1_State : BYTE,tOB1_Step : WORD,tOB1_Tmr : TIMER) -
FC1 – declares 4 bytes of TEMP (
tFC1_RetVal : WORD,tFC1_Flags : BYTE) -
FC2 – declares 4 bytes of TEMP (
tFC2_Index : WORD,tFC2_Sum : INT)
When OB1 runs and calls FC1 which calls FC2, the L addresses are:
| Absolute L address | Symbolic name | Block |
|---|---|---|
| LB 0 – LB 19 | tOB1_State, tOB1_Step, tOB1_Tmr … | OB1 |
| LB 20 – LB 23 | tFC1_RetVal, tFC1_Flags | FC1 |
| LB 24 – LB 27 | tFC2_Index, tFC2_Sum | FC2 |
If a maintenance engineer types L LW 22 in FC2 expecting "the index from FC1", they will actually read FC2's own tFC2_Index. That is the hazard of absolute L access. Always use the symbolic TEMP name the compiler created in the block's interface.
Local Data Sizing for the IM151-8 Variants
The IM151-8 family is shipped in several firmware versions. The amount of maximum local data per priority class differs:
| CPU order number (MLFB) | FW | Max local data per priority class | Total local data budget |
|---|---|---|---|
| 6ES7151-8AB00-0AB0 | ≤ 2.0 | 32 KB | 64 KB |
| 6ES7151-8AB01-0AB0 | ≥ 3.0 | 32 KB | 64 KB |
| 6ES7151-8AB02-0AB0 | ≥ 3.3 | 32 KB | 64 KB |
| 6ES7151-8AB03-0AB0 | ≥ 3.4 | 32 KB | 64 KB |
| 6ES7151-8FB00-0AB0 (F variant) | ≥ 3.0 | 32 KB | 64 KB |
The total local data budget is the sum of the slices allocated to every priority class. If you set every priority class to 32 KB, the total exceeds the budget and the CPU will refuse to start – it logs SF / BF with diagnostic buffer entry "Local data stack overflow". The recommended practice is to leave the 256-byte default and grow a specific class only if you have a real, profiled reason (deep FC nesting in a cyclic interrupt, for example).
To size correctly, add up the TEMP footprint of every block that can be on the call stack from the worst-case OB. For an OB1 scan that may call FC1 → FC2 → FC3, the worst case is OB1 + FC1 + FC2 + FC3, not just the deepest level.
Why Direct L Access from Outside the Block Is a Code Smell
STEP 7 and TIA Portal both allow you to type L MW 20 from any block, including OBs. For M memory this is fine; for L memory it is not. Reasons:
-
No cross-reference integrity. The cross-reference list shows the absolute address, not the block that owns it. You cannot search who wrote to
L24.0because every block that has a TEMP in that byte is a candidate. -
Call-stack fragility. Adding a TEMP variable to a block higher in the call chain silently shifts every address below it. A field device that was supposed to read
L24.0will now read a different bit without a single compile error. -
Re-entrancy surprises. The same FC can be re-entered from two different OBs (e.g. OB1 and OB35). If FC1 writes to
L0.0absolutely, the second caller overwrites the first caller's data because they share the slice. - Compiler optimisation. STEP 7 may re-pack TEMP variables to save bytes. The mapping you assumed in the source is not guaranteed after a rebuild.
The only supported way to exchange data into or out of a block is the formal interface: IN, OUT, IN_OUT parameters, and for FBs the STAT (instance-DB) area. The TEMP area exists for intermediate results inside the block only.
Configuration Procedure in STEP 7 (Classic)
- Open HW Config and double-click the IM151-8 station.
- Open CPU Properties > Local Data (German: Lokaldaten).
- For each priority class, enter the local data size in bytes. 256 is a safe default.
- Click OK, save and compile, and download to the CPU.
- After the next STOP-to-RUN transition, verify in CPU > Module Information > Performance Data that the new sizes are active.
Configuration Procedure in TIA Portal
- In the project tree, select the IM151-8 device.
- Open Properties > System and diagnostic events > Local data (or the dedicated Local data tab in V17+).
- For each OB priority class, set the size. TIA Portal warns if the sum exceeds the device total.
- Compile the project (Hardware and software (rebuild all)) and download.
- Watch the online diagnostics for
SFwith diagnostic buffer text containing "Lokaldaten" / "Local data" – this means the sum is still too high.
Diagnostics: Common L-Stack Faults on IM151-8
| Diagnostic buffer entry | SF LED | Root cause | Fix |
|---|---|---|---|
| Local data stack overflow | On, possibly BF | Sum of configured per-class sizes exceeds device total | Reduce one or more priority class sizes, or split a deeply nested FC into smaller blocks |
| OB not loaded / OBxx priority conflict | On | Two OBs share a priority class but have overlapping local data need | Re-assign priorities so concurrent OBs do not exceed 32 KB combined |
| STOP caused by programming error – area length error with access to LW | On | FC/FB reads an L byte that is outside its declared TEMP | Declare the missing TEMP, never use absolute L access |
| Communication error – local data of OB1 exceeded | On | Heavy S7-communication or OP-communication increased OB1's frame | Raise the OB1 priority-class size by 256 B increments |
The STEP 7 diagnostic buffer gives the exact byte that overflowed. In TIA Portal, open Online & Diagnostics > Diagnostics buffer and double-click the entry – the CPU reports "exceeded XX bytes". The number is the requirement, not the configured size; you must set the configured size to at least that number.
Best Practices for Local Data Usage
-
Always use the symbolic name the compiler creates for each TEMP. Never type
L+ address by hand inside the same block either – let the editor resolve the address from the interface declaration. - Use IN/OUT/IN_OUT for every value that crosses a block boundary. Reserve TEMP for scratch calculations only.
- Pre-declare the entire scratch footprint in the interface, even if some bytes are unused. This freezes the size and prevents accidental re-packing from changing call-stack offsets in other blocks.
- Profile real programs. The default 256 B per class is enough for most ET 200S applications. Raise it only after a real overflow diagnostic.
- Avoid deep FC nesting in cyclic interrupts (OB35). Each level adds its own TEMP frame, and cyclic interrupts can be pre-empted by hardware interrupts, doubling the requirement.
- Document the call chain in the OB header comment so that the next engineer can reproduce the offsets you used during commissioning.
Migrating from a CPU 314 to IM151-8: L-Stack Pitfalls
The IM151-8 is the drop-in replacement for an S7-300 CPU 314 inside an ET 200S station. The local data layout is identical, but the default per-class size is 256 B on both, so most programs transfer without change. Watch for these exceptions:
- Programs that grew the CPU 314's local data to 8 KB per class will not run on the IM151-8 – the total budget is 64 KB, identical to the CPU 314, so the absolute size is fine, but the per-class maximum on the IM151-8 firmware ≤ 2.0 was lower in early releases. Confirm with the firmware release notes for the specific MLFB.
- Programs that access L memory from a different block (a frequent legacy pattern in retrofits) will compile and run, but will produce cross-reference errors after the next compiler re-pack. Fix the project before commissioning.
- Programs that use a shared DB as "global L memory" because they ran out of L space are a clear sign that the function should be split into an FB with STAT instead.
FAQ
Is L memory on the IM151-8 global like M memory?
No. L memory is a slice of the per-priority-class local data stack. It is allocated automatically when an OB is entered, lives for the duration of the OB call chain, and is released when the OB completes. M memory is global and partially retentive.
If I use L0.0 in FC1 and L0.0 in FC2, is that a conflict?
Not at run time – the L-stack is re-allocated per call, so each FC sees its own L0.0 inside its own TEMP frame. It is, however, a maintenance conflict because the absolute address in the source is the same, so the two variables are indistinguishable in a cross-reference. Use the symbolic TEMP name and pass data via IN/OUT.
Where does the L address of a called FC start?
At the first free byte after the calling block's TEMP frame. If OB1 declares 20 bytes of TEMP and calls FC1, FC1's TEMP starts at byte 20 of that priority class's local data slice. If FC1 then calls FC2, FC2 starts at byte 24, and so on.
How large can I make the L-Stack on an IM151-8 PN/DP?
The maximum per priority class is 32 KB, and the total budget across all priority classes is 64 KB. The STEP 7 / TIA Portal default is 256 B per class, which is sufficient for the vast majority of programs. Over-sizing a class so that the total exceeds 64 KB prevents the CPU from starting.
Can I read or write an L address from an OB or another FC?
The CPU will not stop you, but Siemens documentation explicitly recommends against it. Direct L access from outside the owning block breaks cross-reference integrity and breaks when the call-stack footprint changes. Use IN, OUT, IN_OUT, or for FBs STAT (instance DB) instead.