S7-1200 Area Length Error FB5: Resolving ID 2522 Array Bounds

David Krause14 min read
S7-1200SiemensTroubleshooting
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

S7-1200 Area Length Error in FB 5: Resolving ID 2522 Array Out-of-Range

1. Problem Overview

A Simatic S7-1200 CPU 1214C DC/DC/DC programmed with TIA Portal V16 raises a temporary diagnostic message during cyclic OB 1 execution: "Area length error in FB 5 - affecting OB 1 execution - read access Volatile DB area - Incorrect address, operand replaced - Processing will continue (no OB processing)". The PLC is still online, OB 1 is still being executed, and the operands involved are silently replaced by substitutes, but the application logic produces incorrect results. The CPU diagnostic buffer records the violation with internal address details Caddr=16#000001C8, area identifier Volatile DB area, and absolute address 4286579296. The same error is caught inside the FB by the SCL GET_ERROR instruction with local error ID 2522 (decimal) / 16#09DA. The user-supplied routine is an insertion-sort fan-priority selection written entirely in SCL, using 1-based arrays sized to the configured Количество_Вентиляторов (Number of fans). The diagnostic problem is not a corrupted DB, a wrong PLC, or a project inconsistency. It is a classic off-by-one bounds violation that occurs in the WHILE loop used by the sort step. This article documents the failure mode, walks through the exact Caddr/operand-decoding procedure, presents the corrected sort pattern, and verifies the fix in the TIA Portal watch table and PLC trace.

2. Hardware and Software Environment

Parameter Value
CPU SIMATIC S7-1200, 6ES7214-1AG40-0XB0 (CPU 1214C DC/DC/DC)
Firmware V4.4 or later (typical for TIA Portal V16 project)
Work memory 100 KB program / 4 MB load memory (standard variant)
Bit memory / DB 8192 B M, 2048 B process image, optimised/non-optimised DBs both supported
Engineering tool TIA Portal V16, SCL editor for S7-1200/1500
Triggered block FB 5 (insertion sort + priority assignment)
Catch block OB 1 (cyclic main) - error logged but execution continues
Error code caught GET_ERROR returns 16#09DA / decimal 2522
The 1214C DC/DC/DC has the standard S7-1200 operand area model. The S7-1200 System Manual (entry ID 109741593) defines the same volatile read/write semantics for instance-DB temporaries as for local stack: while OB 1 continues to run, the read result is replaced by 0 (or the defined substitute value) and the diagnostic event is written to the diagnostic buffer. This is why the application does not stop, but the resulting sort array contains corrupted indices.

3. Decoding the CPU Diagnostic Buffer

The diagnostic event in this fault is decoded as follows:
Field Value Interpretation
Event Area length error during read access CPU tried to read a value that lies outside the operand range
Affected block FB 5 The function block containing the bad array index
Affecting OB OB 1 Cyclic main, so the error fires every scan that the routine is called
Area Volatile DB area Temporary or instance variables inside the FB's instance DB
Caddr 16#000001C8 (456 dec) Byte offset inside the accessed operand area; corresponds to array element 0 of a 1-based ARRAY[*] of DINT at byte 456 of the instance DB
Absolute address (decimal) 4286579296 Full absolute byte address on the S7-1200 address bus (PII/PIQ/M/T/DB namespace combined); used internally for the diagnostic event only
Replacement "Incorrect address, operand replaced" CPU substituted 0 and continued - the worst kind of failure for a control application
The combination "Caddr 0x1C8 in a Volatile DB area + read access" uniquely points to a read of an element with index 0 inside an ARRAY that was declared with lower bound 1. The Caddr of 0x1C8 is the byte offset of element [0] if the array starts at offset 0x1C8 in the instance DB; on a DINT array that is element 0 at offset 0x1C8, element 1 at 0x1CC, element 2 at 0x1D0, and so on. Because the array was declared 1..N, the read at index 0 falls outside the valid index range and the CPU raises the area length error.

4. Root Cause: Off-By-One in the Insertion-Sort WHILE Loop

