Overview: The Variable Array Index Problem
In SIMATIC WinCC HMI/SCADA projects, a recurring engineering requirement is reading or writing a specific element of a PLC array where the element index is not known at design time. Static tag references such as DB100.DBW0 (element 0) or DB100.DBW2 (element 1, WORD) resolve at compile time and cannot follow an operator selection. Common field scenarios that demand a variable index include:
- Recipe screens that show row N of an ingredient or batch table the operator has scrolled to.
- Alarm history views that display entry 247 of a 1000-element log buffer.
- Multi-axis HMI panels where the operator selects axis 1–16 and the script must pull
AxisData[selectedAxis].CurrentPosition. - Dynamic trend configurations that cycle through indexed process variables for a 1-second strip chart.
- Status pages that look up tag by name from a configurable device list.
WinCC provides several supported mechanisms to make the index dynamic. The table below summarizes the four documented paths and where each is appropriate. Function signatures and behavior come from the SIMATIC WinCC V7.5 system manual and the SIMATIC WinCC Professional V18 system manual.
| Method | WinCC Version | Index Source | Best Use |
|---|---|---|---|
| C-Script array tag | V7.x / Professional | Internal variable | Legacy HMI, high-frequency polling, structured binary buffers |
| Raw Data tag | V7.x / Professional | None (whole block) | Bulk data, S7-300/400, third-party protocols, non-symbolic access |
| VBScript tag helpers | Professional / RT Professional | Internal variable | TIA Portal integration, VBScript-familiar teams |
| JavaScript array methods | WinCC Unified V16+ | Internal variable | Modern web-based HMI, predicate-based lookup |
Prerequisites and Environment
Before writing any script, confirm the engineering environment supports the chosen path.
- Software (V7.x path): SIMATIC WinCC V7.5 SP2 or later. The C-Script reference is part of the standard installation; see the SIMATIC WinCC V7.5 manual.
- Software (Professional path): TIA Portal V17 or later with WinCC Professional V17 / V18. See the SIMATIC WinCC Professional V18 system manual.
- Software (Unified path): TIA Portal V17+ with WinCC Unified V17 or V18. The JavaScript runtime is documented in the SIMATIC WinCC Unified system manual.
- PLC firmware: S7-1200 firmware V4.2+ or S7-1500 firmware V2.0+ for symbolic array tags over the standard S7 protocol. S7-300/400 requires raw data tags for structured arrays in most HMI configurations.
- Network: The PLC connection must be configured (S7 Classic, S7 Plus, or OPC UA). For Unified, OPC UA is the recommended transport.
- WinCC configuration: A tag addressing the DB array base address, an internal tag of the same data type to receive the value (single-element read) or a fixed-size buffer for raw data, and a screen or scheduled action that will host the script.
PLC-Side Array Configuration
Declare the array in a STEP 7 data block. The example below uses an S7-1500 (TIA V18) DB containing a recipe table of 100 elements:
TYPE "RecipeEntry" :
STRUCT
Name : String[32]; // 34 bytes incl. 2-byte S7-1500 string header
SetPoint : REAL; // 4 bytes, offset +34
Tolerance : REAL; // 4 bytes, offset +38
Active : BOOL; // 1 byte, offset +42
END_STRUCT
END_TYPE
DATA_BLOCK "RecipeDB"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
NON_RETAIN
STRUCT
Recipes : ARRAY[0..99] OF "RecipeEntry"; // 100 elements
Count : INT;
END_STRUCT
END_DATA_BLOCK
For non-optimized blocks (S7-300/400), make sure the array starts at an even byte offset, as the S7 protocol aligns 16-bit words on even addresses. With S7_Optimized_Access := 'TRUE', only symbolic addressing is allowed; WinCC must use the symbolic path, which requires the array to be configured as an array tag, not a raw byte block.
STRING[n] occupies n+2 bytes: 2 bytes for the S7 string header (max length, current length), then n bytes of character data. The element size of RecipeEntry above is 46 bytes on most S7-1500 firmwares (34+4+4+1+3 alignment). Always confirm with the PLC's "Monitor/Modify" view or by reading the DB layout in TIA Portal under Info → Compilation.WinCC Tag Configuration Methods
Method 1 — Array Tag (Direct Symbolic Access)
In WinCC Professional / Unified, configure a tag that points to the symbolic DB element:
- Open the HMI tag editor and add a new tag.
- Set the Data Type to the matching element type, e.g.
REALforSetPointorWString[32]forName. - For S7-1500 symbolic, set the address to the full path with bracket index, e.g.
"RecipeDB".Recipes[0]. WinCC presents this as an array tag; enable the Array property and set the length to 100. - For WinCC V7.x with S7-300/400, use absolute addressing:
DB100,DBW0with Array length = 100 × element byte size / 2 (for WORD-aligned elements).
The result is a single WinCC tag that represents the entire array; the script then uses C/VBScript array helpers to extract a specific element.
Method 2 — Raw Data Tag
When symbolic array access is not available (older PLCs, PROFINET gateways, OPC tunneling, or third-party controllers), use a Raw Data tag:
- Create a tag with type Raw Data Type, subtype Byte Array.
- Length = total array size in bytes. For 100 elements of 46 bytes: 4600 bytes.
- In the script, read the entire buffer, then index manually using byte arithmetic.
Raw data is the only documented way to read structured UDT/STRUCT arrays from S7-300/400 where the S7 protocol does not expose element-level symbolic access on the HMI side. The same mechanism is the safest path for non-S7 PLCs that only support bulk block reads.
C-Script Implementation (WinCC V7.x)
C-Script in WinCC V7.x exposes dedicated array functions in the global action context. The relevant signatures from the WinCC C-API reference are:
BOOL GetTagXxxArray(LPVOID lpvValue, LPCTSTR lpszTagName, LPVOID lpValue, int nIndex);
BOOL SetTagXxxArray(LPVOID lpvValue, LPCTSTR lpszTagName, LPVOID lpValue, int nIndex);
int GetTagArrayLength(LPCTSTR lpszTagName);
BOOL GetTagRawData (LPVOID lpvValue, LPCTSTR lpszTagName, BYTE* pData, int nOffset, int nLen);
The third argument is a pointer to the user buffer (a single element), the fourth is the zero-based index. WinCC does not support a true variable-index assignment on the tag name itself in C-Script; the function-call form is the documented pattern. The supported data type suffixes are Bit, Byte, Word, DWord, Float, Double, Char, and the corresponding unsigned U variants.
Example: Reading a single array element
#include "apdefap.h"
void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
DWORD index;
float value[1];
index = GetTagWord(lp, "HMI_SelectedRow");
if (index < (DWORD)GetTagArrayLength("RecipeSetPoint")) {
if (GetTagFloatArray(lp, "RecipeSetPoint", value, (int)index)) {
SetTagFloat(lp, "HMI_DisplaySetPoint", value[0]);
} else {
SetTagWord(lp, "HMI_StatusWord", 0x0001); // read error flag
}
} else {
SetTagWord(lp, "HMI_StatusWord", 0x0002); // out-of-range flag
}
}
Example: Parsing a structured element from raw data
void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
DWORD index;
BYTE buf[64]; // one element of the raw buffer
float setpoint, tolerance;
index = GetTagWord(lp, "HMI_SelectedRow");
if (index >= 100) return;
if (GetTagRawData(lp, "RecipeRaw", buf, (int)(index * 46), 46)) {
// Skip 34 bytes of S7-1500 STRING[32] header+data, then read two REALs
setpoint = *(float*)(buf + 34);
tolerance = *(float*)(buf + 38);
SetTagFloat(lp, "HMI_DisplaySetPoint", setpoint);
SetTagFloat(lp, "HMI_DisplayTolerance", tolerance);
}
}
Raw Data / Byte Array Approach
When symbolic access is not possible, the raw data path is the most efficient for bulk reads because it pulls the whole block in a single call.
- Configure a tag
RecipeRawof type Raw Data, length 4600 bytes. - Configure a trigger (e.g., 1 s) or call via a C-Script event.
- Use
GetTagRawDatato read N bytes starting at offsetindex * elementSize. - Cast the byte buffer to the target type. The S7 protocol delivers data in little-endian; on x86 HMI panels this is the native byte order so no swap is required.
The nOffset parameter of GetTagRawData is in bytes; there is no per-element index. Multiply by the element size to land on the right row. To scan the whole table once, call GetTagRawData with offset 0 and length 4600, then iterate for (i = 0; i < 100; i++) over the buffer with a stride of 46.
Memory Management and Performance
C-Script actions allocate inside a small action-context heap. For arrays larger than a few hundred bytes, use the WinCC runtime heap functions sysMalloc and sysFree documented in the C-Script reference. Avoid C's malloc/free directly; mixing them with the WinCC heap can corrupt the runtime.
float* pBuf = (float*)sysMalloc(100 * sizeof(float));
if (pBuf) {
int i;
for (i = 0; i < 100; i++) {
GetTagFloatArray(lp, "RecipeSetPoint", &pBuf[i], i);
}
// process pBuf ...
sysFree(pBuf);
}
Typical field performance numbers from a WinCC V7.5 SP2 runtime on a 19-inch Comfort Panel:
| Operation | Approx. Time | Notes |
|---|---|---|
Single GetTagFloat
|
10–20 ms | Includes S7 round trip |
100 × GetTagFloatArray
|
1.0–1.5 s | One call per element, dominated by S7 round trips |
Single GetTagRawData (4.6 KB) |
50–80 ms | Bulk read, one S7 round trip |
sysMalloc(100 × 4) |
< 1 ms | Process heap allocation |
GetTagFloatArray loop will overrun the cycle. Use the bulk raw-data read for full-table scans, and reserve per-element calls for single-row fetches on user input.VBScript (WinCC Professional / RT Professional)
In TIA Portal WinCC Professional, VBScript has direct tag-array support. The pattern is:
' Read element [HMI_SelectedRow] from array tag "RecipeSetPoint"
Dim index, value
index = SmartTags("HMI_SelectedRow")
If index >= 0 And index < 100 Then
value = SmartTags("RecipeSetPoint")(index + 1) ' VBScript is 1-based
SmartTags("HMI_DisplaySetPoint") = value
End If
WinCC array tags are exposed as 1-based VB arrays. To read element 5, index with (6). The tag editor shows the array base type; double-click an array element in the tag list to confirm its base and length.
Helper subroutine for safe write-back
Sub SetArrayElement(arrName, idx, val)
Dim tmp()
If idx < 0 Then Exit Sub
ReDim tmp(UBound(SmartTags(arrName)))
tmp = SmartTags(arrName)
If idx <= UBound(tmp) Then
tmp(idx + 1) = val
SmartTags(arrName) = tmp
End If
End Sub
JavaScript (WinCC Unified)
WinCC Unified runs the runtime on a Chromium-based web stack. VBScript is not supported, but the JavaScript array model is standard. Indexes are 0-based as documented in the MDN Array reference:
// Read element [HMI_SelectedRow] from array tag "RecipeSetPoint"
export function Button_Click(screen, item) {
let index = Tags("HMI_SelectedRow").Read();
let arr = Tags("RecipeSetPoint").Read(); // returns native JS array
if (index >= 0 && index < arr.length) {
let value = arr[index]; // 0-based, per ECMA-262
HMIRuntime.Trace("Recipes[" + index + "].SetPoint = " + value);
Tags("HMI_DisplaySetPoint").Write(value);
}
}
For element search by predicate (e.g., find the active recipe by name), use the standard MDN Array.prototype.find():
let recipes = Tags("RecipeData").Read();
let target = "Mix-A";
let entry = recipes.find(r => r.Name === target);
if (entry) {
Tags("HMI_DisplaySetPoint").Write(entry.SetPoint);
}
Other useful ECMA-262 array methods for HMI work, all documented in the MDN Array reference:
-
arr.findIndex(predicate)— returns the 0-based index of the first match, or -1. -
arr.filter(predicate)— returns a new array of all matches; useful for alarm lists. -
arr.map(fn)— transforms every element (e.g., unit conversion for a trend). -
arr.some(predicate)/arr.every(predicate)— boolean checks for status fields.
Multi-Dimensional Array Access
PLC-side multi-dimensional arrays (ARRAY[0..3, 0..9] OF INT) are not natively exposed as array tags in WinCC V7.x. Supported workarounds:
-
Flatten on the PLC side. Copy
Matrix[i,j]into a 1-D bufferMatrixFlat[i*10 + j]in an OB/CyclOB, then read the flat tag from WinCC. -
Raw data. Configure length = 4 × 10 × 2 = 80 bytes, index with byte offset
i*20 + j*2. - STRUCT of arrays. Wrap the 2-D in a UDT of 1-D arrays; WinCC handles the UDT via individual member array tags.
WinCC Unified supports 2-D arrays directly since V17; the JS object is arr[row][col] and the tag editor exposes both dimensions.
Verification
Run a structured verification after implementation:
- Compile / consistency check in TIA Portal — confirm zero errors on the HMI tags. The compiler will flag any array tag whose PLC array length was changed after the HMI project was last regenerated.
-
Tag simulation. Use the WinCC tag simulator to inject a known array value (e.g.,
RecipeSetPoint[5] = 12.34) and verify the script reads the correct element when the index tag is changed. - Cross-check in PLCSIM. Start S7-PLCSIM, monitor the DB element in the PLC, and compare to the HMI display. Any drift indicates a byte-order or length mismatch.
- Cycle test. Automate a 0..99 index sweep from a test PLC script and log the HMI value at each step. Diff against the expected value; tolerance zero is the only correct answer for direct copy.
-
Trigger rate. If the script runs on a 100 ms timer, ensure
GetTagRawDatareturns within the cycle. Use the WinCC performance monitor (RT Performancein the system tray) to check GDI/Tag-RT load; target < 60% sustained. - Error path. Set the index to -1 or 999 and verify the script does not crash. It should clamp or log a status word and the HMI should display a defined error value (e.g., 0.0 or "---").
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
GetTagXxxArray returns 0 (failure) |
Tag not configured as array | Open HMI tag, enable Array property and set length |
| Always reads element 0 | Index variable not refreshed in lp context |
Call GetTagDWord again inside the action; verify trigger |
| Read returns garbage | Wrong element type / length | Verify element size in the DB; check endianness for non-S7 PLCs |
| Script crash on large index | Out-of-bounds read on raw data | Clamp index: if (idx >= length) idx = length-1;
|
VBScript (idx) off by one |
VBScript is 1-based | Use (idx + 1); document in code comment |
JavaScript arr.length is wrong |
Tag configured with different length | Match PLC array bounds to HMI tag array length property |
sysMalloc returns NULL |
Action called too frequently | Increase cycle time; check for memory leak in sysFree paths |
| TIA compile error "Array tag not supported on this PLC" | S7-300 with optimized access | Switch to S7-compatible DB or use raw data tag |
| HMI shows stale value after write | WinCC cache not invalidated | Force SetTagXxx with the new value or call Refresh() (Unified) |
| String array element shows junk first 2 bytes | S7-1500 string header not stripped | Skip first 2 bytes (max length, current length) before reading chars |
FAQ
How do I access a specific array element in WinCC when the index comes from a tag?
Configure the WinCC tag as an Array tag with the same length as the PLC array, then call GetTagXxxArray(lp, "TagName", &buffer, index) in C-Script, SmartTags("TagName")(index + 1) in VBScript, or Tags("TagName").Read()[index] in WinCC Unified JavaScript. The index is 0-based for C and JavaScript, 1-based for VBScript.
Can WinCC read a STRUCT or UDT array from an S7-300/400 symbolically?
Not in WinCC V7.x over the standard S7 path. The supported approach is a Raw Data tag that covers the full block, then byte-level parsing in C-Script using GetTagRawData. WinCC Unified can read UDT elements symbolically when the connection is OPC UA and the server exposes the structure.
What is the performance difference between per-element reads and a single raw-data read?
On a typical Comfort Panel, 100 individual GetTagFloatArray calls take 1.0–1.5 s because of one S7 round trip per call, while a single 4.6 KB GetTagRawData takes 50–80 ms. Use raw data for full-table scans and per-element reads for single-row fetches on user input.
How do I find an array element by a field value rather than by index?
In WinCC Unified JavaScript, read the array as a native JS object and use arr.find(r => r.Name === target) as documented in the MDN Array.prototype.find() reference. In WinCC V7.x C-Script, iterate with a for loop over the raw data buffer and compare the relevant field offset.
Why does my VBScript (idx) read return the wrong element?
VBScript arrays are 1-based and WinCC array tags are exposed as 1-based VB arrays. To read element 0 from the PLC, use SmartTags("ArrayTag")(1). The off-by-one is a documented behavior of the WinCC VBScript wrapper, not a bug.