Fixing WinCC Flexible VBScript Multiplex Tag Index Update Issues

David Krause11 min read
HMI ProgrammingSiemensTroubleshooting
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

1. Problem Overview

A VBScript running in a Siemens WinCC Flexible Runtime HMI reads a comma-separated recipe file (for example, c:\recette.csv) and pushes each row into a Siemens S7-200/S7-300/S7-400 data block through a Multiplex tag. The expected behavior is line-by-line distribution:

  • Row 1 → DB10 with description, value0, value1
  • Row 2 → DB11 with description, value0, value1
  • Row 3 → DB12 with description, value0, value1

Observed behavior: only the last row from the CSV is written, and all multiplexed variables end up pointing at the same DB index. Inside the operator panel, DB10, DB11, DB12 all reflect the description and numeric values of the final CSV record.

The same script behaves correctly when the index variable is an internal HMI tag, but fails when the same variable is a PLC pointer tag such as MW100, DBW0, or any other S7 memory word mapped to a SmartTag.

Symptom signature: Internal tag → script correct. PLC tag → all rows collapsed onto the last index. This is the diagnostic fingerprint of an acquisition mode / cycle mismatch, not a syntax bug.

2. Multiplex Variable Concept in WinCC Flexible

The Multiplex tag type in WinCC Flexible binds several process tags to a single logical address by indexing an offset word. Typical configuration for this application:

WinCC Flexible Tag PLC Address Data Type Role
index MW100 INT (16-bit) Pointer into the multiplexed array
DB0.DESCRI DB[index].DBB0 (40 bytes) STRING[40] Multiplexed description
DB0.VALUE0 DB[index].DBW42 INT Multiplexed value 0
DB0.VALUE1 DB[index].DBW44 INT Multiplexed value 1

When index = 1, DB0.DESCRI resolves to DB1 starting at byte 0, DB0.VALUE0 resolves to DB1 starting at byte 42, and so on. Updating index changes the entire multiplex address space for that scan.

WinCC Flexible Multiplex tags work on the HMI-side polling cycle. The pointer word is sampled, the resolved address is calculated, and that address is then read or written. A write to DB0.DESCRI while the pointer is stale will be sent to the wrong DB.

3. Root Cause Analysis

Three interacting failures explain the collapse. None of them are syntax errors; the VBScript itself is well-formed.

3.1 Acquisition Mode = "On Demand" (default)

If MW100 is configured as Cyclic on demand (the WinCC Flexible default for new tags that are not bound to a screen object), the HMI runtime only refreshes the tag from the PLC when a screen containing the tag is visible. When the script runs from a global scheduler, a button event, or a non-visible screen, SmartTags("index") returns a cached value, not the live PLC value.

The SetValue SmartTags("index"), indexDB line is accepted by the HMI, but the multiplexed tags DB0.DESCRI, DB0.VALUE0, and DB0.VALUE1 are not re-resolved until the next acquisition cycle, which may be many milliseconds later and overlaps with subsequent loop iterations.

3.2 Write Order vs. Acquisition Cycle

The script updates index and then immediately writes to the multiplexed tags. In WinCC Flexible, the multiplex pointer is consumed at the next acquisition scan. Writing DB0.DESCRI in the same line after the SetValue may execute against the old pointer value during the in-flight cycle. The result is that line 1 writes to the last-known DB index (typically DB0 or DB14), and line 2 then writes to DB1, line 3 to DB2 — except the very first write to DB0.DESCRI actually hits whatever DB the pointer previously held, so the visible state in the operator panel shows the last successful write only.

3.3 PLC Program Overwriting MW100

Even when acquisition mode is correct, the S7 CPU may be writing to MW100 faster than the script can complete. If a STEP 7 block moves values into MW100 on every OB1 scan, the pointer is corrupted between iterations. The same symptom can be reproduced with the index in a data block (DBW) if the PLC side is also writing that DBW.

4. Diagnostic Procedure

  1. Open WinCC Flexible ES, project tree → TagsindexProperties → confirm the Acquisition mode field.
  2. Open a Variable Table (VAT) in STEP 7 (or TIA Portal) and watch MW100 live. Toggle the script trigger and observe whether the pointer changes synchronously with the script iterations or drifts.
  3. Add a temporary trace: in the script, write fileLog.WriteLine "Index written: " & indexDB after the SetValue line, then read back via SmartTags("index") on the next line. If the round-tripped value differs from indexDB, the PLC is overwriting it.
  4. Place a temporary screen with the index tag as an I/O field and trigger the script while the screen is active. If the script now works, the issue is acquisition mode (3.1).
  5. Search the STEP 7 program for any L MW100, T MW100, or MW100 reference. Any write to the same word will corrupt the pointer.

5. Solution A — Set Acquisition Mode to "Cyclic Continuous"

This is the primary fix and the one Siemens documentation explicitly recommends for pointer/index tags used outside of screen I/O fields.

  1. In WinCC Flexible ES, navigate to TagsindexProperties.
  2. Set Acquisition mode to Cyclic continuous.
  3. Set the Acquisition cycle to 100 ms (or the smallest supported cycle on your panel; 200 ms is acceptable for human-driven recipe loads).
  4. Recompile and transfer the project to the HMI. A full delta compile is not sufficient; perform a complete compile so the runtime tag database is regenerated.

