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.
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
0after aLinstruction 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
TEMPinstance 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.
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:
- 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.
- 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. - 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:
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
- Open the FB online in STEP 7 or TIA Portal and place the failing instance on the call stack.
-
Monitor AR2 in the STL "Monitor / Modify" view. Confirm it points to the start of the expected instance DB (
DBBxwith offset0.0). -
Set a breakpoint on the network containing the failing
Linstruction. Step through and observe ACCU1 and AR2 before and after each line. -
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. -
Search the FB for
LAR2,+AR2,-AR2,TAR2,CAR. Every modification must be paired with a save and a restore. -
Build a minimal test FB: copy the failing networks into a new FB, replace
TEMPwithSTAT, add theTAR2/LAR2pair, 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:
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 |
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.
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
RETAINorSETattribute as needed, but do not assume the absolute offset matches the source order. - Use
SLICEaccess 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
-
Static review: confirm every
LAR2/+AR2is paired with a save and a restore. Confirm no retained value is declared asTEMP. - PLCSIM reproduction: re-create the failing sequence in PLCSIM with the same instance DB and force the same inputs. The reproduction is the test.
- 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.
- 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.
- 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.
- 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.
- 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.