S7-300 TEMP Variable Retains Value After Power Cycle: Root Cause

David Krause17 min read
S7-300SiemensTroubleshooting
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

1. Problem Overview

A SIMATIC S7-300 system built around a CPU 314C-2DP (order number 6ES7 314-6EH04-0AB0 or earlier 6ES7 314-6CG03-0AB0) reports a programming anomaly that looks, at first glance, like a hardware or firmware defect: a local variable declared inside a Function Block (FB) keeps its last-written Boolean value even after the CPU is powered off, the mains supply removed, the 24 V buffer discharged, and the controller re-started. The bit that should be a transient diagnostic flag is found to be permanently latched to 1 across the cold restart.

Engineers who witness this almost always blame the CPU, the firmware, or STEP 7. The reality is the opposite. According to the SIMATIC S7-300 programming rules documented by Siemens, TEMP variables are not guaranteed to be initialised at the start of a block call, and reading from one before writing to it is a contract violation. When a TEMP is read first, the value returned is whatever happens to occupy the corresponding word in the local-data (L) stack at that moment. The L stack is a region of battery-backed / retentivity-free RAM that is reused by every block invocation, and its residual content survives a power cycle exactly as any other RAM cell does.

Key fact: The CPU is not faulty. The block is the fault. The L stack is doing what it was designed to do; the programmer is the one violating the documented usage rule.

2. Affected Hardware, Firmware, and Software

Component Identifier Notes
CPU 6ES7 314-6CG03-0AB0 / 6ES7 314-6EH04-0AB0 CPU 314C-2DP, integrated Profibus DP master and I/O
Firmware V3.x (and earlier) Behaviour identical across all S7-300 firmware releases; L-stack rules are architectural
STEP 7 STEP 7 V5.5 / V5.6 Original project; same rules apply under TIA Portal V13+ for S7-300/400 targets
Block type FB (Function Block) FC exhibits identical behaviour because both use the L stack
Variable TEMP, declared in InOut / Temp area Bit is read before it is unconditionally written
Communication Profibus DP via SFC14 / SFC15 RET_VAL monitoring is incidental to the bug, not its cause

Reference: SIMATIC S7-300 CPU 314C-2DP Device Manual (Siemens Industry Online Support, entry ID 109751454) and the STEP 7 Programming with STEP 7 V5.5 manual (entry ID 18609689).

3. Failure Symptoms

The field report that motivates this article shows the following reproducible behaviour:

  1. An FB contains a TEMP BOOL (call it bit_x) in its declaration table, used to flag a Profibus DP slave error.
  2. The block monitors RET_VAL of SFC14 (DPRD_DAT) and SFC15 (DPWR_DAT); when RET_VAL is non-zero, the code sets bit_x := TRUE.
  3. After the DP slave is replaced, RET_VAL returns to 0000h. The slave communicates cleanly.
  4. bit_x remains TRUE forever, in every OB cycle, even after CPU stop-run transitions.
  5. Performing a memory reset (MRES) followed by a cold restart does not clear it.
  6. Powering the entire rack down (PS 307 off, backplane discharged) and back up does not clear it.
  7. The bit is finally cleared only by writing FALSE to it explicitly in the code.

From a customer perspective, this is indistinguishable from a retentive flag. From a PLC perspective, the L stack is simply holding its last value because the programmer never overwrote it.

4. Root Cause: TEMP Variable Lifecycle in S7-300

The SIMATIC S7-300/400 CPU family stores block-local variables in two physically separate regions:

Variable class Storage area Initialisation Retentive across power cycle?
TEMP L stack (local data stack) Undefined at the start of every block call Can appear so; physically RAM, content unpredictable
STAT (FB only) Instance DB Initial values from FB declaration Configurable via Non Retain / Retain switch in STEP 7
IN / OUT / IN_OUT Parameters are passed by reference (FB) or by value (FC) Initialised by caller Indirectly via caller or instance DB