Reference: see the WinCC Flexible Communication manual section on tag acquisition modes, and the WinCC Flexible ES system manual tag configuration chapter.

6. Solution B — Force Index Synchronization in Script

Even with cyclic acquisition, the pointer write and the multiplexed writes can race. Force a deterministic handshake:

  1. Write the new index to a "request" tag.
  2. Read it back, confirm it matches.
  3. Yield the VBScript with WScript.Sleep 200 (or call a tag read to trigger a cycle).
  4. Write the multiplexed values.

Production pattern: keep a confirmation DB word. The script writes index = N and ack = 0, then polls ack from the PLC which the CPU sets to 1 after applying the new pointer. Only after ack = 1 does the script write DB0.DESCRI / VALUE0 / VALUE1.

7. Solution C — Use a Dedicated Pointer Data Block

Avoid MW / MW100 for the index because the merker (flag) area is shared with STEP 7 user logic and is often overwritten unintentionally. Move the pointer to a dedicated DB:

Address Symbol Type Comment
DB99.DBW0 RecipeIndex INT 0-based pointer, written only by HMI
DB99.DBX2.0 IndexAck BOOL Set by PLC when RecipeIndex applied
DB99.DBX2.1 IndexBusy BOOL Set by PLC while re-indexing

Reserve DB99 for HMI-driven pointer traffic. Mark it as non-modifiable from STEP 7 user code in the project conventions document.

8. Complete Working VBScript

The script below combines the three fixes: a confirmation handshake, a forced dwell between index and data writes, and bounded error logging. It assumes 4 columns per row, IDs 1..N, and writes to DB10..DB(9+N).

'--- WinCC Flexible VBScript: Recipe CSV import with multiplex tag ---
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Const PATHFILE  = "c:\Recipes\"
Const NBPARAM   = 4         ' 1:ID  2:Description  3:Value0  4:Value1
Const MINDB     = 10
Const MAXDB     = 24
Const IDX_ACK   = "DB99.IndexAck"     ' BOOL confirmation from PLC
Const IDX_PTR   = "DB99.RecipeIndex"  ' INT,  HMI writes only
Const TAG_DESC  = "DB0.DESCRI"
Const TAG_V0    = "DB0.VALUE0"
Const TAG_V1    = "DB0.VALUE1"
Const HMI_DELAY_MS = 200      ' acquisition cycle + safe margin

Dim fso, fileIn, fileLog, lineRead, lineReadArray, lengthArr
Dim id, idx, ackOld, ackNew, retry, MAX_RETRY

MAX_RETRY = 20                ' 20 * 200 ms = 4 s max wait per row

Set fso  = CreateObject("Scripting.FileSystemObject")
Set fileLog = fso.OpenTextFile("c:\Recipes\import.log", ForAppending, True)

If Not fso.FileExists(PATHFILE & "recette.csv") Then
    fileLog.WriteLine Date() & " " & Time() & " ERROR: file not found"
    fileLog.Close : Exit Sub
End If

Set fileIn = fso.OpenTextFile(PATHFILE & "recette.csv", ForReading)
fileLog.WriteLine Date() & " " & Time() & " --- Import start ---"

ackOld = SmartTags(IDX_ACK)   ' capture prior ACK edge

Do Until fileIn.AtEndOfStream
    lineRead       = fileIn.ReadLine
    lineReadArray  = Split(lineRead, ";")
    lengthArr      = UBound(lineReadArray, 1) + 1

    If lengthArr <> NBPARAM Then
        fileLog.WriteLine Date() & " " & Time() & " SKIP bad-cols=" & lengthArr & " line=" & lineRead
    Else
        id  = CInt(lineReadArray(0))
        idx = id + MINDB - 1

        If idx < MINDB Or idx > MAXDB Then
            fileLog.WriteLine Date() & " " & Time() & " SKIP out-of-range id=" & id
        Else
            ' 1. Write pointer
            SetValue SmartTags(IDX_PTR), idx

            ' 2. Wait for PLC to apply pointer and toggle ACK
            retry = 0
            Do
                HMIRuntime.WaitForVariableChange IDX_PTR, , , , HMI_DELAY_MS
                ackNew = SmartTags(IDX_ACK)
                retry = retry + 1
            Loop Until (ackNew <> ackOld) Or (retry >= MAX_RETRY)

            If retry >= MAX_RETRY Then
                fileLog.WriteLine Date() & " " & Time() & " TIMEOUT id=" & id
            Else
                ' 3. Write multiplexed payload (index is now stable)
                SmartTags(TAG_DESC) = CStr(lineReadArray(1))
                SmartTags(TAG_V0)   = CInt(lineReadArray(2))
                SmartTags(TAG_V1)   = CInt(lineReadArray(3))

                fileLog.WriteLine Date() & " " & Time() & " OK id=" & id & " db=" & idx & " desc=" & lineReadArray(1)
            End If

            ackOld = ackNew
        End If
    End If
Loop

