Siemens S7 AR1/AR2 Reuse in FBs: Fix Accumulator 0 Error

David Krause13 min read
SiemensTIA PortalTroubleshooting
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 Summary

When using AR1 and AR2 address registers more than once inside the same network of a Siemens S7 Function Block (FB), the first load or transfer operation may appear to succeed, but subsequent loads leave Accumulator 1 (ACCU1) stuck at 0. Indirect addressing via [AR2, P#0.0] returns either zero or arbitrary data, and any counter or pointer declared as TEMP silently resets on every call. The FB compiles cleanly, the CPU does not enter STOP, and the diagnostic buffer stays empty. The fault only surfaces at runtime, often as motion not happening, setpoints not being written, or counters that never increment past one.

This class of bug is one of the most common traps when engineers new to STL write pointer-based code inside FBs. It is rooted in the role of AR2 as the instance DB pointer and in the lifecycle rules of TEMP variables. The two symptoms usually appear together because they share the same misuse pattern: treating AR2 (or a TEMP counter) as a free scratchpad inside the FB.

Affected platforms: S7-300, S7-400, S7-1200 (in STL), WinAC, and S7-1500 with non-optimized blocks. In TIA Portal with optimized blocks the compiler restricts AR1/AR2 manipulation, but the underlying mechanics remain identical for any code that escapes into STL or uses absolute addressing.

2. Symptoms and Failure Modes

The hallmark symptom is a network where the same AR1 or AR2 register is referenced twice — once for "scratch" work and once for normal instance-DB access — and the second access silently returns zero or stale data. Typical field reports:

  • ACCU1 remains 0 after a L instruction that should have loaded a value.
  • PQD [AR2, P#0.0] writes go to the wrong peripheral output word or no output at all.
  • A counter increments on the first FB call and never again because its TEMP instance is re-initialized.
  • Symbolic reads inside the same FB return correct values, but indirect absolute reads return zero.
  • Behavior changes unpredictably between online modify, full download, and PLC restart.
  • Reproduction depends on the optimization attribute of the block or on firmware version.

Minimal Reproduction

Inside FB1, declare a TEMP DINT counter. In Network 1, increment it. In Network 2, use AR2 to address a PQD and read back the counter indirectly with T PQD [AR2, P#0.0]. On the first scan the counter reads 1; on every subsequent scan it reads 0 and ACCU1 never updates — exactly the symptom reported.

3. Root Cause: AR1/AR2 Architecture and the Instance DB Pointer

The S7 CPU exposes two 32-bit address registers, AR1 and AR2, used for pointer arithmetic in STL. In addition to their scratch role they carry reserved meaning inside an FB:

Register Reserved meaning inside an FB What happens if you overwrite it without save/restore
AR1 Used by the compiler for indirect DI/DO access in several constructs; its base also participates in fully qualified symbolic access patterns. Compiler errors in some versions; runtime access to wrong instance data in others.
AR2 Holds the instance DB offset — the start address of the currently active instance DB. The CPU consults AR2 on every symbolic or fully qualified DI access inside the FB. All subsequent DI reads/writes resolve to the wrong DB word, producing zeros, garbage, or writes to a different DB. ACCU1 may appear to "stay at 0" because the load operand is now resolved against a corrupted DB base.

When you write LAR2 P#10.0 or +AR2 inside an FB without first saving the original value and later restoring it, you silently destroy the link between the FB code and its instance data. The CPU does not raise a fault because the address space is still valid — it just points to the wrong place.

FB Code (STL) L, T, +, - LAR2 / +AR2 T PQD [AR2,P#0.0] AR2 Register Instance DB base Instance DB STAT: counters, state TEMP: scratch only IN / OUT / STAT layout AR2 is the bridge between FB code and instance DB; corrupting it breaks symbolic access

4. Why Accumulator 1 Stays at 0

The accumulator pair (ACCU1, ACCU2) is where the CPU stages operands for every arithmetic, comparison, and load/transfer operation. When you execute L with an operand that resolves to a memory location, the CPU fetches the value at the address computed from the operand and AR2 (in DI mode). If AR2 has been corrupted:

  1. The computed address points outside the instance DB, into memory that is not initialized, or into a DI/DO region the current block has no rights to.
  2. The CPU loads the word at that address into ACCU1. If the address lands in a region that reads as zero — beyond the DB length, or in a peripheral range — ACCU1 becomes 0.
  3. The next instruction may shift, mask, or compare this zero, hiding the fact that AR2 was the actual culprit.

From the programmer's perspective the symptom is "ACCU1 stays at 0 after L", but the real cause is "AR2 no longer points at the instance DB". The fix is to ensure AR2 is correct at the moment the L instruction executes.

5. The TEMP Variable Trap

The second half of the bug — a counter that never increments — is unrelated to AR1/AR2 but commonly appears in the same block because engineers use TEMP variables as scratch counters while also abusing AR2. The rule is:

TEMP variables are re-initialized on every FB call. Their initial values come from the declaration, not from their value on the previous call. If you need a value to persist across calls (a counter, a state flag, a previously computed pointer), declare it as STAT, not TEMP.

For S7-1500 with optimized blocks, the compiler often refuses a TEMP declared as a counter that is meant to retain. For S7-300/400 or non-optimized blocks the compiler is silent and the bug only shows at runtime — exactly as observed in the reproduction case.

6. Diagnostic Procedure

  1. Open the FB online in STEP 7 or TIA Portal and place the failing instance on the call stack.
  2. Monitor AR2 in the STL "Monitor / Modify" view. Confirm it points to the start of the expected instance DB (DBBx with offset 0.0).
  3. Set a breakpoint on the network containing the failing L instruction. Step through and observe ACCU1 and AR2 before and after each line.
  4. Inspect the instance DB online. If a counter is declared as TEMP, it will not appear in the instance DB at all — that is your confirmation that it is being re-initialized.
  5. Search the FB for LAR2, +AR2, -AR2, TAR2, CAR. Every modification must be paired with a save and a restore.
  6. Build a minimal test FB: copy the failing networks into a new FB, replace TEMP with STAT, add the TAR2 / LAR2 pair, and run it in PLCSIM. The reproduction is the test.

7. Solution: Save/Restore AR2 Pattern with STL Examples

The standard pattern when AR2 must be reused inside an FB is to save it before mutation and restore it immediately after the work that needed the alternate pointer:

FB Entry TAR2 — save AR2 to ACCU1 LAR2 P#... — modify AR2 Do work using AR2 LAR2 — restore AR2 from ACCU1 FB Exit

Example: PQD write with AR2 offset

// Network 1 — write a scaled value to a peripheral output using AR2
TAR2                          // ACCU1 := AR2 (instance DB pointer)
LAR2  P#0.0                   // AR2 points at peripheral area
L     #scaled_value           // ACCU1 := scaled value
T     PQD [AR2, P#0.0]        // write to PQD at offset 0
LAR2                          // restore original AR2 from ACCU1

Note that ACCU1 is used as the scratch slot for the saved AR2. If you need a value in ACCU1 across the save/restore, push ACCU1 to ACCU2 first or stash it in a STAT word:

// Network 2 — preserve ACCU1 across save/restore
L     #input_value            // ACCU1 := input
TAR2                          // ACCU1 := saved AR2
LAR2  P#0.0                   // AR2 := peripheral base
T     PQD [AR2, P#0.0]        // write ACCU1 (input_value) to PQD
LAR2                          // restore AR2; ACCU1 is now saved AR2
L     #input_value            // reload working value

The alternative — pushing ACCU1 to a TEMP — is only safe if the TEMP is consumed before any other code touches it. Most engineers prefer the TAR2 / LAR2 pair because it is symmetric and obvious to a reviewer.

Saving and restoring both registers

// Network 3 — paired save/restore of AR1 and AR2
TAR1                          // ACCU1 := AR1
TAR2                          // save ACCU1 to local STAT; then ...
//   work that mutates AR1 and AR2
LAR1                          // restore AR1
LAR2                          // restore AR2

If the FB is single-instance and the instance DB never changes, AR2 can be assumed stable at entry; but if any network mutates it, the save/restore must be local to that network, not deferred to FB end.

8. Solution: Use STAT Instead of TEMP for Retained Counters

Convert any variable that must retain its value across calls to STAT:

Before (failing) After (correct)
VAR_TEMP
  COUNTER3_DINT : DINT;
END_VAR
VAR
  COUNTER3_DINT : DINT;
END_VAR
Re-initialized on every FB call. Counter resets to 0 before each scan. Stored in the instance DB. Counter persists across calls and across STOP→RUN transitions for non-volatile markers.

For S7-1500 optimized blocks, VAR_TEMP is reserved for truly transient values; VAR is automatically mapped to instance data and persists across calls. For S7-300/400, STAT and VAR_TEMP have the same storage semantics with respect to retention; the difference is that STAT participates in the instance DB layout and is therefore online-visible.

Multi-instance note: If you call the FB as a multi-instance inside another FB, the STAT area is part of the parent's instance DB and is fully retained across calls. Treat STAT as your default storage class for any value that survives between calls.

9. TIA Portal Optimized vs Non-Optimized Considerations

TIA Portal's optimized block access hides the physical offset of each variable, so direct AR1/AR2 manipulation on STAT variables is no longer reliable — the compiler may reorder fields for alignment. If you must use AR2-based absolute addressing inside an optimized FB:

  • Declare the relevant region with the RETAIN or SET attribute as needed, but do not assume the absolute offset matches the source order.
  • Use SLICE access or symbolic tags where possible, and reserve AR2 for peripheral or non-instance data.
  • Switch the FB to "non-optimized" (standard) access if your design relies on a fixed layout. Note that this disables several S7-1500 advantages and triggers download-in-run considerations.

For S7-1500 with optimized blocks, prefer SCL constructs (MOVE_BLK, DWORD_TO_INT, PEEK / POKE for peripheral access) over manual AR2 arithmetic. Reserve AR2 / STL manipulation for code that must mirror a legacy S7-300/400 FB. The official STEP 7 and TIA Portal programming manuals describe these constructs in detail on the Siemens Industry Online Support portal at support.industry.siemens.com.

10. Verification and Commissioning

  1. Static review: confirm every LAR2 / +AR2 is paired with a save and a restore. Confirm no retained value is declared as TEMP.
  2. PLCSIM reproduction: re-create the failing sequence in PLCSIM with the same instance DB and force the same inputs. The reproduction is the test.
  3. Online watch: open the instance DB online and verify that the STAT counter increments correctly and that AR2 holds the expected value before and after every modified network.
  4. HMI cross-check: bind the STAT counter to an HMI tag and watch it advance. If it stays flat, the value is still TEMP or local stack.
  5. STOP / RUN cycle test: with S7-300/400, observe whether the counter survives a STOP→RUN transition; if it does not, the value is not in the instance DB and is therefore TEMP or local stack.
  6. Buffer analysis: verify the diagnostic buffer is clean. A clean buffer plus a "stuck at 0" symptom is itself diagnostic — it rules out hardware faults and points firmly at logic.
  7. Cross-version test: if the FB is portable, download it to both S7-300/400 and S7-1500 firmware variants and confirm identical behavior. Variants that disagree usually expose a hidden assumption about AR2 layout or optimization.

11. Common Pitfalls and Best Practices

Pitfall Symptom Fix
Saving AR2 once at FB start and restoring once at FB end. Any early RETURN or BEC skips the restore; later networks see corrupted AR2. Save / restore within each network that mutates AR2, or use a try/finally pattern with a local stack.
Restoring AR2 only at the end of the FB. Mid-FB symbolic reads return wrong data. Restore immediately after the work that needed the alternate AR2.
Declaring counters as TEMP "because they are only used locally". Counter resets to its initial value every call. Promote to STAT (or to VAR on S7-1500).
Using L P#... in FBs without considering that AR2 is implicit. Loads always return 0. Use absolute L DIB n or L DIW [AR2, P#0.0], then ensure AR2 points at the instance DB.
Mixing DI (instance) and DB (open DB) inside the same FB. Symbolic and absolute access refer to different DBs. Use one or the other consistently, and reopen the open DB if you switch.
Assuming AR1 / AR2 are cleared on FB entry. Code that "worked in OB1" fails in FB1. Initialize AR1 / AR2 explicitly if you depend on a known state.
Using LAR2 with a constant that lands in peripheral space, then returning to OB1 without restoring. Subsequent calls in OB1 read wrong data. Always restore AR2 before leaving the FB, even if the work succeeded.

Golden rule: Inside an FB, treat AR2 as read-only. If you must mutate it, save first with TAR2 and restore last with LAR2 in the same network. Outside an FB (in OB1, FC, or an FB with no instance DB) AR1 and AR2 are yours to use freely — but only because the instance-DB bridge is not present.

12. Frequently Asked Questions

Why does Accumulator 1 stay at 0 after L inside my FB?

Most likely AR2 has been overwritten earlier in the network. Because AR2 holds the instance DB pointer that the CPU uses to resolve DI accesses, a corrupted AR2 makes the L read from the wrong DB word — often a zero. Save AR2 with TAR2 before you mutate it and restore with LAR2 after.

Is AR1 also protected inside an FB?

AR1 is not the instance DB pointer, but the compiler and certain instructions assume it points to valid indirect-access data. Modifying AR1 without save / restore can also corrupt subsequent indirect reads. Apply the same TAR1 / LAR1 discipline.

My counter is declared but never increments. Where is it?

If the counter is declared as TEMP, it does not persist across FB calls — STEP 7 re-initializes it on entry. Change the declaration to STAT (or to VAR on S7-1500) and verify the counter now advances in the online instance DB.

Does TIA Portal's optimized block access eliminate this issue?

Partially. Optimized access prevents you from assuming absolute offsets, so accidental AR1 / AR2 misuse is rarer in pure SCL. However, any STL block — even inside an optimized FB — still uses AR2 as the instance pointer, and the same save / restore discipline applies.

Can I use AR2 inside a multi-instance FB?

Yes, with the same rules. AR2 holds the start of the current instance regardless of whether the instance is its own DB or part of a multi-instance parent. Save / restore is required if you mutate AR2 for any other purpose.

Is there a CPU or firmware version that fixes this automatically?

No. The CPU behavior is defined by the instruction set and is consistent across S7-300 / 400 / 1200 / 1500 firmware. The fix is always in the application code: save / restore AR2 and declare persistent values as STAT.

Back to blog