The original FB 5 contains three conceptual sections, all in SCL:
  1. Build the working arrays. Two FOR loops with control variable #i running from 1 to Количество_Вентиляторов. The FOR loop is safe because the start, end, and step values are static and within 1..N.
  2. Insertion sort by Моточасы (motor hours). An outer FOR loop with #i := 2 TO Количество_Вентиляторов, plus an inner WHILE loop:
    WHILE (#j >= 1) AND (Сортировка_Моточасы[#j] > Буфер_Моточасы) DO ... #j := #j - 1; END_WHILE;
  3. Priority assignment and command dispatch. A FOR loop iterates the sorted priority list and starts/stops fans based on ready/auto flags and reserve count.
The bug lives in section 2. The WHILE condition is checked before the array read Сортировка_Моточасы[#j] due to short-circuit evaluation of SCL's AND, but the comparison #j >= 1 is still true when #j reaches 0 on the previous iteration. On the next pass through the loop body, the statement Сортировка_Моточасы[#j + 1] := ... writes index 1, and then #j := #j - 1 decrements to 0. The condition is re-evaluated: #j >= 1 is now false, so the loop exits cleanly only if the previous body did not read Сортировка_Моточасы[#j] with #j = 0. In the failing build, however, the body of the WHILE contains: ```scl #Сортировка_Индекс[#j + 1] := #Сортировка_Индекс[#j]; #Сортировка_Моточасы[#j + 1] := #Сортировка_Моточасы[#j]; ``` These are write accesses to Сортировка_Моточасы[#j] with #j = 0 on the final iteration, which is a write to element 0 of a 1-based array. The CPU's area-length checker sees the write to an element that is below the lower bound, raises the area length error, replaces the operand with 0, and continues. On the next call to FB 5, the same fault fires, polluting the diagnostic buffer. A second aggravating factor: the routine is also executing the post-loop statement Сортировка_Индекс[#j + 1] := Буфер_Индекс; after the loop exits, and a third aggravating factor is the uninitialised access of Сортировка_Моточасы[0] if the WHILE condition is reordered. The safest pattern is to never let #j reach 0 inside the loop, which is achieved with a strict greater-than check on the first iteration or by reordering the conditions.

5. Why the Error Class Is "Read" and Not "Write"

The CPU's diagnostic message says read access. The Siemens support entry "Area length error when writing" clarifies that the same error class is raised for any operand reference whose address is not wholly inside the permitted operand area, and the CPU distinguishes read from write based on the actual access direction at the failing instruction. In this FB the WHILE condition first performs a read of Сортировка_Моточасы[#j] while #j = 0, which is what the CPU reports. The subsequent writes raise a similar event with the write class, and you will see both in the buffer if you let the routine run for a few scans.

6. Concrete Bug Demonstration (Minimal Reproducer)

To reproduce the failure deterministically, set a watch on Сортировка_Моточасы with Monitor all enabled, set Количество_Вентиляторов = 4, and force the inputs so that the initial order of Моточасы is [10, 30, 20, 5]. The diagnostic buffer entry fires on the third execution of the inner WHILE loop for the outer index #i = 3, because that is the iteration where the descending run crosses element 0. The buffer entry references Caddr 16#000001C8 and the same absolute address 4286579296 every time. GET_ERROR in the priority-dispatch step returns 16#09DA / 2522 with ERROR_LANGUAGE := LANG_FB_LANG_ERROR set and the failing block path FB5 / Instance-DB.

7. Correct SCL Insertion-Sort Pattern (1-based, Safe)

The fix is to (a) reorder the WHILE condition so that the read happens after the bounds test, (b) drop the post-loop [#j + 1] access from the loop body, and (c) guarantee that #j never becomes 0 inside the loop. Use the following canonical pattern: ```scl // Build working arrays FOR #i := 1 TO "Количество_Вентиляторов" DO #Сортировка_Индекс[#i] := 0; #Сортировка_Моточасы[#i] := 0; END_FOR; FOR #i := 1 TO "Количество_Вентиляторов" DO IF (#Готовность[#i] = 1) AND (#Авто_Режим[#i] = 1) THEN #Сортировка_Индекс[#i] := #Индекс[#i]; #Сортировка_Моточасы[#i] := #Моточасы[#i]; END_IF; END_FOR; // Insertion sort: stable, 1-based, no off-by-one FOR #i := 2 TO "Количество_Вентиляторов" DO #Буфер_Индекс := #Сортировка_Индекс[#i]; #Буфер_Моточасы := #Сортировка_Моточасы[#i]; #j := #i - 1; // Read of #Сортировка_Моточасы[#j] is guarded by a strict #j > 1 test WHILE (#j > 1) AND (#Сортировка_Моточасы[#j] > #Буфер_Моточасы) DO #Сортировка_Индекс[#j] := #Сортировка_Индекс[#j - 1]; #Сортировка_Моточасы[#j] := #Сортировка_Моточасы[#j - 1]; #j := #j - 1; END_WHILE; // Now #j is either 1 or the position of the first element // not greater than the buffer. The final write is always valid. #Сортировка_Индекс[#j] := #Буфер_Индекс; #Сортировка_Моточасы[#j] := #Буфер_Моточасы; END_FOR; #Приоритет := #Сортировка_Индекс; ``` Three things changed:
  • The lower bound of the WHILE is #j > 1 instead of #j >= 1. Element 1 is the smallest index that can still be compared, so we never read Сортировка_Моточасы[0].
  • The body uses [#j - 1] instead of [#j + 1]. The shift direction is now explicit: the elements above #j are shifted up by writing to #j, not by writing to #j + 1. This matches the standard insertion-sort invariant.
  • The post-loop write is to #j (which is 1 or more), not to #j + 1. The buffer value is dropped into the correct slot without an out-of-range write.
Note: Do not use FOR #i := 1 TO 0 DO or any pattern that lets the loop control variable equal the lower bound of a 1-based array. SCL evaluates the loop start, end, and step once and counts down by the step; off-by-one mistakes there are the most common source of area length errors in S7-1200/1500 SCL code.

8. Using GET_ERROR to Localise the Failure (ID 2522)

The user-supplied FB already has GET_ERROR(#Err) on the post-sort path. The GET_ERROR instruction is documented in the TIA Portal Help (entry SCL programming for S7-1200/1500) and returns a ErrorStruct with fields ERROR_ID, FLT_ID, ERROR_LANGUAGE, BLOCK_NUMBER, and so on. For the fault in this article, ERROR_ID is 16#09DA and the mapped local error ID is decimal 2522. The mapping is:
ERROR_ID (hex) ERROR_ID (dec) Meaning (TiaPortal Help / S7-1200 System Manual)
0x0000 0 No error
0x09DA 2522 Operands area length error - read access outside permitted range
0x09DB 2523 Operands area length error - write access outside permitted range
0x09C4 2500 DB not loaded / not present
0x09C5 2501 Wrong block type / access to deleted block
0x09DD 2525 Pointer / parameter error
0x09E3 2531 DB index error (optimised block access)
If you only see 2522 but never 2523, the failing instruction is a read; if you see 2523, it is a write. The CPU diagnostic buffer class label matches the GET_ERROR result, which is a quick way to confirm the direction. The PLC tag "Instance_DB".Err.ERROR_ID is the canonical place to capture the value for a historian or for HMI display.

9. Additional Hardening for the FB

Once the sort loop is fixed, harden the FB so the same class of failure cannot recur:
  1. Validate Количество_Вентиляторов at block entry. A IF (Количество_Вентиляторов < 1) OR (Количество_Вентиляторов > UPPER_BOUND(Сортировка_Индекс)) THEN RETURN; END_IF; guards the entire block against mis-sized data.
  2. Use the runtime functions LOWER_BOUND and UPPER_BOUND in the loop bounds. Replacing literal 1 and Количество_Вентиляторов with LOWER_BOUND(arr, 1) and UPPER_BOUND(arr, 1) makes the code resilient to a future array resize. This is the same pattern recommended in the SCL manual entry SCL programming for S7-1200/1500.
  3. Wrap the dispatch logic in its own FB with explicit error output. The error code from GET_ERROR can be assigned to the FB's Error output so that the HMI / SCADA logs the failure with a time stamp. The S7-1200 system manual describes how to expose Output parameters on optimised blocks.
  4. Add a CPU_DIAG read at the start of OB 1 to clear the diagnostic buffer in test mode only. This is for commissioning; in production leave the buffer intact.
  5. Configure the instance DB as optimised in the FB properties. The S7-1200 generates symbol-correct offsets and the area-length error class becomes an explicit DB index error (ERROR_ID 2531) that is easier to interpret.

10. Diagnostic Tools in TIA Portal V16

The standard triage sequence for an S7-1200 area length error is:
  1. Open Online & diagnostics on the CPU. Read the diagnostic buffer. Note the Caddr, area, access type, and block number.
  2. In the editor, right-click the FB and choose Open in SCL debugger. Set a breakpoint on the line referenced in the diagnostic buffer. Monitor the array index with a watch table.
  3. Use PLC > Monitor & force > Force on the input tag Количество_Вентиляторов to reproduce with a small array size such as 2 or 3. Smaller arrays reduce the number of insertions and make the off-by-one visible in a single scan.
  4. Open the watch table FB5_sort_test, monitor the priority array, the working sort arrays, and the loop indices #i, #j. Force #j = 0 on the failing iteration to confirm the error message in the buffer.
  5. After the fix, repeat the same force / monitor flow and verify the diagnostic buffer stays clean across 1000+ scan cycles. The recommended acceptance criterion is "no area length events in the last 10 minutes of operation" - see the S7-1200 system manual entry 109741593.

11. Verification Procedure

To confirm the fix is complete and persistent:
Step Action Pass Criterion
V1 Recompile the FB in TIA Portal V16 and download to the S7-1200 in STOP, then RUN. Download completes without compile errors; CPU goes to RUN.
V2 Reset the diagnostic buffer (Online & diagnostics > Clear buffer). Buffer is empty.
V3 Run FB 5 for at least 5 minutes at the rated cycle time. No new "Area length error" event in the buffer.
V4 Watch table: compare Приоритет with the expected order of motor hours. Priority array is monotonically non-decreasing in motor hours.
V5 Force Количество_Вентиляторов = 1 and run for 1 minute. Then force to its maximum value and run for 1 minute. No area length events; GET_ERROR returns ERROR_ID 0.
V6 Check Err.ERROR_ID via HMI tag or trace. 0 throughout the run.
If V3 fails, the SCL code still contains an out-of-range access; repeat the walkthrough of section 4. The most common reason for residual faults is a different sort routine elsewhere in the project that uses the same off-by-one pattern.

12. Frequently Asked Questions

What does CPU error code 2522 mean on a Siemens S7-1200?

ERROR_ID 2522 (16#09DA) is an operand area length error during a read access - the read referenced an address outside the valid range of the operand (array, DB, or tag). On the S7-1200, the CPU records the event in the diagnostic buffer, replaces the operand with 0, and continues execution. The cause is almost always an array index outside the declared lower or upper bound, or a DB index out of range. Cross-check with the S7-1200 system manual entry ID 109741593.

Why does the diagnostic message say "read access" when the sort loop is writing to the array?

The CPU classifies the failing instruction by its actual access direction. In the failing FB 5, the WHILE condition reads Сортировка_Моточасы[#j] first, and that read occurs with #j = 0 on the last iteration. The reads of the array happen before the writes in the loop body, so the first event in the diagnostic buffer is a read. Writes at [#j + 1] produce a separate event with ERROR_ID 2523 (16#09DB) if you let the code keep running. See Siemens support entry 71774 for the write variant.

How do I decode Caddr 16#000001C8 in the diagnostic buffer?

16#000001C8 is 456 decimal. It is the byte offset of the failing operand inside the accessed area. For a 1-based DINT array at instance-DB offset 0x1C8, element 0 would be at byte 0x1C8, element 1 at 0x1CC, and so on. The offset points to an access below the lower bound of a 1-based array, which is exactly the off-by-one pattern in the WHILE loop. The decimal absolute address 4286579296 is the full symbolic address on the S7-1200 address bus and is only used for cross-referencing across the diagnostic entry.

Why did GET_ERROR return 2522 even though the application is still running?

The S7-1200 substitutes 0 for the failing read or write and continues the affected OB. GET_ERROR is a query on the local error state of the calling block; it picks up the latest error in the same call chain and reports it on the ErrorStruct input. The PLC remains in RUN, but the sort output is wrong because element 0 was never written. Capture the error in the FB's output and clear it explicitly in OB 1 to avoid reading stale values on the next scan.

Can I prevent the off-by-one without changing the algorithm?

Yes - use the runtime functions LOWER_BOUND(arr, 1) and UPPER_BOUND(arr, 1) in the loop bounds, and clamp the index with MAX(1, MIN(index, UPPER_BOUND)) before every read. This is the lowest-risk fix because the SCL semantics guarantee a 1-based result that always falls inside the declared array. The same approach is documented for S7-1500 in the SCL manual and works unchanged on the S7-1200. Pair it with the validation block from section 9 for full coverage.

Does the same fix apply to S7-1500 and ET 200SP CPUs?

Yes. SCL semantics are identical across S7-1200, S7-1500, and ET 200SP CPUs that support SCL. The GET_ERROR return values 2522 and 2523 are defined in the same SCL help. The insertion-sort pattern in section 7 is portable without modification. The only platform-specific change is the runtime function set: S7-1500 also exposes PEEK/POKE for byte-level access, which is not available on S7-1200.

Back to blog