Siemens S7 FC Temp Variable Reset Speed Reference Troubleshooting

David Krause13 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

Problem Description

On a Siemens SIMATIC S7-300 (or S7-400) PLC programmed with STEP 7, a machine fails to run at high speed even though the high-speed sensor is actuated. The sensor triggers a speed reference value from a data block (DB), and the value is moved into the local temporary variable #TEMP8 inside FC91. The FC is then expected to forward this value onward — typically scaled and written to a peripheral output word (PQW) that drives the inverter's setpoint input.

Symptom signature from the field:

  • Sensor input bit goes TRUE when the part is in position.
  • The associated DB word updates correctly (visible in VAT or HMI).
  • #TEMP8 shows the correct value while online monitoring the FC, but only on the cycle in which the move instruction executes.
  • The inverter never receives the high-speed reference, or receives it for a single scan and then falls back to the default (zero) speed.

From the field: "I have an issue with one of my machines, it won't go into high speed. The sensor triggers a speed reference from a DB, this is sent to #TEMP8 on FC91 of my program. I can't work out what is stopping the FC telling the inverter to speed up."

Root Cause: TEMP Variable Lifecycle in S7 Function Blocks

The failure mode is not a wiring or sensor problem and it is not a programming error in the sense that a wrong instruction was selected — it is a misunderstanding of the storage class assigned to #TEMP8 when the variable was declared at the top of FC91.

Every variable declared in the TEMP row of a Siemens S7 FC (or in the TEMP row of any FB/OB) lives in the CPU's local data stack (L-Stack). The L-Stack is the scratchpad memory region that STEP 7 allocates for the duration of the block call and then reclaims when the block exits (BE). On the next call, the same memory bytes are handed back to the block but STEP 7 does NOT guarantee that the bytes still contain the values they held at the end of the previous call. The CPU firmware may overwrite them at any time, especially when other OBs/F Bs are active and consume the L-Stack.

Consequences for FC91:

