Fixing WinCC Flexible VBScript Tag Update Timing in Runtime

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

Problem Overview

When engineers use VBScript in WinCC flexible 2007 (SP2/SP3, Hotfixes 1–5) running on a Panel PC (e.g., SIMATIC Panel PC 677, 877, or 477) to iterate through an S7-300 CPU (CPU 31x-2 DP/PN) data block and persist values to a text file on the local drive, the script often behaves differently in the Visual Studio debugger than in runtime. The classic symptom: the loop counter increments, the file is created and written, but every line in savedata.txt contains the value from the first address (DB 310 DBD 0) repeated 100 times rather than 100 sequential records from DBD 0, DBD 4, DBD 8, … DBD 396.

This is not a scripting bug; it is a timing/sequencing issue between WinCC flexible's asynchronous tag acquisition and the synchronous execution of a VBScript procedure. The debugger inserts implicit waits between breakpoints that mask the race condition; runtime does not.

Root Cause Analysis

WinCC flexible 2007 uses two independent update paths:

  1. Tag acquisition (asynchronous): The HMI runtime cyclically polls the PLC for configured tags at the rate defined in the tag's Acquisition mode and Cycle (default 1 s for "Cyclic in use", 2 s for "Cyclic continuous"). Each tag value is buffered in a tag image.
  2. VBScript execution (synchronous): A VBScript procedure triggered by an event (e.g., value change, click) runs to completion in the HMI script interpreter. It does not wait for the next tag acquisition tick.

When a script reads SmartTags("DataTag").Value after changing SmartTags("DataPointer").Value, the tag image for DataTag still holds the value associated with the previous address because the acquisition cycle has not elapsed. The result is a Do…Loop that runs at full interpreter speed (often < 1 ms per iteration) while the tag system updates at 1–2 s per cycle. Every iteration reads the stale value.

Diagnostic confirmation: Add HMIRuntime.Trace (or a write to a diagnostic tag) inside the loop. If all 100 trace entries contain identical values while the DataPointer increments, you have confirmed the race condition. If the values change sporadically (e.g., every 1000 iterations), your acquisition cycle is the bottleneck.

Solution 1 – Use a Local VBScript Variable for the Loop Counter

The simplest and most reliable fix: stop using a tag as the loop counter. Tags introduce the acquisition-cycle delay; plain VBScript Long variables are updated instantly by the interpreter. Use the local variable to index the array of tag values read at the start, or to recompute a single address each iteration through a tag that does not depend on PLC polling (e.g., an internal HMI tag set via SmartTags(...).Value = ... used purely for pointer arithmetic that the PLC never sees).

' corrected_wincc_flexible_script.vbs
Option Explicit

Dim fso, file, text, i, rawValue, line
Set fso = CreateObject("Scripting.FileSystemObject")
Const ForWriting = 2
Const TristateUseDefault = -2

' Ensure target directory exists
If Not fso.FolderExists("c:\visu") Then
    fso.CreateFolder "c:\visu"
End If

Set file = fso.OpenTextFile("c:\visu\savedata.txt", ForWriting, True, TristateUseDefault)

' Read the static pointer (start address) once from a tag, then iterate locally
Dim startAddr
startAddr = SmartTags("DataPointer").Value     ' read once

For i = 0 To 99
    ' Point the PLC-visible tag at the next DB offset (4 bytes per REAL)
    SmartTags("DataPointer").Value = startAddr + (i * 4)
    ' Force the runtime to re-acquire NOW by reading the pointer itself,
    ' but use the local value of i for the actual loop control
    ' (the previous version used SmartTags("DataPointer").Value as the loop guard,
    '  which is what caused the race)
    rawValue = SmartTags("DataTag").Value
    line = "Record " & i & ", DB310.DBD" & (startAddr + i*4) & ", Value=" & rawValue
    file.WriteLine line
Next

file.Close
Set file = Nothing
Set fso = Nothing

The key change is that the loop bound (0 To 99) is now a literal, not a tag. The DataPointer tag is only written to the HMI tag image; the actual loop control uses the i local variable. Note that for genuine per-iteration freshness of DataTag, the acquisition cycle of DataTag must still be short enough (see Acquisition Cycle Tuning).

Solution 2 – Use an Array Tag

For datasets of 100–1000 elements, the cleanest pattern is to declare DataTag as an array tag (internal HMI tag) and fill it once with a single acquisition burst, then walk the array in pure VBScript with no further PLC round-trips.

  1. In WinCC flexible, create an internal tag DataArray of type Real and set Array count = 100.
  2. Map a single tag DataTag to DB310.DBD[DataPointer] where DataPointer is a 16-bit Int tag.
  3. From a periodic scheduler (e.g., 1 s) or from a single VBScript, call the read routine that increments DataPointer, waits one acquisition cycle, copies SmartTags("DataTag").Value into the local array slot, and advances.
