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.
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.
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
- Open WinCC Flexible ES, project tree → Tags →
index→ Properties → confirm the Acquisition mode field. - Open a Variable Table (VAT) in STEP 7 (or TIA Portal) and watch
MW100live. Toggle the script trigger and observe whether the pointer changes synchronously with the script iterations or drifts. - Add a temporary trace: in the script, write
fileLog.WriteLine "Index written: " & indexDBafter theSetValueline, then read back viaSmartTags("index")on the next line. If the round-tripped value differs fromindexDB, the PLC is overwriting it. - Place a temporary screen with the
indextag 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). - Search the STEP 7 program for any
L MW100,T MW100, orMW100reference. 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.
- In WinCC Flexible ES, navigate to Tags →
index→ Properties. - Set Acquisition mode to
Cyclic continuous. - Set the Acquisition cycle to 100 ms (or the smallest supported cycle on your panel; 200 ms is acceptable for human-driven recipe loads).
- 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:
- Write the new index to a "request" tag.
- Read it back, confirm it matches.
- Yield the VBScript with
WScript.Sleep 200(or call a tag read to trigger a cycle). - 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
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
- Place a 1-second cyclic trigger on the import script in Schedules.
- Set up a watch table in STEP 7 / TIA that monitors
DB10..DB24byte 0 (description) and the INT words at offset 42 and 44. - 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.
- Check
import.logforOKlines and the absence ofTIMEOUT. - Force a timeout by disconnecting the PLC ACK bit. Confirm the script logs
TIMEOUTand continues to the next row rather than halting. - 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.