Variable Class Storage Location Retention Across Cycles Suitable for Holding a Speed Reference?
TEMP L-Stack (local data) No — undefined on next call No, unless re-initialized every scan
STAT (FB only) Instance DB (DI) Yes (until overwritten by logic) Yes — preferred for latched state
IN / OUT / IN_OUT (FC) Caller-supplied pointers Yes (value comes from caller's actual parameter) Yes — pass the DB word directly
M (Merker) Bit/byte/word/double-word memory area Yes Workable, but pollutes global namespace

From the discussion: "If you want to use this speed from the DB in #TEMP8 you have to move the value every cycle into #TEMP8 or use static variable." This is the exact mechanical answer. The move instruction in Network X of FC91 may only execute on the edge of the sensor input; on every subsequent scan where the sensor is steady, the move is skipped and #TEMP8 is read undefined.

Why Online Monitoring Misleads the Engineer

STEP 7's online monitor shows TEMP values in bold/italicized style and updates them as the FC runs. What the engineer observes is correct for the current scan — the value is present while Network X is being executed. The minute the FC exits (BE) and is called again from OB1, the value is gone. The next monitor pass shows the variable either as ???, as zero, or as a stale value from the last L-Stack consumer that used the same byte offset. This produces the confusing impression that the FC "loses" the value between cycles.

From the field: "A Temp variable should be initialised at the beginning of FC or FB!" — Wizard's reply is the canonical rule. TEMP must be written before it is read, on every single execution of the block.

Diagnostic Procedure

  1. Open FC91 in STEP 7 (SIMATIC Manager → Blocks → FC91 → right-click → Open).
  2. Switch to STL/FBD and look at the variable declaration table (Interface pane at the top). Confirm that TEMP8 is declared under TEMP (not under IN and not as part of an FB's STAT row).
  3. Establish an online connection to the CPU (PLC → Connect to Target System → Online).
  4. Monitor FC91 (Monitor / Modify → Monitor). Watch #TEMP8 while toggling the high-speed sensor input.
  5. Insert a VAT (Variable Table) and add the following tags for cross-reference:
    // VAT for FC91 speed reference debug
    DB_SpeedRef.Word      // Source DB word the sensor triggers
    I_HighSpeedSensor     // Sensor input bit
    MW_LstackProbe        // Optional: probe the suspected L-Stack address
    PQW_InverterSetpoint  // Output to inverter (peripheral output word)
    FC91_TEMP8_Local      // Visible only in FC91 monitor, not in VAT
  6. Trigger the sensor manually and single-scan with Debug → Single Scan if your CPU supports it, or use a break point in FC91 (right-click network → Set Breakpoint).
  7. Capture the scan-by-scan state of #TEMP8 and of the inverter's PQW. Confirm that #TEMP8 is non-zero in the cycle the move executes and zero (or garbage) in the next cycle.
  8. Use the Cross Reference (References → Cross References) in SIMATIC Manager to find every read of #TEMP8 and every write to it. There must be at least one unconditional write before every read.

Solution A — Move the DB Value Into #TEMP8 Every Cycle (Keep FC)

If you want to retain the FC architecture (no instance DB, no FB migration), the move from the DB into #TEMP8 must happen unconditionally at the top of FC91, on every call. A move that is gated on the sensor edge is not sufficient — that only writes the value on the rising edge of the sensor input.

Example structure in STL (the move is in Network 1, before any reads of TEMP8):

// FC91 — Network 1: initialise local speed reference
      L     DB_SpeedRef.ActualSpeed     // DBW from the calling DB
      T     #TEMP8                      // TEMP, lives for this scan only

// FC91 — Network 2: scale and clamp
      L     #TEMP8
      L     16384                       // ±100% in INT scaling
      *I
      T     #TEMP9                      // scaled value

// FC91 — Network 3: output to inverter
      L     #TEMP9
      T     PQW 288                     // or DB word + later UNSCALE

Rules to follow in this approach:

  • The initialising move must be in a network that executes before any consumer of #TEMP8.
  • Do not gate the initialising move on the sensor or on any condition that can be FALSE while the FC is still being called.
  • Use the same TEMP variable name throughout; do not re-declare in a nested call.

Solution B — Convert FC91 to FB91 With a STAT Variable

The more robust engineering answer is to convert FC91 to FB91 and declare the speed reference as a STAT variable backed by an instance DB. STAT lives in the instance DB (DI) and is retained across calls until the FB writes over it. This matches the lifecycle the programmer originally assumed.

Migration steps:

  1. In SIMATIC Manager, rename FC91 to FB91 (or create FB91 as a copy).
  2. Open FB91 and move the declaration of TEMP8 from the TEMP row to the STAT row. Rename to SpeedRef for clarity.
  3. Generate an instance DB: right-click FB91 → Instance DB → Instance (e.g., DB91).
  4. Replace every reference to #TEMP8 with #SpeedRef.
  5. Update OB1 (or wherever FB91 is called) to invoke FB91 with DB91 as the instance.
  6. Re-compile and download.

STL skeleton:

// FB91 — Interface
VAR
   SpeedRef : INT;       // STAT — retained across scans
END_VAR
BEGIN
   // Network 1: latch on sensor rising edge
      A     I_HighSpeedSensor
      FP    M_EdgeMemory
      JCN   END1
      L     DB_SpeedRef.ActualSpeed
      T     #SpeedRef
END1: NOP 0

   // Network 2: scale and output
      L     #SpeedRef
      L     16384
      *I
      T     PQW 288

From the field: "If you use a static variable, don't forget to change the value after restart of the plc. Otherwise your speed will be the old value, when you declare the value as a retain variable." Decide explicitly whether SpeedRef is to survive a warm restart. If yes, declare it with the RETAIN attribute; if not, leave it non-retained so that a CPU restart returns the inverter to a safe zero reference.

Solution C — Pass the DB Word Directly as an FC Input/Output

If you want to keep FC91 as an FC, eliminate the local TEMP entirely. Promote the speed reference to a formal parameter of FC91 and pass the DB word's symbolic name from OB1's call:

// FC91 declaration
VAR_INPUT
   SpeedRef : INT;      // pass DBW from OB1
END_INPUT

OB1 call:

      CALL  FC91
         SpeedRef := DB_SpeedRef.ActualSpeed

FCs only see their input parameters as pointers to the actual argument; the value is fetched from the DB word each time the input is read, so the speed reference is always current and there is no TEMP to lose. This is the cleanest design for a stateless function.

Verification Steps

  1. Go online and open FC91 / FB91 in monitor mode. Confirm the source instruction (move, load, or FB input assignment) is highlighted each cycle.
  2. Force the sensor input TRUE via Monitor/Modify → Modify with Trigger set to Trigger now. Watch the inverter's PQW track the scaled value within one OB1 cycle.
  3. Use VAT to trend the inverter setpoint, the DB source word, and (in the FB variant) the instance DB's SpeedRef. All three should be non-zero and stable while the sensor is held.
  4. Toggle the sensor off and on repeatedly. Confirm the high-speed reference is re-applied on every rising edge (FB case) or continuously (FC with unconditional move case).
  5. Power-cycle the CPU (STOP → RUN, or full restart) and verify the expected retention behaviour:
    • FC: speed reference must come from the DB unconditionally.
    • FB non-retained STAT: resets to zero on restart — inverter returns to a safe reference.
    • FB RETAIN STAT: holds last value across restart — confirm this is intentional.
  6. Run a PLCSIM simulation with S7-PLCSIM if available: load the program, trigger the input, and trace the tag values in the PLCSIM VAT to confirm logic before deploying to the real machine.

Common Pitfalls and Field-Engineering Caveats

  • Stale TEMP reads from a previous OB. When the L-Stack is consumed by a higher-priority OB (e.g., OB35 cyclic interrupt), the bytes that FC91 used last cycle can be overwritten. The classic symptom is that #TEMP8 occasionally holds a value from another block. Move every cycle to avoid this.
  • Implicit initialisation by STEP 7. Some STEP 7 versions initialise declared TEMPs to zero on first call after download. Engineers sometimes mistake this for "the value being preserved" — it is not.
  • Multi-instance FBs and shared STAT. If you have nested FBs and the inner FB is called more than once in a cycle, STATs accumulate calls incorrectly unless you use multi-instance capability (FB with STAT of inner FB type).
  • RETAIN vs. non-RETAIN. A RETAIN'd STAT will keep its last value after a warm restart, including any value that was unsafe at the moment of power loss. Always confirm with the safety review whether this is acceptable for the drive train.
  • Compiler warnings on unused TEMP. STEP 7 will warn on TEMPs that are declared but never written. The warning is a clue: a TEMP that is read but never written is the exact bug class being discussed here.
  • Watchdog timeouts when an initialising network is moved inside a deeply nested branch — the move gets skipped and the FC reads garbage, but the OB1 cycle is still within the watchdog, so you get no diagnostic event. Always place the initialiser at the absolute top of the FC body.

Best-Practice Pattern for S7 FCs and FBs

  • Treat TEMP as transient scratch. Never store a value that must outlive a single scan in TEMP. Use the formal parameters (IN/OUT/IN_OUT) or — for state that must be latched — use STAT in an FB.
  • Initialise TEMP at the top of the block in a dedicated Network 1 labelled // initialise locals. This makes the lifecycle explicit and survives code reviews.
  • Prefer FB over FC for any block that has state (latches, edges, last-value memory, mode flags). Use FC for purely combinational transforms (scaling, conversion, math).
  • Declare FB STATs with the appropriate retention class. Default non-retained for safety-critical setpoints; explicitly retained only when called out in the design spec.
  • Use symbolic addresses and avoid the #TEMPn numbering convention. Rename to #SpeedRef, #ScaledValue, etc., so the next engineer immediately sees the intent.
  • Write to a TEMP only once per scan, at the top, and read it only below that point. Multiple writes to the same TEMP obscure the data flow and make online debugging ambiguous.

Reference: TEMP / STAT / IN Storage-Class Summary

Class Allowed In Storage Initialised When Retained? Visible Outside Block?
TEMP OB, FB, FC L-Stack (volatile) Unspecified on each call No No
STAT FB only Instance DB (DI) At instance DB generation / restart Optional (RETAIN attribute) No (only through the FB)
IN FB, FC Caller-supplied (pointer in L-Stack) By caller before call Yes (from caller's value) No
OUT FB, FC Caller-supplied (pointer in L-Stack) Caller reads after call Yes (to caller) No
IN_OUT FB, FC Caller-supplied (pointer in L-Stack) Caller reads and supplies Yes (read-modify-write) No
VAR_TEMP (SCL) Same as TEMP L-Stack Unspecified on each call No No
VAR (SCL STAT) FB only (as SCL) Instance DB At instance DB generation Optional No

FAQ

Why does my Siemens S7 FC lose the value in #TEMP8 between scans?

TEMP variables in S7 FCs (and the TEMP row of any block) are stored in the CPU's local data stack and are undefined at the start of every block call. If you only write to #TEMP8 on a conditional edge, every subsequent scan reads garbage. Either initialise the TEMP unconditionally at the top of the FC, convert the FC to an FB and use a STAT variable, or pass the DB word directly as an FC input parameter.

Should I convert FC91 to an FB to fix the high-speed reference problem?

Usually yes. An FB's STAT variables live in the instance DB and are retained across scans, which matches the lifecycle a latched speed reference needs. Generate an instance DB (e.g., DB91) and declare the speed reference as STAT instead of TEMP. If the speed must survive a CPU restart, mark the STAT as RETAIN; otherwise leave it non-retained so the inverter returns to a safe zero reference.

How do I monitor TEMP variables in STEP 7 to confirm the bug?

Open the FC online and watch the variable in the block's monitor pane. You will see #TEMP8 take the correct value during the scan where the move executes, then drop to zero, ???, or a stale value on the next scan. Cross-check with a VAT on the DB source word, the sensor input, and the inverter's PQW. The pattern — DB correct, sensor correct, PQW always zero, TEMP8 intermittent — confirms the TEMP-lifecycle root cause.

Can I keep #TEMP8 inside an FC and make it retain its value?

No. The TEMP storage class is, by definition, non-retained. The only ways to make a value persist across FC calls are: (a) move the source DB word into the TEMP unconditionally on every call, (b) promote the value to a STAT in an FB with an instance DB, (c) hold the value in a Merker (MW) or global DB word, or (d) pass the DB word directly through an FC IN/OUT parameter so no local is needed.

Will a PLC warm restart clear my FB STAT if I use RETAIN?

No — that is the point of RETAIN. A RETAIN'd STAT keeps its last value across a warm restart (power loss with backup battery OK, or STOP→RUN on S7-300/400). If the retained value is not a safe default for your inverter, leave the STAT non-retained so a restart returns the drive to zero reference. The choice must be explicit in your design spec and verified in your safety review.

Back to blog