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 detailsCaddr=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 |
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 |
4. Root Cause: Off-By-One in the Insertion-Sort WHILE Loop
The original FB 5 contains three conceptual sections, all in SCL:-
Build the working arrays. Two
FORloops with control variable#irunning from1toКоличество_Вентиляторов. The FOR loop is safe because the start, end, and step values are static and within 1..N. -
Insertion sort by Моточасы (motor hours). An outer
FORloop with#i := 2 TO Количество_Вентиляторов, plus an innerWHILEloop:
WHILE (#j >= 1) AND (Сортировка_Моточасы[#j] > Буфер_Моточасы) DO ... #j := #j - 1; END_WHILE; -
Priority assignment and command dispatch. A
FORloop iterates the sorted priority list and starts/stops fans based on ready/auto flags and reserve count.
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 theWHILE 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 theWHILE 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 > 1instead 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#jare 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.
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 hasGET_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) |
"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:-
Validate
Количество_Вентиляторовat block entry. AIF (Количество_Вентиляторов < 1) OR (Количество_Вентиляторов > UPPER_BOUND(Сортировка_Индекс)) THEN RETURN; END_IF;guards the entire block against mis-sized data. -
Use the runtime functions
LOWER_BOUNDandUPPER_BOUNDin the loop bounds. Replacing literal1andКоличество_ВентиляторовwithLOWER_BOUND(arr, 1)andUPPER_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. -
Wrap the dispatch logic in its own FB with explicit error output. The error code from
GET_ERRORcan 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. -
Add a
CPU_DIAGread 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. -
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:- Open Online & diagnostics on the CPU. Read the diagnostic buffer. Note the Caddr, area, access type, and block number.
- 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.
- 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. - Open the watch table FB5_sort_test, monitor the priority array, the working sort arrays, and the loop indices
#i,#j. Force#j = 0on the failing iteration to confirm the error message in the buffer. - 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. |
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.