1. Problem Overview: Scaling Beyond the Optimized-DB Model
On a SIMATIC S7-1500 programmed with TIA Portal V14 (and subsequent releases), the standard "one Function Block + one Instance DB per motor" pattern breaks down when a machine scales to several hundred drives. A representative scale-out looks like this:
| Resource | Value (typical 572-motor project) | Notes |
|---|---|---|
| Motor FBs | 572 | Each with its own instance DB |
| Comfort HMI screens | 572 | One per motor faceplate |
| Tags per screen | ~7 | State, command, fault, current, hours, etc. |
| Raw HMI tag count | ~4,000 | Exceeds the 2,048 tag ceiling |
The instinctive fix is to declare a pointer P#ptr_DB_HMI, swap it inside a CASE block to point at Motor1_DB, Motor2_DB, ... MotorN_DB, and let the HMI ride on a single mirrored DB. The plan collapses the moment the motor DBs are flagged Optimized — the S7-1500 CPU refuses symbolic pointer arithmetic on optimized blocks because the load memory layout no longer carries a fixed offset table the firmware can decompose into byte/bit/word slices at scan time.
2. Why Optimized DBs Reject Pointer Operations
An optimized DB stores each tag at whatever offset the compiler chooses, ordered for fastest access and minimum RAM footprint. The compiler is free to re-pad, re-align, and reorder elements between compilations. There is no published offset map the user can dereference with P#DBxx.DBXy.z — and there cannot be, because the offsets are not stable.
Concretely, three symptoms appear when you try pointer work on optimized DBs:
-
Compiler error 1A89 / 8025 — "The data type of the actual parameter is not compatible with the data type of the formal parameter" — when an
ANYpointer is passed into an FB that uses an optimized instance. - Compiler error 8022 — "The variable does not exist or the area of the variable is invalid" — when an indirect DB number is used with a symbolic tag.
- Load memory growth without execution benefit — symbolic-only access forces the HMI to read every tag by name, multiplying the OPC UA / S7comm round-trips.
The fix is not to de-optimize the motor DBs (that destroys scan-time performance and symbolic trace). The fix is to introduce a structure the compiler can index natively: an Array DB.
3. The S7-1500 Array DB — the Native Solution
The Array DB is a DB whose only data view is a typed array of a UDT. It is created in one step (Project tree → Add new block → Data block → Array DB) and exists only on the S7-1500/1500T CPU family; the S7-1200 cannot host it. Inside the DB you see a single line such as:
[Array_DB_1].Motor[0..571]
UDT "Motor_type"
From this point, the compiler exposes the array as a true indexed structure. To address a specific motor you write:
// Symbolic indexed access — no pointer needed
#stMotor := "dbMotors".Motor[iIdx];
"dbMotors".Motor[iIdx].bRun := TRUE;
Because the index iIdx is a runtime variable, the same code path serves all 572 motors. The 572 separate instance DBs are gone; you have one DB whose length scales linearly.
4. The THIS Pointer — Multi-Instance Style Without FBs
When you drag an element of an Array DB into an SCL editor, the syntax does not show the path. Instead it shows:
// Inside FC_MotorLogic, "Motor[0]" was dropped into code:
#stLocal := THIS.Motor; // (FBD/FUP) or
THIS.RampDone := TRUE; // boolean slice
THIS is the array-element reference for the duration of the call. It behaves like the implicit INPUT of an FB instance: the block is "self-aware" of which element is active. This is the cleanest way to write a single motor logic routine that the HMI (or any caller) can address by index without ever constructing a pointer.
| Style | Pointer required? | Works on optimized DB? | Recommended for 500+ motors? |
|---|---|---|---|
| FB + instance DB per motor | No | Yes | No (DB explosion) |
| Single DB + P# pointer switching | Yes | No | No |
| Array DB + indexed access | No | Yes | Yes |
| Array DB + THIS reference | No | Yes | Yes (cleanest) |
5. UDT as the Single-Tag Wrapper for the HMI
Comfort Panels and WinCC Runtime Professional will not bind to an ARRAY[..] OF UDT directly — the HMI tag editor exposes only scalar tags. The work-around used in TIA V14 (and unchanged in V15.1, V16, V17) is to wrap the array in a UDT and bind the HMI to a single tag whose data type is that UDT. At runtime the HMI's VBScript or the panel's dynamic dialog references the field by symbolic path.
Define UDT_Motor as the full motor record (state, command, fault word, current, hours, etc.). The Array DB then becomes ARRAY[0..571] OF "UDT_Motor". The HMI gets one tag: HMI_tag = dbMotors.Motor[iHmiIdx] where the index is re-mapped every screen change.
6. The 2,048 HMI Tag Limit — How to Live With It
The Comfort Panel firmware (WinCC V14, part of TIA Portal V14) enforces a hard cap of 2,048 tags per connection on the smaller 4"–9" panels and 4,096 on the 10"–22" models. Exceeding the cap produces the runtime alarm "Tag limit exceeded — connection X" and tags are dropped in the order they were declared. Strategies:
- UDT collapse — bind one tag per UDT instance; the panel uses the index variable to pick which instance.
-
Screen-level multiplexing — open one motor faceplate, drive its
iIdxtag from a navigation list, refresh the UDT on every index change. - Split across panels — install two KP/TP panels, each binding half the array. Useful when the array exceeds the HMI's own element-count cap.
- Promote to WinCC Professional / Unified — Unified Comfort panels (since V16) lift the cap and expose the array index directly; the legacy workaround becomes unnecessary.
7. SCL Code Generation From a Spreadsheet
When the dispatch logic — 257+ CASE branches, or a 572-element IF iIdx = ... ladder — is too long to type, generate it. Build a 3-column spreadsheet:
| A — Constant (yellow) | B — Reference (orange) | C — SCL output |
|---|---|---|
| 0 | Motor_0 | 0: stMotor := "dbMotors".Motor[0]; |
| 1 | Motor_1 | 1: stMotor := "dbMotors".Motor[1]; |
| 2 | Motor_2 | 2: stMotor := "dbMotors".Motor[2]; |
| ... | ... | ... |
| 571 | Motor_571 | 571: stMotor := "dbMotors".Motor[571]; |
Formula in column C (Excel/LibreOffice syntax):
=A2 & ": stMotor := \"dbMotors\".Motor[" & A2 & "];"
Copy column C, paste into the SCL editor. A 572-row dispatch is generated in one operation. The same technique produces a 572-row faceplate tag list for the HMI: each line binds HMI_Faceplate[i].Tag to dbMotors.Motor[i].Field. Maintenance of the motor list becomes a spreadsheet problem, not a TIA Portal problem.
8. Step-by-Step Refactor (TIA V14, S7-1500)
-
Inventory the data. Open the project, list every per-motor tag, group them into a single UDT named
UDT_Motor. Mark bit fields, INT, REAL, and DATE_AND_TIME explicitly — implicit typing in the UDT saves space. -
Create the Array DB. Project tree → Add new block → Data block → "Array DB" type. Choose
UDT_Motoras the array element, set[0..571]. Leave the "Optimized block access" attribute enabled. -
Replace per-motor FBs. Convert the motor FB into a stateless FC that receives
UDT_MotorbyIN_OUT. The FC's internal state moves into the array element itself. The single FC replaces 572 instance DBs. -
Generate dispatch code. Use the spreadsheet trick above to build the
CASE iIdx OFblock in the OB1 (or OB82-grade cyclic OB if the cycle time requires it). -
Re-bind the HMI. In the HMI tag editor, delete the 4,000+ per-motor tags. Create one tag of type
UDT_Motorpointing todbMotors.Motor[iHmiIdx]. Add a separate INT tagiHmiIdxfor the index; drive it from the navigation list. -
Enable "Multiplex tag" on the HMI connection. TIA V14 introduces this property on the HMI connection: it lets the panel switch the symbolic source of a UDT tag at runtime by writing the index variable. Without it the tag is statically bound to
Motor[0]. - Compile and download. Compile the PLC first (so the HMI gets the new tag list), then compile the HMI. The Online > Compare function will report the diff in tag count — a 95% reduction is typical.
9. Verification and Diagnostics
After the refactor, validate three things before sign-off:
-
PLC cycle time. Open Online > Diagnostics > Cycle time. With 572 motors in a single array, expect the cycle time to decrease 10–30% because the compiler no longer has to resolve 572 separate instance DB contexts. If cycle time climbs, an indirect dispatch is looping — look for a
FORthat calls the motor FC inside anotherFOR. - HMI refresh rate. On the Comfort Panel, open the "System diagnostics → Performance" screen. The "Tags / second" counter should remain at or above the previous rate; if it drops, the multiplex tag is being re-pointed more often than the screen-redraw budget allows.
- Optimized-block attribute. In the project tree, right-click the array DB, Properties → Attributes. Confirm "Optimized block access" is ticked. If the compiler silently demoted it to non-optimized (often after a bulk copy-paste from a S7-1200 source), the pointer ban is back and scan time suffers.
| Symptom | Likely cause | Fix |
|---|---|---|
| Compiler 1A89 / 8025 on ANY pointer | DB attribute was reset to non-optimized | Re-tick "Optimized block access" in DB properties |
| Runtime "Tag limit exceeded" on HMI | Multiplex tag not enabled | Connection properties → "Multiplex tag" = Yes |
| All faceplates show Motor[0] data |
iHmiIdx tag not bound to a write-capable PLC tag |
Bind iHmiIdx to a real PLC INT, write from navigation list |
| Array DB option greyed out | CPU is S7-1200, not S7-1500 | Use ARRAY[..] OF UDT inside a standard DB on S7-1200 |
| Cycle time doubled after refactor | FC copies the whole UDT on every call | Pass UDT by IN_OUT (reference) instead of IN/OUT-by-value |
10. Field-Proven Caveats
-
Move-block-variant is not a substitute for a pointer.
MOVE_BLK_VARIANT(and its predecessorBLK_MOV) copies memory, it does not redirect HMI bindings. It is useful for mirroring but does not solve the index-routing problem. - VBScript on the panel is a last resort. WinCC Comfort's VBS can iterate an HMI-side array, but the data still has to come from the PLC by tag. The round-trip cost defeats real-time monitoring; reserve VBS for non-critical navigation logic.
- Security-rated motors lose their instance DB. If a motor is part of a F-runtime / F-CPU safety program, the safety FB is required to own its instance. The Array-DB pattern applies to the standard user program only; safety instances remain one-per-motor by design (SIL 2/3 audit reasons).
- Watch the array upper bound. An array of 572 UDTs of 64 bytes each occupies ~36 KB of load memory. The S7-1500 CPU 1511 has 150 KB of work memory for data — comfortable, but on the CPU 1511-1 PN with bit-oriented safety adders, always check the memory budget after the refactor.
- TIA V14 → V15+ migration. The Array-DB concept survives all subsequent TIA Portal releases unchanged. The "Multiplex tag" property was promoted to a first-class option in V16 and is now under "HMI tag → Settings → Multiplexing".
Why does my S7-1500 reject a P# pointer to a DB marked as optimized?
Optimized blocks do not expose a fixed offset table — the compiler is free to re-pack the data on every build. Symbolic pointer / ANY / P# access requires "Non-optimized block access"; the runtime cannot compute a stable base address otherwise. Convert the design to an Array DB or accept a non-optimized DB and its scan-time penalty.
What is the maximum number of elements in a Comfort Panel HMI tag list?
WinCC Comfort V14 caps internal tags at 2,048 on 4"–9" panels and 4,096 on 10"–22" panels. Each external tag is also counted. The fix is to wrap a UDT and bind a single multiplexed tag, then re-point the tag's index from a navigation list — typical project size drops from 4,000+ tags to a handful.
Can the S7-1200 host an Array DB?
No. The Array DB was introduced in TIA Portal V14 exclusively for the S7-1500 / 1500T CPUs. On the S7-1200, declare ARRAY[0..n] OF "UDT_Motor" inside a standard global DB — the compiler emits the same indexed-access code path, but you lose the array-DB specific diagnostics.
How does the THIS pointer differ from a P# pointer inside an FC?
THIS is a compile-time, array-element reference bound the moment you drag a tag into the editor. It needs no P# arithmetic, no ANY marshalling, and works on optimized blocks. A P# pointer is a runtime address built from area, byte, and bit offsets; the runtime cannot build it for optimized data because the offsets are not published.
Is the multiplex tag property available in TIA Portal V14?
It was added in V14 SP1 and is configured on the HMI connection ("Properties → Multiplex tag = enabled"). On earlier V14 builds you must use a separate INT tag and a VBScript event to re-point the symbolic tag, which is more fragile and slower at runtime.