WinCC Dynamic Array Element Access with Variable Index

David Krause13 min read
HMI / SCADASiemensTutorial / How-to
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

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
Index numbering convention. PLC arrays in STEP 7 are 0-based for S7-1200/1500 and 1-based in legacy SCL for S7-300/400 when using index brackets; however, VBScript on the HMI side is 1-based. JavaScript (WinCC Unified) is 0-based as documented in the MDN Array reference. Always declare and clamp the index in the same base as the consumer.

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 type sizing. S7-1500 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:

  1. Open the HMI tag editor and add a new tag.
  2. Set the Data Type to the matching element type, e.g. REAL for SetPoint or WString[32] for Name.
  3. 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.
  4. For WinCC V7.x with S7-300/400, use absolute addressing: DB100,DBW0 with 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:

  1. Create a tag with type Raw Data Type, subtype Byte Array.
  2. Length = total array size in bytes. For 100 elements of 46 bytes: 4600 bytes.
  3. 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);
    }
}
Endianness. S7-300/400/1500 and the S7 protocol deliver data in little-endian byte order for the standard S7 communication path. For OPC UA connections, the byte order follows OPC UA default (little-endian). When connecting to third-party controllers, verify the byte order in the protocol documentation and use byte-swap helpers if needed.

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.

  1. Configure a tag RecipeRaw of type Raw Data, length 4600 bytes.
  2. Configure a trigger (e.g., 1 s) or call via a C-Script event.
  3. Use GetTagRawData to read N bytes starting at offset index * elementSize.
  4. 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
Cycle budget. If the script runs on a 100 ms scheduled action, a 100-element 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.
WinCC Unified array tag configuration. The tag editor expects a known length at compile time. Configure 100 elements in the Array length property; the runtime delivers a JS array of that length. The maximum array length in the editor is 32,767 elements (Int16 index); for larger data sets, use a raw data / blob tag and decode it in JavaScript.

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:

  1. Flatten on the PLC side. Copy Matrix[i,j] into a 1-D buffer MatrixFlat[i*10 + j] in an OB/CyclOB, then read the flat tag from WinCC.
  2. Raw data. Configure length = 4 × 10 × 2 = 80 bytes, index with byte offset i*20 + j*2.
  3. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Trigger rate. If the script runs on a 100 ms timer, ensure GetTagRawData returns within the cycle. Use the WinCC performance monitor (RT Performance in the system tray) to check GDI/Tag-RT load; target < 60% sustained.
  6. 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.

Back to blog