' array_fill.vbs  (run from a 1-s scheduler or chained scripts)
Dim idx
idx = SmartTags("DataPointer").Value
SmartTags("DataArray")(idx) = SmartTags("DataTag").Value
SmartTags("DataPointer").Value = idx + 1
If SmartTags("DataPointer").Value >= 100 Then
    SmartTags("DataPointer").Value = 0
    ' trigger the "write to file" script here, e.g. via a tag event
End If

Once DataArray is fully populated, the file-write script operates only on internal memory — no further tag acquisition, no race condition.

Performance: WinCC flexible 2007's VBScript engine can address approximately 5000 array element accesses per second on a Panel PC 677 (1.5 GHz). For arrays larger than 5000 elements, see Performance Optimization.

Solution 3 – Chained Script Approach (UDT-Friendly)

When the data block contains UDTs (User-Defined Data Types) of mixed type — e.g., REAL, BYTE, BOOL, REAL, BYTE, BOOL, REAL, REAL, BYTE, REAL, REAL, BYTE, REAL (13 fields, 30 bytes total) — array tags become cumbersome because the UDT member types do not map to a single VBScript array element. The field-proven approach is to use a chain of value-change-triggered scripts: each script reads one logical "column" of the dataset and persists it.

  1. Create a PLC-side "ready" handshake tag DB310.DBX0.0 (BOOL) that the CPU sets after writing a new UDT record.
  2. WinCC flexible: configure a Value change event on the handshake tag that calls Sub ReadUDTRecord.
  3. Inside ReadUDTRecord, read each member of the UDT in turn (each its own configured tag with explicit DBW/DBB/DBX address), format the line, and append to the open text file.
' ReadUDTRecord.vbs  (called on rising edge of handshake tag)
Dim fso, file, text, record
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists("c:\visu\savedata.txt") Then
    fso.CreateTextFile "c:\visu\savedata.txt", True
End If
Set file = fso.OpenTextFile("c:\visu\savedata.txt", 8, True, -2) ' 8 = ForAppending

' UDT members — read each one as its own HMI tag with fixed address
record = SmartTags("UDT_Real1").Value & "," & _
         SmartTags("UDT_Byte1").Value & "," & _
         CInt(SmartTags("UDT_Bool1").Value) & "," & _
         SmartTags("UDT_Real2").Value & "," & _
         SmartTags("UDT_Byte2").Value & "," & _
         CInt(SmartTags("UDT_Bool2").Value) & "," & _
         SmartTags("UDT_Real3").Value & "," & _
         SmartTags("UDT_Real4").Value & "," & _
         SmartTags("UDT_Byte3").Value & "," & _
         SmartTags("UDT_Real5").Value & "," & _
         SmartTags("UDT_Real6").Value & "," & _
         SmartTags("UDT_Byte4").Value & "," & _
         SmartTags("UDT_Real7").Value

file.WriteLine record
file.Close
Set file = Nothing
Set fso = Nothing

This pattern is naturally deterministic: the script only runs when the PLC has signaled a new record, so the tag values are guaranteed fresh. The trade-off is the CPU of the Panel PC: every record spawns a full VBScript invocation plus FileSystemObject overhead. On a 677-class Panel PC, expect ~50–80 records per second sustained throughput.

Solution 4 – Acquisition Cycle Tuning

If you must keep the loop-style approach, shorten the acquisition cycle of the source tag so the race window shrinks. In WinCC flexible ES, open the tag properties and change:

Property Default Recommended for tight loops PLC impact
Acquisition mode Cyclic in use Cyclic continuous +1–2 % CPU on S7-300
Acquisition cycle 1 s 100 ms +10x communication load
Update on tag change (PLC-side PUT/GET) Off On where supported Reduces polled load

Even at 100 ms cycle, a Do…Loop running faster than 10 iterations per second will still see stale values on some iterations. Combining a shortened acquisition cycle with a local loop variable is the most robust combination.

File I/O Best Practices on Panel PCs

The WinCE/Windows XP Embedded file system on Panel PC 677/877 is sensitive to power-loss corruption. Apply these rules:

  • Open the file, write all records, close it — in one procedure. Long-lived open file handles are killed by screen-changes or runtime restarts, leaving zero-length files.
  • Use the full path c:\visu\ (or \Storage Card\visu\ for CF-based panels). c:\ root writes are restricted on some locked-down images.
  • For large datasets, write to a temp file (savedata.tmp) and rename to savedata.txt on completion; this guarantees the downstream consumer never reads a partial file.
  • Use file.WriteLine (VBScript adds CR+LF) rather than building line buffers in VBScript strings — 100x faster on long loops.
' atomic_write.vbs
Dim fso, tmp, final
Set fso = CreateObject("Scripting.FileSystemObject")
Set tmp = fso.OpenTextFile("c:\visu\savedata.tmp", 2, True, -2)
For i = 0 To 99
    tmp.WriteLine SmartTags("DataArray")(i)
Next
tmp.Close
fso.DeleteFile "c:\visu\savedata.txt", True
fso.MoveFile "c:\visu\savedata.tmp", "c:\visu\savedata.txt"
Set fso = Nothing

Performance Optimization