The STEP 7 manual is explicit: "Before a local variable is read, a value must be assigned to it. The only exception to this rule is the first 20 bytes of the local data of an OB, which are filled by the operating system." The diagnostic flow in STEP 7 Programming with STEP 7 V5.5 describes this in section 5.4 ("Block-local variables") and repeats it in the OB1 / OB100 description.

Concretely, when an FB is called, the S7-300 operating system performs the following sequence:

  1. Reserves a slice of the L stack for the called block (size defined in the block's local-data requirement, displayed in the FB properties).
  2. Does not initialise the slice. The bytes are simply claimed.
  3. Loads input parameters into the slice as they are read.
  4. Executes the block code, which may read from any byte in the slice at any time.
  5. On block exit, the slice is logically released (its slot is free for the next call) but the RAM is not zeroed.

If the programmer reads bit_x before the assignment bit_x := TRUE, the read operation pulls whatever byte is currently in that L-stack slot. After the faulty DP slave wrote 1 into that slot, the slot keeps that 1 until something else overwrites it. The slot may be reused by the same FB on the next cycle, by a different FB, or by the OB itself, depending on call nesting. The value persists across STOP-RUN, power-off, and power-on because the L stack is just RAM in the work-memory area and the OS does not scrub it on restart.

5. The L-Stack Architecture in Detail

The L stack (local data stack) is a 1-byte-wide memory area located in the CPU's work memory. It is not part of the load memory, the system memory, or any DB. Each time the operating system distributes blocks (OB, FB, FC, SFB, SFC) to the call stack, it allocates a frame whose size is the block's declared local data requirement (visible in STEP 7 under Block > Object Properties > General, Part 2). Summed over all simultaneously active OBs, this requirement must not exceed the CPU's local data size (e.g. 1 024 bytes for CPU 314C; 8 192 bytes for CPU 319).

Capacity check: If the L stack overflows, the CPU goes into STOP with diagnostic buffer entry "STOP caused by overflow of the local data stack" (event ID 0x21A4). A permanently-1 bit is not an overflow symptom; it is almost always a programming defect.

The CPU's load memory (where compiled FBs live) is non-volatile (MMC card or flash). The work memory (which holds the L stack, the process image, the M flags, and the runtime instance DBs) is volatile RAM. The L stack is in that RAM, so on power loss the contents are physically undefined. On the next start-up, however, the OS does not wipe work memory; it just declares it available. This is why an L-stack slot can still hold a 1 that was written before the outage.

6. Why the Bit Survives Power Off-On

The mechanism is straightforward once the architecture is understood:

  1. During the original fault event, the FB executes the line IF RET_VAL <> 0 THEN bit_x := TRUE; END_IF;. The CPU writes a 1 into the L-stack byte that holds bit_x.
  2. On every subsequent cycle, the FB reads bit_x at the top of the code, before any write. The L-stack byte is still 1, so the read returns 1.
  3. The IF condition is checked. RET_VAL is now 0, so the assignment branch is skipped. bit_x is never overwritten.
  4. The block ends. The L-stack slot is logically released. The byte 01 stays in RAM.
  5. Power is removed. The volatile RAM discharges. The byte 01 is still present, in physical charge form, on the capacitor-smoothed state of the memory cells.
  6. Power returns. The OS re-initialises the system data, process image, and M/DB areas, but the work memory (including the L stack) is not scrubbed. The byte is still 01.
  7. The next call to the FB allocates a new L-stack frame. By coincidence (or by deterministic address reuse on the same CPU firmware), the new frame lands on a slot that still contains 01. The read returns 1 again.

This is the "permanent" bit the field engineer observes. The CPU is doing exactly what its firmware specification describes; the programmer violated the documented TEMP initialisation rule.

7. Diagnostic Procedure

Use the following checklist to confirm that the reported issue is a TEMP-initialisation defect and not a CPU fault.

  1. Open the FB declaration table in STEP 7 (or TIA Portal). Identify every TEMP variable. Mark any TEMP that is read before being unconditionally written.
  2. Cross-reference the TEMP in the code section. Use Edit > Find / Replace on the variable name; every occurrence is listed. The first occurrence after the block's BEGIN line should be an assignment, not a read.
  3. Check call nesting. If the FB calls other FBs/FCs that share the L stack, the TEMP may be clobbered between reads. This is a related but distinct bug (L-stack overlap); see Siemens FAQ entry "Why is the value of a TEMP variable not output?" (entry ID 21377308).
  4. Monitor the L stack with a watch table. In STEP 7, open Monitor/Modify on the instance DB (for STAT) and on the L stack using the symbol L 0.0, L 1.0 ... The L stack view is only available online and only while the block is actively executing. The default view shows offsets from the current block's frame base.
  5. Force a write before the next read. Insert a single line at the very top of the code: bit_x := FALSE;. Re-download the block. Power-cycle the CPU. Observe the bit. If the bit now starts as 0 and behaves correctly, the TEMP rule violation is confirmed.
  6. Rule out MRES interaction. A memory reset (MRES) does not clear work memory. To truly wipe work memory, use PLC > Clear/Reset > Reset to Factory Settings (if available) or pull the MMC, power-cycle, and re-insert. A bit that survives even that is a real retentive storage problem, not an L-stack artefact.
Heuristic: If a TEMP variable appears to be retentive across power cycle, MRES, and STOP-RUN, the L-stack theory is almost certainly correct. Real retentive behaviour is a property of M flags, DB variables with the Retain attribute, or system data areas — never TEMP.

8. Correct Programming Patterns

Siemens recommends three patterns, in order of preference.

8.1 Unconditional write before any read

The simplest fix. Place an explicit initialisation at the top of every code section that uses the TEMP:

FUNCTION_BLOCK FB_DpDiag
VAR_TEMP
  bit_x : BOOL;
  ret_save : INT;
END_VAR
BEGIN
  // ---- Rule: always write before read ----
  ret_save := 0;          // initialise first
  bit_x := FALSE;         // initialise first

  ret_save := DPRD_DAT(
    LADDR := W#16#100,
    RECORD := P#DB1.DBX0.0 BYTE 10,
    RET_VAL := ret_save); // RET_VAL reused; OK because we read ret_save only after the next write

  IF ret_save <> 0 THEN
    bit_x := TRUE;        // explicit write, but rule already satisfied above
  END_IF;
END_FUNCTION_BLOCK

Once the unconditional bit_x := FALSE; is in place, the residual value in the L stack cannot influence the logic regardless of the previous fault history.

8.2 Promote TEMP to STAT

If the bit represents state that should genuinely persist across the FB's call (e.g. a latched-alarm flag), move it from VAR_TEMP to VAR (static). The variable is then stored in the instance DB, not the L stack, and is initialised from the FB declaration's Initial Value column on every cold restart of the instance.

FUNCTION_BLOCK FB_DpDiag
VAR
  bit_x : BOOL := FALSE;   // STAT; stored in instance DB, initialised on startup
END_VAR
VAR_TEMP
  ret_save : INT;
END_VAR
BEGIN
  ret_save := DPRD_RET_VAL := SFC14(...);
  IF ret_save <> 0 THEN
    bit_x := TRUE;
  END_IF;
END_FUNCTION_BLOCK

To make the STAT behave as fully retentive, open the instance DB in STEP 7, select the bit_x row, and tick Non Retain = No (i.e. the variable is retained). Retentive STATs survive power cycle, STOP-RUN, and even MRES only when the MMC is preserved.

8.3 Use a global M flag or a global DB

For a single-bit diagnostic that must be visible across multiple blocks or for HMI alarming, declare it in a global DB (e.g. DB99 "Global_Diag") or use an M flag. The M area on the S7-300 can be made retentive in PLC > Properties > Retentive Memory by entering the byte count to retain (e.g. MB0 .. MB15).

// In a global DB (DB99)
DATA_BLOCK DB99
STRUCT
  bDpSlaveError : BOOL;   // initial value FALSE
  bAck : BOOL;
END_STRUCT
END_DATA_BLOCK

9. Why the SFC14 / SFC15 RET_VAL Was the Wrong Place to Look

The original code used the SFC14 (DPRD_DAT) and SFC15 (DPWR_DAT) return values to drive the diagnostic bit. The SFC manual is explicit: RET_VAL contains the error code from the I/O access; 0000h means the data record was read/written successfully; any non-zero value is an error code defined in the SFC help. The SFC is not at fault.

The bug is in how the bit is stored, not how the error is detected. The Profibus layer is doing the right thing; the block is mishandling its own state. Replacing the slave, monitoring RET_VAL going to 0, and still seeing the bit latched are all symptoms of a block-level state-handling defect.

RET_VAL (hex) Meaning (SFC14/15) Recommended action
0000 No error Process data valid
80A0 Negative acknowledgement from module Slave not at configured address, or removed
80A1 DP slave failure Slave not reachable; check wiring and address
80A2 DP slave diagnostic buffer overflow Reduce diagnostic traffic
80B0 SFC call not permitted Re-initialise DP master
80B1 Length of consistent data exceeded Reduce RECORD length to max 32 / 16 bytes
80B2 Consistent data access not permitted Wrong SFC for the configured data length
80C0 Data not yet read from module Repeat call; transient during slave startup
80C2 Data not yet valid from module Repeat call; transient

Reference: SFC14 / SFC15 help in the STEP 7 system help / manual collection (entry ID 44238104).

10. Field Commissioning: Detecting TEMP Bugs in Existing Code

Use a static analysis pass to find suspect TEMP variables in an existing project before deployment:

  1. In STEP 7 V5.5, open the S7 program, right-click the Blocks container, and choose Check Block Consistency. The compiler will report uninitialised TEMP reads on FBs that violate the rule.
  2. In TIA Portal V13+, use Program > Compile > Software (rebuild all). The compiler emits warnings such as "Area of variable 'bit_x' may not be initialised" for offenders.
  3. Enable the option "Warn about uninitialised local variables" in Options > Settings > PLC programming > Compiler to escalate the warning.
  4. Cross-check the S7 program against the Siemens supplied example project "S7_Basics" for correct TEMP usage; the sample code in the CPU 314C-2DP manual shows unconditional writes in every example FB.

11. Verification Procedure

After applying one of the three fix patterns, verify the behaviour with the following procedure. Use a real PLC on the bench; simulation alone is not sufficient because the L-stack retention issue is hardware/RAM dependent.

  1. Download the modified FB to the CPU. Perform an online > download to PG to confirm the change reached the MMC.
  2. Set the CPU to RUN. Observe bit_x with a watch table on the instance DB (for STAT) or on the L-stack offset (for TEMP). Confirm initial value is 0.
  3. Simulate a Profibus fault by unplugging the slave or by setting RET_VAL to a non-zero value in a test branch. Confirm bit_x transitions to 1 as designed.
  4. Restore the fault. Confirm bit_x transitions back to 0 on the next cycle (or, for STAT with Retain = Yes, that it stays 1 only if the design intent is to latch).
  5. Perform a STOP-RUN transition. Confirm bit_x behaves as specified (TEMP: reset to undefined, treated as FALSE by the unconditional write; STAT non-retain: reset to initial value; STAT retain: holds last value).
  6. Power off the PS 307 for at least 30 s. Restore power. Confirm bit_x behaviour matches step 5.
  7. Perform MRES. Re-download the program. Re-run steps 1-6. The expected outcome: bit_x follows the design intent across all reset conditions.

12. Preventive Coding Rules

Capture the following rules in your internal coding standard and enforce them in code reviews:

Rule Why it matters
Always write a TEMP before reading it. Defines initial state; prevents L-stack residual bleed.
Declare diagnostic flags as VAR (STAT), not VAR_TEMP. STATs are stored in the instance DB and have controlled initial values.
Set Retain explicitly on STATs that must survive power cycle. Default for new STATs is non-retain; reviewers must opt in.
Document the L-stack budget per OB. Prevents STOP conditions from overflow; surfaces accidental growth.
Avoid passing TEMPs by reference to other FBs/FCs. Sharing the L stack across blocks can clobber or leak values.
Use the compiler's "uninitialised TEMP" warning; treat it as an error in CI. Catches violations at build time, not at commissioning time.

13. Related Issues and Edge Cases

The same root cause produces several other symptoms that are worth listing for field engineers:

  • Random values in STAT after FC call: An FC shares the L stack with its caller. If the FC writes into a TEMP that is later used by the caller's TEMP at the same offset, data leakage occurs. Symptom: random bits in TEMP at unpredictable moments.
  • L-stack overflow at high OB priority: If an OB1, OB35, and OB82 all instantiate the same FB simultaneously, the L-stack frames sum. CPU 314C supplies 1 024 bytes of local data; if the budget is exceeded, the highest-priority OB takes precedence and the lower-priority OBs are skipped. See Siemens FAQ entry ID 21377308.
  • Cross-block RET_VAL pollution: If the caller of SFC14 reads RET_VAL from a TEMP that was also used as a working variable by a sub-block, the value can change between the SFC call and the read. Always use a dedicated, locally initialised TEMP for RET_VAL.
  • Time-stamp inconsistencies on OB100 (warm restart): On warm restart, OB100 is executed and its local data is initialised by the OS, but the rest of the L stack is not. The OB100's own TEMPs are safe; TEMPs in FBs called from OB100 are subject to the same rules as in OB1.

14. FAQ

Why does my TEMP variable keep its value after a power cycle on an S7-300 CPU 314C-2DP?

The L stack is volatile RAM that the operating system does not scrub on restart. If your code reads the TEMP before writing to it, the read returns whatever byte is currently at that L-stack offset, which can be a value left over from a previous block call or from a prior power-up. Promote the variable to STAT (stored in the instance DB with a defined initial value) or add an unconditional assignment at the top of the block.

Is the CPU 314C-2DP firmware defective when a TEMP variable appears retentive?

No. The behaviour is fully compliant with the SIMATIC S7-300 architecture described in the STEP 7 programming manual. TEMP variables are not required to be initialised by the operating system; only the first 20 bytes of an OB's local data are. Reading a TEMP before writing to it is undefined behaviour, and the observed value is whatever happens to be in the L-stack slot.

What is the difference between TEMP, STAT, and M flags for storing a DP-slave error bit?

TEMP variables live in the L stack and are reset to undefined on every block call — they must be unconditionally written before being read. STAT variables are stored in the instance DB of the FB and have a defined initial value; they can be made retentive. M flags (M0.0 etc.) live in the system memory and can be configured retentive in the CPU properties. For a diagnostic bit that the HMI or another block must read across cycles, use STAT (preferred) or a global DB variable; avoid TEMP for anything that resembles state.

How do I clear the stuck bit without rewriting the logic?

From a watch table, force the L-stack offset (for TEMP) or the instance DB bit (for STAT) to 0. In STEP 7 use Monitor/Modify with the operand L <offset>.<bit> for the active block. For a permanent fix, modify the FB to write the bit before reading it, recompile, and download to the CPU.

Does a memory reset (MRES) clear the L stack on a CPU 314C-2DP?

MRES clears the work memory regions that the OS re-initialises on startup (process image, M area, instance DBs loaded from the MMC), but the L stack itself is not explicitly zeroed. In practice the residual content of the L stack is determined by what the OS allocates on top of it during the subsequent cold restart. A bit that survives MRES is a strong indicator of a TEMP read-before-write violation, not a CPU defect.

Are SFC14 (DPRD_DAT) and SFC15 (DPWR_DAT) the source of the problem?

No. SFC14 reads a consistent data record from a Profibus DP slave and returns any error in RET_VAL (codes 80A0h, 80A1h, 80C0h, etc.). SFC15 writes a consistent data record. Both SFCs are working as specified. The bug is in the block that stores the error status — using a TEMP variable without initialising it.

Back to blog