fileIn.Close
fileLog.WriteLine Date() & " " & Time() & " --- Import end ---"
fileLog.Close
Key technique: HMIRuntime.WaitForVariableChange blocks the script until the next acquisition cycle has consumed the new pointer value. This collapses the timing race that originally caused all rows to land on the same DB.

9. Alternative: Avoid Multiplex Tags Entirely

For new projects, consider a flat array tag structure instead of a multiplex tag. A pointer is a runtime construct with a well-documented set of edge cases; an indexed DB read can be emulated with raw DBW tags and a single scaling script.

Example: define DB0.DESC[0..14] as a STRING array tag and DB0.V0[0..14] as an INT array tag. The script then writes element-by-element without any pointer indirection:

SmartTags("DB0.DESC[" & i & "]") = CStr(lineReadArray(1))
SmartTags("DB0.V0[" & i & "]")   = CInt(lineReadArray(2))
SmartTags("DB0.V1[" & i & "]")   = CInt(lineReadArray(3))

This pattern works on Comfort Panels and WinCC (TIA) with no acquisition mode surprises. It is not available on the older WinCC Flexible 2008 SP3 / SP5 platforms, which is why the multiplex workaround is still common there.

10. Verification Procedure

  1. Place a 1-second cyclic trigger on the import script in Schedules.
  2. Set up a watch table in STEP 7 / TIA that monitors DB10..DB24 byte 0 (description) and the INT words at offset 42 and 44.
  3. Run the import from a test recipe of 5 rows. Confirm that after the script returns, the watch table shows distinct values per DB. Each DB should match the corresponding CSV row.
  4. Check import.log for OK lines and the absence of TIMEOUT.
  5. Force a timeout by disconnecting the PLC ACK bit. Confirm the script logs TIMEOUT and continues to the next row rather than halting.
  6. Reload the original failing recipe. Confirm the symptom no longer reproduces.

11. Edge Cases and Safety

Failure Mode Detection Mitigation
PLC overwrites MW100 mid-loop Watch table shows constant or wrong value Use dedicated DB99 pointer area
Acquisition cycle slower than loop First row correct, subsequent rows off-by-one Add WaitForVariableChange
CSV row count exceeds DB range Log shows SKIP out-of-range Validate ID at file load, abort if any out of range
DB string truncated Description shorter than expected in DB Match STRING length to DB definition; use Left() defensively
Locale decimal separator Value0 = 0 or wrong magnitude Replace . with Replace(..., ",", ".") for non-US locales
File locked by another process OpenTextFile raises error 70 Wrap in On Error Resume Next + retry loop, max 3 attempts
Panel reboots during write Partial DB updates on PLC Use a "commit" bit; PLC only applies payload when commit = 1

Always issue a commit handshake for production recipe loads. The script sets DB99.Commit = 1 only after all multiplexed writes have been acknowledged. The STEP 7 program copies the staging area to the live area on the rising edge of the commit bit. This is the same pattern used by Siemens' built-in Recipe View in TIA Portal and prevents the panel from ever leaving the PLC in a half-written state.

Why does the same script work with an internal HMI tag but fail with a PLC tag like MW100?

Internal HMI tags update synchronously inside the script's execution context, so the multiplex pointer is consumed immediately. A PLC tag is bound to the HMI's acquisition cycle; if the acquisition mode is Cyclic on demand or the cycle is too slow, the pointer is read stale and the multiplexed payload lands on the wrong DB. Switch the tag to Cyclic continuous with a 100-200 ms cycle, and add a WaitForVariableChange barrier in the script.

What acquisition mode should I use for a pointer index tag in WinCC Flexible?

Use Cyclic continuous with a 100 ms cycle for human-driven recipes and 50 ms for fast automatic loads. Never rely on the default Cyclic on demand for a tag that drives multiplex resolution from outside a screen I/O field. The setting is in Tag Properties → Acquisition mode; see the WinCC Flexible Communication manual for the full state table.

How do I prevent the PLC from overwriting the HMI's index pointer?

Reserve a dedicated data block, e.g. DB99.DBW0, for HMI-driven pointer traffic, and document that no STEP 7 user block is allowed to write to it. If you must use MW words, choose a range outside the user-programmer's typical flag area (for example, MW400-MW498 on an S7-300) and never reuse it for internal logic.

Is there a way to import the recipe without a multiplex tag at all?

Yes. On TIA Portal / WinCC (Comfort Panels and later) define an array tag such as DB0.DESC[0..14] and write to indexed elements directly with SmartTags("DB0.DESC[" & i & "]") = .... This eliminates the pointer indirection and the associated acquisition race. The array-tag approach is not available on WinCC Flexible 2008, which is why the multiplex pattern is still common on legacy panels.

My CSV uses comma decimals; VALUE0 ends up as zero. What is the fix?

VBScript on a Windows CE / Windows Embedded panel uses the panel's regional setting, not the developer's locale. Replace the decimal separator explicitly: CInt(Replace(lineReadArray(2), ",", ".")) if the HMI is set to German/French, or the reverse for US English panels. Add a Replace pass on every numeric column to make the script locale-independent.

Back to blog