WinCC flexible 2007's VBScript engine is single-threaded and synchronously blocks the HMI display for the duration of a procedure. A 100 ms script causes a 100 ms display freeze. For datasets > 500 elements, apply these mitigations:

Technique Speedup Caveat
Use file.WriteLine directly to file (no in-memory string assembly) 5–10x Cannot use SmartTags(...).Read inside tight loops
Batch the loop into 10-record chunks, release control with DoEvents-equivalent Prevents watchdog timeouts WinCC flexible has no native DoEvents; use scheduler events
Write binary via ADODB.Stream instead of text FileSystemObject 3–5x for numeric data Not human-readable
Pre-allocate the file size with file.size = ... (not available in WinCC flexible; consider direct FileSystem API via CreateObject("ADODB.Stream")) 2x Higher complexity

Verification Procedure

  1. Build a checksum. After the script completes, read the file back, compute a sum of all numeric values, and compare to a known good value from the PLC. A constant checksum across multiple runs confirms every address was visited.
  2. Check the file timestamp. Use fso.GetFile(path).DateLastModified; if it is newer than the trigger event time, the script did run.
  3. Enable WinCC flexible trace. Start → SIMATIC → WinCC flexible → Trace Viewer. Filter on the script name. Confirm the procedure started and finished at the expected times.
  4. Watch the acquisition state. Add a temporary text field bound to DataTag with a 100 ms refresh. Run the script. If the on-screen value never changes between iterations, your acquisition cycle is the limiter.

Troubleshooting Matrix

Symptom Likely cause Fix
All 100 lines identical, debugger works fine Loop counter is a tag, race with acquisition Use local VBScript Long variable (Solution 1)
First line correct, all others from DBD 0 Same as above, but DataPointer writes coalesced Use array tag (Solution 2)
Every other line is correct, every other is stale Acquisition cycle ~2x loop iteration time Shorten acquisition cycle (Solution 4)
File created, but always 0 bytes Runtime killed during write, no Close Move all writes inside one Open/Write/Close block
Error "Permission denied" on c:\visu\savedata.txt File locked by another process (e.g., open in Notepad on Panel PC) Write to .tmp then rename
UDT member values truncated or swapped Address offset miscalculated for BOOL packing Verify each tag address with PLCSIM in STEP 7
Script works in WinCC flexible 2008 SP3 but not 2007 2007's GetFile requires existing file; 2008+ creates automatically Use OpenTextFile(path, 2, True, ...) to force creation
Display freezes during long save Single-threaded VBScript blocks UI Chunk the work into scheduler events (1 s each)

Migration Note – From WinCC flexible to TIA Portal

If you are porting this code to a TIA Portal WinCC Comfort/Advanced panel, the same race condition exists, but the remedy differs:

  • Use HMIRuntime.Tags with .Read synchronous mode (forces a one-shot read, not from cache) at the cost of higher latency per call.
  • For UDTs, configure the tag as a PLC UDT type directly in the HMI tag list; the entire UDT is then read in a single .Read call, eliminating per-field races.
  • The Scripting.FileSystemObject COM object is still available on Comfort Panels (Windows CE 6.0 / WEC 7) and WinCC Advanced Runtime on PC, with identical method signatures.

Why does my VBScript work in the Visual Studio debugger but fail in WinCC flexible runtime?

The debugger inserts implicit waits between statements and forces tag refresh on each read. In runtime, VBScript executes synchronously while tag acquisition runs on an independent 1–2 s cycle, so a Do…Loop that uses a tag as the loop counter reads the same stale value on every iteration. Replace the tag-based loop guard with a local VBScript Long variable.

Can I use an array tag to avoid the race entirely?

Yes. Declare an internal HMI tag of type Real with array count = number of records. From a scheduler-driven script, increment a pointer, read the single indexed PLC tag, and copy the value into the array slot. Once the array is full, the file-write script operates only on internal memory with no further PLC polling.

What is the fastest way to write 1000 REAL values to a text file on a Panel PC 677?

Open the file with FileSystemObject.OpenTextFile in ForWriting mode, then call file.WriteLine inside a For…Next loop driven by a local counter. Avoid building a large in-memory string. On a 1.5 GHz Panel PC 677, expect ~3000 records/second for plain text and ~10,000 records/second for binary via ADODB.Stream.

My DB contains a UDT with mixed REAL/BYTE/BOOL members. How do I save it line by line?

Configure one HMI tag per UDT member with explicit DBW/DBB/DBX addresses. Trigger a VBScript from a PLC-driven handshake bit (BOOL in the same DB) on its rising edge. Inside the script, read all 13 member tags, format them as a CSV line, and append to the file. This avoids the acquisition race because the script only runs when the PLC guarantees a complete new record.

How do I make the file write atomic so a power loss does not corrupt it?

Write to savedata.tmp, close it, delete the existing savedata.txt, then rename savedata.tmp to savedata.txt. The rename is atomic on NTFS and the FAT variant used by Windows CE 6.0, so downstream consumers never see a half-written file.

Back to blog